Compare commits

...

886 Commits

Author SHA1 Message Date
Sabe Jones
76773d27c6 4.101.1 2019-06-18 23:29:07 -05:00
Sabe Jones
b3e81177ec fix(content): correct Mage weapon price, smarten Rebirth banner 2019-06-18 23:29:00 -05:00
Sabe Jones
020726f6f8 4.101.0 2019-06-18 20:05:15 -05:00
Sabe Jones
9f9b0c73c3 fix(event): 30 days hath July? uh, no 2019-06-18 19:49:55 -05:00
Sabe Jones
2c03c114da chore(sprites): compile 2019-06-18 19:20:23 -05:00
Sabe Jones
8e9500fed2 feat(event): Summer Splash 2019 2019-06-18 19:20:14 -05:00
Sabe Jones
377f849f9e Merge branch 'free-rebirth-limit' into release 2019-06-18 16:02:12 -05:00
Sabe Jones
8bbada540d feat(rebirth): add banner to buy modal 2019-06-18 15:24:50 -05:00
Matteo Pagliazzi
ee867a9d30 add tests 2019-06-17 21:04:22 +02:00
Matteo Pagliazzi
14a4f42b50 indentation 2019-06-17 20:47:55 +02:00
Matteo Pagliazzi
2cbb0a85a4 limit free rebirth to once every 45 days 2019-06-17 20:37:18 +02:00
Sabe Jones
975e068fe2 4.100.2 2019-06-14 12:35:37 +00:00
Sabe Jones
5a5f1d6895 Revert "Performance: Inbox Paging / loading (#11157)"
This reverts commit 5630e8cc8e.
2019-06-14 12:34:38 +00:00
Sabe Jones
1db0cda6b1 4.100.1 2019-06-13 13:14:32 -05:00
Sabe Jones
f987585cf1 fix(quests): content corrections 2019-06-13 13:13:55 -05:00
Sabe Jones
0325ae9636 fix(avatar): style correction 2019-06-13 13:10:06 -05:00
Sabe Jones
4c4fbfe790 fix(backgrounds): put errant BGs on 3x3 grid 2019-06-13 12:22:28 -05:00
Sabe Jones
6b59262e3e Challenge privacy fix (#11222)
* fix(challenges): filter out private content API-side

* fix(challenges): cleaner fix + test
2019-06-13 09:27:47 -05:00
negue
5630e8cc8e Performance: Inbox Paging / loading (#11157)
* load messages per conversation

* only sort ones in ui

*  add contributor to message

* fix correct message layout/message

* mugenScroll on chatMessages

* fix lint, no mugen-scroll, use own scroll handler

* fix height / margin of modal + use button to load more

* fix tests

* user data from inbox

* style "load earlier messages"

*  move mapMessage to the inbox api result / extract sentMessage of members-api-controller

* fix test back

* fix test

* keep last scroll position

* just set the Id of the returned message instead of all other properties

* fix add new messages (buttons were hidden) + load more

* item-mounted debounce to trigger the re-scrolling
2019-06-13 15:18:50 +02:00
Matteo Pagliazzi
5268bbb8a9 fix(subscription): reset lastReminderDate when creating a new subscription 2019-06-12 17:21:08 +02:00
Sabe Jones
34faf2fd50 4.100.0 2019-06-11 15:27:34 -05:00
Sabe Jones
896950dbf0 chore(sprites): compile 2019-06-11 15:26:50 -05:00
Sabe Jones
424c50f2a4 chore(news): Bailey
also direct mod contact form to HTTPS
2019-06-11 15:26:38 -05:00
Sabe Jones
f235a64d96 Merge branch 'sabrecat/pet-achievements' into release 2019-06-11 15:12:34 -05:00
Sabe Jones
286abe334e fix(notifications): add missing enum 2019-06-11 14:50:17 -05:00
Sabe Jones
81333a3074 feat(notifications): alert user to achievements 2019-06-11 13:06:00 -05:00
Matteo Pagliazzi
3c31945524 Email subs reminders release (#11216)
* add subscriptions reminders emails

* add field to record the last sub reminder
2019-06-10 22:37:30 +02:00
Sabe Jones
103945f7c5 feat(content): Dolphin Pet Quest
and revise Mind Over Matter achievement
2019-06-10 14:42:38 -05:00
Sabe Jones
b9d9a17aca 4.99.1 2019-06-06 15:54:18 -05:00
Sabe Jones
a0b6f576d2 Merge branch 'release' into develop 2019-06-04 16:49:17 -05:00
Sabe Jones
30036963b1 4.99.0 2019-06-04 16:48:53 -05:00
Sabe Jones
e1fe48bee4 chore(sprites): compile 2019-06-04 16:48:16 -05:00
Sabe Jones
77b19ffe97 feat(content): Armoire items and Backgrounds June 2019 2019-06-04 16:48:05 -05:00
Sabe Jones
e9a5e084fc fix(tests): use baseline quest with fewer update calls 2019-06-04 16:09:01 -05:00
Sabe Jones
12250a93f1 feat(basic-auth): allow multiple auth pairs (#11204) 2019-06-04 15:52:25 -05:00
Sabe Jones
7094e75dd8 feat(achievements): new pet-related cheevos 2019-06-04 10:31:25 -05:00
Sabe Jones
74c93955f8 Merge branch 'release' into develop 2019-06-03 15:22:54 -05:00
Sabe Jones
e4b2ef6599 4.98.1 2019-06-03 15:22:30 -05:00
Sabe Jones
a26c2bce23 chore(date): Bailey and disable potions 2019-06-03 15:22:21 -05:00
Matteo Pagliazzi
6cd00897ed fix(deps): do not update minor changes 2019-06-02 12:19:11 +02:00
greenkeeper[bot]
3c442318d8 Update js2xmlparser to the latest version 🚀 (#11019)
* fix(package): update js2xmlparser to version 4.0.0

* chore(package): update lockfile package-lock.json

* fix package-lock

* fix js2xmlparser usage
2019-06-01 14:45:05 +02:00
greenkeeper[bot]
fd3f7e2b0e Update pageres to the latest version 🚀 (#10981)
* fix(package): update pageres to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-06-01 14:40:53 +02:00
Matteo Pagliazzi
6f0bd0b913 fix(package-lock.json): fix missing entry 2019-06-01 13:22:00 +02:00
greenkeeper[bot]
5b7621ceb3 fix(package): update merge-stream to version 2.0.0 (#11190) 2019-06-01 13:19:01 +02:00
greenkeeper[bot]
b8b414cae5 Update chromedriver to the latest version 🚀 (#11116)
* chore(package): update chromedriver to version 73.0.0

* chore(package): update lockfile package-lock.json
2019-06-01 09:48:52 +02:00
Sabe Jones
783338f1f0 fix(strings): misformatted variables in pt_BR 2019-05-31 14:56:26 -05:00
HydeHunter2
fb6ca0c73b Add chevron buttons to sub-items in the menu (#11158)
* Add arrow buttons to sub-items

* Fix mistyped in width

* Add 'v-once' to never changing elements(icons)

* Add animation and ability to close tabs

* Fix typo in rotate

* Only 1 opened tab

* Add animation to dropdown elements

* Close menu after clicking tab

* Improved closing the menu after clicking on links

* Remove browser prefixes

* Add 'active' class to some tabs after clicking

* Return ability to open tabs from Desktop

* pin 'currency-tray' at top of menu

* Revert "pin 'currency-tray' at top of menu"

This reverts commit 12d4144a11.

* Fix display of 'Groups' sub-items
2019-05-31 12:51:29 +02:00
greenkeeper[bot]
5792f54aa0 Update validator to the latest version 🚀 (#11188)
* fix(package): update validator to version 11.0.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:41:58 +02:00
greenkeeper[bot]
3c943c8f23 Update gulp-imagemin to the latest version 🚀 (#11195)
* fix(package): update gulp-imagemin to version 6.0.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:28:38 +02:00
garayj
4b2fd60e47 FIX Guild suggestion incorrectly identifies account as new #11159 (#11197)
* Changed the suggested guild string to a translatable string and made changes so that the string only shows within 60 days of user profile creation.

* Removed whitespace.
2019-05-31 12:26:15 +02:00
Alexey Pyltsyn
75281ab638 Remove default color in Instagram icon (#11196) 2019-05-31 12:23:00 +02:00
greenkeeper[bot]
d46c9967fb Update axios to the latest version 🚀 (#11198)
* fix(package): update axios to version 0.19.0

* chore(package): update lockfile package-lock.json
2019-05-31 12:19:53 +02:00
Sabe Jones
4132a39b90 4.98.0 2019-05-30 15:14:04 -05:00
Sabe Jones
36cc229dd2 Merge branch 'develop' into release 2019-05-30 15:13:57 -05:00
Sabe Jones
ef0c11c2bd chore(sprites): compile 2019-05-30 15:13:35 -05:00
Sabe Jones
3a8312832c feat(content): new freebie glasses
Fixes #11171
2019-05-30 15:13:23 -05:00
Sabe Jones
df17753bb6 Merge branch 'release' into develop 2019-05-28 15:48:51 -05:00
Sabe Jones
c9b3c646eb 4.97.0 2019-05-28 15:48:07 -05:00
Sabe Jones
cd67877ef3 chore(sprites): compile 2019-05-28 15:45:16 -05:00
Sabe Jones
251e1d45af feat(content): May 2019 subscriber set 2019-05-28 15:45:04 -05:00
Matteo Pagliazzi
1a97d69edd Merge pull request #10865 from ianoxley/navbar-a11y-alt-text
Add text alternatives for navbar items
2019-05-26 11:59:04 +02:00
Matteo Pagliazzi
7baa7427a0 Merge pull request #11178 from Alys/mute-ban-block-notification-messages
adjust error messages for muted and banned users, and system flagging error
2019-05-26 11:49:24 +02:00
Matteo Pagliazzi
270078a030 Merge pull request #11170 from HabitRPG/Yutsuten-party-chat-translations
Party Chat Translations (Continuation of #10019)
2019-05-26 11:45:12 +02:00
Alys
bb2768071d change "all caps" to "all capital letters" for clarity
I found this question in the Help guild:
'I'm deleting my account and aren't able to find the "cap" to type in
"delete" in order to finish deleting process. Any ideas were that
"cap" is?'
2019-05-26 16:43:42 +10:00
Alys
ec75de5a90 add swear words - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2019-05-26 16:25:33 +10:00
Alys
b9b944ba29 replace similar messages about chat privileges removed with one generic one 2019-05-26 07:59:23 +10:00
Alys
fa9553b371 removed mention of Transifex and added link to new Weblate site 2019-05-25 19:57:29 +10:00
Mateus Etto
8d5cbbe9be Docker setup for development (#11165)
* Basic docker setup for development

* Add missing environment variable to client

* Install gulp-cli into docker image for tests
2019-05-24 14:07:21 -05:00
Sabe Jones
3a339a4a09 Merge branch 'release' into develop 2019-05-23 13:41:14 -05:00
Sabe Jones
324b16b8cd 4.96.1 2019-05-23 13:41:00 -05:00
Sabe Jones
8c005aa05a fix(shops): remove outdated Featured Item
and Bailey
2019-05-23 13:40:06 -05:00
Sabe Jones
664cf5a47b Merge branch 'release' into develop 2019-05-21 15:29:40 -05:00
Sabe Jones
71926cff51 4.96.0 2019-05-21 15:29:22 -05:00
Matteo Pagliazzi
e971f49c85 fix(test): fix stripe error message output 2019-05-21 15:17:57 -05:00
Sabe Jones
01657f573d feat(quests): create Hatching Potion category 2019-05-21 14:48:44 -05:00
Sabe Jones
95613dcfb8 Merge branch 'sabrecat/potion-quests' into release 2019-05-21 14:28:46 -05:00
Matteo Pagliazzi
bcf304984d Task notes now disappear when they are deleted from the main task. Fix #11152 (#11167)
* Task notes now disappear when they are deleted from the main task.

* Html component changed back to Markdown. Markdown logic now accounts for if the value is an empty string.

* if-else statement to be sure that the markdown library doesn't create issues with empty strings.
2019-05-20 11:46:44 +02:00
Chris Pomerville
08d84ba691 hides item count in quest sidebar section for non-participants (#11183) 2019-05-20 11:33:47 +02:00
Matteo Pagliazzi
4007c26801 fix(test): fix stripe error message output 2019-05-20 11:31:20 +02:00
Alys
344c20fd99 improve error notification shown when a player is blocked
This improves the wording in the error notification that a player
sees when they've been blocked.

It also also changes markdown links to plain text because raw
markdown was being shown in some locations (for example when you saw
the error messages on the command line while using an API command).

These changes have been discussed with and approved by beffymaroo
and the other mods.
2019-05-18 21:38:38 +10:00
Alys
f5c2c39f6a adjust error messages for muted users and system flagging error 2019-05-18 17:12:24 +10:00
Sabe Jones
0379f341b9 4.95.1 2019-05-16 14:07:57 -05:00
Sabe Jones
8498ab9fe2 chore(news): Bailey 2019-05-16 14:07:39 -05:00
Jose Garay
bda3fb5f4d if-else statement to be sure that the markdown library doesn't create issues with empty strings. 2019-05-15 08:10:44 -07:00
Matteo Pagliazzi
eff8db0afd fix issue with language being undefined, refactoring auth middleware, new tests 2019-05-15 16:54:55 +02:00
Matteo Pagliazzi
8cce38ede1 Merge branch 'party-chat-translations' of https://github.com/Yutsuten/habitica into Yutsuten-party-chat-translations 2019-05-15 15:20:40 +02:00
Mateus Etto
ab0dae8df3 Translate messages only after serialization 2019-05-15 21:26:23 +09:00
Mateus Etto
0824af05b7 Remove unneeded parameter on method call 2019-05-15 19:45:42 +09:00
Mateus Etto
28bf024990 Move translateMessage to libs 2019-05-15 19:07:17 +09:00
Mateus Etto
8c59420d4e Fix quest cancel test 2019-05-15 17:28:05 +09:00
Mateus Etto
8a30ac0607 Add translation support for quest cancel 2019-05-15 16:55:37 +09:00
Mateus Etto
e1984762b5 Fix some tests 2019-05-15 16:55:27 +09:00
Mateus Etto
0360326f41 Change translateSystemMessages to be a class method 2019-05-15 15:46:32 +09:00
Mateus Etto
f3f215abea Fix typo on schema 2019-05-15 13:50:57 +09:00
Jose Garay
feb98a5ac7 Html component changed back to Markdown. Markdown logic now accounts for if the value is an empty string. 2019-05-14 19:06:22 -07:00
Sabe Jones
95de11cf7a Merge branch 'release' into develop 2019-05-14 16:35:29 -05:00
Sabe Jones
5bb336cedc 4.95.0 2019-05-14 16:34:29 -05:00
Sabe Jones
790614a9d4 chore(sprites): compile 2019-05-14 16:33:51 -05:00
Sabe Jones
977968df19 feat(content): Sunshine Potions 2019-05-14 16:33:42 -05:00
HydeHunter2
3df056105c Remove markdown elements from PM (#11150)
* Remove markdown elements from PM

* Replacing limit number of characters to limit number of lines

* Add ellipsis
2019-05-14 22:14:10 +02:00
Sabe Jones
0eb7e10e7f fix(sprites): rebuild including Wolf 2019-05-14 13:22:45 -05:00
Alys
0908fa2a48 change Load Tools icon that mods and staff use (#11169)
The crown icon makes it clearer to us that it's for the special
mod/staff tools. The pencil icon is too similar to normal
edit/compose icons and we were clicking it by mistake, with
accidental double-clicks resulting in users being briefly banned by
mistake since the ban icon is directly "under" the Load Tools icon.

I've checked with the other mods and they feel that the crown will
avoid that problem.
2019-05-14 14:04:28 +02:00
Sabe Jones
71904d106f chore(sprites): compile 2019-05-13 11:02:19 -05:00
Sabe Jones
56040eebaf feat(content): Magic Hatching Potion Quest 2019-05-13 11:02:07 -05:00
Jose Garay
2094a4d4b8 Task notes now disappear when they are deleted from the main task. 2019-05-11 23:47:24 -07:00
Sabe Jones
2048f3ef76 fix(string): correct stat reference 2019-05-10 07:23:20 -05:00
Sabe Jones
e9163a1bb2 fix(test): Feathered Friends date range 2019-05-09 14:53:55 -05:00
Sabe Jones
54fd910db2 fix(test): Feathered Friends date range 2019-05-09 14:53:38 -05:00
Sabe Jones
3c0cd7067a 4.94.1 2019-05-09 14:32:23 -05:00
Sabe Jones
5a473eb0e0 Merge branch 'develop' into release 2019-05-09 14:32:14 -05:00
Sabe Jones
e6fcdf62ef fix(sprites): dojo resize fix 2019-05-09 14:31:28 -05:00
Sabe Jones
09e748bc92 feat(content): reenable Feathered Friends bundle 2019-05-09 14:27:22 -05:00
Sabe Jones
90532b0763 Merge branch 'develop' into Yutsuten/party-chat-translations 2019-05-08 15:13:53 -05:00
Matteo Pagliazzi
9151690f86 Better group plan and subscription cancellation (#11132)
* wip: better group plan cancellation

* add cancelation confirm modal

* abstract confirm modal for subs

* abstract canceled modal for subs

* working code

* add missing files

* fix text and margins

* fix(cancel modal): share css and add close icon
2019-05-08 21:37:02 +02:00
Sabe Jones
c125ac4d93 fix(client): avoid TypeError in store state 2019-05-08 12:18:42 -05:00
Sabe Jones
aa61c1fe06 Merge branch 'release' into develop 2019-05-07 15:57:44 -05:00
Sabe Jones
c6a7ee3f56 4.94.0 2019-05-07 15:57:14 -05:00
Sabe Jones
411213f381 chore(sprites): compile 2019-05-07 15:57:02 -05:00
Sabe Jones
b2dabcaf98 feat(content): Armoire items and backgrounds 5/19 2019-05-07 15:56:51 -05:00
chen
2f3927fcaa column-background should have 100% width (#11142) 2019-05-03 15:57:00 +02:00
negue
f84562446d fix loading owned messages (#11147) 2019-05-03 15:53:35 +02:00
Sabe Jones
0a840ca952 4.93.4 2019-05-03 08:47:40 -05:00
HydeHunter2
c0837e3b3c Fix challenge update (#11148)
* Fix challenge update

Fix some problem in challenge update

* Fix test

* Move leader to noUpdate

* Move leader to noUpdate
2019-05-03 15:35:56 +02:00
HydeHunter2
95c1893b0c Fix challenge update (#11148)
* Fix challenge update

Fix some problem in challenge update

* Fix test

* Move leader to noUpdate

* Move leader to noUpdate
2019-05-03 15:35:12 +02:00
Sabe Jones
d8ea3bd23a 4.93.3 2019-05-02 14:25:07 -05:00
Sabe Jones
0ce11b82df Merge branch 'develop' into release 2019-05-02 14:25:02 -05:00
Sabe Jones
b4c47b4afd chore(sprites): compile 2019-05-02 14:24:39 -05:00
Sabe Jones
4777850601 chore(event): end Flinging 2019-05-02 14:24:29 -05:00
Sabe Jones
9a3e208c9b Merge branch 'release' into develop 2019-04-30 14:17:32 -05:00
Sabe Jones
792d5998b0 4.93.2 2019-04-30 14:17:14 -05:00
Sabe Jones
53515fd3f4 chore(news): Bailey 2019-04-30 14:17:05 -05:00
Sabe Jones
946147a4aa 4.93.1 2019-04-29 13:45:04 -05:00
Sabe Jones
983ea7c6c7 fix(strings): mixed-up armor notes 2019-04-29 13:44:52 -05:00
Sabe Jones
1135ab946e Sabrecat/groups quick wins (#11146)
* WIP(groups): quickish wins

* WIP(groups): two quick wins
1. Don't show task creation button if user is not leader or manager
2. Don't require JS confirm() for approving tasks

* fix(group-plans): allow delete from options button

* fix(group-plans): update tasksOrder when task deleted

* fix(group-tasks): dismiss notification when user takes action

* refactor(tasks): DRY out create button styling

* fix(group-tasks): sync after claiming/unclaiming

* fix(claiming): better sync and notif handling

* fix(tasks): force sync instead of explicitly clearing notif

* fix(tasks): reposition task creation button

* fix(group-tasks): default to single completion

* fix(group-tasks): move completion condition field above approval switch

* fix(group-tasks): todo validation error and approval notif dismissal

* fix(group-tasks): default single completion on client

* fix(group-tasks): move completion condition up more

* fix(group-tasks): maintain client-side user assignment list

* fix(group-tasks): remove approval notifications when task deleted

* fix(group-tasks): send assigned task to top of task list

* fix(group-tasks): remove useless tag filter dropdown

* feat(group-tasks): notify user of assigned task

* fix(group-tasks): don't allow approval of tasks w/ no approval request

* fix(tests): adjust expectations

* fix(group-tasks): more sensible action on assignment notif click
2019-04-29 13:38:28 -05:00
HydeHunter2
5a15c73fca Reload after rebirth (#11125)
* Add restart after rebirth

Page will be reloaded after purchasing "Orb of Rebirth"

* Remove restart after closing achievement

This reload is not needed, as the page now reloads immediately after purchasing "Orb of Rebirth"

* Move rebirth notification to modal

* Delete references to rebirth notification
2019-04-27 19:32:33 +02:00
HydeHunter2
3f99c14a37 Seasonal alert removed for items you own (#11135) 2019-04-27 19:28:35 +02:00
HydeHunter2
9e515d96c3 Remove reward button for non-leader/non-admin (#11136) 2019-04-27 19:26:02 +02:00
HydeHunter2
40e0017b17 Separate tags of different types (#11123)
Challenge, group and user tags are separated
2019-04-27 19:22:09 +02:00
HydeHunter2
251563690e Fix tag text overlapping (#11124)
* Fix word-wrapping in user

* Fix word-wrapping in taskModal
2019-04-27 19:21:05 +02:00
negue
83070e211d Inbox: Add API to list conversations (#11110)
* Add API to list inbox conversations

* fix test + add api doc

* use `.lean()`

* orderBy after the the grouped conversations are loaded

* fix ordering
2019-04-26 18:45:05 +02:00
Matteo Pagliazzi
3be075ad43 fix(tests): Items Utils > castItemVal fix fn call 2019-04-26 00:02:53 +02:00
Matteo Pagliazzi
043e0fb819 fix #9514: await user update client sid 2019-04-25 22:49:58 +02:00
Matteo Pagliazzi
9d473cc92e wip: fix setting (some) items values in the hall of heroes (#11133) 2019-04-25 22:41:43 +02:00
Sabe Jones
99dec2eb0c 4.93.0 2019-04-25 13:55:22 -05:00
Sabe Jones
cbb04d221d Merge branch 'develop' into release 2019-04-25 13:55:17 -05:00
Sabe Jones
1b93f20451 chore(sprites): compile 2019-04-25 13:54:58 -05:00
Sabe Jones
0f374abd27 feat(content): subscriber mystery set 2019-04-25 13:42:49 -05:00
Sabe Jones
a4a8ac6c5e Merge branch 'release' into develop 2019-04-23 15:30:37 -05:00
Sabe Jones
3854c6f62f 4.92.6 2019-04-23 15:30:14 -05:00
Sabe Jones
82cd53f8cb chore(news): Bailey 2019-04-23 15:29:15 -05:00
Sabe Jones
6f7cd96e9f Improved number validation (#11131)
* fix(purchasing): more number validation

* test(purchasing): add error cases
Also refactor NaN check and create client mixin

* fix(purchasing): cover "purchase" cases
2019-04-23 15:19:49 -05:00
Phillip Thelen
821fc1d9c0 Fix 500 server error when trying to log in with wrong username (#11126) 2019-04-23 18:38:56 +02:00
negue
e4eca4b767 Challenges: infinite scroll (#11112)
* debounced infinite scroll on challenges

* add back the normal link color
2019-04-19 16:22:37 +02:00
Matteo Pagliazzi
bd3de3c48c Spells Performances (#11104)
* spells: only select needed fields

* fix tests

* add comment
2019-04-19 16:18:32 +02:00
Sabe Jones
9f09a0396b 4.92.5 2019-04-18 14:16:19 -05:00
Sabe Jones
f16e0bd044 chore(news): Bailey
also conclude email A/B testing
2019-04-18 14:15:28 -05:00
Melior
c91de90fff Update from Weblate (#11118)
* 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 (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1767 of 1767 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% (293 of 293 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.6% (292 of 293 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.5% (480 of 482 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (482 of 482 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% (153 of 153 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% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.4% (65 of 66 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (169 of 169 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% (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 100.0% (128 of 128 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% (144 of 144 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

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

* 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 100.0% (27 of 27 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (217 of 217 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% (211 of 211 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 97.8% (415 of 424 strings)

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

* Translated using Weblate (Italian)

Currently translated at 96.6% (410 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (416 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 98.1% (416 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (French)

Currently translated at 99.7% (423 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 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% (638 of 638 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% (638 of 638 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% (638 of 638 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% (638 of 638 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1766 of 1767 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.9% (1766 of 1767 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 99.8% (637 of 638 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

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

* 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% (56 of 56 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (56 of 56 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Slovak)

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (7 of 7 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (7 of 7 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (7 of 7 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (7 of 7 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 96.6% (410 of 424 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Polish)

Currently translated at 97.6% (414 of 424 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 97.4% (413 of 424 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (126 of 126 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (126 of 126 strings)

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

* 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 100.0% (126 of 126 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (78 of 78 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (78 of 78 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (78 of 78 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (78 of 78 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (78 of 78 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (15 of 15 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/zh_Hant/

* Translated using Weblate (Russian)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/ru/

* Translated using Weblate (Japanese)

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/ja/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (26 of 26 strings)

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

* 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 (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 (Russian)

Currently translated at 100.0% (56 of 56 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Slovak)

Currently translated at 99.6% (332 of 333 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (1767 of 1767 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% (1767 of 1767 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% (1767 of 1767 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1767 of 1767 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (1767 of 1767 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (1767 of 1767 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (1767 of 1767 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (1767 of 1767 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.0% (1751 of 1767 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.3% (1755 of 1767 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.4% (1757 of 1767 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.6% (292 of 293 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (Polish)

Currently translated at 100.0% (293 of 293 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 99.3% (479 of 482 strings)

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

* Translated using Weblate (Polish)

Currently translated at 99.5% (480 of 482 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (482 of 482 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (6 of 6 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (6 of 6 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (153 of 153 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (153 of 153 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (153 of 153 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/zh_Hans/

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/zh_Hant/

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (36 of 36 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (31 of 31 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (31 of 31 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (31 of 31 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (12 of 12 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Danish)

Currently translated at 96.9% (64 of 66 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Polish)

Currently translated at 98.4% (65 of 66 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (4 of 4 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (169 of 169 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (8 of 8 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (8 of 8 strings)

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

* 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 (Chinese (Simplified))

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (144 of 144 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (128 of 128 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (638 of 638 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% (638 of 638 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.6% (636 of 638 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (27 of 27 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% (27 of 27 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (27 of 27 strings)

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

* 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 (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 (English (Pirate))

Currently translated at 100.0% (57 of 57 strings)

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

* 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 (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 (Russian)

Currently translated at 100.0% (57 of 57 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Chinese (Traditional))

Currently translated at 99.5% (216 of 217 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (211 of 211 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% (211 of 211 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% (211 of 211 strings)

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

* Translated using Weblate (Danish)

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (211 of 211 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (English (Pirate))

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/en@pirate/
2019-04-18 13:50:19 -05:00
Sabe Jones
380fb0abf4 fix(tasks): reposition task creation button 2019-04-18 13:09:59 -05:00
Sabe Jones
43b607aedd fix(tasks): force sync instead of explicitly clearing notif 2019-04-18 13:09:59 -05:00
Sabe Jones
4c832ad36c fix(claiming): better sync and notif handling 2019-04-18 13:09:59 -05:00
Sabe Jones
76ae41875d Group Plans quick wins (#11107)
* WIP(groups): quickish wins

* WIP(groups): two quick wins
1. Don't show task creation button if user is not leader or manager
2. Don't require JS confirm() for approving tasks

* fix(group-plans): allow delete from options button

* fix(group-plans): update tasksOrder when task deleted

* fix(group-tasks): dismiss notification when user takes action

* refactor(tasks): DRY out create button styling

* fix(group-tasks): sync after claiming/unclaiming
2019-04-15 10:48:27 -05:00
HydeHunter2
7a5a856ac6 Add message of cancelled quest in party chat (#11106)
* Add message of cancelled quest in party chat

Issue #11093

* Delete trailing spaces

For successful passing the test

* Add test of cancelled quest's message

Also, added an explanation that partyMembers[1] hasn't accepted the invitation in the 'cancels a quest' test

* Fix: import Group

Import Group to pass Lint syntax test

* Move save function to Promise.all

* Fix moving save to Promise.all
2019-04-14 17:55:20 +02:00
Sabe Jones
0bfd709116 4.92.4 2019-04-11 19:55:40 -05:00
Sabe Jones
d7f6c3bc1b fix(challenges): style in challenges too 2019-04-11 19:55:33 -05:00
Sabe Jones
3eb164adcc fix(chat): restore non-contributor styling 2019-04-11 19:55:33 -05:00
Sabe Jones
7bd2cbcf04 4.92.3 2019-04-11 13:10:54 -05:00
Sabe Jones
087498760a fix(modals): upgrade Bootstrap 2019-04-11 13:10:31 -05:00
Sabe Jones
0e26fcce98 4.92.2 2019-04-11 13:05:14 -05:00
Sabe Jones
8d0060d511 chore(news): Bailey 2019-04-11 13:05:04 -05:00
Sabe Jones
0af94b2b44 Merge branch 'develop' of https://github.com/HabitRPG/habitica into develop 2019-04-11 12:35:12 -05:00
Sabe Jones
a7b5b6e20e Revert "fix(equipment): hack partially addressing #11015"
This reverts commit 0be1f3eb7c.
2019-04-11 12:16:16 -05:00
Melior
5f93aad925 Update from Weblate (#11111)
* Translated using Weblate (German)

Currently translated at 100.0% (206 of 206 strings)

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

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

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

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (412 of 417 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

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

Translated using Weblate (German)

Currently translated at 100.0% (309 of 309 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (206 of 206 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (424 of 424 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (137 of 137 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (226 of 226 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (126 of 126 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% (309 of 309 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% (78 of 78 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (15 of 15 strings)

Translation: Habitica/Death
Translate-URL: https://translate.habitica.com/projects/habitica/death/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (26 of 26 strings)

Translation: Habitica/Defaulttasks
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/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 (United Kingdom))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (333 of 333 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (1767 of 1767 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% (293 of 293 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.6% (292 of 293 strings)

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

* Translated using Weblate (Russian)

Currently translated at 99.5% (480 of 482 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (482 of 482 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% (153 of 153 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% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (27 of 27 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (66 of 66 strings)

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

* Translated using Weblate (Russian)

Currently translated at 98.4% (65 of 66 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (169 of 169 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% (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 100.0% (128 of 128 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% (144 of 144 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (638 of 638 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (638 of 638 strings)

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

* 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 100.0% (27 of 27 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (217 of 217 strings)

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

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (217 of 217 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% (211 of 211 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 97.8% (415 of 424 strings)

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

* Translated using Weblate (Italian)

Currently translated at 96.6% (410 of 424 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
2019-04-11 12:13:39 -05:00
negue
4289becccc Performance: My Challenges (#11065)
* use the same paging for "myChallenges" only loads 10 per call

* challenges: show loading above the load-more button (instead at the top)

* purple loading message and flat load-more button

* remove comment

* show loadMore button only when the request has 10 entries

* challenge card ui
2019-04-10 22:09:04 +02:00
Sabe Jones
eeddd3f366 Merge branch 'release' into develop 2019-04-09 16:26:42 -05:00
Sabe Jones
6ce2caecf9 4.92.1 2019-04-09 16:26:26 -05:00
Sabe Jones
6b933914ef fix(pets): disallow hatching quest pets with Wacky potions 2019-04-09 16:25:49 -05:00
Sabe Jones
060e68ef95 fix(tests): content coverage for wacky pots 2019-04-09 15:20:25 -05:00
Sabe Jones
a486ded6dd fix(tests): content coverage for wacky pots 2019-04-09 15:20:05 -05:00
Sabe Jones
7884f4ce9a Merge branch 'release' into develop 2019-04-09 15:03:03 -05:00
Sabe Jones
1c82fa012d 4.92.0 2019-04-09 15:02:35 -05:00
Sabe Jones
924723bce6 feat(content): Garden Potions and Spring Avatar Customizations 2019-04-09 14:59:30 -05:00
Sabe Jones
b696dde6ba fix(veggies): Call them Garden Potions 2019-04-09 14:36:36 -05:00
Sabe Jones
e8d0557cb6 Merge branch 'release' into sabrecat/veggie-potions 2019-04-09 10:28:03 -05:00
Sabe Jones
cdb6acd4a0 fix(veggies): Flying Pig alignment 2019-04-09 10:27:03 -05:00
Matteo Pagliazzi
128bd4b9cd fix(challenges): check if subscribed only when in group 2019-04-07 15:57:11 +02:00
Michael Toth
fc5f6a31ee Updates classlocked gear item description (#11108) 2019-04-07 14:30:23 +02:00
Sabe Jones
1f0fa500bb fix(footer): remove unofficial Reddit link 2019-04-05 17:16:11 -05:00
Sabe Jones
36276d5d3f 4.91.2 2019-04-05 08:43:21 -05:00
Sabe Jones
5c2c87f523 fix(veggies): pinning and sorting 2019-04-04 10:35:22 -05:00
Sabe Jones
c4f2dafc95 Merge branch 'sabrecat/20190401' into sabrecat/veggie-potions 2019-04-04 09:21:05 -05:00
Alys
f09b65e108 add swear word - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc
This adds "pewdiepie" to the list of banned words because
"subscribe to pewdiepie" is semi-common spam in the Tavern,
and because there is sometimes hate speech in the YouTube
channel and in some material posted by pewdiepie's fans.
2019-04-03 20:54:58 +10:00
Randi Miller
a0f42b0e3e Add min-width to PM for wrapping fixes #11060 (#11081) 2019-04-02 16:22:23 -05:00
Matteo Pagliazzi
e10655a5b4 fix(logs): fix FCM logging 2019-04-02 23:19:01 +02:00
Matteo Pagliazzi
d519940931 Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2019-04-02 23:05:16 +02:00
Matteo Pagliazzi
58a43f51d8 downgrade mongoose 2019-04-02 23:04:59 +02:00
Sabe Jones
d25a5fcd57 Merge branch 'release' into develop 2019-04-02 15:55:35 -05:00
Sabe Jones
4085d7a5bb 4.91.1 2019-04-02 15:54:39 -05:00
Matteo Pagliazzi
6c4e1a326f fix(logger): log promise in unhandledRejection event 2019-04-02 22:32:57 +02:00
Sabe Jones
9cf8c0a824 Merge branch 'release' into develop 2019-04-02 13:09:46 -05:00
Sabe Jones
e1f9643ffd 4.91.0 2019-04-02 13:09:16 -05:00
Sabe Jones
1d5c1d5a5f chore(sprites): compile 2019-04-02 13:08:52 -05:00
Sabe Jones
df7c0a005c feat(content): Armoire and Backgrounds March 2019 2019-04-02 13:08:40 -05:00
Matteo Pagliazzi
c60481ab34 Amplitude fixes v2 (#11100)
* fix(analytics): properly catch and log errors

* misc

* refactor

* more refactor

* fallback for user id

* fix tests
2019-04-02 18:44:39 +02:00
Sabe Jones
13818b7634 fix(scripts): update admin migrations
Refactore "full stable" to current format, add email to GDPR deletion output, fix path in bulk email script
2019-04-02 16:35:46 +00:00
Sabe Jones
b2e834c74c Merge branch 'release' into develop 2019-04-02 07:54:59 -05:00
Sabe Jones
4a9cfe8ce5 4.90.4 2019-04-02 07:54:33 -05:00
Sabe Jones
2419b219ba chore(event): end April Fool's
and fix end date of Gala items
2019-04-02 07:53:37 -05:00
Matteo Pagliazzi
0b8ce63c76 WIP: Improve User model performances (#10832)
* wip: define items as mixed objects

* add default owned gear

* mark modified

* more mark modified

* more mark modified

* more mark modified

* more mark modified

* fix common tests

* fix common tests

* update mongoose

* add itemsUtils

* use new util function in hall controller

* add tests for items utils

* update website/server to mark all items as modified

* start updating common code

* update login incentives

* update unlock

* remove changes to package-lock.json

* remove changes to package.json
2019-04-01 19:24:18 +02:00
Sabe Jones
95e541ae75 Merge branch 'release' into develop 2019-04-01 10:27:24 -05:00
Sabe Jones
85c6c19235 4.90.3 2019-04-01 10:26:04 -05:00
Sabe Jones
07e4b2c463 fix(event): include stable 2019-04-01 10:24:55 -05:00
Sabe Jones
7b2081ab03 chore(sprites): compile 2019-04-01 10:17:32 -05:00
Sabe Jones
e749e42665 feat(event): April Fools 2019 2019-04-01 10:17:05 -05:00
negue
0b82722d27 performance: private messages - API (#11077)
* paging for inbox

* clean up
2019-03-31 20:52:53 +02:00
negue
f35ef3a046 Multiple checks for pinnedItems (#11031)
* remove null/undefined entries from pinnedItems when an item is toggled - more inner checks + test

* drawer: fix when there isn't a page available

* rollback cleaning up pinnedEntries on item-toggle

* remove "re-setting" pinnedItems

* remove the filter
2019-03-31 20:41:37 +02:00
Matteo Pagliazzi
5656b9c6ca fix(challenge): allow members to be loaded correctly, fix #11091 2019-03-31 20:36:06 +02:00
Matteo Pagliazzi
1e3d7acf06 update deps 2019-03-31 20:17:10 +02:00
Matteo Pagliazzi
b7e391e074 Merge branch 'develop' of github.com:HabitRPG/habitica into develop 2019-03-31 20:09:12 +02:00
greenkeeper[bot]
b2368e7804 Update superagent to the latest version 🚀 (#11090)
* fix(package): update superagent to version 5.0.2

* chore(package): update lockfile package-lock.json
2019-03-31 20:07:42 +02:00
greenkeeper[bot]
85880d6bb5 Update nodemailer to the latest version 🚀 (#11084)
* fix(package): update nodemailer to version 6.0.0

* chore(package): update lockfile package-lock.json
2019-03-31 20:06:45 +02:00
greenkeeper[bot]
352b8143f3 Update amplitude-js to the latest version 🚀 (#11073)
* fix(package): update amplitude-js to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-03-31 20:06:12 +02:00
Matteo Pagliazzi
7fe3870297 Merge branch 'greenkeeper/karma-4.0.1' into develop 2019-03-31 20:05:26 +02:00
Matteo Pagliazzi
aff32b0e71 Merge branch 'develop' into greenkeeper/karma-4.0.1 2019-03-31 20:05:16 +02:00
Matteo Pagliazzi
4ff56b17e7 Merge branch 'greenkeeper/sinon-7.2.4' into develop 2019-03-31 20:01:38 +02:00
Matteo Pagliazzi
03283d2e52 Merge branch 'develop' into greenkeeper/sinon-7.2.4 2019-03-31 20:01:32 +02:00
Matteo Pagliazzi
07909ac93a Merge branch 'greenkeeper/image-size-0.7.0' into develop 2019-03-31 19:58:51 +02:00
Matteo Pagliazzi
506b155cfa Merge branch 'develop' into greenkeeper/image-size-0.7.0 2019-03-31 19:58:42 +02:00
Matteo Pagliazzi
80bd41928c Merge branch 'greenkeeper/nightwatch-1.0.16' into develop 2019-03-31 19:56:13 +02:00
Matteo Pagliazzi
be3bd25f00 Merge branch 'develop' into greenkeeper/nightwatch-1.0.16 2019-03-31 19:55:57 +02:00
greenkeeper[bot]
3e365f2b4e Update csv-stringify to the latest version 🚀 (#10893)
* fix(package): update csv-stringify to version 5.1.0

* chore(package): update lockfile package-lock.json
2019-03-31 19:50:52 +02:00
greenkeeper[bot]
e1b08e3a20 fix(package): update autoprefixer to version 9.4.0 (#10887) 2019-03-31 19:44:42 +02:00
Matteo Pagliazzi
01281b6414 fix(markdown): make sure to only render strings, fix #11080 2019-03-31 19:38:44 +02:00
Sabe Jones
0b65ac6c4f 4.90.2 2019-03-29 13:57:42 -05:00
Sabe Jones
0d12686a10 chore(news): Bailey 2019-03-29 13:57:02 -05:00
Sabe Jones
8c4d1a67ac 4.90.1 2019-03-28 16:23:28 -05:00
Sabe Jones
e58fbcc34c chore(news): Bailey 2019-03-28 16:23:11 -05:00
Ian Oxley
6fba71ea2c Add :focus styles to match :hover styles
Add :focus styles to the .habitica-menu-dropdown.
These match the existing :hover styles.
2019-03-28 20:38:17 +00:00
Ian Oxley
5755bfc952 Merge branch 'develop' into navbar-a11y-alt-text 2019-03-28 20:19:04 +00:00
Matteo Pagliazzi
1a66a680dc Revert "Add <button> and focus styles for keyboard users" (#11089) 2019-03-27 20:56:00 +01:00
Sabe Jones
fc08b753cd Merge branch 'release' into develop 2019-03-26 20:23:22 -05:00
Sabe Jones
7a73f5bb83 4.90.0 2019-03-26 20:22:52 -05:00
Sabe Jones
6ce1f6f32e chore(sprites): compile 2019-03-26 20:22:35 -05:00
Sabe Jones
50278db1d6 feat(content): Mystery Items March 2019 2019-03-26 20:22:26 -05:00
Sabe Jones
1195560b0c feat(content): Veggie Hatching Potions 2019-03-26 15:45:29 -05:00
Sabe Jones
33c639e28b chore(sprites): compile 2019-03-25 20:19:13 -05:00
Sabe Jones
c2b106564f feat(event): April Fool pronk 2019-03-25 17:26:38 -05:00
Matteo Pagliazzi
fe636b9bd2 Merge pull request #10819 from ianoxley/9796-keyboard-a11y
Add <button> and focus styles for keyboard users
2019-03-24 12:29:07 +01:00
Ian Oxley
7835fe1deb Fix outline issue in Firefox 2019-03-23 22:13:03 +00:00
Ian Oxley
8fb0d0899d Fix checkbox alignment for dailies 2019-03-23 22:12:43 +00:00
Matteo Pagliazzi
abb8dc4dc1 Merge pull request #11078 from ChesterSng/11602-display-message-when-social-auth-acc-uses-password
Fixes #11062 Display information message when social authentication account uses password to login
2019-03-23 18:36:12 +01:00
Matteo Pagliazzi
910a76db68 Merge pull request #11064 from randi2kewl/todo-calendar-highlight-today
Added a highlight on today's date in To-Dos cal
2019-03-23 18:29:15 +01:00
Matteo Pagliazzi
2f792d51f8 Merge pull request #11069 from randi2kewl/11047-hall-search-case-insensitive
Fixes #11047 Hall search case insensitive
2019-03-23 18:22:53 +01:00
Matteo Pagliazzi
6cc925b535 Merge pull request #10988 from HabitRPG/sabrecat/platform-analytics-tweak
Set registered platform on analytics only once
2019-03-23 18:19:15 +01:00
Chester Sng
87d86ee632 Add test case for user that uses social authentication 2019-03-23 20:04:09 +08:00
Randi Miller
b387d77128 Lint fixes 2019-03-22 16:18:58 -04:00
Randi Miller
e644ae83fd Overrides for generateUser and test adjustment 2019-03-22 14:44:27 -04:00
Randi Miller
ae21680a5f Add border-radius to highlighted tile 2019-03-22 14:23:04 -04:00
Randi Miller
c1767eca1b Move datepicker css and color fixes 2019-03-22 14:17:36 -04:00
Chester Sng
d20cd1bbf1 Remove unused string constant in front.js 2019-03-22 19:36:13 +08:00
Chester Sng
3e45f5af41 Add check for the existence of user's password before attempting to authenticate 2019-03-22 19:30:00 +08:00
Bonnie L
23cc2b9d21 catch promise rejection when user submits a message to group. fixes #10970 (#10978)
* catch promise rejection when user submits a message to group

* switch response exception handling to try/catch to avoid mixing aysnc/await with promises
2019-03-21 16:54:10 -05:00
JSn1nj4
58c4fcd506 Fix sellModal data caching (#11044)
* Several deps set to "optional" in lock file

This was done automatically when running `npm i` on Windows.

Node v10.15.2
NPM 6.4.1

* Begin working on issue #10687

Key files to look into:
- website/client/components/inventory/item.vue
- website/client/components/shops/market/sellModal.vue

File notes for me:

item.vue: provides wrapper for displaying item data.

sellModel.vue: for displaying items player intends to sell
- lines 10-21: check `item` computed property and load `item` component
- lines 151-153: ask how return line is evaluated to an `item`

The last one could be important to figuring out this caching issue.
Since the property is computed, it's possible that line is evaluating to
a truthy value that Vue is seeing as unchanged, even if the actual
`item` object is different each time.

* Pick up from sellModal.vue:155

This is where the item context seems to be set. The Vue dev tools
indicate that the `<item>` component is updating. It's only the `item`
reference inside of the `<sellModal>` component that isn't updating.

Issue #10687

* Remove (v-once) directive

This was preventing data referenced within nested elements from
updating.

Issue #10687

* Revert package-lock.json to "develop" version

This removes the "optional" settings that were automatically added by NPM to a handful of dependencies while working on PR #11044.

* Attempt to rerun tests

The most recent change shouldn't have caused tests to fail.
2019-03-21 16:52:00 -05:00
Alec Brickner
2878abc130 Center allocatable stat points (#11053) 2019-03-21 16:49:38 -05:00
Dmitry
a8cb6e3409 Second empty head option was removed from edit avatar - hair - style (#11061) 2019-03-21 16:47:17 -05:00
Alys
2caa540006 allow moderators/staff to always see Report button for a flagged message (#11068) 2019-03-21 16:46:00 -05:00
Sabe Jones
a2261e3591 4.89.0 2019-03-21 16:36:16 -05:00
Sabe Jones
90d498ff96 chore(sprites): compile 2019-03-21 16:36:01 -05:00
Sabe Jones
928327e02a feat(content): Magic Hatching Potions March 2019 2019-03-21 16:34:19 -05:00
Randi Miller
f454700722 extending equipment modal hack to address #11015 (#11070)
* extending equipment modal hack to address #11015

* fix(modal): call hack only once, remove more dangling body props
2019-03-21 13:37:35 -05:00
Matteo Pagliazzi
83bce24e1f Merge branch 'develop' into 9796-keyboard-a11y 2019-03-21 16:05:18 +01:00
Sabe Jones
e7979a99e6 Merge branch 'release' into develop 2019-03-19 19:53:54 -05:00
Sabe Jones
5c648af2ea 4.88.0 2019-03-19 19:53:20 -05:00
Sabe Jones
2ad4bee816 chore(sprites): compile 2019-03-19 19:53:11 -05:00
Sabe Jones
e0291cf432 feat(event): Spring Fling 2019 2019-03-19 19:52:59 -05:00
Randi Miller
bac9121153 Changes color scheme for datepicker fixes #11064 2019-03-19 15:44:06 -04:00
Sabe Jones
d178928c45 fix(analytics): remove excess promise layer 2019-03-18 15:51:49 -05:00
Sabe Jones
9a6aa5f443 merge negue-ui-fixes into develop
Squashed commit of the following:

commit d0628da7557e718b4d144283949f6572718d9b45
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 19:34:39 2019 +0100

    fix quest-tooltip padding/sizes

commit c16bc0fe4521e01520628ddc84af54930b15e066
Merge: 24e3fe2a6 163f31688
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 18:58:51 2019 +0100

    Merge branch 'fix/quest-tooltip-cells' into negue-ui-fixes

commit 24e3fe2a6c8e384722d95f7edd9ce9e4ce16960c
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Mar 15 18:49:47 2019 +0100

    use item margin instead of container padding

commit 03ab975193b2007e24fcbb971fc7d4169bfb3ac7
Author: negue <eugen.bolz@gmail.com>
Date:   Thu Mar 14 21:41:43 2019 +0100

    fix input heights

commit 3aa18f1105a02655066e10a82cf53be9340a6bf1
Author: negue <eugen.bolz@gmail.com>
Date:   Thu Mar 14 20:58:22 2019 +0100

    revert margin-bottom on checklist

commit d8f533fda1d3833ab2f4aeb3bd8001d4a7881e8b
Merge: fbd01ab79 7d45dcfee
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:10:34 2019 -0500

    Merge branch 'fix/shops-ui' into negue-ui-fixes

commit fbd01ab79d5ea8144c191cd51a9c11216418493a
Merge: 65517d259 546e260e3
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:09:03 2019 -0500

    Merge branch 'fix/more-ui' into negue-ui-fixes

commit 65517d25947144f57b84fad84ce709ac7d008119
Merge: 5e935230a 2dcec78a4
Author: Sabe Jones <sabrecat@gmail.com>
Date:   Thu Mar 14 11:08:20 2019 -0500

    Merge branch 'fix/tasks-ui' into negue-ui-fixes

commit 546e260e36
Author: negue <eugen.bolz@gmail.com>
Date:   Sat Mar 9 23:13:54 2019 +0100

    fix checkbox check mark

commit 46ea2010f8
Author: negue <eugen.bolz@gmail.com>
Date:   Sat Mar 9 22:48:20 2019 +0100

    remove bottom margin on a collapsed checklist

commit 7d45dcfee5
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Mar 6 22:45:09 2019 +0100

    fix star/empty colors - fix quest layout - countBadge z-index

commit f9b9e75c18
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 20:14:49 2019 +0100

    shops - timeTravelers: refactor filter logic

commit 6d3e9e0de0
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 20:06:02 2019 +0100

    shops-seasonal: refactor filter logic

commit aa2949e9d1
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 19:54:23 2019 +0100

    shops - quest: refactor filter logic + quest items margins

commit ad62b7a358
Author: negue <eugen.bolz@gmail.com>
Date:   Mon Mar 4 19:50:51 2019 +0100

    shop: equipment-cards background + market filter logic (as other pages)

commit 2dcec78a4a
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 21:02:10 2019 +0100

    remove space between notes and checklist

commit 31fab9b66d
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:45:59 2019 +0100

    remove margin of checklist items

commit 28b134a19b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:33:43 2019 +0100

    show `Options` instead of  `Show More`

commit cafcfb6112
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:30:22 2019 +0100

    remove pointer on disabled task-controls

commit 7acbcc424b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:25:36 2019 +0100

    remove margin of task-title markdown-p-tag

commit f667ab957b
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:25:16 2019 +0100

    40px height search + tags button

commit 2080ecb7e8
Author: negue <eugen.bolz@gmail.com>
Date:   Wed Feb 27 20:24:35 2019 +0100

    show grabbing cursor while dragging

commit 163f316889
Author: negue <eugen.bolz@gmail.com>
Date:   Fri Feb 15 23:26:40 2019 +0100

    replace the questInfo to use table-like behavior, expands to the content
2019-03-18 15:06:32 -05:00
Randi Miller
e277a088ee Added some additional comments 2019-03-17 04:20:21 -04:00
Randi Miller
e083df64e4 fixes #11047 Switches Hall query to use lowercase name 2019-03-17 04:00:50 -04:00
Sabe Jones
dae37c17d6 Merge branch 'release' into develop 2019-03-15 10:37:22 -05:00
Sabe Jones
1d81916674 4.87.3 2019-03-15 10:36:45 -05:00
Sabe Jones
2a225c2376 chore(event): end Pi Day 2019-03-15 10:31:08 -05:00
Ian Oxley
bd9d9d31c3 Update :focus outline for men icons
Make the .svg-icon focus style match its hover style.
2019-03-14 20:44:09 +00:00
Sabe Jones
f1b5ce9c66 Merge branch 'release' into develop 2019-03-14 14:11:25 -05:00
Matteo Pagliazzi
c08b25101b 4.87.2 2019-03-14 19:43:24 +01:00
Matteo Pagliazzi
9fd0e27c66 fix(stripe): fix buying gems 2019-03-14 19:43:10 +01:00
Matteo Pagliazzi
994082a1d8 payments success state: add note about auto renewal 2019-03-14 19:36:56 +01:00
Sabe Jones
0e6e7adb06 4.87.1 2019-03-14 12:26:03 -05:00
Sabe Jones
8486b9631f Merge branch 'develop' into release 2019-03-14 12:25:55 -05:00
Sabe Jones
3f04c8abf5 chore(email): next split test iteration 2019-03-14 12:21:46 -05:00
Sabe Jones
89e1c69728 fix(string): correct set membership for blue hairbow 2019-03-14 11:23:25 -05:00
Sabe Jones
5e935230a4 Merge branch 'release' into develop 2019-03-14 07:32:41 -05:00
Sabe Jones
9ad50be6ca fix(test): update food const 2019-03-14 07:32:32 -05:00
Sabe Jones
1ff3f3d4e7 Merge branch 'release' into develop 2019-03-14 07:05:35 -05:00
Sabe Jones
0b35aefdc9 4.87.0 2019-03-14 07:05:01 -05:00
Sabe Jones
0d374d817c chore(email): end split test 2019-03-14 07:02:45 -05:00
Sabe Jones
034058301d Merge branch 'sabrecat/pi-day' into release 2019-03-14 07:00:52 -05:00
Sabe Jones
ae2d50c5b8 feat(pie): immediate pie for new users 2019-03-14 06:51:47 -05:00
Randi Miller
fff16a86a5 Added a highlight on today's date in To-Dos cal 2019-03-14 03:40:03 -04:00
Matteo Pagliazzi
c7309ae179 fix(amazon): disabled state for button, fix bug where checkout button would not appear 2019-03-13 19:19:30 +01:00
Matteo Pagliazzi
ef42fba049 fix(paypal): button styles 2019-03-13 18:34:41 +01:00
Sabe Jones
d75f926136 Merge branch 'release' into develop 2019-03-12 19:30:52 -05:00
Sabe Jones
78612a91dd 4.86.2 2019-03-12 19:30:09 -05:00
Sabe Jones
8bcd93075b chore(news): Bailey 2019-03-12 19:29:51 -05:00
Sabe Jones
f318afb8cf Revert "replace the questInfo to use table-like behavior, expands to the content (#10995)"
This reverts commit 3ebd37f7cb.
2019-03-12 19:20:56 -05:00
Sabe Jones
b954379f38 Revert "fixes Tasks UI (#11036)"
This reverts commit f548103f4c.
2019-03-12 19:20:40 -05:00
Sabe Jones
e5c060a80b chore(sprites): compile 2019-03-12 17:10:50 -05:00
Sabe Jones
6668aae89b feat(event): Pi Day 2019-03-12 17:10:38 -05:00
Matteo Pagliazzi
9b62804a5c fix tavern/chat ui (#11056)
* add markdown-formatting link - hide textarea if guidelines not accepting - chat ui

* fix item-with-icon height/margins . public guild item member count
2019-03-11 18:32:07 +01:00
negue
d21e29462c fix: shop ui (#11046)
* shop: equipment-cards background + market filter logic (as other pages)

* shops - quest: refactor filter logic + quest items margins

* shops-seasonal: refactor filter logic

* shops - timeTravelers: refactor filter logic

* fix star/empty colors - fix quest layout - countBadge z-index
2019-03-11 18:26:23 +01:00
Matteo Pagliazzi
6bccd2a866 New Payments Buttons (#11045)
* start implementing separate amazon button component

* wio

* switch amazon to new flow

* adjust amazon pay button size

* design buttons and add them to the settings page

* abstract css

* fixes and buy gems modal

* group plans css

* new buttons for gifts, fix padding in settings

* gift modal styles

* final style fixes
2019-03-11 18:23:47 +01:00
negue
caea222330 fix item-with-icon height/margins . public guild item member count 2019-03-09 22:19:55 +01:00
negue
8ecbdc1448 add markdown-formatting link - hide textarea if guidelines not accepting - chat ui 2019-03-09 21:10:20 +01:00
Sabe Jones
ee20b1eea8 4.86.1 2019-03-07 13:30:58 -06:00
Sabe Jones
ff62c6eea0 Merge branch 'develop' into release 2019-03-07 13:30:49 -06:00
Melior
57cd4d44cd Translated using Weblate (German) (#11052)
Currently translated at 100.0% (206 of 206 strings)

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

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

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

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.8% (412 of 417 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (169 of 169 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1753 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (153 of 153 strings)

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

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (211 of 211 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (36 of 36 strings)

Translation: Habitica/Loadingscreentips
Translate-URL: https://translate.habitica.com/projects/habitica/loadingscreentips/ru/

Translated using Weblate (Russian)

Currently translated at 99.7% (1752 of 1757 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (309 of 309 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (137 of 137 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

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

Translated using Weblate (Russian)

Currently translated at 100.0% (417 of 417 strings)

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

Translated using Weblate (German)

Currently translated at 100.0% (309 of 309 strings)

Translation: Habitica/Content
Translate-URL: https://translate.habitica.com/projects/habitica/content/de/
2019-03-07 13:30:19 -06:00
Sabe Jones
e989503cfa Merge branch 'develop' into release 2019-03-07 13:28:34 -06:00
Sabe Jones
431cec7634 fix(strings): remove some trailing spaces 2019-03-07 13:28:05 -06:00
Matteo Pagliazzi
2a367ab3a7 Revert "New payments buttons and new Amazon checkout flow (#10940)"
This reverts commit 77f3bb53de.
2019-03-07 16:58:49 +01:00
Matteo Pagliazzi
bc91dd81e9 Revert "fix(amazon): make sure to use up to date data"
This reverts commit 298a79b58d.
2019-03-07 16:58:38 +01:00
Sabe Jones
201085651b Merge branch 'release' into develop 2019-03-05 15:44:04 -06:00
Sabe Jones
bb395a7ad8 4.86.0 2019-03-05 15:43:35 -06:00
Sabe Jones
264aad79ac chore(sprites): compile 2019-03-05 15:43:29 -06:00
Sabe Jones
9c797e6a54 feat(content): Armoire and Backgrounds March 2019 2019-03-05 15:43:19 -06:00
Sabe Jones
e6c3d00665 fix(emails): send split-test mailings for social reg accounts 2019-03-05 14:55:45 -06:00
negue
660928323e fix header design (#11013)
* statsbar-component for a closer tooltip

* fix scss scope, twice
2019-03-04 22:01:45 +01:00
Matteo Pagliazzi
298a79b58d fix(amazon): make sure to use up to date data 2019-03-04 22:00:40 +01:00
Matteo Pagliazzi
77f3bb53de New payments buttons and new Amazon checkout flow (#10940)
* start implementing separate amazon button component

* wio

* switch amazon to new flow
2019-03-04 21:51:17 +01:00
Alec Brickner
17d8a7b706 Fix mismatch between pet and star badge (#11041) 2019-03-03 17:48:51 +01:00
Alec Brickner
1d8a5b1952 Keep mystery items synced with server (#11039) 2019-03-03 17:39:42 +01:00
negue
f548103f4c fixes Tasks UI (#11036)
* show grabbing cursor while dragging

* 40px height search + tags button

* remove margin of task-title markdown-p-tag

* remove pointer on disabled task-controls

* show `Options` instead of  `Show More`

* remove margin of checklist items

* remove space between notes and checklist
2019-03-03 17:28:58 +01:00
negue
2595ccb2b4 refactor filters to work like in guilds/challenges (#11023) 2019-03-03 17:24:59 +01:00
negue
ceb4288b17 Footer Design / Instagram Link (#11022)
* re-add instagram icon+link

* fix donate button hover - smaller icons if not desktop

* lower margin on the small icons

* Update appFooter.vue

remove DevBlog link from footer
2019-03-03 17:22:55 +01:00
negue
3ebd37f7cb replace the questInfo to use table-like behavior, expands to the content (#10995) 2019-03-03 17:17:45 +01:00
Matteo Pagliazzi
ff3df55639 Merge branch 'negue/flagpm' into develop 2019-03-03 17:12:19 +01:00
Matteo Pagliazzi
080c4b3e20 Merge branch 'develop' into negue/flagpm 2019-03-03 17:12:11 +01:00
Alec Brickner
e939799800 Ensure profile page gets synced (#11032) 2019-03-03 17:05:38 +01:00
Sabe Jones
b7797b3e6c 4.85.5 2019-03-01 16:22:33 -06:00
Sabe Jones
55bd35d7d3 chore(monthly): announce Challenges, end potions 2019-03-01 16:22:19 -06:00
Sabe Jones
b9ae03f795 4.85.4 2019-02-28 16:02:53 -06:00
Sabe Jones
75f6398de2 chore(news): Bailey 2019-02-28 16:02:46 -06:00
greenkeeper[bot]
4004887ddd chore(package): update lockfile package-lock.json 2019-02-28 17:23:10 +00:00
greenkeeper[bot]
ef4d761e0c chore(package): update karma to version 4.0.1 2019-02-28 17:23:04 +00:00
Sabe Jones
a1fb702d1e Merge branch 'release' into develop 2019-02-27 16:17:23 -06:00
Sabe Jones
868759d3e8 4.85.3 2019-02-27 16:17:01 -06:00
Matteo Pagliazzi
45a9d6d17b ios: cancel invalid subscriptions without errors (#11035) 2019-02-27 18:48:13 +01:00
Matteo Pagliazzi
cc766d2260 gcp stackdriver tracing: attach user id (#11033) 2019-02-27 18:47:33 +01:00
Sabe Jones
e710a00e74 Merge branch 'release' into develop 2019-02-26 17:53:19 -06:00
Sabe Jones
bad06ba449 4.85.2 2019-02-26 17:52:59 -06:00
Sabe Jones
ccb088e127 Merge branch 'sabrecat/email-split' into release 2019-02-26 17:52:48 -06:00
Sabe Jones
4a4d48aed8 chore(email): expand to 4 variants 2019-02-26 10:14:55 -06:00
Sabe Jones
da8006506b Merge branch 'release' into develop 2019-02-26 10:09:26 -06:00
Sabe Jones
d451d01b18 4.85.1 2019-02-26 10:08:41 -06:00
Sabe Jones
53555a0f16 fix(navbar): update b-vue toggle syntax 2019-02-26 10:07:48 -06:00
Sabe Jones
962236846a Merge branch 'release' into develop 2019-02-25 17:00:41 -06:00
Sabe Jones
aae8f2923c 4.85.0 2019-02-25 17:00:13 -06:00
Sabe Jones
61956336ea chore(sprites): compile 2019-02-25 16:59:37 -06:00
Sabe Jones
0be1f3eb7c fix(equipment): hack partially addressing #11015 2019-02-25 16:59:28 -06:00
Sabe Jones
fe04b56ecc feat(content): February Mystery Items 2019-02-25 16:58:56 -06:00
Matteo Pagliazzi
7e772924f6 4.84.8 2019-02-24 14:06:19 +01:00
Sabe Jones
901d11c61f fix(logging): move Stackdriver init higher up (#11025) 2019-02-24 14:05:59 +01:00
Matteo Pagliazzi
80b15ac5e9 4.84.7 2019-02-23 18:22:59 +01:00
Matteo Pagliazzi
78b49b9c7e gcp: add stackdriver tracing (#11024) 2019-02-23 18:20:09 +01:00
Matteo Pagliazzi
8874558827 fix package-lock.json 2019-02-23 11:05:20 +01:00
Sabe Jones
ea5ce64db6 fix(email): use updated slugs
and genericize logic for easier tweaking later
2019-02-22 15:47:24 -06:00
Sabe Jones
10b69986c0 chore(email): split test for welcome message 2019-02-22 15:13:32 -06:00
Cameron Billings
2d35009bee Update conditional render to only show Apply Sort Options to Party Header text in party modals (#10984) 2019-02-21 13:35:17 -06:00
Chester Sng
d267f09d04 Enable Username to be searched in Hall of Heroes - fixes #10972 (#10980)
* Add if block to search for username if not valid uuid

* Add validationError check

* Modify test case and added test case for username

* Update description of API

* Update Test

* Correct test

* Change placeholder text in heroes.vue

* Refactor code

* Add quotes

* Update hall.js
2019-02-21 13:33:15 -06:00
Sabe Jones
f23dcf59ff 4.84.6 2019-02-21 12:46:58 -06:00
Sabe Jones
8eb9402c0e fix(achievements): better coloration by piyo 2019-02-21 12:44:30 -06:00
Sabe Jones
a7f2579f6c 4.84.5 2019-02-20 14:35:41 -06:00
Sabe Jones
ada09f3d5a fix(challenges): use b-dropdown-form 2019-02-20 14:17:34 -06:00
Sabe Jones
8fdee5a669 4.84.4 2019-02-19 18:07:08 -06:00
Sabe Jones
3e4d245eba chore(sprites): compile 2019-02-19 18:06:30 -06:00
Sabe Jones
b21cd4a2b6 feat(content): Mythical Marvels Bundle
and end Valentine's miscellany
2019-02-19 18:06:21 -06:00
greenkeeper[bot]
e956bbdf79 chore(package): update lockfile package-lock.json 2019-02-18 15:45:04 +00:00
greenkeeper[bot]
446154b97f chore(package): update sinon to version 7.2.4 2019-02-18 15:44:58 +00:00
Sabe Jones
18de42b13d 4.84.3 2019-02-15 16:07:56 -06:00
Sabe Jones
98fd509530 Hotfix: correct issues from PRs rollout (#10993)
* fix(various): correct issues from PRs rollout
1. Send Gems modal opens from profiles again
2. Contributor titles appear on hover again
3. Tags dropdown in tasks appears again
4. Only user's own @mentions get highlighted again

* fix(test): correct order of css classes in expect
2019-02-15 16:01:33 -06:00
Sabe Jones
d932d6d448 4.84.2 2019-02-14 16:06:08 -06:00
Sabe Jones
be4c777382 fix(hall): bogus msg reference and undef fn
Also couple of styling fixes
2019-02-14 15:51:15 -06:00
Sabe Jones
375a1f3156 4.84.1 2019-02-14 13:41:16 -06:00
Sabe Jones
ca2f2ba9ce Merge branch 'develop' into release 2019-02-14 13:39:52 -06:00
Sabe Jones
165ca8737b chore(news): Blog Bailey 2019-02-14 13:25:40 -06:00
Sabe Jones
f137342d88 fix(analytics): still more registeredThrough cleanup 2019-02-12 16:12:53 -06:00
Sabe Jones
94db493974 fix(analytics): don't also send prop along with event 2019-02-12 16:07:14 -06:00
Sabe Jones
ee5c761680 fix(analytics): set reg platform only once 2019-02-12 16:04:14 -06:00
Sabe Jones
b711c1672b fix(lint): use curly apostrophe and single quotes 2019-02-12 13:48:53 -06:00
Sabe Jones
d675e80555 fix(lint): use curly apostrophe and single quotes 2019-02-12 13:48:13 -06:00
Sabe Jones
2c1ca7629d Merge branch 'release' into develop 2019-02-12 07:35:56 -06:00
Sabe Jones
b5d5367363 4.84.0 2019-02-12 07:20:20 -06:00
Sabe Jones
13af1fa88d chore(sprites): compile 2019-02-12 07:20:11 -06:00
Sabe Jones
b721155f01 feat(event): Valentine's 2019 and Magic Hatching Potions 2019-02-12 07:19:58 -06:00
Sabe Jones
b8aacc03e3 Upgrade to NPM Amplitude package (#10952)
* chore(analytics): upgrade to NPM Amplitude pkg

* chore(build): run package-lock on Linux

* refactor(analytics): use preferred Amplitude v3+ syntax

* refactor(analytics): more v3+ syntax

* chore(npm): attempt updating package lock
2019-02-10 19:34:52 +01:00
negue
844d3fbf37 refresh gear overview (#10971)
* refresh bought seasonal gear

* just "subscribe" to the _v change instead of returning the value

* subscribe in vue instead of lib
2019-02-10 19:32:14 +01:00
Sabe Jones
4d1b239231 Track specific items dropped by Armoire (#10977)
* feat(analytics): track specific items dropped by Armoire

* fix(lint): remove console log

* fix(analytics): address test failures

* fix(analytics): add missed if block

* fix(analytics): seriously tho actually fix
2019-02-10 19:30:49 +01:00
negue
b9aaccdf13 refactored the highlightUsers-method, only matches mentions and not emails (#10982) 2019-02-10 19:27:22 +01:00
Matteo Pagliazzi
0155491a68 Upgrade vue (#10983)
* deps: update

* fix tests
2019-02-10 19:03:35 +01:00
Sabe Jones
ef412c7185 Merge branch 'release' into develop 2019-02-08 06:08:05 -06:00
Sabe Jones
c15b55808e 4.83.2 2019-02-08 06:07:41 -06:00
Sabe Jones
28ddebf4a9 fix(sprites): load new spritesheet in Vue 2019-02-08 06:06:58 -06:00
Sabe Jones
5f0919d1c5 4.83.1 2019-02-07 16:32:02 -06:00
negue
0cbd6fb4d7 Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/header/menu.vue
2019-02-07 19:16:10 +01:00
Matteo Pagliazzi
2eab8b2c8b Merge branch 'GiacomoLaw-10954-wikiafix' into develop 2019-02-07 18:20:33 +01:00
Matteo Pagliazzi
b35bd18282 Merge branch '10954-wikiafix' of https://github.com/GiacomoLaw/habitica into GiacomoLaw-10954-wikiafix 2019-02-07 18:20:05 +01:00
Chester Sng
732a46d2db Add else block so items with warning notes will not display sell options (#10965) 2019-02-07 18:02:38 +01:00
Justin Horner
4f1d4aa73a Move hr to be contained within hasClass div (#10964) 2019-02-07 17:58:46 +01:00
negue
92f2079b76 fix emails in chat (#10912)
* additional regex checks to ignore the <tag attribute="content">

* extract highlightUsers method - add test

* refactor highlight regex

* refactor the regex without `lookbehind`

* remove unneeded regex
2019-02-07 17:55:36 +01:00
Phillip Thelen
63f5773172 Implement URL handling for profile modal (#10844)
* Implement URL handling for profile modal

* Fix issue where paths would break when using back button

* move tiers import to index
2019-02-07 17:25:30 +01:00
Sabe Jones
93290ec6d5 Merge branch 'release' into develop 2019-02-05 15:44:40 -06:00
Sabe Jones
be6c2a002f 4.83.0 2019-02-05 15:42:57 -06:00
Sabe Jones
e6bd67a53a chore(sprites): compile 2019-02-05 15:42:46 -06:00
Sabe Jones
6ab3bac96c feat(content): Armoire and BGs 2019-02-05 15:42:33 -06:00
Sabe Jones
ee97da1112 Merge branch 'release' into develop 2019-02-05 17:49:22 +00:00
Sabe Jones
3c948beb84 chore(i18n): update locales 2019-02-05 17:43:50 +00:00
Sabe Jones
f590c485dc Merge branch 'release' into develop 2019-02-04 16:52:53 -06:00
Sabe Jones
64063544e6 4.82.3 2019-02-04 16:52:28 -06:00
Sabe Jones
0910f65fc0 chore(news): Bailey 2019-02-04 16:52:17 -06:00
Sabe Jones
fdc0e0f5fe Merge branch 'release' into develop 2019-02-02 13:11:25 -06:00
Sabe Jones
d225a7ca54 4.82.2 2019-02-02 13:11:05 -06:00
Sabe Jones
5b7c4bf03f fix(event): end Cake 2019-02-02 13:10:56 -06:00
Sabe Jones
dddd8269b6 Merge branch 'release' into develop 2019-02-02 13:04:35 -06:00
Sabe Jones
fd724a36ff 4.82.1 2019-02-02 13:03:13 -06:00
Sabe Jones
5b329db357 chore(event): end Winter Wonderland 2019-02-02 13:03:04 -06:00
Sabe Jones
c1cad5c0a9 Merge branch 'release' into develop 2019-01-31 17:12:21 -06:00
Sabe Jones
94b5ed9dab 4.82.0 2019-01-31 17:11:53 -06:00
Sabe Jones
0ac976e8c1 chore(sprites): compile 2019-01-31 17:11:44 -06:00
Sabe Jones
b1f42dcac9 feat(content): Habitica Birthday 2019 2019-01-31 17:10:56 -06:00
Sabe Jones
ed8bd84257 Merge branch 'release' into develop 2019-01-30 09:14:11 -06:00
Sabe Jones
4e8c08ba9b 4.81.1 2019-01-30 09:13:39 -06:00
Sabe Jones
e294ed836d fix(subscription): correct mystery set 2019-01-30 09:13:17 -06:00
Sabe Jones
c86da9783b Merge branch 'release' into develop 2019-01-30 09:06:42 -06:00
Sabe Jones
64a608e7e7 4.81.0 2019-01-28 16:10:57 -06:00
Sabe Jones
767f844cea chore(sprites): compile 2019-01-28 16:10:38 -06:00
Sabe Jones
00a686dcf6 feat(content): subscriber items Jan 2019 2019-01-28 16:10:22 -06:00
negue
0ca3c1f94d fix POST-chat.test (author_name format) 2019-01-27 21:29:45 +01:00
negue
2f699e24d7 update test to use the correct author_name format 2019-01-27 19:05:00 +01:00
negue
faa0611ab2 Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm 2019-01-27 18:14:51 +01:00
Giacomo Lawrance
08c8f26b80 Merge branch 'develop' into 10954-wikiafix 2019-01-26 18:03:50 +00:00
Carl Vuorinen
8904c58510 Make profile modal nav tabs better responsive (#10946)
* Make profile modal nav tabs better responsive

Remove offset & specific width  and use flexbox centering
Fixes #10944

* Change profile modal nav height to min-height

So that height changes accordingly if nav elements wrap to second row
2019-01-26 18:59:17 +01:00
greenkeeper[bot]
d10f1304de Update nodemailer to the latest version 🚀 (#10922)
* fix(package): update nodemailer to version 5.0.0

* chore(package): update lockfile package-lock.json
2019-01-26 18:51:52 +01:00
Lucas Heim
e28992060c Staff sweetness duplicated two handed description (#10936)
* Creating default encryption test to improve coverage

* Revert "Creating default encryption test to improve coverage"

This reverts commit 5d5f8efabb.

* Removing two-handed item duplicated description from all locales
2019-01-26 18:48:21 +01:00
Sabe Jones
df860c9401 fix(quests): repair negative quest scrolls on purchase (#10953) 2019-01-26 18:46:52 +01:00
Mira-M
87923b7f0d Add username to challenge csv export (#10956)
- Adds the member's username to the challenge csv
- Updates to unit tests
- Issue #10955

Supports the ability for challenge owners to judge challenge winners
after the added functionality enabling users to modify their name
(display name). The added field ties the user to their results by
their username.
2019-01-26 18:44:48 +01:00
Sabe Jones
b2d6a9474d 4.80.9 2019-01-24 13:35:46 -06:00
Sabe Jones
5fac4a943c chore(news): Bailey 2019-01-24 13:35:32 -06:00
Sabe Jones
5d0be7bc72 Merge branch 'release' into develop 2019-01-24 13:14:19 -06:00
Giacomo Lawrance
9e6394c38c changed wikia links to fandom
Fixes issue #10954
2019-01-24 11:21:52 +00:00
Matteo Pagliazzi
cc97935ffd 4.80.8 2019-01-23 17:20:05 +01:00
Matteo Pagliazzi
6ea4d96830 add extra condition to skip ssl check 2019-01-23 17:19:57 +01:00
Matteo Pagliazzi
a63ba51497 4.80.7 2019-01-23 17:12:13 +01:00
Matteo Pagliazzi
04a7fd25a6 allow skipping SSL check with secret key (#10962) 2019-01-23 17:10:11 +01:00
Sabe Jones
7cb045781b 4.80.6 2019-01-18 15:13:34 -06:00
Sabe Jones
f8d799d55c chore(news): Bailey 2019-01-18 15:13:25 -06:00
Sabe Jones
5bd9fcc99d Merge branch 'release' into develop 2019-01-18 14:59:37 -06:00
Alessio Libardi
84cafc4081 Special Pets that you don't own will only show GreyPaw print | Fixes #10866 (#10949)
* Added isSpecial function to determine wether a pet is special or not

* Special pets that you don't own show only grey pawprint

* PetEgg HatchingPotion icon only visible if the pet is not special
2019-01-18 14:58:38 -06:00
andrzejZdobywca
d77a17112c setting new password using Return key (#10937) 2019-01-18 14:58:18 -06:00
Matteo Pagliazzi
4f9d97d38f 4.80.5 2019-01-18 12:10:15 +01:00
Matteo Pagliazzi
40060e8ff5 update ssl check 2019-01-18 11:43:02 +01:00
Sabe Jones
508d97d374 4.80.4 2019-01-16 10:22:14 -06:00
Sabe Jones
fd125352b7 fix(g1g1): remove additional banner 2019-01-16 10:22:00 -06:00
Sabe Jones
f8bd1be4a3 4.80.3 2019-01-16 06:24:07 -06:00
Sabe Jones
ec37524164 chore(promo): end G1G1 deal 2019-01-15 18:57:10 -06:00
negue
c66d2cb469 refactor properties - added another "inbox" check 2019-01-14 23:17:26 +01:00
Sabe Jones
87b9e72b56 4.80.2 2019-01-14 12:39:03 -06:00
Sabe Jones
962662fe7c chore(news): Bailey 2019-01-14 12:38:35 -06:00
Matteo Pagliazzi
f63d2e47f0 do not show "Notification not found" error messages (#10931)
* start fixing #10391

* do not show snackbars for notifications not found

* revert message changes

* update tests

* add new test

* fix property
2019-01-13 11:43:02 +01:00
Sabe Jones
349a1032b6 4.80.1 2019-01-11 20:00:31 +00:00
Sabe Jones
476131835d chore(i18n): update locales 2019-01-11 19:59:54 +00:00
Sabe Jones
8237b7f2de chore(news): Bailey 2019-01-11 13:37:58 -06:00
Sabe Jones
6ee2b3690a chore(i18n): update locales 2019-01-10 23:28:33 +00:00
Sabe Jones
a08cca807a 4.80.0 2019-01-09 01:38:46 +00:00
Sabe Jones
8c51f36784 chore(i18n): update locales 2019-01-09 01:35:24 +00:00
Sabe Jones
d35f81cdae chore(sprites): compile 2019-01-08 19:30:49 -06:00
Sabe Jones
1d1b25391f feat(content): customizations and dinosaurs 2019-01-08 19:30:41 -06:00
greenkeeper[bot]
ee09c76c08 chore(package): update lockfile package-lock.json 2019-01-08 11:19:57 +00:00
greenkeeper[bot]
c478748436 fix(package): update image-size to version 0.7.0 2019-01-08 11:19:52 +00:00
negue
61606cb69d change to User ID - change mail var _DISPLAYNAME _DISPLAY_NAME 2019-01-04 21:04:41 +01:00
Sabe Jones
ec81c02d72 4.79.0 2019-01-04 20:04:19 +00:00
Sabe Jones
166da3c2f8 chore(i18n): update locales 2019-01-04 20:04:12 +00:00
Sabe Jones
23b72a673d chore(sprites): compile 2019-01-04 13:59:27 -06:00
Sabe Jones
d22b4bb2f7 feat(content): Armoire and Backgrounds Jan 2019 2019-01-04 13:58:51 -06:00
Sabe Jones
aaebd4da77 4.78.2 2019-01-03 22:31:27 +00:00
Sabe Jones
b128e7874e chore(i18n): update locales 2019-01-03 22:31:16 +00:00
Juliusdotsh
1e10e20a24 apidocs should say how to provide comment when flagging #10916 (#10925) 2019-01-03 11:31:03 +01:00
Sabe Jones
b1aeb8ed87 Merge branch 'release' into develop 2019-01-02 17:08:50 -06:00
Sabe Jones
2cb80e2275 4.78.1 2019-01-02 22:14:24 +00:00
Sabe Jones
ee32e24ff2 chore(i18n): update locales 2019-01-02 22:14:05 +00:00
Sabe Jones
af40c437be chore(news): Bailey
also fix erroneously active hatching potion and remove concluded bundle 
from pinned items
2019-01-02 16:10:57 -06:00
Sabe Jones
a99150c485 Merge branch 'release' into develop 2019-01-01 07:14:04 -06:00
Sabe Jones
696b67204d fix(lint): ’ 2019-01-01 07:13:41 -06:00
Sabe Jones
4f3536e887 Merge branch 'release' into develop 2018-12-31 23:48:59 +00:00
Sabe Jones
07e5bf1437 4.78.0 2018-12-31 23:48:21 +00:00
Sabe Jones
d2ca738256 chore(i18n): update locales 2018-12-31 23:48:11 +00:00
Sabe Jones
f7983f39eb feat(event): New Year's 2018-19 2018-12-31 17:44:13 -06:00
Rene Cordier
7c954f7073 Prevent progress being cleared when quest ends (#10870)
* Prevent progress being cleared when quest ends

changing group tests to make sure it keeps user's progress

fix and remove .only() from tests

* fix tests and check null case for clearing up user's quest without resetting progress
2018-12-28 19:16:21 +01:00
Sabe Jones
82d0e737a6 4.77.6 2018-12-27 15:47:36 +00:00
Sabe Jones
f8213aaf1b chore(i18n): update locales 2018-12-27 15:47:25 +00:00
Matteo Pagliazzi
2335ad4167 fix(gems modal): remove duplicate id property, fix #10919 2018-12-27 15:04:14 +01:00
negue
d84631255b extract site urls from translations (#10909) 2018-12-23 19:56:21 +01:00
Raymond Grumney
0b352b9103 Unit Tests; 1 fix to appFooter debug menu (#10894)
* Built

* Update .editorconfig

* Fixed filename ('translaotr.js' => 'translator.js')

* Fixed Filename

* User Unit Tests

* Writing tests for avatar.vue

* writing tests for getters/user.js

* Writing Tests for avatar.vue

* Writing Tests for avatar.vue

* writing tests for store/getters/user.js

* renamed file

* Integrated test into user.test.js

* Reverting

* Fetching most recent version

* +1 Level now adds a level

* updating to current version

* Minor edits to pass lint test

* Removing non-functional Tests

* Cleaning up
2018-12-23 19:45:59 +01:00
negue
19b75c6257 drag tags to reorder (#10911)
* drag tags to reorder

* change color, remove unneeded sortTag-call
2018-12-23 19:45:05 +01:00
negue
b66904a3a7 notification click always shows the modal, setting only controls the notification timeout (#10913) 2018-12-23 19:35:30 +01:00
Phillip Thelen
cfbfec34aa Allow gems and subs to be gifted through in-app-purchases (#10892)
* Allow gems to be gifted through IAPs

* implement non recurring IAP subscriptions

* fix localization issue in error

* fix non renewing subscription handling

* Fix lint error

* fix tests

* move findbyId mock to helper file

* undo package-lock changes

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

* feat(subscription): promo banner on main page

* fix(banners): remove extraneous margin adjustment

* fix(banners): various

* feat(promotion): gift 1, get 1

* fix(promo): various

* chore(promo): add Bailey

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

* fix(promo): turns out Winter is meaningful
2018-12-18 15:28:53 -06:00
Sabe Jones
bd1f6918ba fix(sprites): wolf and penguin tweaks 2018-12-18 12:22:46 -06:00
Ian Oxley
3d99a64e96 Fix outline around dropdown toggle menu in Firefox
When the menu is open and has the focus shift the outline to the
`.habitica-menu-dropdown-toggle` child element.
2018-12-18 15:58:16 +00:00
Ian Oxley
76f9204417 Update CSS to fix notification layout
Set `.row` inside a `.notification` to use `width: 100%`, and set margin
to zero.
2018-12-18 15:31:45 +00:00
Matteo Pagliazzi
5b21e62647 fix(i18n): format 2018-12-18 15:02:03 +01:00
Matteo Pagliazzi
5e76d6df21 fix(i18n): position of $ sign 2018-12-17 13:12:35 +01:00
greenkeeper[bot]
fad59b9a8d chore(package): update lockfile package-lock.json 2018-12-16 20:42:47 +00:00
greenkeeper[bot]
f218a432ec chore(package): update nightwatch to version 1.0.16 2018-12-16 20:42:43 +00:00
Matteo Pagliazzi
7348145b7d fix(success-modal): misc fixes 2018-12-16 15:39:37 +01:00
Sabe Jones
ba2832d21f 4.75.5 2018-12-14 21:22:05 +00:00
Sabe Jones
688f5084a1 chore(i18n): update locales 2018-12-14 21:19:56 +00:00
Sabe Jones
d5955b8889 Merge branch 'develop' into release 2018-12-14 15:15:06 -06:00
Sabe Jones
01fd17ee3f fix(script): revert email query 2018-12-14 15:14:50 -06:00
Sabe Jones
979497dd35 fix(deletion): user delete bugs
Correct lookup in GDPR script, and address a TypeError when deleting a user with no tasks
2018-12-14 00:54:59 +00:00
Sabe Jones
1d3db244ba 4.75.4 2018-12-11 18:26:58 -06:00
Sabe Jones
80ca074352 fix(deploy): correct Dockerfile 2018-12-11 18:26:07 -06:00
Sabe Jones
6d4f9e0759 4.75.3 2018-12-11 20:48:40 +00:00
Sabe Jones
d096695559 Usernames Misc: Bulk Email and New Party Modal (#10898)
* feat(migrations): genericize bulk email

* chore(migrations): archive one-offs

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

* fix(modal): styling

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

* chore(migrations): archive one-offs

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

* fix(modal): styling

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

* gems purchase success dialog

* amazon and stripe support for gems message, translations

* subscriptions success message

* subscriptions success message

* group plans success message and many fixes

* finish success messages for payments

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

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

Add `isPressed` computed function. Use this to set the value of
`aria-pressed`.
2018-11-26 22:34:15 +00:00
Ian Oxley
53d8d2fc6a Fix :focus CSS for the dropdown menu
Add `:focus` style to the `.habitica-menu-dropdown` class, instead of
the `.habitica-menu-dropdown-toggle` class.
2018-11-26 22:31:57 +00:00
Sabe Jones
dbe2143b7a fix(mystery-items): add missing set sprite 2018-11-26 14:23:18 -06:00
Sabe Jones
7b687280d7 Merge branch 'release' into develop 2018-11-26 19:10:05 +00:00
Sabe Jones
8db6c8bd4f 4.73.2 2018-11-26 19:09:42 +00:00
Sabe Jones
bf91dacb94 chore(i18n): update locales 2018-11-26 19:01:55 +00:00
Sabe Jones
33e0892e95 chore(event): end Thanksgiving tweaks 2018-11-26 12:59:23 -06:00
Phillip Thelen
42b146d5d0 Attach client to chat messages (#10845)
* Attach client to chat messages

* Word

* Design tweaks

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

* Fixes #10857 and #10856 display tag markdown.
2018-11-26 10:44:41 +01:00
Matteo Pagliazzi
6181328ac1 feat(footer): always show expanded footer (#10862) 2018-11-26 10:37:29 +01:00
Ian Oxley
08c6e8298c Fix linting errors
Remove parentheses and add trailing comma.
2018-11-24 17:12:12 +00:00
Ian Oxley
5a30b0cf1f Add aria-label to Habitica logo 2018-11-23 22:44:31 +00:00
Ian Oxley
cf847cd1d8 Add aria-label to notifaction and user menus
Add aria-label to give a text equivalent for non-visual users.
2018-11-23 22:44:31 +00:00
Ian Oxley
5753d3e648 Improve a11y for the dropdown menu
Add role="button" to make the component report itself as a button.

Add tabindex so the menu toggle can receive keyboard focus.

Add keydown handlers for `<Enter>` and `<Space>` so the dropdown menu
toggle responds to keyboard input.

Set the aria-pressed attribute to true if the menu is open, or false if
it is closed.
2018-11-23 22:44:30 +00:00
Ian Oxley
4718e5e5ea Improve a11y for the sync links
Add `role="link"` so it shows up as a link in VoiceOver.

Add `tabindex="0" so it can receive keyboard focus, and a keyup handler
for the enter key so it will respond to `<Enter>` keypresses.

Add `aria-label="$t('sync')"` to add text for non-visual users.

Add aria-label to mobile sync icon link.
2018-11-23 22:44:06 +00:00
Ian Oxley
9a2dbace30 Add aria-label to Gems and Gold icons
Add `aria-label` attributes to the gems and gold icons in the menu.

Make the gems icon a link, with a href set to `#buy-gems`, which is the element
that contains the gems dialog.
2018-11-23 22:30:43 +00:00
Sabe Jones
c64d4b0914 4.73.1 2018-11-23 20:20:56 +00:00
Sabe Jones
f94fd0d69d chore(i18n): update locales 2018-11-23 20:17:56 +00:00
Sabe Jones
2cd66436bc Merge branch 'release' into develop 2018-11-22 18:05:28 -06:00
Sabe Jones
81a17738b8 4.73.0 2018-11-22 21:09:31 +00:00
Sabe Jones
b6b953ec46 chore(i18n): update locales 2018-11-22 21:09:20 +00:00
Sabe Jones
ab34c83a9d chore(sprites): compile 2018-11-22 15:05:33 -06:00
Sabe Jones
9cea86f4e0 feat(content): Turkey Day 2018 2018-11-22 15:05:03 -06:00
Sabe Jones
1b7a705bf9 Merge branch 'paglias/migration-no-recursion' into release 2018-11-22 13:44:46 -06:00
negue
e2c5b9058b more checks on the item.klass, also added the specialClass checks (#10859) 2018-11-22 14:35:34 +01:00
Matteo Pagliazzi
cc751960ac save state and close tab afetr success 2018-11-21 11:42:32 +01:00
Matteo Pagliazzi
433c73c9d3 paypal: restore url after purchase 2018-11-21 11:25:36 +01:00
Matteo Pagliazzi
680c2162a7 paypal: new redirects 2018-11-21 11:19:55 +01:00
Alys
a2b38ffb02 add two slurs - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc 2018-11-21 19:48:50 +10:00
Matteo Pagliazzi
6395870c00 fix(stable): remove progress number from petItem 2018-11-21 10:35:59 +01:00
Sabe Jones
9562ba432f Merge branch 'release' into develop 2018-11-20 21:02:17 +00:00
Sabe Jones
8a3a83de37 4.72.0 2018-11-20 21:01:41 +00:00
Sabe Jones
745edd731d chore(i18n): update locales 2018-11-20 20:59:23 +00:00
Sabe Jones
53b195931c chore(sprites): compile 2018-11-20 14:55:56 -06:00
Sabe Jones
37ae467fff feat(content): Frost Hatching Potions 2018-11-20 14:55:45 -06:00
negue
52a7112591 some ui fixes / requested changes 2018-11-20 17:20:58 +01:00
negue
d7d7d64b45 move computed-props to methods - refactor mountItem to use the states inside (#10853) 2018-11-20 12:28:26 +01:00
Ian Oxley
fd13771088 Set background on the dropdown toggle
Set the background to `$white` to stop Firefox falling back to the
default grey background for a button.
2018-11-19 19:40:41 +00:00
Ian Oxley
c9d725ec20 Fix +ve / -ve habit control buttons
Align the icons centrally within the positive / negative controls.
2018-11-19 19:34:01 +00:00
negue
3203bffeaa refactor update the message to use .save - fix test 2018-11-18 22:37:09 +01:00
negue
3f47cdd9a2 merge fixes 2018-11-18 22:12:41 +01:00
negue
8b15d94ae1 Merge remote-tracking branch 'origin/develop' into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/chat/chatMessages.vue
#	website/common/locales/en/messages.json
2018-11-18 22:04:33 +01:00
Matteo Pagliazzi
e76bdbd62d Fix for #10814, prevent ParallelSave errors (#10852)
* fix(group leave): prevent ParallelSave errors while leaving a group with multiple group or challenge tasks

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

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

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

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

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

* fix lint
2018-11-08 11:11:04 +01:00
Matteo Pagliazzi
ee2858199b update(http-proxy-middleware): update to 0.19, closes #10650 2018-11-08 11:08:42 +01:00
Sabe Jones
b1dd79f75c fix(invites): bogus validation errors 2018-11-07 15:58:17 -06:00
Sabe Jones
1ac4dd8171 feat(usernames): invite by username 2018-11-07 15:00:22 -06:00
Sabe Jones
4f86abd6b2 Merge branch 'develop' into sabrecat/usernames-master 2018-11-07 08:39:57 -06:00
Sabe Jones
23b0688abb Merge branch 'develop' into sabrecat/usernames-master 2018-11-06 19:56:45 -06:00
Sabe Jones
38efe83cc7 fix(usernames): various
Partial fixage for autocomplete @ing
Don't add username to chat message if user is unverified
Fix flying pets
Fix console error about avatar intro
2018-11-06 19:55:06 -06:00
Ian Oxley
302bc899d7 Update reward button and toggle menu
Add `<button>` tag to the 3 dots toggle menu, and the reward control.

Update the CSS to add focus styles, in addition to hover styles, for the
3 dots toggle menu.
2018-11-07 00:06:30 +00:00
Ian Oxley
8048cf9a97 Add <button> tag to shop items
Make the `.item` element a `<button>` so it can receive keyboard focus,
and be activated via the spacebar.
2018-11-06 23:24:34 +00:00
Ian Oxley
20548daccf Use <button> for habits +/- buttons
Use the `<button>` tag instead of a `<div>`. This makes the element
naturally focusable with the keyboard, and makes the HTML more semantic.

Update the CSS to align the icons in the centre of the `<button>`.
2018-11-06 22:55:51 +00:00
Sabe Jones
2dadd74097 Merge branch 'release' into develop 2018-11-06 16:53:35 -06:00
Sabe Jones
9f494360ef Merge branch 'release' into develop 2018-11-06 15:02:24 -06:00
Sabe Jones
4122bbdecf 4.69.1 2018-11-06 15:02:03 -06:00
Sabe Jones
256a3abc26 fix(event): de-Festivalize class change modal 2018-11-06 15:01:57 -06:00
Sabe Jones
b7f3c0f389 Merge branch 'release' into develop 2018-11-06 20:43:12 +00:00
Sabe Jones
dca00bf4b7 4.69.0 2018-11-06 20:42:50 +00:00
Sabe Jones
d31c8913d3 chore(i18n): update locales 2018-11-06 20:40:54 +00:00
Sabe Jones
d839d57299 chore(sprites): compile 2018-11-06 14:32:55 -06:00
Sabe Jones
ccc3b4d337 feat(content): Armoire and Backgrounds 2018/11 2018-11-06 14:31:50 -06:00
Matteo Pagliazzi
7195ac15b9 fix(group-plan-tasks): show checkmark when task completed 2018-11-05 15:04:45 +01:00
Sabe Jones
a5ef6a129e fix(auth): new users should start verified 2018-11-03 16:03:53 -05:00
Sabe Jones
38f5d63d29 fix(likes): accessibility tweaks 2018-11-03 13:16:32 -05:00
Sabe Jones
43194b71ce fix(usernames): RYA positioning and contrib tier in PMs 2018-11-03 12:50:59 -05:00
Matteo Pagliazzi
e4a347a3cb update gulp-nodemon 2018-11-03 14:08:46 +01:00
Sabe Jones
7eaf3e04ab fix(modals): button styling 2018-11-02 16:48:35 -05:00
Sabe Jones
b6b03751c4 fix(modals): maybe got it?!? 2018-11-02 16:27:08 -05:00
Sabe Jones
818d5e4eb6 fix(modals): better stack??? 2018-11-02 14:58:32 -05:00
Sabe Jones
f871c7cf63 fix(modal): various 2018-11-02 14:26:39 -05:00
Kirsty
e9eddec0c4 check pet is hatchable before highlighting (#10797) 2018-11-02 17:16:11 +01:00
Nathanael Farley
a48a6a292d If user's cron will happen later today, start the task yesterday. (#10783)
* If user's cron will happen later today, start the task yesterday.

* Added default dayStart to taskDefaults.

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

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

This reverts commit e1467f2fc33cfb11e6a4fc667460df6a48b69d45.

* Removed defaults from taskDefault arguments.

* Got user from $store in copyAsTodoModal.vue.

* Fixed tests for taskDefaults to include mock user.

* Fix shouldDo tests when run in GMT timezone.

* Added test to taskDefault; added utcOffset to taskDefault.

* Replaced utcOffset with zone.

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

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

* sinon changes

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

* Made new webhook API info translateable.
2018-10-26 16:46:36 +02:00
Sabe Jones
95b283676a Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-25 05:32:41 -05:00
Sabe Jones
6e7e81206a fix(profile): better username/UUID styling 2018-10-25 05:29:02 -05:00
Sabe Jones
af74cc7c64 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-24 20:01:00 -05:00
Sabe Jones
a9e2a17077 fix(chat): no min height on autocomplete dropdown 2018-10-24 19:59:54 -05:00
Sabe Jones
92057dbe17 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-24 19:41:21 -05:00
Sabe Jones
b4ab525be5 fix(chat): better @ searching, no chat card borders 2018-10-24 19:39:50 -05:00
Sabe Jones
d3c464d5ea Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-24 18:40:43 -05:00
Sabe Jones
804fe1c6d5 fix(usernames): various
z-index modals above Resting banner
force reload after verify username
add missing e-mail validation on frontpage
let Yesterdaily modal float behind username modal
2018-10-24 18:39:54 -05:00
Erdenesukh Tsendjav
3d757c7814 trying something 2018-10-24 16:31:46 -05:00
negue
3c5025a78e fix background empty / resetCallback - refactor pet methods/components 2018-10-24 20:43:15 +02:00
Sabe Jones
cd9630332d Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-22 16:30:12 -05:00
Sabe Jones
ed21a37e5a feat(usernames): show in party header, profiles, inbox convo list 2018-10-22 16:29:47 -05:00
negue
16256ee190 fix issues 2018-10-22 21:22:26 +02:00
Sabe Jones
fdecc8ce16 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-19 17:44:52 -05:00
Sabe Jones
3cc49f6637 fix(chat): match hyphen in @name regex 2018-10-19 17:44:18 -05:00
Sabe Jones
47f49f4256 Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-19 16:22:11 -05:00
Sabe Jones
4f4bb52360 fix(chat): padding adjustment and tooltip placement 2018-10-19 16:20:13 -05:00
Sabe Jones
3748b3046b Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-19 16:04:23 -05:00
Sabe Jones
5cd0f56811 fix(usernames): let verify modal grow to content 2018-10-19 16:03:48 -05:00
negue
456c5e57bc refactor petItem - pet image states 2018-10-18 19:58:14 +02:00
Sabe Jones
185b20995a Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-17 18:31:16 -05:00
Sabe Jones
fdf2e590ea fix(chat): better likes and display name font sizing 2018-10-17 18:28:58 -05:00
Sabe Jones
994123c387 chore(sprites): compile 2018-10-17 15:32:20 -05:00
Sabe Jones
273590716c Merge branch 'sabrecat/veteran-verified' into sabrecat/usernames-master 2018-10-17 15:29:44 -05:00
Sabe Jones
6818a094ee Merge branch 'sabrecat/username-display' into sabrecat/usernames-master 2018-10-17 15:29:35 -05:00
Sabe Jones
c99855cef4 Merge branch 'sabrecat/force-username-modal' into sabrecat/usernames-master 2018-10-17 15:29:23 -05:00
Sabe Jones
6845943ed0 feat(usernames): vet pet announcement 2018-10-17 15:27:54 -05:00
Sabe Jones
044fe17757 feat(usernames): Veteran Pet ladder award for affected users 2018-10-17 14:43:33 -05:00
Sabe Jones
ad0ede8d01 fix(model): don't break if auth.local undefined 2018-10-17 13:59:39 -05:00
Sabe Jones
23815e89e1 WIP(usernames): display in chat areas 2018-10-17 12:57:57 -05:00
Sabe Jones
cfd19ac694 fix(usernames): various
Better modal positioning
Correct text colors for input field
Don't show "already taken" if username has other errors
2018-10-15 11:27:49 -05:00
SabreCat
0897ab5dc9 fix(settings): don't center align display name issues 2018-10-14 23:25:03 +00:00
Sabe Jones
5c6e8a7331 fix(usernames): add display name verification to site settings
also corrrect some validation logic in force-verify modal
2018-10-14 23:06:41 +00:00
Sabe Jones
c576c5261e fix(username): Add @ prepend 2018-10-14 12:21:52 -05:00
negue
2c250bfcd9 fix tests / lint 2018-10-14 15:28:00 +02:00
Sabe Jones
bbd98517ff fix(test): copypasta and string trouble 2018-10-13 21:36:14 -05:00
Sabe Jones
392b54aa7b fix(usernames): remove more ratzen fratzen dupe strings 2018-10-13 21:05:07 -05:00
Sabe Jones
60b26d4ec0 fix(test): string is miscapitalized :( 2018-10-13 20:56:09 -05:00
Sabe Jones
fa1fef11d6 feat(usernames): modal to force verification 2018-10-13 12:53:16 -05:00
negue
75e3f15352 remove the v3 api method 2018-10-10 20:50:10 +02:00
negue
5670be26c7 review fixes 2018-10-10 20:46:43 +02:00
negue
9fc03cb91a cleanup 2018-10-08 19:08:14 +02:00
Mateus Etto
ef07abfd28 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/chat.js
#	website/server/models/group.js
2018-10-08 18:44:02 +09:00
negue
7e80406181 fix flagging in the new inbox collection - move flag private message to api/v4 2018-10-02 21:23:29 +02:00
negue
60a6f6f2f6 fix merge issues 2018-09-30 15:37:14 +02:00
negue
92d68e5c6e Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/components/chat/chatCard.vue
#	website/client/components/chat/chatMessages.vue
#	website/common/locales/en/messages.json
#	website/server/libs/slack.js
2018-09-22 19:18:08 +02:00
Keith Holliday
1c51e62e43 Merged in develop 2018-09-10 09:42:51 -05:00
Mateus Etto
b28adc6b42 Merge branch 'develop' into party-chat-translations 2018-08-29 20:07:19 +09:00
Keith Holliday
f049d29d1b Added username invite 2018-08-16 17:40:53 -05:00
Mateus Etto
c814eabb29 Merge branch 'develop' into party-chat-translations 2018-07-21 16:06:38 +09:00
Mateus Etto
4dc19a8c60 Merge branch 'develop' into party-chat-translations 2018-07-12 06:28:44 +09:00
negue
6e7d8d93fe fix test import 2018-06-25 20:23:04 +02:00
negue
bc861133e1 fix doc path 2018-06-25 18:48:43 +02:00
negue
31ef21f25c Merge branch 'develop' of https://github.com/HabitRPG/habitica into negue/flagpm
# Conflicts:
#	website/client/store/actions/chat.js
2018-06-25 18:47:47 +02:00
Mateus Etto
89f6a9b07e Merge branch 'develop' into party-chat-translations
# Conflicts:
#	test/api/unit/models/group.test.js
2018-06-20 20:24:38 +09:00
Matteo Pagliazzi
fd700f92ae remove node 8 2018-06-18 10:17:38 +02:00
Matteo Pagliazzi
f41665f5a9 update deps 2018-06-18 10:04:03 +02:00
Matteo Pagliazzi
8b385c0b7b update deps 2018-06-18 10:01:09 +02:00
Matteo Pagliazzi
282f8db933 Merge branch 'develop' into greenkeeper/update-to-node-10 2018-06-18 09:57:39 +02:00
Matteo Pagliazzi
662b08c242 update engines 2018-06-18 09:57:35 +02:00
negue
97e1465899 fix ci - revert reporter to user var 2018-06-13 22:55:28 +02:00
negue
7cb0f5145d update margin / color 2018-06-11 22:53:43 +02:00
negue
5b6217a0bf pass userId for admin-calls 2018-06-11 22:39:07 +02:00
negue
09e4c88606 add tests + fix lint 2018-06-08 21:50:52 +02:00
negue
13bae96708 hide report button if already reported 2018-06-08 21:16:11 +02:00
negue
6241001eef remove unneeded try/catch 2018-06-08 21:03:26 +02:00
negue
dc5722d0de report & mark message 2018-06-08 20:26:26 +02:00
negue
e3b2443029 move reportFlagModal to menu.vue 2018-06-08 19:15:21 +02:00
negue
ca5927fe73 sendInboxFlagNotification (for private message flag content) 2018-06-02 19:27:28 +02:00
negue
5a0eed7eae copy chatReporter - fix unknown apiMessages - add api for flagging pm 2018-06-02 18:18:00 +02:00
negue
532881e679 enable flagging private message - v-once for simple strings/icons - fix flag notification 2018-06-02 18:16:24 +02:00
negue
27d763a46c add slack debug calls (logger.info) 2018-06-02 18:14:53 +02:00
Mateus Etto
a23926f34f Merge branch 'develop' into party-chat-translations 2018-05-19 10:21:54 +09:00
Mateus Etto
118198b594 Fixed tests that contains random data 2018-05-19 00:26:28 +09:00
Mateus Etto
97e80e2093 Implemented requested changes 2018-05-18 23:08:37 +09:00
Mateus Etto
2588befceb Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-05-06 00:46:47 +09:00
Matteo Pagliazzi
b7e601be16 Merge branch 'develop' into greenkeeper/update-to-node-10 2018-04-27 19:49:11 +02:00
greenkeeper[bot]
395676fcb1 Update engines to node 10 in package.json 2018-04-27 11:41:03 +00:00
greenkeeper[bot]
cc4df1c995 Update to node 10 in .nvmrc 2018-04-27 11:41:01 +00:00
greenkeeper[bot]
48eada2c37 Update to node 10 in .travis.yml 2018-04-27 11:40:58 +00:00
Mateus Etto
f09274225a Update chat schema: add 'info' field 2018-04-25 21:28:02 +09:00
Mateus Etto
03b66abe70 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/quests.js
#	website/server/controllers/api-v3/tasks/groups.js
#	website/server/controllers/api-v3/user/spells.js
#	website/server/models/group.js
2018-04-25 21:15:49 +09:00
Mateus Etto
9d41fb0252 Add tests + some small adjusts 2018-03-25 00:01:20 +09:00
Mateus Etto
012fa2f8ef Improve readability of translateSystemMessages function 2018-03-18 12:09:03 +09:00
Mateus Etto
00d8d9d0cc Merge branch 'develop' into party-chat-translations 2018-03-17 00:08:20 +09:00
Mateus Etto
cc4772c75a Update test 2018-03-12 23:48:09 +09:00
Mateus Etto
fabaaa6d92 Merge branch 'develop' into party-chat-translations 2018-03-12 22:35:13 +09:00
Mateus Etto
854696728a Fix message when boss don't attack 2018-03-12 21:50:23 +09:00
Mateus Etto
314926cc06 Hard coded messages now using i18n strings 2018-03-12 21:45:41 +09:00
Mateus Etto
68fa834946 spellName -> spell; grammar fix 2018-03-12 21:08:20 +09:00
Mateus Etto
09440d5adf Merge branch 'develop' into party-chat-translations 2018-03-02 19:28:41 +09:00
Mateus Etto
1724bfc553 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/models/group.js
2018-02-28 19:42:19 +09:00
Mateus Etto
8304f99ecb Added comment explaining the new info object 2018-02-26 20:00:47 +09:00
Mateus Etto
d2fcdf4493 Merge branch 'develop' into party-chat-translations
# Conflicts:
#	website/server/controllers/api-v3/user.js
2018-02-26 19:55:32 +09:00
Mateus Etto
649404ac6a Fix travis-ci error 2018-02-19 20:57:31 +09:00
Mateus Etto
774db2564f Moving some strings around 2018-02-19 20:17:03 +09:00
Mateus Etto
521a1e646d Missing message (user claim task) 2018-02-19 20:07:05 +09:00
Mateus Etto
2619ac37d9 Fix mistake on tavern_boss_rage_tired 2018-02-19 00:36:49 +09:00
Mateus Etto
2ce9b319a0 Target username instead of uuid 2018-02-19 00:24:30 +09:00
Mateus Etto
477c23dd67 Save username instead of uuid (no queries necessary anymore) 2018-02-19 00:18:29 +09:00
Mateus Etto
7af71d1457 Fix errors 2018-02-18 19:44:17 +09:00
Mateus Etto
3171550de2 Fix syntax problems found when running tests 2018-02-18 17:14:18 +09:00
Mateus Etto
6477801d3e Translation support for missing sendChat calls 2018-02-18 15:26:27 +09:00
Mateus Etto
14798ced82 Translation support for a lot of messages in party 2018-02-17 22:17:08 +09:00
Mateus Etto
34d37cefcc Boss damage messages translation support 2018-02-17 19:41:15 +09:00
Mateus Etto
bd21933cea Quest started translation support 2018-02-17 17:15:14 +09:00
2072 changed files with 68603 additions and 52623 deletions

View File

@@ -4,7 +4,7 @@
# Pull Request
[Please see these instructions for adding a pull request](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
[Please see these instructions for adding a pull request](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API)
# Requesting a feature
@@ -12,4 +12,4 @@ Habitica uses [Trello](https://trello.com/b/EpoYEYod/habitica) to track feature
# Contributing Code
See [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)
See [Contributing to Habitica](http://habitica.fandom.com/wiki/Contributing_to_Habitica#Coders_.28Web_.26_Mobile.29)

View File

@@ -1,4 +1,4 @@
[//]: # (Note: See http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Note: See http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API for more info)
[//]: # (Put Issue # here, if applicable. This will automatically close the issue if your PR is merged in)
Fixes put_#_and_issue_numer_here

1
.gitignore vendored
View File

@@ -40,6 +40,7 @@ test/client/unit/coverage
test/client/e2e/reports
test/client-old/spec/mocks/translations.js
yarn.lock
.gitattributes
# Elastic Beanstalk Files
.elasticbeanstalk/*

2
.nvmrc
View File

@@ -1 +1 @@
8
10

View File

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

View File

@@ -1,4 +1,4 @@
FROM node:8
FROM node:10
ENV ADMIN_EMAIL admin@habitica.com
ENV AMAZON_PAYMENTS_CLIENT_ID amzn1.application-oa2-client.68ed9e6904ef438fbc1bf86bf494056e
@@ -8,6 +8,7 @@ ENV BASE_URL https://habitica.com
ENV FACEBOOK_KEY 128307497299777
ENV GA_ID UA-33510635-1
ENV GOOGLE_CLIENT_ID 1035232791481-32vtplgnjnd1aufv3mcu1lthf31795fq.apps.googleusercontent.com
ENV LOGGLY_CLIENT_TOKEN ab5663bf-241f-4d14-8783-7d80db77089a
ENV NODE_ENV production
ENV STRIPE_PUB_KEY pk_85fQ0yMECHNfHTSsZoxZXlPSwSNfA

View File

@@ -1,18 +1,5 @@
FROM node:8
# Install global packages
RUN npm install -g gulp-cli mocha
# Clone Habitica repo and install dependencies
RUN mkdir -p /usr/src/habitrpg
WORKDIR /usr/src/habitrpg
RUN git clone https://github.com/HabitRPG/habitica.git /usr/src/habitrpg
RUN cp config.json.example config.json
RUN npm install
# Create Build dir
RUN mkdir -p ./website/build
# Start Habitica
EXPOSE 3000
CMD ["npm", "start"]
FROM node:10
WORKDIR /code
COPY package*.json /code/
RUN npm install
RUN npm install -g gulp-cli mocha

View File

@@ -7,6 +7,6 @@ Habitica [![Build Status](https://travis-ci.org/HabitRPG/habitica.svg?branch=dev
We need more programmers! Your assistance will be greatly appreciated.
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths).
For an introduction to the technologies used and how the software is organized, refer to [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths).
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
To set up a local install of Habitica for development and testing on various platforms, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).

View File

@@ -8,4 +8,4 @@ minimal dependencies on the developer's local platform. It can be used
on a variety of systems including Windows, Mac OS X, and Linux.
Instructions for using the Habitica Vagrant environment are in
[Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
[Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).

View File

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

View File

@@ -1,14 +1,45 @@
version: "3"
services:
client:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "run", "client:dev"]
depends_on:
- server
environment:
- NODE_ENV=development
- BASE_URL=http://server:3000
image: habitica
networks:
- habitica
ports:
- "8080:8080"
volumes:
- '.:/usr/src/habitrpg'
- .:/code
- /code/node_modules
server:
build:
context: .
dockerfile: ./Dockerfile-Dev
command: ["npm", "start"]
depends_on:
- mongo
environment:
- NODE_ENV=development
- NODE_DB_URI=mongodb://mongo/habitrpg
image: habitica
networks:
- habitica
ports:
- "3000:3000"
volumes:
- '.:/usr/src/habitrpg'
- .:/code
- /code/node_modules
mongo:
image: mongo:3.4
networks:
- habitica
ports:
- "27017:27017"
networks:
habitica:
driver: bridge

View File

@@ -16,7 +16,7 @@ const IMG_DIST_PATH = 'website/client/assets/images/sprites/';
const CSS_DIST_PATH = 'website/client/assets/css/sprites/';
function checkForSpecialTreatment (name) {
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame/;
let regex = /^hair|skin|beard|mustach|shirt|flower|^headAccessory_special_\w+Ears|^eyewear_special_\w+TopFrame|^eyewear_special_\w+HalfMoon/;
return name.match(regex) || name === 'head_0';
}

View File

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

View File

@@ -19,7 +19,7 @@ const Timer = require('./utils/timer');
const connectToDb = require('./utils/connect').connectToDb;
const closeDb = require('./utils/connect').closeDb;
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.wikia.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
const message = '`This party\'s collection quest has been made easier! For details, refer to http://habitica.fandom.com/wiki/User_blog:LadyAlys/Collection_Quests_are_Now_Easier`';
const timer = new Timer();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,88 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190131_habit_birthday';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const inc = {
'items.food.Cake_Skeleton': 1,
'items.food.Cake_Base': 1,
'items.food.Cake_CottonCandyBlue': 1,
'items.food.Cake_CottonCandyPink': 1,
'items.food.Cake_Shade': 1,
'items.food.Cake_White': 1,
'items.food.Cake_Golden': 1,
'items.food.Cake_Zombie': 1,
'items.food.Cake_Desert': 1,
'items.food.Cake_Red': 1,
'achievements.habitBirthdays': 1,
};
const set = {};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.armor_special_birthday2018 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2019'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2019', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2017 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2018'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2018', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2016 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2017'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2017', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday2015 !== 'undefined') {
set['items.gear.owned.armor_special_birthday2016'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2016', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_birthday !== 'undefined') {
set['items.gear.owned.armor_special_birthday2015'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday2015', _id: uuid()}};
} else {
set['items.gear.owned.armor_special_birthday'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_birthday', _id: uuid()}};
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: push}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-01-15')},
};
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,62 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190530_halfmoon_glasses';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {
'items.gear.owned.eyewear_special_blackHalfMoon': true,
'items.gear.owned.eyewear_special_blueHalfMoon': true,
'items.gear.owned.eyewear_special_greenHalfMoon': true,
'items.gear.owned.eyewear_special_pinkHalfMoon': true,
'items.gear.owned.eyewear_special_redHalfMoon': true,
'items.gear.owned.eyewear_special_whiteHalfMoon': true,
'items.gear.owned.eyewear_special_yellowHalfMoon': true,
};
set.migration = MIGRATION_NAME;
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$set: set}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-05-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],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

@@ -0,0 +1,59 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190618_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();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-05-18')},
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

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

View File

@@ -17,7 +17,7 @@ function setUpServer () {
setUpServer();
// Replace this with your migration
const processUsers = require('../scripts/gdpr-delete-users.js');
const processUsers = require('./users/mystery-items.js');
processUsers()
.then(function success () {
process.exit(0);

View File

@@ -1,93 +0,0 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181023_veteran_pet_ladder';
import { model as User } from '../../website/server/models/user';
function processUsers (lastId) {
let query = {
migration: {$ne: MIGRATION_NAME},
'flags.verifiedUsername': true,
};
let fields = {
'items.pets': 1,
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
return User.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
count++;
let set = {migration: MIGRATION_NAME};
if (user.items.pets['Bear-Veteran']) {
set['items.pets.Fox-Veteran'] = 5;
} else if (user.items.pets['Lion-Veteran']) {
set['items.pets.Bear-Veteran'] = 5;
} else if (user.items.pets['Tiger-Veteran']) {
set['items.pets.Lion-Veteran'] = 5;
} else if (user.items.pets['Wolf-Veteran']) {
set['items.pets.Tiger-Veteran'] = 5;
} else {
set['items.pets.Wolf-Veteran'] = 5;
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return user.update({_id: user._id}, {$set: set}).exec();
}
function displayData () {
console.warn(`\n${count} users processed\n`);
return exiting(0);
}
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
}
}
process.exit(code);
}
module.exports = processUsers;

View File

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

View File

@@ -1,67 +1,24 @@
/* eslint-disable no-console */
const MIGRATION_NAME = 'full-stable';
import each from 'lodash/each';
import keys from 'lodash/keys';
import content from '../../website/common/script/content/index';
const migrationName = 'full-stable.js';
const authorName = 'Sabe'; // in case script author needs to know when their ...
const authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; // ... own data is done
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
/*
* Award users every extant pet and mount
*/
const connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE
let monk = require('monk');
let dbUsers = monk(connectionString).get('users', { castIds: false });
function processUsers (lastId) {
// specify a query to limit the affected users (empty for all users):
let query = {
'profile.name': 'SabreCat',
};
if (lastId) {
query._id = {
$gt: lastId,
};
}
dbUsers.find(query, {
sort: {_id: 1},
limit: 250,
fields: [
], // specify fields we are interested in to limit retrieved data (empty if we're not reading data):
})
.then(updateUsers)
.catch((err) => {
console.log(err);
return exiting(1, `ERROR! ${ err}`);
});
}
let progressCount = 1000;
let count = 0;
function updateUsers (users) {
if (!users || users.length === 0) {
console.warn('All appropriate users found and modified.');
displayData();
return;
}
let userPromises = users.map(updateUser);
let lastUser = users[users.length - 1];
return Promise.all(userPromises)
.then(() => {
processUsers(lastUser._id);
});
}
function updateUser (user) {
async function updateUser (user) {
count++;
let set = {
migration: migrationName,
};
const set = {};
set.migration = MIGRATION_NAME;
each(keys(content.pets), (pet) => {
set[`items.pets.${pet}`] = 5;
@@ -88,30 +45,40 @@ function updateUser (user) {
set[`items.mounts.${mount}`] = true;
});
dbUsers.update({_id: user._id}, {$set: set});
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (count % progressCount === 0) console.warn(`${count } ${ user._id}`);
if (user._id === authorUuid) console.warn(`${authorName } processed`);
return await User.update({_id: user._id}, {$set: set}).exec();
}
function displayData () {
console.warn(`\n${ count } users processed\n`);
return exiting(0);
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.local.username': 'olson22',
};
function exiting (code, msg) {
code = code || 0; // 0 = success
if (code && !msg) {
msg = 'ERROR!';
}
if (msg) {
if (code) {
console.error(msg);
} else {
console.log(msg);
const fields = {
_id: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
}
process.exit(code);
}
module.exports = processUsers;
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

View File

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

View File

@@ -0,0 +1,73 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190314_pi_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++;
const inc = {
'items.food.Pie_Skeleton': 1,
'items.food.Pie_Base': 1,
'items.food.Pie_CottonCandyBlue': 1,
'items.food.Pie_CottonCandyPink': 1,
'items.food.Pie_Shade': 1,
'items.food.Pie_White': 1,
'items.food.Pie_Golden': 1,
'items.food.Pie_Zombie': 1,
'items.food.Pie_Desert': 1,
'items.food.Pie_Red': 1,
};
const set = {};
set.migration = MIGRATION_NAME;
set['items.gear.owned.head_special_piDay'] = false;
set['items.gear.owned.shield_special_piDay'] = false;
const push = [
{type: 'marketGear', path: 'gear.flat.head_special_piDay', _id: uuid()},
{type: 'marketGear', path: 'gear.flat.shield_special_piDay', _id: uuid()},
];
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({_id: user._id}, {$inc: inc, $set: set, $push: {pinnedItems: {$each: push}}}).exec();
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
'auth.timestamps.loggedin': {$gt: new Date('2019-02-15')},
};
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,81 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20181203_take_this';
import { v4 as uuid } from 'uuid';
import { model as User } from '../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
const set = {};
let push;
set.migration = MIGRATION_NAME;
if (typeof user.items.gear.owned.back_special_takeThis !== 'undefined') {
push = false;
} else if (typeof user.items.gear.owned.body_special_takeThis !== 'undefined') {
set['items.gear.owned.back_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.back_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.head_special_takeThis !== 'undefined') {
set['items.gear.owned.body_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.body_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.armor_special_takeThis !== 'undefined') {
set['items.gear.owned.head_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.head_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.weapon_special_takeThis !== 'undefined') {
set['items.gear.owned.armor_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.armor_special_takeThis', _id: uuid()}};
} else if (typeof user.items.gear.owned.shield_special_takeThis !== 'undefined') {
set['items.gear.owned.weapon_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.weapon_special_takeThis', _id: uuid()}};
} else {
set['items.gear.owned.shield_special_takeThis'] = false;
push = {pinnedItems: {type: 'marketGear', path: 'gear.flat.shield_special_takeThis', _id: uuid()}};
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
if (push) {
return await User.update({_id: user._id}, {$set: set, $push: push}).exec();
} else {
return await User.update({_id: user._id}, {$set: set}).exec();
}
}
module.exports = async function processUsers () {
let query = {
migration: {$ne: MIGRATION_NAME},
challenges: '00708425-d477-41a5-bf27-6270466e7976',
};
const fields = {
_id: 1,
items: 1,
};
while (true) { // eslint-disable-line no-constant-condition
const users = await User // eslint-disable-line no-await-in-loop
.find(query)
.limit(250)
.sort({_id: 1})
.select(fields)
.lean()
.exec();
if (users.length === 0) {
console.warn('All appropriate users found and modified.');
console.warn(`\n${count} users processed\n`);
break;
} else {
query._id = {
$gt: users[users.length - 1],
};
}
await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop
}
};

8209
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,20 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "4.67.1",
"version": "4.101.1",
"main": "./website/server/index.js",
"dependencies": {
"@google-cloud/trace-agent": "^3.6.0",
"@slack/client": "^3.8.1",
"accepts": "^1.3.5",
"amazon-payments": "^0.2.7",
"amplitude": "^3.5.0",
"amplitude-js": "^5.0.0",
"apidoc": "^0.17.5",
"apn": "^2.2.0",
"autoprefixer": "^8.5.0",
"aws-sdk": "^2.329.0",
"axios": "^0.18.0",
"autoprefixer": "^9.4.0",
"aws-sdk": "^2.432.0",
"axios": "^0.19.0",
"axios-progress-bar": "^1.2.0",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
@@ -26,16 +28,16 @@
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^3.0.1",
"bcrypt": "^3.0.5",
"body-parser": "^1.18.3",
"bootstrap": "^4.1.1",
"bootstrap-vue": "^2.0.0-rc.9",
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"bootstrap-vue": "^2.0.0-rc.18",
"compression": "^1.7.4",
"cookie-session": "^1.3.3",
"coupon-code": "^0.4.5",
"cross-env": "^5.1.5",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"csv-stringify": "^4.3.1",
"csv-stringify": "^5.1.0",
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
@@ -46,30 +48,30 @@
"got": "^9.0.0",
"gulp": "^4.0.0",
"gulp-babel": "^7.0.1",
"gulp-imagemin": "^4.1.0",
"gulp-nodemon": "^2.2.1",
"gulp-imagemin": "^6.0.0",
"gulp-nodemon": "^2.4.1",
"gulp.spritesmith": "^6.9.0",
"habitica-markdown": "^1.3.0",
"hellojs": "^1.15.1",
"hellojs": "^1.18.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.10.2",
"image-size": "^0.7.0",
"in-app-purchase": "^1.11.3",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"js2xmlparser": "^4.0.0",
"lodash": "^4.17.10",
"merge-stream": "^1.0.0",
"merge-stream": "^2.0.0",
"method-override": "^3.0.0",
"moment": "^2.22.1",
"moment-recur": "^1.0.7",
"mongoose": "^5.3.4",
"mongoose": "^5.4.19",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^1.0.2",
"node-sass": "^4.9.0",
"nodemailer": "^4.6.4",
"ora": "^2.1.0",
"pageres": "^4.1.1",
"nodemailer": "^6.0.0",
"ora": "^3.2.0",
"pageres": "^5.1.0",
"passport": "^0.4.0",
"passport-facebook": "^2.0.0",
"passport-google-oauth20": "1.0.0",
@@ -80,42 +82,42 @@
"ps-tree": "^1.0.0",
"pug": "^2.0.3",
"rimraf": "^2.4.3",
"sass-loader": "^7.0.0",
"sass-loader": "^7.0.3",
"shelljs": "^0.8.2",
"short-uuid": "^3.0.0",
"smartbanner.js": "^1.9.1",
"smartbanner.js": "^1.11.0",
"stripe": "^5.9.0",
"superagent": "^3.8.3",
"superagent": "^5.0.2",
"svg-inline-loader": "^0.8.0",
"svg-url-loader": "^2.3.2",
"svgo": "^1.0.5",
"svgo": "^1.2.0",
"svgo-loader": "^2.1.0",
"universal-analytics": "^0.4.16",
"universal-analytics": "^0.4.17",
"update": "^0.7.4",
"upgrade": "^1.1.0",
"url-loader": "^1.0.0",
"useragent": "^2.1.9",
"uuid": "^3.0.1",
"validator": "^10.5.0",
"validator": "^11.0.0",
"vinyl-buffer": "^1.0.1",
"vue": "^2.5.16",
"vue": "^2.6.10",
"vue-loader": "^14.2.2",
"vue-mugen-scroll": "^0.2.1",
"vue-router": "^3.0.0",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"vuedraggable": "^2.15.0",
"vue-template-compiler": "^2.6.10",
"vuedraggable": "^2.20.0",
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.12.0",
"webpack-merge": "^4.0.0",
"winston": "^2.4.2",
"webpack-merge": "^4.1.3",
"winston": "^2.4.3",
"winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4"
},
"private": true,
"engines": {
"node": "^8.9.4",
"npm": "^5.6.0"
"node": "^10",
"npm": "^6"
},
"scripts": {
"lint": "eslint --ext .js,.vue .",
@@ -144,15 +146,15 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.16",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.4.1",
"chromedriver": "^2.38.3",
"chromedriver": "^73.0.0",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.1",
"coveralls": "^3.0.3",
"cross-spawn": "^6.0.5",
"eslint": "^4.19.1",
"eslint-config-habitrpg": "^4.0.0",
@@ -162,9 +164,9 @@
"eslint-plugin-mocha": "^5.0.0",
"eventsource-polyfill": "^0.9.6",
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"http-proxy-middleware": "^0.19.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^3.0.0",
"karma": "^4.0.1",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
@@ -179,19 +181,16 @@
"lcov-result-merger": "^3.0.0",
"mocha": "^5.1.1",
"monk": "^6.0.6",
"nightwatch": "^0.9.21",
"puppeteer": "^1.4.0",
"nightwatch": "^1.0.16",
"puppeteer": "^1.14.0",
"require-again": "^2.0.0",
"selenium-server": "^3.12.0",
"sinon": "^4.5.0",
"sinon": "^7.2.4",
"sinon-chai": "^3.0.0",
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.12.0",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.22.2"
},
"optionalDependencies": {
"memwatch-next": "^0.3.0",
"node-rdkafka": "^2.3.0"
}
"optionalDependencies": {}
}

View File

@@ -27,12 +27,13 @@ async function _deleteAmplitudeData (userId, email) {
if (response) console.log(`${response.status} ${response.statusText}`);
}
async function _deleteHabiticaData (user) {
async function _deleteHabiticaData (user, email) {
await User.update(
{_id: user._id},
{$set: {
'auth.local.passwordHashMethod': 'bcrypt',
'auth.local.email': email,
'auth.local.hashed_password': '$2a$10$QDnNh1j1yMPnTXDEOV38xOePEWFd4X8DSYwAM8XTmqmacG5X0DKjW',
'auth.local.passwordHashMethod': 'bcrypt',
}}
);
const response = await axios.delete(
@@ -52,7 +53,7 @@ async function _deleteHabiticaData (user) {
if (response) {
console.log(`${response.status} ${response.statusText}`);
if (response.status === 200) console.log(`${user._id} removed. Last login: ${user.auth.timestamps.loggedin}`);
if (response.status === 200) console.log(`${user._id} (${email}) removed. Last login: ${user.auth.timestamps.loggedin}`);
}
}
@@ -75,7 +76,7 @@ async function _processEmailAddress (email) {
} else {
for (const user of users) {
await _deleteAmplitudeData(user._id, email); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user); // eslint-disable-line no-await-in-loop
await _deleteHabiticaData(user, email); // eslint-disable-line no-await-in-loop
}
}
}

View File

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

View File

@@ -1 +1 @@
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.wikia.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).
For information about writing and running tests, see [Using Your Local Install to Modify Habitica's Website and API](http://habitica.fandom.com/wiki/Using_Your_Local_Install_to_Modify_Habitica%27s_Website_and_API).

View File

@@ -335,14 +335,13 @@ describe('analyticsService', () => {
let data, itemSpy;
beforeEach(() => {
Visitor.prototype.event.yields();
itemSpy = sandbox.stub().returnsThis();
Visitor.prototype.event.returns({
send: sandbox.stub(),
});
Visitor.prototype.transaction.returns({
item: itemSpy,
send: sandbox.stub().returnsThis(),
send: sandbox.stub().yields(),
});
data = {

View File

@@ -5,7 +5,9 @@ import {
BadRequest,
InternalServerError,
NotFound,
NotificationNotFound,
} from '../../../../website/server/libs/errors';
import i18n from '../../../../website/common/script/i18n';
describe('Custom Errors', () => {
describe('CustomError', () => {
@@ -66,6 +68,23 @@ describe('Custom Errors', () => {
expect(notAuthorizedError.message).to.eql('Custom Error Message');
});
describe('NotificationNotFound', () => {
it('is an instance of NotFound', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr).to.be.an.instanceOf(NotFound);
});
it('it returns an http code of 404', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr.httpCode).to.eql(404);
});
it('returns a standard message', () => {
const notificationNotFoundErr = new NotificationNotFound();
expect(notificationNotFoundErr.message).to.eql(i18n.t('messageNotificationNotFound'));
});
});
});
describe('BadRequest', () => {

View File

@@ -0,0 +1,113 @@
/* eslint-disable camelcase */
import {
validateItemPath,
getDefaultOwnedGear,
castItemVal,
} from '../../../../../website/server/libs/items/utils';
describe('Items Utils', () => {
describe('getDefaultOwnedGear', () => {
it('clones the result object', () => {
const res1 = getDefaultOwnedGear();
res1.extraProperty = true;
const res2 = getDefaultOwnedGear();
expect(res2).not.to.have.property('extraProperty');
});
});
describe('validateItemPath', () => {
it('returns false if not an item path', () => {
expect(validateItemPath('notitems.gear.owned.item')).to.equal(false);
});
it('returns true if a valid schema path', () => {
expect(validateItemPath('items.gear.equipped.weapon')).to.equal(true);
expect(validateItemPath('items.currentPet')).to.equal(true);
expect(validateItemPath('items.special.snowball')).to.equal(true);
});
it('works with owned gear paths', () => {
expect(validateItemPath('items.gear.owned.head_armoire_crownOfHearts')).to.equal(true);
expect(validateItemPath('items.gear.owned.head_invalid')).to.equal(false);
});
it('works with pets paths', () => {
expect(validateItemPath('items.pets.Wolf-CottonCandyPink')).to.equal(true);
expect(validateItemPath('items.pets.Wolf-Invalid')).to.equal(false);
});
it('works with eggs paths', () => {
expect(validateItemPath('items.eggs.LionCub')).to.equal(true);
expect(validateItemPath('items.eggs.Armadillo')).to.equal(true);
expect(validateItemPath('items.eggs.NotAnArmadillo')).to.equal(false);
});
it('works with hatching potions paths', () => {
expect(validateItemPath('items.hatchingPotions.Base')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.StarryNight')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.Invalid')).to.equal(false);
});
it('works with food paths', () => {
expect(validateItemPath('items.food.Cake_Base')).to.equal(true);
expect(validateItemPath('items.food.Cake_Invalid')).to.equal(false);
});
it('works with mounts paths', () => {
expect(validateItemPath('items.mounts.Cactus-Base')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invisible')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invalid')).to.equal(false);
});
it('works with quests paths', () => {
expect(validateItemPath('items.quests.atom3')).to.equal(true);
expect(validateItemPath('items.quests.invalid')).to.equal(false);
});
});
describe('castItemVal', () => {
it('returns the item val untouched if not an item path', () => {
expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string');
});
it('returns the item val untouched if an unsupported path', () => {
expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string');
expect(castItemVal('items.currentPet', 'a string')).to.equal('a string');
expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string');
});
it('converts values for pets paths to numbers', () => {
expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5);
expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5);
});
it('converts values for eggs paths to numbers', () => {
expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5);
expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5);
expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5);
});
it('converts values for hatching potions paths to numbers', () => {
expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5);
expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5);
expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5);
});
it('converts values for food paths to numbers', () => {
expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5);
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
});
it('converts values for mounts paths to numbers', () => {
expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5);
expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5);
expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5);
});
it('converts values for quests paths to numbers', () => {
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
});
});
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -24,16 +25,16 @@ describe('Apple Payments', () => {
headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: 'com.habitrpg.ios.Habitica.21gems',
transactionId: token,
}]);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
});
afterEach(() => {
@@ -49,7 +50,7 @@ describe('Apple Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -61,7 +62,7 @@ describe('Apple Payments', () => {
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData').returns([]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -70,8 +71,8 @@ describe('Apple Payments', () => {
});
it('errors if the user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
sinon.stub(user, 'canGetGems').resolves(false);
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -82,14 +83,14 @@ describe('Apple Payments', () => {
});
it('errors if amount does not exist', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
sinon.stub(user, 'canGetGems').resolves(true);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: 'badProduct',
transactionId: token,
}]);
await expect(applePayments.verifyGemPurchase(user, receipt, headers))
await expect(applePayments.verifyGemPurchase({user, receipt, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -130,8 +131,8 @@ describe('Apple Payments', () => {
transactionId: token,
}]);
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await applePayments.verifyGemPurchase(user, receipt, headers);
sinon.stub(user, 'canGetGems').resolves(true);
await applePayments.verifyGemPurchase({user, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -151,6 +152,38 @@ describe('Apple Payments', () => {
user.canGetGems.restore();
});
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
iapGetPurchaseDataStub.restore();
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
.returns([{productId: gemsCanPurchase[0].productId,
transactionId: token,
}]);
const gift = {uuid: receivingUser._id};
await applePayments.verifyGemPurchase({user, gift, receipt, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt);
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(iapGetPurchaseDataStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE,
amount: gemsCanPurchase[0].amount,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {
@@ -167,9 +200,9 @@ describe('Apple Payments', () => {
nextPaymentProcessing = moment.utc().add({days: 2});
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -186,7 +219,7 @@ describe('Apple Payments', () => {
productId: sku,
transactionId: token,
}]);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
@@ -297,9 +330,9 @@ describe('Apple Payments', () => {
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -314,7 +347,7 @@ describe('Apple Payments', () => {
user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = receipt;
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

@@ -6,6 +6,7 @@ import iap from '../../../../../website/server/libs/inAppPurchases';
import {model as User} from '../../../../../website/server/models/user';
import common from '../../../../../website/common';
import moment from 'moment';
import {mockFindById, restoreFindById} from '../../../../helpers/mongoose.helper';
const i18n = common.i18n;
@@ -24,12 +25,12 @@ describe('Google Payments', () => {
headers = {};
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').returnsPromise().resolves({});
paymentBuyGemsStub = sinon.stub(payments, 'buyGems').resolves({});
});
afterEach(() => {
@@ -44,7 +45,7 @@ describe('Google Payments', () => {
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -55,7 +56,7 @@ describe('Google Payments', () => {
it('should throw an error if productId is invalid', async () => {
receipt = `{"token": "${token}", "productId": "invalid"}`;
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -64,9 +65,9 @@ describe('Google Payments', () => {
});
it('should throw an error if user cannot purchase gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(false);
sinon.stub(user, 'canGetGems').resolves(false);
await expect(googlePayments.verifyGemPurchase(user, receipt, signature, headers))
await expect(googlePayments.verifyGemPurchase({user, receipt, signature, headers}))
.to.eventually.be.rejected.and.to.eql({
httpCode: 401,
name: 'NotAuthorized',
@@ -77,8 +78,8 @@ describe('Google Payments', () => {
});
it('purchases gems', async () => {
sinon.stub(user, 'canGetGems').returnsPromise().resolves(true);
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
sinon.stub(user, 'canGetGems').resolves(true);
await googlePayments.verifyGemPurchase({user, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
@@ -99,6 +100,34 @@ describe('Google Payments', () => {
expect(user.canGetGems).to.be.calledOnce;
user.canGetGems.restore();
});
it('gifts gems', async () => {
const receivingUser = new User();
await receivingUser.save();
mockFindById(receivingUser);
const gift = {uuid: receivingUser._id};
await googlePayments.verifyGemPurchase({user, gift, receipt, signature, headers});
expect(iapSetupStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledOnce;
expect(iapValidateStub).to.be.calledWith(iap.GOOGLE, {
data: receipt,
signature,
});
expect(iapIsValidatedStub).to.be.calledOnce;
expect(iapIsValidatedStub).to.be.calledWith({});
expect(paymentBuyGemsStub).to.be.calledOnce;
expect(paymentBuyGemsStub).to.be.calledWith({
user: receivingUser,
paymentMethod: googlePayments.constants.PAYMENT_METHOD_GOOGLE,
amount: 5.25,
headers,
});
restoreFindById();
});
});
describe('subscribe', () => {
@@ -116,12 +145,12 @@ describe('Google Payments', () => {
nextPaymentProcessing = moment.utc().add({days: 2});
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({});
.resolves({});
iapIsValidatedStub = sinon.stub(iapModule, 'isValidated')
.returns(true);
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').returnsPromise().resolves({});
paymentsCreateSubscritionStub = sinon.stub(payments, 'createSubscription').resolves({});
});
afterEach(() => {
@@ -193,9 +222,9 @@ describe('Google Payments', () => {
expirationDate = moment.utc();
iapSetupStub = sinon.stub(iapModule, 'setup')
.returnsPromise().resolves();
.resolves();
iapValidateStub = sinon.stub(iapModule, 'validate')
.returnsPromise().resolves({
.resolves({
expirationDate,
});
iapGetPurchaseDataStub = sinon.stub(iapModule, 'getPurchaseData')
@@ -210,7 +239,7 @@ describe('Google Payments', () => {
user.purchased.plan.planId = subKey;
user.purchased.plan.additionalData = {data: receipt, signature};
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').returnsPromise().resolves({});
paymentCancelSubscriptionSpy = sinon.stub(payments, 'cancelSubscription').resolves({});
});
afterEach(function () {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ describe('slack', () => {
},
message: {
id: 'chat-id',
username: 'author',
user: 'Author',
uuid: 'author-id',
text: 'some text',
@@ -50,11 +51,11 @@ describe('slack', () => {
expect(IncomingWebhook.prototype.send).to.be.calledOnce;
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: 'flagger (flagger-id; language: flagger-lang) flagged a message',
text: 'flagger (flagger-id; language: flagger-lang) flagged a group message',
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `Author - author@example.com - author-id\n${timestamp}`,
author_name: `@author Author (author@example.com; author-id)\n${timestamp}`,
title: 'Flag in Some group - (private guild)',
title_link: undefined,
text: 'some text',
@@ -110,7 +111,7 @@ describe('slack', () => {
});
it('noops if no flagging url is provided', () => {
sandbox.stub(nconf, 'get').withArgs('SLACK:FLAGGING_URL').returns('');
sandbox.stub(nconf, 'get').withArgs('SLACK_FLAGGING_URL').returns('');
sandbox.stub(logger, 'error');
let reRequiredSlack = requireAgain('../../../../website/server/libs/slack');

View File

@@ -16,7 +16,7 @@ describe('auth middleware', () => {
describe('auth with headers', () => {
it('allows to specify a list of user field that we do not want to load', (done) => {
const authWithHeaders = authWithHeadersFactory({
userFieldsToExclude: ['items', 'flags', 'auth.timestamps'],
userFieldsToExclude: ['items'],
});
req.headers['x-api-user'] = user._id;
@@ -27,11 +27,34 @@ describe('auth middleware', () => {
const userToJSON = res.locals.user.toJSON();
expect(userToJSON.items).to.not.exist;
expect(userToJSON.flags).to.not.exist;
expect(userToJSON.auth.timestamps).to.not.exist;
expect(userToJSON.auth).to.exist;
done();
});
});
it('makes sure some fields are always included', (done) => {
const authWithHeaders = authWithHeadersFactory({
userFieldsToExclude: [
'items', 'auth.timestamps',
'preferences', 'notifications', '_id', 'flags', 'auth', // these are always loaded
],
});
req.headers['x-api-user'] = user._id;
req.headers['x-api-key'] = user.apiToken;
authWithHeaders(req, res, (err) => {
if (err) return done(err);
const userToJSON = res.locals.user.toJSON();
expect(userToJSON.items).to.not.exist;
expect(userToJSON.auth.timestamps).to.exist;
expect(userToJSON.auth).to.exist;
expect(userToJSON.notifications).to.exist;
expect(userToJSON.preferences).to.exist;
expect(userToJSON._id).to.exist;
expect(userToJSON.flags).to.exist;
done();
});

View File

@@ -73,6 +73,56 @@ describe('redirects middleware', () => {
expect(res.redirect).to.have.not.been.called;
});
it('does not redirect if passed skip ssl request param is passed with corrrect key', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'test-key';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.have.not.been.called;
});
it('does redirect if skip ssl request param is passed with incorrrect key', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns('test-key');
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front?skipSSLCheck=INVALID';
req.query.skipSSLCheck = 'INVALID';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.be.calledOnce;
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front?skipSSLCheck=INVALID');
});
it('does redirect if skip ssl check key is not set', () => {
let nconfStub = sandbox.stub(nconf, 'get');
nconfStub.withArgs('BASE_URL').returns('https://habitica.com');
nconfStub.withArgs('IS_PROD').returns(true);
nconfStub.withArgs('SKIP_SSL_CHECK_KEY').returns(null);
req.header = sandbox.stub().withArgs('x-forwarded-proto').returns('http');
req.originalUrl = '/static/front';
req.query.skipSSLCheck = 'INVALID';
const attachRedirects = requireAgain(pathToRedirectsMiddleware);
attachRedirects.forceSSL(req, res, next);
expect(res.redirect).to.be.calledOnce;
expect(res.redirect).to.be.calledWith('https://habitica.com/static/front');
});
});
context('forceHabitica', () => {

View File

@@ -1,7 +1,7 @@
import moment from 'moment';
import { v4 as generateUUID } from 'uuid';
import validator from 'validator';
import { sleep } from '../../../helpers/api-unit.helper';
import { sleep, translationCheck } from '../../../helpers/api-unit.helper';
import {
SPAM_MESSAGE_LIMIT,
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
@@ -32,8 +32,19 @@ describe('Group Model', () => {
privacy: 'private',
});
let _progress = {
up: 10,
down: 8,
collectedItems: 5,
};
questLeader = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Quest Leader' },
items: {
quests: {
@@ -45,20 +56,40 @@ describe('Group Model', () => {
party.leader = questLeader._id;
participatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Participating Member' },
});
sleepingParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Sleeping Participating Member' },
preferences: { sleep: true },
});
nonParticipatingMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Non-Participating Member' },
});
undecidedMember = new User({
party: { _id: party._id },
party: {
_id: party._id,
quest: {
progress: _progress,
},
},
profile: { name: 'Undecided Member' },
});
@@ -240,7 +271,16 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member attacks Wailing Whale for 5.0 damage. Wailing Whale attacks party for 7.5 damage.`',
info: {
bossDamage: '7.5',
quest: 'whale',
type: 'boss_damage',
user: 'Participating Member',
userDamage: '5.0',
},
});
});
it('applies damage only to participating members of party', async () => {
@@ -313,7 +353,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`You defeated Wailing Whale! Questing party members receive the rewards of victory.`',
info: { quest: 'whale', type: 'boss_defeated' },
});
});
it('calls finishQuest when boss has <= 0 hp', async () => {
@@ -356,7 +399,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'trex_undead', type: 'boss_rage' },
});
expect(party.quest.progress.hp).to.eql(383.5);
expect(party.quest.progress.rage).to.eql(0);
});
@@ -406,7 +452,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
expect(Group.prototype.sendChat).to.be.calledWith({
message: quest.boss.rage.effect('en'),
info: { quest: 'lostMasterclasser4', type: 'boss_rage' },
});
expect(party.quest.progress.rage).to.eql(0);
let drainedUser = await User.findById(participatingMember._id);
@@ -457,7 +506,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 5 Bars of Soap.`',
info: {
items: { soapBars: 5 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made', async () => {
@@ -468,7 +525,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWith('`Participating Member found 0 Bars of Soap.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Bars of Soap.`',
info: {
items: { soapBars: 0 },
quest: 'atom1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends a chat message if no progress is made on quest with multiple items', async () => {
@@ -477,7 +542,7 @@ describe('Group Model', () => {
party.quest.active = false;
await party.startQuest(questLeader);
Group.prototype.sendChat.reset();
Group.prototype.sendChat.resetHistory();
await party.save();
await Group.processQuestProgress(participatingMember, progress);
@@ -485,9 +550,15 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Blue Fins/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/0 Fire Coral/);
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`Participating Member found 0 Fire Coral, 0 Blue Fins.`',
info: {
items: { blueFins: 0, fireCoral: 0 },
quest: 'dilatoryDistress1',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('handles collection quests with multiple items', async () => {
@@ -496,7 +567,7 @@ describe('Group Model', () => {
party.quest.active = false;
await party.startQuest(questLeader);
Group.prototype.sendChat.reset();
Group.prototype.sendChat.resetHistory();
await party.save();
await Group.processQuestProgress(participatingMember, progress);
@@ -504,8 +575,14 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledOnce;
expect(Group.prototype.sendChat).to.be.calledWithMatch(/`Participating Member found/);
expect(Group.prototype.sendChat).to.be.calledWithMatch(/\d* (Tracks|Broken Twigs)/);
expect(Group.prototype.sendChat).to.be.calledWithMatch({
message: sinon.match(/`Participating Member found/).and(sinon.match(/\d* (Tracks|Broken Twigs)/)),
info: {
quest: 'evilsanta2',
type: 'user_found_items',
user: 'Participating Member',
},
});
});
it('sends message about victory', async () => {
@@ -516,7 +593,10 @@ describe('Group Model', () => {
party = await Group.findOne({_id: party._id});
expect(Group.prototype.sendChat).to.be.calledTwice;
expect(Group.prototype.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
expect(Group.prototype.sendChat).to.be.calledWith({
message: '`All items found! Party has received their rewards.`',
info: { type: 'all_items_found' },
});
});
it('calls finishQuest when all items are found', async () => {
@@ -569,7 +649,7 @@ describe('Group Model', () => {
});
it('throws an error if no uuids or emails are passed in', async () => {
await expect(Group.validateInvitations(null, null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -579,7 +659,7 @@ describe('Group Model', () => {
});
it('throws an error if only uuids are passed in, but they are not an array', async () => {
await expect(Group.validateInvitations({ uuid: 'user-id'}, null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({ uuids: 'user-id'}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -589,7 +669,7 @@ describe('Group Model', () => {
});
it('throws an error if only emails are passed in, but they are not an array', async () => {
await expect(Group.validateInvitations(null, { emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: 'user@example.com'}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -599,27 +679,27 @@ describe('Group Model', () => {
});
it('throws an error if emails are not passed in, and uuid array is empty', async () => {
await expect(Group.validateInvitations([], null, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({uuids: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
});
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('inviteMissingUuid');
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
});
it('throws an error if uuids are not passed in, and email array is empty', async () => {
await expect(Group.validateInvitations(null, [], res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
});
expect(res.t).to.be.calledOnce;
expect(res.t).to.be.calledWith('inviteMissingEmail');
expect(res.t).to.be.calledWith('inviteMustNotBeEmpty');
});
it('throws an error if uuids and emails are passed in as empty arrays', async () => {
await expect(Group.validateInvitations([], [], res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({emails: [], uuids: []}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -639,7 +719,7 @@ describe('Group Model', () => {
uuids.push('one-more-uuid'); // to put it over the limit
await expect(Group.validateInvitations(uuids, emails, res)).to.eventually.be.rejected.and.eql({
await expect(Group.validateInvitations({uuids, emails}, res)).to.eventually.be.rejected.and.eql({
httpCode: 400,
message: 'Bad request.',
name: 'BadRequest',
@@ -657,36 +737,288 @@ describe('Group Model', () => {
emails.push(`user-${i}@example.com`);
}
await Group.validateInvitations(uuids, emails, res);
await Group.validateInvitations({uuids, emails}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if only user ids are passed in', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], null, res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if only emails are passed in', async () => {
await Group.validateInvitations(null, ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if both uuids and emails are passed in', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if uuids are passed in and emails are an empty array', async () => {
await Group.validateInvitations(['user-id', 'user-id2'], [], res);
await Group.validateInvitations({uuids: ['user-id', 'user-id2'], emails: []}, res);
expect(res.t).to.not.be.called;
});
it('does not throw an error if emails are passed in and uuids are an empty array', async () => {
await Group.validateInvitations([], ['user1@example.com', 'user2@example.com'], res);
await Group.validateInvitations({uuids: [], emails: ['user1@example.com', 'user2@example.com']}, res);
expect(res.t).to.not.be.called;
});
});
describe('translateSystemMessages', () => {
it('translate quest_start', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_damage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_damage',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
bossDamage: 3.7,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_dont_attack', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_dont_attack',
user: questLeader.profile.name,
quest: 'basilist',
userDamage: 15.3,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_rage',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate boss_defeated', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'boss_defeated',
quest: 'lostMasterclasser3',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate user_found_items', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'user_found_items',
user: questLeader.profile.name,
quest: 'lostMasterclasser1',
items: {
ancientTome: 3,
forbiddenTome: 2,
hiddenTome: 1,
},
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate all_items_found', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'all_items_found',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_party', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_party',
user: questLeader.profile.name,
class: 'wizard',
spell: 'earth',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate spell_cast_user', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'spell_cast_user',
user: questLeader.profile.name,
class: 'special',
spell: 'snowball',
target: participatingMember.profile.name,
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_cancel', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_cancel',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate quest_abort', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'quest_abort',
user: questLeader.profile.name,
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_quest_completed', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_quest_completed',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage_tired', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage_tired',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_rage', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_rage',
quest: 'dysheartener',
scene: 'market',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate tavern_boss_desperation', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'tavern_boss_desperation',
quest: 'stressbeast',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
it('translate claim_task', async () => {
questLeader.preferences.language = 'en';
party.chat = [{
info: {
type: 'claim_task',
user: questLeader.profile.name,
task: 'Feed the pet',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
translationCheck(toJSON.chat[0].text);
});
});
describe('toJSONCleanChat', () => {
it('shows messages with 1 flag to non-admins', async () => {
party.chat = [{
flagCount: 1,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(1);
});
it('shows messages with >= 2 flag to admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
const admin = new User({'contributor.admin': true});
let toJSON = await Group.toJSONCleanChat(party, admin);
expect(toJSON.chat.length).to.equal(1);
});
it('doesn\'t show flagged messages to non-admins', async () => {
party.chat = [{
flagCount: 3,
info: {
type: 'quest_start',
quest: 'basilist',
},
}];
let toJSON = await Group.toJSONCleanChat(party, questLeader);
expect(toJSON.chat.length).to.equal(0);
});
});
});
context('Instance Methods', () => {
@@ -976,20 +1308,22 @@ describe('Group Model', () => {
});
it('formats message', () => {
const chatMessage = party.sendChat('a new message', {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
const chatMessage = party.sendChat({
message: 'a new message', user: {
_id: 'user-id',
profile: { name: 'user name' },
contributor: {
toObject () {
return 'contributor object';
},
},
},
backer: {
toObject () {
return 'backer object';
backer: {
toObject () {
return 'backer object';
},
},
},
});
}}
);
const chat = chatMessage;
@@ -1006,7 +1340,7 @@ describe('Group Model', () => {
});
it('formats message as system if no user is passed in', () => {
const chat = party.sendChat('a system message');
const chat = party.sendChat({message: 'a system message'});
expect(chat.text).to.eql('a system message');
expect(validator.isUUID(chat.id)).to.eql(true);
@@ -1021,7 +1355,7 @@ describe('Group Model', () => {
});
it('updates users about new messages in party', () => {
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1035,7 +1369,7 @@ describe('Group Model', () => {
type: 'guild',
});
group.sendChat('message');
group.sendChat({message: 'message'});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1045,7 +1379,7 @@ describe('Group Model', () => {
});
it('does not send update to user that sent the message', () => {
party.sendChat('message', {_id: 'user-id', profile: { name: 'user' }});
party.sendChat({message: 'message', user: {_id: 'user-id', profile: { name: 'user' }}});
expect(User.update).to.be.calledOnce;
expect(User.update).to.be.calledWithMatch({
@@ -1057,7 +1391,7 @@ describe('Group Model', () => {
it('skips sending new message notification for guilds with > 5000 members', () => {
party.memberCount = 5001;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1065,7 +1399,7 @@ describe('Group Model', () => {
it('skips sending messages to the tavern', () => {
party._id = TAVERN_ID;
party.sendChat('message');
party.sendChat({message: 'message'});
expect(User.update).to.not.be.called;
});
@@ -1163,16 +1497,17 @@ describe('Group Model', () => {
expect(party.quest.members).to.eql(expectedQuestMembers);
});
it('applies updates to user object directly if user is participating', async () => {
it('applies updates to user object directly if user is participating (without resetting progress, except progress.down)', async () => {
await party.startQuest(participatingMember);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
});
it('applies updates to other participating members', async () => {
it('applies updates to other participating members (without resetting progress, except progress.down)', async () => {
await party.startQuest(nonParticipatingMember);
questLeader = await User.findById(questLeader._id);
@@ -1180,18 +1515,21 @@ describe('Group Model', () => {
sleepingParticipatingMember = await User.findById(sleepingParticipatingMember._id);
expect(participatingMember.party.quest.key).to.eql('whale');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.completed).to.eql(null);
expect(sleepingParticipatingMember.party.quest.key).to.eql('whale');
expect(sleepingParticipatingMember.party.quest.progress.up).to.eql(10);
expect(sleepingParticipatingMember.party.quest.progress.down).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(0);
expect(sleepingParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(sleepingParticipatingMember.party.quest.completed).to.eql(null);
expect(questLeader.party.quest.key).to.eql('whale');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(0);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.completed).to.eql(null);
});
@@ -1202,6 +1540,9 @@ describe('Group Model', () => {
undecidedMember = await User.findById(undecidedMember._id);
expect(nonParticipatingMember.party.quest.key).to.not.eql('whale');
expect(nonParticipatingMember.party.quest.progress.up).to.eql(10);
expect(nonParticipatingMember.party.quest.progress.down).to.eql(8);
expect(nonParticipatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(undecidedMember.party.quest.key).to.not.eql('whale');
});
@@ -1369,8 +1710,9 @@ describe('Group Model', () => {
let userQuest = participatingMember.party.quest;
expect(userQuest.key).to.eql('whale');
expect(userQuest.progress.up).to.eql(10);
expect(userQuest.progress.down).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(0);
expect(userQuest.progress.collectedItems).to.eql(5);
expect(userQuest.completed).to.eql(null);
});
@@ -1392,7 +1734,7 @@ describe('Group Model', () => {
let quest;
beforeEach(() => {
quest = questScrolls.whale;
quest = questScrolls.armadillo;
party.quest.key = quest.key;
party.quest.active = false;
party.quest.leader = questLeader._id;
@@ -1540,6 +1882,36 @@ describe('Group Model', () => {
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
});
it('gives out other pet-related quest achievements', async () => {
quest = questScrolls.rock;
party.quest.key = quest.key;
questLeader.achievements.quests = {
mayhemMistiflying1: 1,
yarn: 1,
mayhemMistiflying2: 1,
egg: 1,
mayhemMistiflying3: 1,
slime: 2,
};
await questLeader.save();
await party.finishQuest(quest);
let [
updatedLeader,
updatedParticipatingMember,
updatedSleepingParticipatingMember,
] = await Promise.all([
User.findById(questLeader._id).exec(),
User.findById(participatingMember._id).exec(),
User.findById(sleepingParticipatingMember._id).exec(),
]);
expect(updatedLeader.achievements.mindOverMatter).to.eql(true);
expect(updatedParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
expect(updatedSleepingParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
});
it('gives xp and gold', async () => {
await party.finishQuest(quest);
@@ -1670,16 +2042,23 @@ describe('Group Model', () => {
});
});
it('sets user quest object to a clean state', async () => {
it('updates participating members quest object to a clean state (except for progress)', async () => {
await party.finishQuest(quest);
let updatedLeader = await User.findById(questLeader._id);
questLeader = await User.findById(questLeader._id);
participatingMember = await User.findById(participatingMember._id);
expect(updatedLeader.party.quest.completed).to.eql('whale');
expect(updatedLeader.party.quest.progress.up).to.eql(0);
expect(updatedLeader.party.quest.progress.down).to.eql(0);
expect(updatedLeader.party.quest.progress.collectedItems).to.eql(0);
expect(updatedLeader.party.quest.RSVPNeeded).to.eql(false);
expect(questLeader.party.quest.completed).to.eql('armadillo');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(8);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
expect(participatingMember.party.quest.completed).to.eql('armadillo');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(8);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);
expect(participatingMember.party.quest.RSVPNeeded).to.eql(false);
});
});
@@ -1843,6 +2222,62 @@ describe('Group Model', () => {
expect(options.chat).to.eql(chat);
});
it('sends webhooks for users with webhooks triggered by system messages', async () => {
let guild = new Group({
name: 'some guild',
type: 'guild',
});
let memberWithWebhook = new User({
guilds: [guild._id],
webhooks: [{
type: 'groupChatReceived',
url: 'http://someurl.com',
options: {
groupId: guild._id,
},
}],
});
let memberWithoutWebhook = new User({
guilds: [guild._id],
});
let nonMemberWithWebhooks = new User({
webhooks: [{
type: 'groupChatReceived',
url: 'http://a-different-url.com',
options: {
groupId: generateUUID(),
},
}],
});
await Promise.all([
memberWithWebhook.save(),
memberWithoutWebhook.save(),
nonMemberWithWebhooks.save(),
]);
guild.leader = memberWithWebhook._id;
await guild.save();
const groupMessage = guild.sendChat({message: 'Test message.'});
await groupMessage.save();
await sleep();
expect(groupChatReceivedWebhook.send).to.be.calledOnce;
let args = groupChatReceivedWebhook.send.args[0];
let webhooks = args[0].webhooks;
let options = args[1];
expect(webhooks).to.have.a.lengthOf(1);
expect(webhooks[0].id).to.eql(memberWithWebhook.webhooks[0].id);
expect(options.group).to.eql(guild);
expect(options.chat).to.eql(groupMessage);
});
it('sends webhooks for each user with webhooks in group', async () => {
let guild = new Group({
name: 'some guild',

View File

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

View File

@@ -65,11 +65,11 @@ describe('GET /challenges/:challengeId/export/csv', () => {
const sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},${sortedMembers[3].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[5]).to.equal('');
});
@@ -78,10 +78,10 @@ describe('GET /challenges/:challengeId/export/csv', () => {
const res = await members[1].get(`/challenges/${challenge._id}/export/csv`);
const sortedMembers = _.sortBy([members[1], members[2], groupLeader], '_id');
const splitRes = res.split('\n');
expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[0]).to.equal('UUID,Display Name,Username,Task,Value,Notes,Streak,Task,Value,Notes,Streak');
expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},${sortedMembers[0].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},${sortedMembers[1].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},${sortedMembers[2].auth.local.username},habit:Task 1,0,,0,todo:Task 2,0,,0`);
expect(splitRes[4]).to.equal('');
});
});

View File

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

View File

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

View File

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

View File

@@ -40,6 +40,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge.group).to.eql({
_id: publicGuild._id,
@@ -62,6 +70,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
@@ -79,6 +95,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
@@ -101,6 +125,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
@@ -118,6 +150,14 @@ describe('GET challenges/user', () => {
_id: publicGuild.leader._id,
id: publicGuild.leader._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
@@ -131,7 +171,7 @@ describe('GET challenges/user', () => {
});
});
it('should return not return challenges in user groups if we send member true param', async () => {
it('should not return challenges in user groups if we send member true param', async () => {
let challenges = await member.get(`/challenges/user?member=${true}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id });
@@ -174,6 +214,28 @@ describe('GET challenges/user', () => {
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist;
});
it('should not return challenges user doesn\'t have access to, even with query parameters', async () => {
let { group, groupLeader } = await createAndPopulateGroup({
groupDetails: {
name: 'TestPrivateGuild',
summary: 'summary for TestPrivateGuild',
type: 'guild',
privacy: 'private',
},
});
let privateChallenge = await generateChallenge(groupLeader, group, {categories: [{
name: 'academics',
slug: 'academics',
}]});
await groupLeader.post(`/challenges/${privateChallenge._id}/join`);
let challenges = await nonMember.get('/challenges/user?categories=academics&owned=not_owned');
let foundChallenge = _.find(challenges, { _id: privateChallenge._id });
expect(foundChallenge).to.not.exist;
});
});
context('official challenge is present', () => {

View File

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

View File

@@ -56,11 +56,11 @@ describe('PUT /challenges/:challengeId', () => {
tasksOrder: 'new order',
official: true,
shortName: 'new short name',
leader: member._id,
// applied
name: 'New Challenge Name',
description: 'New challenge description.',
leader: member._id,
});
expect(res.prize).to.equal(0);
@@ -76,9 +76,17 @@ describe('PUT /challenges/:challengeId', () => {
expect(res.shortName).not.to.equal('new short name');
expect(res.leader).to.eql({
_id: member._id,
id: member._id,
profile: {name: member.profile.name},
_id: user._id,
id: user._id,
profile: {name: user.profile.name},
auth: {
local: {
username: user.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
expect(res.name).to.equal('New Challenge Name');
expect(res.description).to.equal('New challenge description.');

View File

@@ -63,11 +63,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${user.profile.name} (${user.id}; language: en) flagged a message`,
text: `${user.profile.name} (${user.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${anotherUser.profile.name} - ${anotherUser.auth.local.email} - ${anotherUser._id}\n${timestamp}`,
author_name: `@${anotherUser.auth.local.username} ${anotherUser.profile.name} (${anotherUser.auth.local.email}; ${anotherUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,
@@ -98,11 +98,11 @@ describe('POST /chat/:chatId/flag', () => {
/* eslint-disable camelcase */
expect(IncomingWebhook.prototype.send).to.be.calledWith({
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a message`,
text: `${newUser.profile.name} (${newUser.id}; language: en) flagged a group message`,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: `${newUser.profile.name} - ${newUser.auth.local.email} - ${newUser._id}\n${timestamp}`,
author_name: `@${newUser.auth.local.username} ${newUser.profile.name} (${newUser.auth.local.email}; ${newUser._id})\n${timestamp}`,
title: 'Flag in Test Guild',
title_link: `${BASE_URL}/groups/guild/${group._id}`,
text: TEST_MESSAGE,

View File

@@ -257,7 +257,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${user.profile.name} - ${user.auth.local.email} - ${user._id}`,
author_name: `@${user.auth.local.username} ${user.profile.name} (${user.auth.local.email}; ${user._id})`,
title: 'Slur in Test Guild',
title_link: `${BASE_URL}/groups/guild/${groupWithChat.id}`,
text: testSlurMessage,
@@ -310,7 +310,7 @@ describe('POST /chat', () => {
attachments: [{
fallback: 'Slur Message',
color: 'danger',
author_name: `${members[0].profile.name} - ${members[0].auth.local.email} - ${members[0]._id}`,
author_name: `@${members[0].auth.local.username} ${members[0].profile.name} (${members[0].auth.local.email}; ${members[0]._id})`,
title: 'Slur in Party - (private party)',
title_link: undefined,
text: testSlurMessage,

View File

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

View File

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

View File

@@ -50,6 +50,14 @@ describe('GET /groups/:groupId/invites', () => {
_id: invited._id,
id: invited._id,
profile: {name: invited.profile.name},
auth: {
local: {
username: invited.auth.local.username,
},
},
flags: {
verifiedUsername: true,
},
});
});
@@ -58,7 +66,7 @@ describe('GET /groups/:groupId/invites', () => {
let invited = await generateUser();
await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]});
let res = await user.get('/groups/party/invites');
expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']);
expect(res[0]).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(res[0].profile).to.have.all.keys(['name']);
});
@@ -76,7 +84,7 @@ describe('GET /groups/:groupId/invites', () => {
let res = await leader.get(`/groups/${group._id}/invites`);
expect(res.length).to.equal(30);
res.forEach(member => {
expect(member).to.have.all.keys(['_id', 'id', 'profile']);
expect(member).to.have.all.keys(['_id', 'auth', 'flags', 'id', 'profile']);
expect(member.profile).to.have.all.keys(['name']);
});
}).timeout(10000);

View File

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

View File

@@ -149,7 +149,7 @@ describe('POST /group', () => {
).to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotCreatePublicGuildWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
});

View File

@@ -23,6 +23,73 @@ describe('Post /groups/:groupId/invite', () => {
});
});
describe('username invites', () => {
it('returns an error when invited user is not found', async () => {
const fakeID = 'fakeuserid';
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [fakeID],
}))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('userWithUsernameNotFound', {username: fakeID}),
});
});
it('returns an error when inviting yourself to a group', async () => {
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [inviter.auth.local.lowerCaseUsername],
}))
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('cannotInviteSelfToGroup'),
});
});
it('invites a user to a group by username', async () => {
const userToInvite = await generateUser();
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [userToInvite.auth.local.lowerCaseUsername],
})).to.eventually.deep.equal([{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
}]);
await expect(userToInvite.get('/user'))
.to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
});
it('invites multiple users to a group by uuid', async () => {
const userToInvite = await generateUser();
const userToInvite2 = await generateUser();
await expect(inviter.post(`/groups/${group._id}/invite`, {
usernames: [userToInvite.auth.local.lowerCaseUsername, userToInvite2.auth.local.lowerCaseUsername],
})).to.eventually.deep.equal([
{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
{
id: group._id,
name: groupName,
inviter: inviter._id,
publicGuild: false,
},
]);
await expect(userToInvite.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
await expect(userToInvite2.get('/user')).to.eventually.have.nested.property('invitations.guilds[0].id', group._id);
});
});
describe('user id invites', () => {
it('returns an error when inviter has no chat privileges', async () => {
let inviterMuted = await inviter.update({'flags.chatRevoked': true});
@@ -33,7 +100,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -93,7 +160,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('inviteMissingUuid'),
message: t('inviteMustNotBeEmpty'),
});
});
@@ -195,7 +262,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -228,7 +295,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('inviteMissingEmail'),
message: t('inviteMustNotBeEmpty'),
});
});
@@ -266,7 +333,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}),
message: t('inviteLimitReached', {techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}),
});
});
@@ -369,7 +436,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});
@@ -459,7 +526,7 @@ describe('Post /groups/:groupId/invite', () => {
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('cannotInviteWhenMuted'),
message: t('chatPrivilegesRevoked'),
});
});

View File

@@ -25,9 +25,9 @@ describe('GET /heroes/:heroId', () => {
it('validates req.params.heroId', async () => {
await expect(user.get('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('invalidReqParams'),
code: 404,
error: 'NotFound',
message: t('userWithIDNotFound', {userId: 'invalidUUID'}),
});
});
@@ -40,7 +40,7 @@ describe('GET /heroes/:heroId', () => {
});
});
it('returns only necessary hero data', async () => {
it('returns only necessary hero data given user id', async () => {
let hero = await generateUser({
contributor: {tier: 23},
});
@@ -53,4 +53,24 @@ describe('GET /heroes/:heroId', () => {
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
it('returns only necessary hero data given username', async () => {
let hero = await generateUser({
contributor: {tier: 23},
});
let heroRes = await user.get(`/hall/heroes/${hero.auth.local.username}`);
expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys
'_id', 'id', 'balance', 'profile', 'purchased',
'contributor', 'auth', 'items',
]);
expect(heroRes.auth.local).not.to.have.keys(['salt', 'hashed_password']);
expect(heroRes.profile).to.have.all.keys(['name']);
});
it('returns correct hero using search with difference case', async () => {
await generateUser({}, { username: 'TestUpperCaseName123' });
let heroRes = await user.get('/hall/heroes/TestuPPerCasEName123');
expect(heroRes.auth.local.username).to.equal('TestUpperCaseName123');
});
});

View File

@@ -27,8 +27,6 @@ describe('GET /inbox/messages', () => {
toUserId: user.id,
message: 'fourth',
});
await user.sync();
});
it('returns the user inbox messages as an array of ordered messages (from most to least recent)', async () => {
@@ -45,4 +43,27 @@ describe('GET /inbox/messages', () => {
expect(messages[2].text).to.equal('second');
expect(messages[3].text).to.equal('first');
});
it('returns four messages when using page-query ', async () => {
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(user.post('/members/send-private-message', {
toUserId: user.id,
message: 'fourth',
}));
}
await Promise.all(promises);
const messages = await user.get('/inbox/messages?page=1');
expect(messages.length).to.equal(4);
});
it('returns only the messages of one conversation', async () => {
const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`);
expect(messages.length).to.equal(3);
});
});

View File

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

View File

@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/read', () => {
await expect(user.post(`/notifications/${dummyId}/read`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -16,7 +16,7 @@ describe('POST /notifications/:notificationId/see', () => {
await expect(user.post(`/notifications/${dummyId}/see`)).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -18,7 +18,7 @@ describe('POST /notifications/read', () => {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

@@ -18,7 +18,7 @@ describe('POST /notifications/see', () => {
notificationIds: [dummyId],
})).to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
error: 'NotificationNotFound',
message: t('messageNotificationNotFound'),
});
});

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,67 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #norenewsubscribe', () => {
let endpoint = '/iap/ios/norenew-subscribe';
let sku = 'com.habitrpg.ios.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(applePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
applePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {receipt: 'receipt'},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

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

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import applePayments from '../../../../../../website/server/libs/payments/apple';
describe('payments : apple #verify', () => {
@@ -9,11 +9,19 @@ describe('payments : apple #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').returnsPromise().resolves({});
verifyStub = sinon.stub(applePayments, 'verifyGemPurchase').resolves({});
});
afterEach(() => {
@@ -31,10 +39,31 @@ describe('payments : apple #verify', () => {
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][2]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {
receipt: 'receipt',
},
gift: {
uuid: '1',
}});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

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

View File

@@ -0,0 +1,97 @@
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #norenewsubscribe', () => {
let endpoint = '/iap/android/norenew-subscribe';
let sku = 'com.habitrpg.android.habitica.subscription.3month';
let user;
beforeEach(async () => {
user = await generateUser();
});
it('verifies sub key', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingSubscriptionCode'),
});
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint, {
sku,
})).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let subscribeStub;
beforeEach(async () => {
subscribeStub = sinon.stub(googlePayments, 'noRenewSubscribe').resolves({});
});
afterEach(() => {
googlePayments.noRenewSubscribe.restore();
});
it('makes a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
'profile.name': 'sender',
'purchased.plan.customerId': 'customer-id',
'purchased.plan.planId': 'basic_3mo',
'purchased.plan.lastBillingDate': new Date(),
balance: 2,
});
await user.post(endpoint, {
sku,
transaction: {
receipt: 'receipt',
signature: 'signature',
},
gift: {
uuid: '1',
},
});
expect(subscribeStub).to.be.calledOnce;
expect(subscribeStub.args[0][0].user._id).to.eql(user._id);
expect(subscribeStub.args[0][0].sku).to.eql(sku);
expect(subscribeStub.args[0][0].receipt).to.eql('receipt');
expect(subscribeStub.args[0][0].signature).to.eql('signature');
expect(subscribeStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(subscribeStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

View File

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

View File

@@ -1,4 +1,4 @@
import {generateUser} from '../../../../../helpers/api-integration/v3';
import {generateUser, translate as t} from '../../../../../helpers/api-integration/v3';
import googlePayments from '../../../../../../website/server/libs/payments/google';
describe('payments : google #verify', () => {
@@ -9,11 +9,19 @@ describe('payments : google #verify', () => {
user = await generateUser();
});
it('verifies receipt existence', async () => {
await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({
code: 400,
error: 'BadRequest',
message: t('missingReceipt'),
});
});
describe('success', () => {
let verifyStub;
beforeEach(async () => {
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').returnsPromise().resolves({});
verifyStub = sinon.stub(googlePayments, 'verifyGemPurchase').resolves({});
});
afterEach(() => {
@@ -30,11 +38,30 @@ describe('payments : google #verify', () => {
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0]._id).to.eql(user._id);
expect(verifyStub.args[0][1]).to.eql('receipt');
expect(verifyStub.args[0][2]).to.eql('signature');
expect(verifyStub.args[0][3]['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][3]['x-api-user']).to.eql(user._id);
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
it('gifts a purchase', async () => {
user = await generateUser({
balance: 2,
});
await user.post(endpoint, {
transaction: {receipt: 'receipt', signature: 'signature'},
gift: {uuid: '1'},
});
expect(verifyStub).to.be.calledOnce;
expect(verifyStub.args[0][0].user._id).to.eql(user._id);
expect(verifyStub.args[0][0].receipt).to.eql('receipt');
expect(verifyStub.args[0][0].signature).to.eql('signature');
expect(verifyStub.args[0][0].gift.uuid).to.eql('1');
expect(verifyStub.args[0][0].headers['x-api-key']).to.eql(user.apiToken);
expect(verifyStub.args[0][0].headers['x-api-user']).to.eql(user._id);
});
});
});

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