mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-05-21 11:50:06 -05:00
Compare commits
505 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ce2caecf9 | |||
| 6b933914ef | |||
| 060e68ef95 | |||
| 1c82fa012d | |||
| 924723bce6 | |||
| b696dde6ba | |||
| e8d0557cb6 | |||
| cdb6acd4a0 | |||
| 1f0fa500bb | |||
| 36276d5d3f | |||
| 5c2c87f523 | |||
| c4f2dafc95 | |||
| f09b65e108 | |||
| a0f42b0e3e | |||
| e10655a5b4 | |||
| d519940931 | |||
| 58a43f51d8 | |||
| d25a5fcd57 | |||
| 4085d7a5bb | |||
| 6c4e1a326f | |||
| 9cf8c0a824 | |||
| e1f9643ffd | |||
| 1d5c1d5a5f | |||
| df7c0a005c | |||
| c60481ab34 | |||
| 13818b7634 | |||
| b2e834c74c | |||
| 4a9cfe8ce5 | |||
| 2419b219ba | |||
| 0b8ce63c76 | |||
| 95e541ae75 | |||
| 85c6c19235 | |||
| 07e4b2c463 | |||
| 7b2081ab03 | |||
| e749e42665 | |||
| 0b82722d27 | |||
| f35ef3a046 | |||
| 5656b9c6ca | |||
| 1e3d7acf06 | |||
| b7e391e074 | |||
| b2368e7804 | |||
| 85880d6bb5 | |||
| 352b8143f3 | |||
| 7fe3870297 | |||
| aff32b0e71 | |||
| 4ff56b17e7 | |||
| 03283d2e52 | |||
| 07909ac93a | |||
| 506b155cfa | |||
| 80bd41928c | |||
| be3bd25f00 | |||
| 3e365f2b4e | |||
| e1b08e3a20 | |||
| 01281b6414 | |||
| 0b65ac6c4f | |||
| 0d12686a10 | |||
| 8c4d1a67ac | |||
| e58fbcc34c | |||
| 1a66a680dc | |||
| fc08b753cd | |||
| 7a73f5bb83 | |||
| 6ce1f6f32e | |||
| 50278db1d6 | |||
| 1195560b0c | |||
| 33c639e28b | |||
| c2b106564f | |||
| fe636b9bd2 | |||
| 7835fe1deb | |||
| 8fb0d0899d | |||
| abb8dc4dc1 | |||
| 910a76db68 | |||
| 2f792d51f8 | |||
| 6cc925b535 | |||
| 87d86ee632 | |||
| b387d77128 | |||
| e644ae83fd | |||
| ae21680a5f | |||
| c1767eca1b | |||
| d20cd1bbf1 | |||
| 3e45f5af41 | |||
| 23cc2b9d21 | |||
| 58c4fcd506 | |||
| 2878abc130 | |||
| a8cb6e3409 | |||
| 2caa540006 | |||
| a2261e3591 | |||
| 90d498ff96 | |||
| 928327e02a | |||
| f454700722 | |||
| 83bce24e1f | |||
| e7979a99e6 | |||
| 5c648af2ea | |||
| 2ad4bee816 | |||
| e0291cf432 | |||
| bac9121153 | |||
| d178928c45 | |||
| 9a6aa5f443 | |||
| e277a088ee | |||
| e083df64e4 | |||
| dae37c17d6 | |||
| 1d81916674 | |||
| 2a225c2376 | |||
| bd9d9d31c3 | |||
| f1b5ce9c66 | |||
| c08b25101b | |||
| 9fd0e27c66 | |||
| 994082a1d8 | |||
| 0e6e7adb06 | |||
| 8486b9631f | |||
| 3f04c8abf5 | |||
| 89e1c69728 | |||
| 5e935230a4 | |||
| 9ad50be6ca | |||
| 1ff3f3d4e7 | |||
| 0b35aefdc9 | |||
| 0d374d817c | |||
| 034058301d | |||
| ae2d50c5b8 | |||
| fff16a86a5 | |||
| c7309ae179 | |||
| ef42fba049 | |||
| d75f926136 | |||
| 78612a91dd | |||
| 8bcd93075b | |||
| f318afb8cf | |||
| b954379f38 | |||
| e5c060a80b | |||
| 6668aae89b | |||
| 9b62804a5c | |||
| d21e29462c | |||
| 6bccd2a866 | |||
| caea222330 | |||
| 8ecbdc1448 | |||
| ee20b1eea8 | |||
| ff62c6eea0 | |||
| 57cd4d44cd | |||
| e989503cfa | |||
| 431cec7634 | |||
| 2a367ab3a7 | |||
| bc91dd81e9 | |||
| 201085651b | |||
| bb395a7ad8 | |||
| 264aad79ac | |||
| 9c797e6a54 | |||
| e6c3d00665 | |||
| 660928323e | |||
| 298a79b58d | |||
| 77f3bb53de | |||
| 17d8a7b706 | |||
| 1d8a5b1952 | |||
| f548103f4c | |||
| 2595ccb2b4 | |||
| ceb4288b17 | |||
| 3ebd37f7cb | |||
| ff3df55639 | |||
| 080c4b3e20 | |||
| e939799800 | |||
| b7797b3e6c | |||
| 55bd35d7d3 | |||
| b9ae03f795 | |||
| 75f6398de2 | |||
| 4004887ddd | |||
| ef4d761e0c | |||
| a1fb702d1e | |||
| 868759d3e8 | |||
| 45a9d6d17b | |||
| cc766d2260 | |||
| e710a00e74 | |||
| bad06ba449 | |||
| ccb088e127 | |||
| 4a4d48aed8 | |||
| da8006506b | |||
| d451d01b18 | |||
| 53555a0f16 | |||
| 962236846a | |||
| aae8f2923c | |||
| 61956336ea | |||
| 0be1f3eb7c | |||
| fe04b56ecc | |||
| 7e772924f6 | |||
| 901d11c61f | |||
| 80b15ac5e9 | |||
| 78b49b9c7e | |||
| 8874558827 | |||
| ea5ce64db6 | |||
| 10b69986c0 | |||
| 2d35009bee | |||
| d267f09d04 | |||
| f23dcf59ff | |||
| 8eb9402c0e | |||
| a7f2579f6c | |||
| ada09f3d5a | |||
| 8fdee5a669 | |||
| 3e4d245eba | |||
| b21cd4a2b6 | |||
| e956bbdf79 | |||
| 446154b97f | |||
| 18de42b13d | |||
| 98fd509530 | |||
| d932d6d448 | |||
| be4c777382 | |||
| 375a1f3156 | |||
| ca2f2ba9ce | |||
| 165ca8737b | |||
| f137342d88 | |||
| 94db493974 | |||
| ee5c761680 | |||
| b711c1672b | |||
| d675e80555 | |||
| 2c1ca7629d | |||
| b5d5367363 | |||
| 13af1fa88d | |||
| b721155f01 | |||
| b8aacc03e3 | |||
| 844d3fbf37 | |||
| 4d1b239231 | |||
| b9aaccdf13 | |||
| 0155491a68 | |||
| ef412c7185 | |||
| c15b55808e | |||
| 28ddebf4a9 | |||
| 5f0919d1c5 | |||
| 0cbd6fb4d7 | |||
| 2eab8b2c8b | |||
| b35bd18282 | |||
| 732a46d2db | |||
| 4f1d4aa73a | |||
| 92f2079b76 | |||
| 63f5773172 | |||
| 93290ec6d5 | |||
| be6c2a002f | |||
| e6bd67a53a | |||
| 6ab3bac96c | |||
| ee97da1112 | |||
| 3c948beb84 | |||
| f590c485dc | |||
| 64063544e6 | |||
| 0910f65fc0 | |||
| fdc0e0f5fe | |||
| d225a7ca54 | |||
| 5b7c4bf03f | |||
| dddd8269b6 | |||
| fd724a36ff | |||
| 5b329db357 | |||
| c1cad5c0a9 | |||
| 94b5ed9dab | |||
| 0ac976e8c1 | |||
| b1f42dcac9 | |||
| ed8bd84257 | |||
| 4e8c08ba9b | |||
| e294ed836d | |||
| c86da9783b | |||
| 64a608e7e7 | |||
| 767f844cea | |||
| 00a686dcf6 | |||
| 0ca3c1f94d | |||
| 2f699e24d7 | |||
| faa0611ab2 | |||
| 08c8f26b80 | |||
| 8904c58510 | |||
| d10f1304de | |||
| e28992060c | |||
| df860c9401 | |||
| 87923b7f0d | |||
| b2d6a9474d | |||
| 5fac4a943c | |||
| 5d0be7bc72 | |||
| 9e6394c38c | |||
| cc97935ffd | |||
| 6ea4d96830 | |||
| a63ba51497 | |||
| 04a7fd25a6 | |||
| 7cb045781b | |||
| f8d799d55c | |||
| 5bd9fcc99d | |||
| 84cafc4081 | |||
| d77a17112c | |||
| 4f9d97d38f | |||
| 40060e8ff5 | |||
| 508d97d374 | |||
| fd125352b7 | |||
| f8bd1be4a3 | |||
| ec37524164 | |||
| c66d2cb469 | |||
| 87b9e72b56 | |||
| 962662fe7c | |||
| f63d2e47f0 | |||
| 349a1032b6 | |||
| 476131835d | |||
| 8237b7f2de | |||
| 6ee2b3690a | |||
| a08cca807a | |||
| 8c51f36784 | |||
| d35f81cdae | |||
| 1d1b25391f | |||
| ee09c76c08 | |||
| c478748436 | |||
| 61606cb69d | |||
| ec81c02d72 | |||
| 166da3c2f8 | |||
| 23b72a673d | |||
| d22b4bb2f7 | |||
| aaebd4da77 | |||
| b128e7874e | |||
| 1e10e20a24 | |||
| b1aeb8ed87 | |||
| 2cb80e2275 | |||
| ee32e24ff2 | |||
| af40c437be | |||
| a99150c485 | |||
| 696b67204d | |||
| 4f3536e887 | |||
| 07e5bf1437 | |||
| d2ca738256 | |||
| f7983f39eb | |||
| 7c954f7073 | |||
| 82d0e737a6 | |||
| f8213aaf1b | |||
| 2335ad4167 | |||
| d84631255b | |||
| 0b352b9103 | |||
| 19b75c6257 | |||
| b66904a3a7 | |||
| cfbfec34aa | |||
| 88f28188a1 | |||
| fce5be2e8f | |||
| 1d68cbfaa2 | |||
| a44222a350 | |||
| 4d2510e322 | |||
| 3b5ed33e03 | |||
| 0a6bf92b6b | |||
| 05ae40bc2e | |||
| 57e96ea092 | |||
| ea49b5b8e0 | |||
| 11a5de714a | |||
| f74b4d3e73 | |||
| ab6fdb99af | |||
| e3efa557dd | |||
| a6cea47789 | |||
| 5a200a88bd | |||
| 545499ea0b | |||
| 3e7f4229ec | |||
| 56d5f77b6e | |||
| efdf5a2e16 | |||
| add3a2887f | |||
| 0ed7c7596a | |||
| 55a452694f | |||
| cd4d5f83ff | |||
| 8220199e49 | |||
| bd1f6918ba | |||
| 3d99a64e96 | |||
| 76f9204417 | |||
| 5b21e62647 | |||
| 5e76d6df21 | |||
| fad59b9a8d | |||
| f218a432ec | |||
| 7348145b7d | |||
| ba2832d21f | |||
| 688f5084a1 | |||
| d5955b8889 | |||
| 01fd17ee3f | |||
| 979497dd35 | |||
| 1d3db244ba | |||
| 80ca074352 | |||
| 6d4f9e0759 | |||
| d096695559 | |||
| a2c8b8b05c | |||
| 8750701c08 | |||
| 900676bf0a | |||
| e219daf44c | |||
| 1b12a6b51f | |||
| 8441b0a3d6 | |||
| 0667695390 | |||
| 4d322c1bf6 | |||
| a855ddacc7 | |||
| 9d48ef7322 | |||
| 73ecdced01 | |||
| 1c17b415f0 | |||
| 2991f7acfb | |||
| 9dbfb565bb | |||
| f42e22b58f | |||
| 95f3315796 | |||
| 18ab57eb91 | |||
| 0dcbd8ccb8 | |||
| ed82b46f7b | |||
| dcc3044685 | |||
| 5cd62d7052 | |||
| 5e232d8c9f | |||
| 13793f8b3c | |||
| 55f875f95a | |||
| 14576be374 | |||
| 4cb2c26475 | |||
| b66a0b76ef | |||
| dd313b17b5 | |||
| bb01475e02 | |||
| 757959529b | |||
| 71ad1957b1 | |||
| 3ba5ea1d2d | |||
| 902da35f2b | |||
| aaa16a9527 | |||
| b98e95ee45 | |||
| 757160d6b7 | |||
| 4cf68eb018 | |||
| e91d5e5664 | |||
| b9e12aca3e | |||
| 53ca9475ee | |||
| e212842b50 | |||
| d2f7cba43d | |||
| 84558f79d6 | |||
| 178e59f287 | |||
| 7acccc0763 | |||
| 8ac21d2fd4 | |||
| 7873800f87 | |||
| 727041f020 | |||
| de0c62a37f | |||
| 68f420991e | |||
| f211ebeb0a | |||
| b91ef9f539 | |||
| ac0601630e | |||
| 3239491144 | |||
| b912a83f22 | |||
| fb3a9740bd | |||
| 817c943860 | |||
| 0aba448c48 | |||
| c3b0a73507 | |||
| b218eb2c00 | |||
| 37d9f76fea | |||
| 7f2e12ba23 | |||
| 21b43287e3 | |||
| 357b48dc8f | |||
| 2cbd78b139 | |||
| ee5dd5842b | |||
| 074b8138de | |||
| cd0b9c0a96 | |||
| a09516944d | |||
| b82660823d | |||
| fde4402fbb | |||
| 5fe0776074 | |||
| d218f316d3 | |||
| f19e69948a | |||
| e6807d36b5 | |||
| 1ece230621 | |||
| d934d9d759 | |||
| bf7fabb20a | |||
| 88a2f317d8 | |||
| a30c4379a6 | |||
| b509c6631d | |||
| 5a725fa4b0 | |||
| 6b5173ecbf | |||
| 53d8d2fc6a | |||
| dbe2143b7a | |||
| 7b687280d7 | |||
| 8db6c8bd4f | |||
| bf91dacb94 | |||
| 33e0892e95 | |||
| 42b146d5d0 | |||
| 2bebaf2cf8 | |||
| 6181328ac1 | |||
| cc751960ac | |||
| 433c73c9d3 | |||
| 680c2162a7 | |||
| 52a7112591 | |||
| fd13771088 | |||
| c9d725ec20 | |||
| 3203bffeaa | |||
| 3f47cdd9a2 | |||
| 8b15d94ae1 | |||
| 8f2435c37c | |||
| 302bc899d7 | |||
| 8048cf9a97 | |||
| 20548daccf | |||
| 1815d2b6d3 | |||
| 14cba76ba8 | |||
| 3d757c7814 | |||
| 2c250bfcd9 | |||
| 75e3f15352 | |||
| 5670be26c7 | |||
| 9fc03cb91a | |||
| 7e80406181 | |||
| 60a6f6f2f6 | |||
| 92d68e5c6e | |||
| 6e7d8d93fe | |||
| bc861133e1 | |||
| 31ef21f25c | |||
| fd700f92ae | |||
| f41665f5a9 | |||
| 8b385c0b7b | |||
| 282f8db933 | |||
| 662b08c242 | |||
| 97e1465899 | |||
| 7cb0f5145d | |||
| 5b6217a0bf | |||
| 09e4c88606 | |||
| 13bae96708 | |||
| 6241001eef | |||
| dc5722d0de | |||
| e3b2443029 | |||
| ca5927fe73 | |||
| 5a0eed7eae | |||
| 532881e679 | |||
| 27d763a46c | |||
| b7e601be16 | |||
| 395676fcb1 | |||
| cc4df1c995 | |||
| 48eada2c37 |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/*
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '8'
|
||||
- '10'
|
||||
services:
|
||||
- mongodb
|
||||
cache:
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM node:8
|
||||
FROM node:10
|
||||
|
||||
# Install global packages
|
||||
RUN npm install -g gulp-cli mocha
|
||||
|
||||
@@ -7,6 +7,6 @@ Habitica [.
|
||||
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).
|
||||
|
||||
+1
-1
@@ -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).
|
||||
|
||||
+81
-113
@@ -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_PASSWORD": "password",
|
||||
"SITE_HTTP_AUTH_USERNAME": "admin",
|
||||
"SLACK_FLAGGING_FOOTER_LINK": "https://habitrpg.github.io/flag-o-rama/",
|
||||
"SLACK_FLAGGING_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_SUBSCRIPTIONS_URL": "https://hooks.slack.com/services/id/id/id",
|
||||
"SLACK_URL": "https://hooks.slack.com/services/some-url",
|
||||
"SMTP_HOST": "example.com",
|
||||
"SMTP_PASS": "password",
|
||||
"SMTP_PORT": 587,
|
||||
"SMTP_SERVICE": "Gmail",
|
||||
"SMTP_TLS": "true",
|
||||
"SMTP_USER": "user@example.com",
|
||||
"STRIPE_API_KEY": "aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY": "22223333444455556666777788889999",
|
||||
"TEST_DB_URI": "mongodb://localhost/habitrpg_test",
|
||||
"TRANSIFEX_SLACK_CHANNEL": "transifex",
|
||||
"WEB_CONCURRENCY": 1,
|
||||
"SKIP_SSL_CHECK_KEY": "key",
|
||||
"ENABLE_STACKDRIVER_TRACING": "false"
|
||||
}
|
||||
|
||||
+5
-5
@@ -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()
|
||||
));
|
||||
));
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
@@ -17,7 +17,7 @@ function setUpServer () {
|
||||
setUpServer();
|
||||
|
||||
// Replace this with your migration
|
||||
const processUsers = require('./users/20181122_turkey_day.js');
|
||||
const processUsers = require('./users/mystery-items.js');
|
||||
processUsers()
|
||||
.then(function success () {
|
||||
process.exit(0);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,70 +1,13 @@
|
||||
import monk from 'monk';
|
||||
import nconf from 'nconf';
|
||||
/* eslint-disable no-console */
|
||||
const MIGRATION_NAME = 'mystery_items_201903';
|
||||
const MYSTERY_ITEMS = ['armor_mystery_201903', 'head_mystery_201903'];
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
Generated
+2892
-2619
File diff suppressed because it is too large
Load Diff
+37
-38
@@ -1,17 +1,19 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.73.1",
|
||||
"version": "4.92.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",
|
||||
"autoprefixer": "^9.4.0",
|
||||
"aws-sdk": "^2.432.0",
|
||||
"axios": "^0.18.0",
|
||||
"axios-progress-bar": "^1.2.0",
|
||||
"babel-core": "^6.26.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.16",
|
||||
"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",
|
||||
@@ -50,10 +52,10 @@
|
||||
"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",
|
||||
@@ -62,13 +64,13 @@
|
||||
"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": "^3.0.0",
|
||||
"nodemailer": "^6.0.0",
|
||||
"ora": "^3.2.0",
|
||||
"pageres": "^4.1.1",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.0.0",
|
||||
@@ -80,17 +82,17 @@
|
||||
"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": "^4.0.0",
|
||||
"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",
|
||||
@@ -98,24 +100,24 @@
|
||||
"uuid": "^3.0.1",
|
||||
"validator": "^10.5.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": "^2.40.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",
|
||||
@@ -164,7 +166,7 @@
|
||||
"expect.js": "^0.3.1",
|
||||
"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": "^6.3.5",
|
||||
"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": {}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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).
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/* eslint-disable camelcase */
|
||||
import {
|
||||
validateItemPath,
|
||||
getDefaultOwnedGear,
|
||||
} 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
@@ -71,7 +72,7 @@ describe('Apple Payments', () => {
|
||||
|
||||
it('errors if the user cannot purchase gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(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',
|
||||
@@ -89,7 +90,7 @@ describe('Apple Payments', () => {
|
||||
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',
|
||||
@@ -131,7 +132,7 @@ describe('Apple Payments', () => {
|
||||
}]);
|
||||
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await applePayments.verifyGemPurchase(user, receipt, headers);
|
||||
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', () => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
@@ -66,7 +67,7 @@ describe('Google Payments', () => {
|
||||
it('should throw an error if user cannot purchase gems', async () => {
|
||||
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',
|
||||
@@ -78,7 +79,7 @@ describe('Google Payments', () => {
|
||||
|
||||
it('purchases gems', async () => {
|
||||
sinon.stub(user, 'canGetGems').resolves(true);
|
||||
await googlePayments.verifyGemPurchase(user, receipt, signature, headers);
|
||||
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', () => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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' },
|
||||
});
|
||||
|
||||
@@ -1163,16 +1194,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 +1212,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 +1237,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 +1407,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);
|
||||
});
|
||||
|
||||
@@ -1670,16 +1709,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('whale');
|
||||
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('whale');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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('');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -333,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')}),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,21 @@ 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);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -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'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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'),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,6 +9,14 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,6 +9,14 @@ 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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,7 +18,12 @@ describe('GET /user/anonymized', () => {
|
||||
'profile.name': 'profile',
|
||||
'purchased.plan': 'purchased plan',
|
||||
contributor: 'contributor',
|
||||
invitations: 'invitations',
|
||||
invitations: {
|
||||
guilds: ['guild1', 'guild2'],
|
||||
party: {
|
||||
_id: 'partyid',
|
||||
},
|
||||
},
|
||||
'items.special.nyeReceived': 'some',
|
||||
'items.special.valentineReceived': 'some',
|
||||
webhooks: [{url: 'https://somurl.com'}],
|
||||
|
||||
@@ -94,9 +94,6 @@ describe('POST /user/auth/reset-password-set-new-one', () => {
|
||||
userId: user._id,
|
||||
expiresAt: moment().add({days: 1}),
|
||||
}));
|
||||
await user.update({
|
||||
auth: 'not an object with valid fields',
|
||||
});
|
||||
|
||||
await expect(api.post(`${endpoint}`, {
|
||||
code,
|
||||
|
||||
@@ -45,7 +45,7 @@ describe('POST /user/auth/local/login', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||
message: t('accountSuspended', { communityManagerEmail: nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL'), userId: user._id }),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -110,4 +110,22 @@ describe('POST /user/auth/local/login', () => {
|
||||
let isValidPassword = await bcryptCompare(textPassword, user.auth.local.hashed_password);
|
||||
expect(isValidPassword).to.equal(true);
|
||||
});
|
||||
|
||||
it('user uses social authentication and has no password', async () => {
|
||||
await user.unset({
|
||||
'auth.local.hashed_password': 1,
|
||||
});
|
||||
|
||||
await user.sync();
|
||||
expect(user.auth.local.hashed_password).to.be.undefined;
|
||||
|
||||
await expect(api.post(endpoint, {
|
||||
username: user.auth.local.username,
|
||||
password: 'any-password',
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('invalidLoginCredentialsLong'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('PUT /user/auth/update-email', () => {
|
||||
})).to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
error: 'NotAuthorized',
|
||||
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL') }),
|
||||
message: t('cannotFulfillReq', { techAssistanceEmail: nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL') }),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration/v4';
|
||||
|
||||
describe('POST /members/flag-private-message/:messageId', () => {
|
||||
let userToSendMessage;
|
||||
let messageToSend = 'Test Private Message';
|
||||
|
||||
beforeEach(async () => {
|
||||
userToSendMessage = await generateUser();
|
||||
});
|
||||
|
||||
it('Allows players to flag their own private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let senderMessages = await userToSendMessage.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInSendersInbox = _.find(senderMessages, (message) => {
|
||||
return message.uuid === receiver._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInSendersInbox).to.exist;
|
||||
await expect(userToSendMessage.post(`/members/flag-private-message/${sendersMessageInSendersInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Flags a private message', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
});
|
||||
|
||||
it('Returns an error when user tries to flag a private message that is already flagged', async () => {
|
||||
let receiver = await generateUser();
|
||||
|
||||
await userToSendMessage.post('/members/send-private-message', {
|
||||
message: messageToSend,
|
||||
toUserId: receiver._id,
|
||||
});
|
||||
|
||||
let receiversMessages = await receiver.get('/inbox/messages');
|
||||
|
||||
let sendersMessageInReceiversInbox = _.find(receiversMessages, (message) => {
|
||||
return message.uuid === userToSendMessage._id && message.text === messageToSend;
|
||||
});
|
||||
|
||||
expect(sendersMessageInReceiversInbox).to.exist;
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`)).to.eventually.be.ok;
|
||||
|
||||
await expect(receiver.post(`/members/flag-private-message/${sendersMessageInReceiversInbox.id}`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 400,
|
||||
error: 'BadRequest',
|
||||
message: t('messageGroupChatFlagAlreadyReported'),
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,300 @@
|
||||
import Avatar from 'client/components/avatar';
|
||||
import Vue from 'vue';
|
||||
import generateStore from 'client/store';
|
||||
|
||||
context('avatar.vue', () => {
|
||||
let Constructr;
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
Constructr = Vue.extend(Avatar);
|
||||
|
||||
vm = new Constructr({
|
||||
propsData: {
|
||||
member: {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
preferences: {
|
||||
hair: {},
|
||||
},
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}).$mount();
|
||||
|
||||
vm.$store = generateStore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('hasClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: { lvl: 17 },
|
||||
preferences: { disableClasses: true },
|
||||
flags: { classSelected: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports class status', () => {
|
||||
expect(vm.hasClass).to.equal(false);
|
||||
|
||||
vm.member.preferences.disableClasses = false;
|
||||
vm.member.flags.classSelected = true;
|
||||
|
||||
expect(vm.hasClass).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBuffed', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('accurately reports if buffed', () => {
|
||||
expect(vm.isBuffed).to.equal(undefined);
|
||||
|
||||
vm.member.stats.buffs = { str: 1 };
|
||||
|
||||
expect(vm.isBuffed).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('paddingTop', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('defaults to 28px', () => {
|
||||
vm.avatarOnly = true;
|
||||
expect(vm.paddingTop).to.equal('28px');
|
||||
});
|
||||
|
||||
it('is 24.5px if user has a pet', () => {
|
||||
vm.member.items = {
|
||||
currentPet: { name: 'Foo' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('24.5px');
|
||||
});
|
||||
|
||||
it('is 0px if user has a mount', () => {
|
||||
vm.member.items = {
|
||||
currentMount: { name: 'Bar' },
|
||||
};
|
||||
|
||||
expect(vm.paddingTop).to.equal('0px');
|
||||
});
|
||||
|
||||
it('can be overriden', () => {
|
||||
vm.overrideTopPadding = '27px';
|
||||
expect(vm.paddingTop).to.equal('27px');
|
||||
});
|
||||
});
|
||||
|
||||
describe('costumeClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
preferences: {},
|
||||
};
|
||||
});
|
||||
|
||||
it('returns if showing equiped gear', () => {
|
||||
expect(vm.costumeClass).to.equal('equipped');
|
||||
});
|
||||
it('returns if wearing a costume', () => {
|
||||
vm.member.preferences = { costume: true };
|
||||
expect(vm.costumeClass).to.equal('costume');
|
||||
});
|
||||
});
|
||||
|
||||
describe('visualBuffs', () => {
|
||||
it('returns an array of buffs', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'Warrior',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.visualBuffs).to.include({snowball: 'snowman'});
|
||||
expect(vm.visualBuffs).to.include({spookySparkles: 'ghost'});
|
||||
expect(vm.visualBuffs).to.include({shinySeed: 'avatar_floral_Warrior'});
|
||||
expect(vm.visualBuffs).to.include({seafoam: 'seafoam_star'});
|
||||
});
|
||||
});
|
||||
|
||||
describe('backgroundClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member.preferences = { background: 'pony' };
|
||||
});
|
||||
|
||||
it('shows the background', () => {
|
||||
expect(vm.backgroundClass).to.equal('background_pony');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { background: 'character' };
|
||||
|
||||
expect(vm.backgroundClass).to.equal('background_character');
|
||||
});
|
||||
|
||||
it('returns to a blank string if not showing background', () => {
|
||||
vm.withBackground = false;
|
||||
vm.avatarOnly = true;
|
||||
|
||||
expect(vm.backgroundClass).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialMountClass', () => {
|
||||
it('checks if riding a Kangaroo', () => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
class: 'None',
|
||||
},
|
||||
items: {},
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal(undefined);
|
||||
|
||||
vm.member.items = {
|
||||
currentMount: ['Kangaroo'],
|
||||
};
|
||||
|
||||
expect(vm.specialMountClass).to.equal('offset-kangaroo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('skinClass', () => {
|
||||
it('returns current skin color', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
});
|
||||
|
||||
it('returns if sleep or not', () => {
|
||||
vm.member = {
|
||||
stats: {},
|
||||
preferences: {
|
||||
skin: 'blue',
|
||||
sleep: false,
|
||||
},
|
||||
};
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue');
|
||||
|
||||
vm.member.preferences.sleep = true;
|
||||
|
||||
expect(vm.skinClass).to.equal('skin_blue_sleep');
|
||||
});
|
||||
});
|
||||
|
||||
context('methods', () => {
|
||||
describe('getGearClass', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: { Hat: 'Fancy Tophat' },
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
|
||||
it('returns undefined if no match', () => {
|
||||
expect(vm.getGearClass('foo')).to.equal(undefined);
|
||||
});
|
||||
|
||||
it('returns the matching gear', () => {
|
||||
expect(vm.getGearClass('Hat')).to.equal('Fancy Tophat');
|
||||
});
|
||||
|
||||
it('can be overridden', () => {
|
||||
vm.overrideAvatarGear = { Hat: 'Dapper Bowler' };
|
||||
|
||||
expect(vm.getGearClass('Hat')).to.equal('Dapper Bowler');
|
||||
});
|
||||
});
|
||||
|
||||
describe('hideGear', () => {
|
||||
it('returns no weapon equipped', () => {
|
||||
vm.member.items.gear.equipped = {};
|
||||
expect(vm.hideGear('weapon')).to.equal(false);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
items: {
|
||||
gear: {
|
||||
equipped: {
|
||||
weapon: {
|
||||
baseWeapon: 'Spoon',
|
||||
twoHanded: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: { costume: false },
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
describe('show avatar', () => {
|
||||
beforeEach(() => {
|
||||
vm.member = {
|
||||
stats: {
|
||||
buffs: {
|
||||
snowball: false,
|
||||
seafoam: false,
|
||||
spookySparkles: false,
|
||||
shinySeed: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
it('does if not showing visual buffs', () => {
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
|
||||
let buffs = vm.member.stats.buffs;
|
||||
|
||||
buffs.snowball = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.snowball = false;
|
||||
buffs.spookySparkles = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.spookySparkles = false;
|
||||
buffs.shinySeed = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.shinySeed = false;
|
||||
buffs.seafoam = true;
|
||||
expect(vm.showAvatar()).to.equal(false);
|
||||
|
||||
buffs.seafoam = false;
|
||||
vm.showVisualBuffs = false;
|
||||
expect(vm.showAvatar()).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import {shallow} from '@vue/test-utils';
|
||||
import {mount} from '@vue/test-utils';
|
||||
import Vue from 'vue';
|
||||
|
||||
import CategoryTags from 'client/components/categories/categoryTags.vue';
|
||||
|
||||
@@ -6,7 +7,7 @@ describe('Category Tags', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(CategoryTags, {
|
||||
wrapper = mount(CategoryTags, {
|
||||
propsData: {
|
||||
categories: [],
|
||||
},
|
||||
@@ -27,8 +28,10 @@ describe('Category Tags', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(wrapper.contains('.category-label')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('test');
|
||||
return Vue.nextTick().then(() => {
|
||||
expect(wrapper.contains('.category-label')).to.eq(true);
|
||||
expect(wrapper.find('.category-label').text()).to.eq('test');
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a habitica official in purple', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { shallow } from '@vue/test-utils';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import SidebarSection from 'client/components/sidebarSection.vue';
|
||||
|
||||
@@ -6,7 +6,7 @@ describe('Sidebar Section', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(function () {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
wrapper = mount(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
},
|
||||
@@ -39,7 +39,7 @@ describe('Sidebar Section', () => {
|
||||
});
|
||||
|
||||
it('can hide contents by default', () => {
|
||||
wrapper = shallow(SidebarSection, {
|
||||
wrapper = mount(SidebarSection, {
|
||||
propsData: {
|
||||
title: 'Hello World',
|
||||
show: false,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { shallow, createLocalVue } from '@vue/test-utils';
|
||||
import { mount, createLocalVue } from '@vue/test-utils';
|
||||
|
||||
import TaskColumn from 'client/components/tasks/column.vue';
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('Task Column', () => {
|
||||
};
|
||||
let stubs = ['b-modal']; // <b-modal> is a custom component and not tested here
|
||||
|
||||
return shallow(TaskColumn, {
|
||||
return mount(TaskColumn, {
|
||||
propsData: {
|
||||
type,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import {highlightUsers} from '../../../../../website/client/libs/highlightUsers';
|
||||
import habiticaMarkdown from 'habitica-markdown';
|
||||
|
||||
describe('highlightUserAndEmail', () => {
|
||||
it('highlights displayname', () => {
|
||||
const text = 'hello @displayedUser with text after';
|
||||
|
||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||
});
|
||||
|
||||
it('highlights username', () => {
|
||||
const text = 'hello @user';
|
||||
|
||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@user</span>');
|
||||
});
|
||||
|
||||
it('not highlights any email', () => {
|
||||
const text = habiticaMarkdown.render('hello@example.com');
|
||||
|
||||
const result = highlightUsers(text, 'example', 'displayedUser');
|
||||
expect(result).to.not.contain('<span class="at-highlight">@example</span>');
|
||||
});
|
||||
|
||||
|
||||
it('complex highlight', () => {
|
||||
const plainText = 'a bit more @mentions to @use my@mentions.com broken.@mail.com';
|
||||
|
||||
const text = habiticaMarkdown.render(plainText);
|
||||
|
||||
const result = highlightUsers(text, 'use', 'mentions');
|
||||
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { data, gems, buffs, preferences, tasksOrder } from 'client/store/getters/user';
|
||||
|
||||
context('user getters', () => {
|
||||
describe('data', () => {
|
||||
it('returns the user\'s data', () => {
|
||||
expect(data({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
lvl: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).lvl).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('gems', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(gems({
|
||||
state: {
|
||||
user: {
|
||||
data: { balance: 4.5 },
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buffs', () => {
|
||||
it('returns the user\'s buffs', () => {
|
||||
expect(buffs({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
stats: {
|
||||
buffs: [1],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})(0)).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('preferences', () => {
|
||||
it('returns the user\'s preferences', () => {
|
||||
expect(preferences({
|
||||
state: {
|
||||
user: {
|
||||
data: {
|
||||
preferences: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tasksOrder', () => {
|
||||
it('returns the user\'s tasksOrder', () => {
|
||||
expect(tasksOrder({
|
||||
state: {
|
||||
user: {
|
||||
tasksOrder: {
|
||||
masters: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
})('master')).to.equal(1);
|
||||
|
||||
expect(tasksOrder()).to.not.equal('null');
|
||||
expect(tasksOrder()).to.not.equal('undefined');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gems as userGems } from 'client/store/getters/user';
|
||||
|
||||
describe('userGems getter', () => {
|
||||
it('returns the user\'s gems', () => {
|
||||
expect(userGems({
|
||||
state: {
|
||||
user: {
|
||||
data: {balance: 4.5},
|
||||
},
|
||||
},
|
||||
})).to.equal(18);
|
||||
});
|
||||
});
|
||||
@@ -62,6 +62,18 @@ describe('inAppRewards', () => {
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('ignores null/undefined entries', () => {
|
||||
user.pinnedItems = testPinnedItems;
|
||||
user.pinnedItems.push(null);
|
||||
user.pinnedItems.push(undefined);
|
||||
user.pinnedItemsOrder = testPinnedItemsOrder;
|
||||
|
||||
let result = inAppRewards(user);
|
||||
|
||||
expect(result[2].path).to.eql('armoire');
|
||||
expect(result[9].path).to.eql('potion');
|
||||
});
|
||||
|
||||
it('does not return seasonal items which have been unpinned', () => {
|
||||
if (officialPinnedItems.length === 0) {
|
||||
return; // if no seasonal items, this test is not applicable
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
@@ -16,6 +17,10 @@ describe('shared.ops.buy', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -26,7 +31,6 @@ describe('shared.ops.buy', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(analytics, 'track');
|
||||
|
||||
@@ -158,7 +158,7 @@ describe('shared.ops.buyArmoire', () => {
|
||||
|
||||
expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
|
||||
expect(user.stats.gp).to.eql(100);
|
||||
expect(analytics.track).to.be.calledOnce;
|
||||
expect(analytics.track).to.be.calledTwice;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import errorMessage from '../../../../website/common/script/libs/errorMessage';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
|
||||
function buyGear (user, req, analytics) {
|
||||
let buyOp = new BuyMarketGearOperation(user, req, analytics);
|
||||
@@ -24,6 +25,10 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
defaultsDeep(user, {
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
@@ -34,7 +39,6 @@ describe('shared.ops.buyMarketGear', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
|
||||
sinon.stub(shared, 'randomVal');
|
||||
|
||||
@@ -93,6 +93,22 @@ describe('shared.ops.hatch', () => {
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('does not allow hatching quest pet egg using wacky potion', (done) => {
|
||||
user.items.eggs = {Bunny: 1};
|
||||
user.items.hatchingPotions = {Veggie: 1};
|
||||
user.items.pets = {};
|
||||
try {
|
||||
hatch(user, {params: {egg: 'Bunny', hatchingPotion: 'Veggie'}});
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('messageInvalidEggPotionCombo'));
|
||||
expect(user.items.pets).to.be.empty;
|
||||
expect(user.items.eggs).to.eql({Bunny: 1});
|
||||
expect(user.items.hatchingPotions).to.eql({Veggie: 1});
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
context('successful hatching', () => {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import {addPinnedGear} from '../../../website/common/script/ops/pinnedGearUtils';
|
||||
|
||||
describe('shared.ops.pinnedGearUtils.addPinnedGear', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser();
|
||||
});
|
||||
|
||||
it('not adds an item with empty properties to pinnedItems', () => {
|
||||
addPinnedGear(user, undefined, undefined);
|
||||
|
||||
expect(user.pinnedItems.length).to.be.eql(0);
|
||||
});
|
||||
});
|
||||
@@ -9,13 +9,14 @@ import hatchingPotions from '../../website/common/script/content/hatching-potion
|
||||
|
||||
describe('hatchingPotions', () => {
|
||||
describe('all', () => {
|
||||
it('is a combination of drop and premium potions', () => {
|
||||
it('is a combination of drop, premium, and wacky potions', () => {
|
||||
let dropNumber = Object.keys(hatchingPotions.drops).length;
|
||||
let premiumNumber = Object.keys(hatchingPotions.premium).length;
|
||||
let wackyNumber = Object.keys(hatchingPotions.wacky).length;
|
||||
let allNumber = Object.keys(hatchingPotions.all).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each potion', () => {
|
||||
|
||||
@@ -47,6 +47,18 @@ describe('stable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('wackyPets', () => {
|
||||
it('contains a pet for each wacky potion * each drop egg', () => {
|
||||
let numberOfWackyPotions = Object.keys(potions.wacky).length;
|
||||
let numberOfDropEggs = Object.keys(eggs.drops).length;
|
||||
let numberOfWackyPets = Object.keys(stable.wackyPets).length;
|
||||
let expectedTotal = numberOfWackyPotions * numberOfDropEggs;
|
||||
|
||||
expect(numberOfWackyPets).to.be.greaterThan(0);
|
||||
expect(numberOfWackyPets).to.equal(expectedTotal);
|
||||
});
|
||||
});
|
||||
|
||||
describe('specialPets', () => {
|
||||
it('each value is a valid translation string', () => {
|
||||
each(stable.specialPets, (pet) => {
|
||||
@@ -107,10 +119,11 @@ describe('stable', () => {
|
||||
let questNumber = Object.keys(stable.questPets).length;
|
||||
let specialNumber = Object.keys(stable.specialPets).length;
|
||||
let premiumNumber = Object.keys(stable.premiumPets).length;
|
||||
let wackyNumber = Object.keys(stable.wackyPets).length;
|
||||
let allNumber = Object.keys(stable.petInfo).length;
|
||||
|
||||
expect(allNumber).to.be.greaterThan(0);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber);
|
||||
expect(allNumber).to.equal(dropNumber + questNumber + specialNumber + premiumNumber + wackyNumber);
|
||||
});
|
||||
|
||||
it('contains basic information about each pet', () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { requester } from './requester';
|
||||
import {
|
||||
getDocument as getDocumentFromMongo,
|
||||
updateDocument as updateDocumentInMongo,
|
||||
unsetDocument as unsetDocumentInMongo,
|
||||
} from '../mongo';
|
||||
import {
|
||||
assign,
|
||||
@@ -29,6 +30,18 @@ class ApiObject {
|
||||
return this;
|
||||
}
|
||||
|
||||
async unset (options) {
|
||||
if (isEmpty(options)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await unsetDocumentInMongo(this._docType, this, options);
|
||||
|
||||
_updateLocalParameters((this, options));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async sync () {
|
||||
let updatedDoc = await getDocumentFromMongo(this._docType, this);
|
||||
|
||||
|
||||
@@ -13,10 +13,16 @@ import * as Tasks from '../../../../website/server/models/task';
|
||||
// parameter, such as the number of wolf eggs the user has,
|
||||
// , you can do so by passing in the full path as a string:
|
||||
// { 'items.eggs.Wolf': 10 }
|
||||
export async function generateUser (update = {}) {
|
||||
let username = (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = 'password';
|
||||
let email = `${username}@example.com`;
|
||||
//
|
||||
// To manually set a username, email or password pass it in as
|
||||
// an object for the second parameter. Only overrides need to be
|
||||
// added. Items that don't exist will be autogenerated.
|
||||
// Example: generateUser({}, { username: 'TestName' }) adds user
|
||||
// with the 'TestName' username.
|
||||
export async function generateUser (update = {}, overrides = {}) {
|
||||
let username = overrides.username || (Date.now() + generateUUID()).substring(0, 20);
|
||||
let password = overrides.password || 'password';
|
||||
let email = overrides.email || `${username}@example.com`;
|
||||
|
||||
let user = await requester().post('/user/auth/local/register', {
|
||||
username,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../website/server/models/task';
|
||||
export {translate} from './translate';
|
||||
|
||||
|
||||
export function generateUser (options = {}) {
|
||||
let user = new User(options).toObject();
|
||||
|
||||
|
||||
@@ -98,6 +98,19 @@ export async function updateDocument (collectionName, doc, update) {
|
||||
});
|
||||
}
|
||||
|
||||
// Unset a property in the database.
|
||||
// Useful for testing.
|
||||
export async function unsetDocument (collectionName, doc, update) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
collection.updateOne({ _id: doc._id }, { $unset: update }, (updateErr) => {
|
||||
if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDocument (collectionName, doc) {
|
||||
let collection = mongoose.connection.db.collection(collectionName);
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import mongoose from 'mongoose';
|
||||
|
||||
export async function mockFindById (response) {
|
||||
const mockFind = {
|
||||
select () {
|
||||
return this;
|
||||
},
|
||||
lean () {
|
||||
return this;
|
||||
},
|
||||
exec () {
|
||||
return Promise.resolve(response);
|
||||
},
|
||||
};
|
||||
sinon.stub(mongoose.Model, 'findById').returns(mockFind);
|
||||
}
|
||||
|
||||
export function restoreFindById () {
|
||||
return mongoose.Model.findById.restore();
|
||||
}
|
||||
@@ -65,7 +65,7 @@ apt-get install -qq ntp
|
||||
echo Installing nvm, node and global node modules...
|
||||
/vagrant/vagrant_scripts/install_node.sh
|
||||
|
||||
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally"
|
||||
echo "'vagrant up' is finished. Continue with the instructions at http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally"
|
||||
|
||||
# Uncomment both lines to autostart the habitica server when provisioning
|
||||
# echo Starting Habitica server...
|
||||
|
||||
@@ -15,10 +15,11 @@ setupNconf(configFile);
|
||||
// @TODO: Do we need? const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations',
|
||||
// 'FACEBOOK_KEY', 'GOOGLE_CLIENT_ID', 'NODE_ENV', 'BASE_URL', 'GA_ID',
|
||||
// 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY',
|
||||
// 'worldDmg', 'mods', 'IS_MOBILE', 'PUSHER:KEY', 'PUSHER:ENABLED'];
|
||||
// 'worldDmg', 'mods', 'IS_MOBILE'];
|
||||
|
||||
const AMAZON_SELLER_ID = nconf.get('AMAZON_PAYMENTS:SELLER_ID') || nconf.get('AMAZON_PAYMENTS_SELLER_ID');
|
||||
const AMAZON_CLIENT_ID = nconf.get('AMAZON_PAYMENTS:CLIENT_ID') || nconf.get('AMAZON_PAYMENTS_CLIENT_ID');
|
||||
const AMAZON_SELLER_ID = nconf.get('AMAZON_PAYMENTS_SELLER_ID');
|
||||
const AMAZON_CLIENT_ID = nconf.get('AMAZON_PAYMENTS_CLIENT_ID');
|
||||
const AMAZON_MODE = nconf.get('AMAZON_PAYMENTS_MODE');
|
||||
|
||||
let env = {
|
||||
NODE_ENV: '"production"',
|
||||
@@ -26,15 +27,16 @@ let env = {
|
||||
AMAZON_PAYMENTS: {
|
||||
SELLER_ID: `"${AMAZON_SELLER_ID}"`,
|
||||
CLIENT_ID: `"${AMAZON_CLIENT_ID}"`,
|
||||
MODE: `"${AMAZON_MODE}"`,
|
||||
},
|
||||
EMAILS: {
|
||||
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL')}"`,
|
||||
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS:TECH_ASSISTANCE_EMAIL')}"`,
|
||||
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS:PRESS_ENQUIRY_EMAIL')}"`,
|
||||
COMMUNITY_MANAGER_EMAIL: `"${nconf.get('EMAILS_COMMUNITY_MANAGER_EMAIL')}"`,
|
||||
TECH_ASSISTANCE_EMAIL: `"${nconf.get('EMAILS_TECH_ASSISTANCE_EMAIL')}"`,
|
||||
PRESS_ENQUIRY_EMAIL: `"${nconf.get('EMAILS_PRESS_ENQUIRY_EMAIL')}"`,
|
||||
},
|
||||
};
|
||||
|
||||
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY PUSHER:KEY PUSHER:ENABLED LOGGLY_CLIENT_TOKEN'
|
||||
'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY GOOGLE_CLIENT_ID AMPLITUDE_KEY LOGGLY_CLIENT_TOKEN'
|
||||
.split(' ')
|
||||
.forEach(key => {
|
||||
env[key] = `"${nconf.get(key)}"`;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Running
|
||||
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.wikia.com/wiki/Setting_up_Habitica_Locally).
|
||||
For information about installing and running Habitica locally, see [Setting up Habitica Locally](http://habitica.fandom.com/wiki/Setting_up_Habitica_Locally).
|
||||
|
||||
# Preparation Reading
|
||||
- Vue 2 (https://vuejs.org)
|
||||
@@ -18,4 +18,4 @@ The project is developed directly in the `develop` branch as long as we'll be ab
|
||||
|
||||
So far most of the work has been on the template, so there's no complex logic to understand. The only thing I would suggest you to read about is Vuex for data management: it's basically a Flux implementation: there's a central store that hold the data for the entire app, and every change to the data must happen through an action, the data cannot be mutated directly.
|
||||
|
||||
For further resources, see [Guidance for Blacksmiths](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.wikia.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
|
||||
For further resources, see [Guidance for Blacksmiths](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths), and in particular the ["Website Technology Stack" section](http://habitica.fandom.com/wiki/Guidance_for_Blacksmiths#Website_Technology_Stack).
|
||||
|
||||
+67
-76
@@ -11,22 +11,23 @@ div
|
||||
#app(:class='{"casting-spell": castingSpell}')
|
||||
banned-account-modal
|
||||
amazon-payments-modal(v-if='!isStaticPage')
|
||||
payments-success-modal
|
||||
snackbars
|
||||
router-view(v-if="!isUserLoggedIn || isStaticPage")
|
||||
template(v-else)
|
||||
template(v-if="isUserLoaded")
|
||||
div.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
.resting-banner(v-show="showRestingBanner", ref="restingBanner")
|
||||
span.content
|
||||
span.label.d-inline.d-sm-none {{ $t('innCheckOutBannerShort') }}
|
||||
span.label.d-none.d-sm-inline {{ $t('innCheckOutBanner') }}
|
||||
span.separator |
|
||||
span.resume(@click="resumeDamage()") {{ $t('resumeDamage') }}
|
||||
div.closepadding(@click="hideBanner()")
|
||||
.closepadding(@click="hideBanner()")
|
||||
span.svg-icon.inline.icon-10(aria-hidden="true", v-html="icons.close")
|
||||
notifications-display
|
||||
app-menu(:class='{"restingInn": showRestingBanner}' :style="{ marginTop: bannerHeight + 'px' }")
|
||||
app-menu
|
||||
.container-fluid
|
||||
app-header(:class='{"restingInn": showRestingBanner}')
|
||||
app-header
|
||||
buyModal(
|
||||
:item="selectedItemToBuy || {}",
|
||||
:withPin="true",
|
||||
@@ -49,6 +50,13 @@ div
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#loading-screen-inapp {
|
||||
#melior {
|
||||
margin: 0 auto;
|
||||
@@ -78,6 +86,11 @@ div
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.notification {
|
||||
border-radius: 1000px;
|
||||
background-color: $green-10;
|
||||
@@ -88,42 +101,10 @@ div
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
overflow-x: hidden;
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: calc(100% - 56px); /* 56px is the menu */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
|
||||
.resting-banner {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
background-color: $blue-10;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1300;
|
||||
display: flex;
|
||||
@@ -139,14 +120,10 @@ div
|
||||
.closepadding {
|
||||
margin: 11px 24px;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
position: relative;
|
||||
right: 0;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
|
||||
span svg path {
|
||||
stroke: $blue-500;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
@@ -169,6 +146,30 @@ div
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss'>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.closepadding span svg path {
|
||||
stroke: #FFF;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
/* @TODO: The modal-open class is not being removed. Let's try this for now */
|
||||
.modal {
|
||||
overflow-y: scroll !important;
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: .9 !important;
|
||||
background-color: $purple-100 !important;
|
||||
}
|
||||
|
||||
/* Push progress bar above modals */
|
||||
#nprogress .bar {
|
||||
z-index: 1600 !important; /* Must stay above nav bar */
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { loadProgressBar } from 'axios-progress-bar';
|
||||
@@ -185,7 +186,10 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import { setup as setupPayments } from 'client/libs/payments';
|
||||
import amazonPaymentsModal from 'client/components/payments/amazonModal';
|
||||
import paymentsSuccessModal from 'client/components/payments/successModal';
|
||||
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import { CONSTANTS, getLocalSetting, removeLocalSetting } from 'client/libs/userlocalManager';
|
||||
|
||||
import svgClose from 'assets/svg/close.svg';
|
||||
import bannedAccountModal from 'client/components/bannedAccountModal';
|
||||
@@ -205,6 +209,7 @@ export default {
|
||||
SelectMembersModal,
|
||||
amazonPaymentsModal,
|
||||
bannedAccountModal,
|
||||
paymentsSuccessModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -220,7 +225,6 @@ export default {
|
||||
loading: true,
|
||||
currentTipNumber: 0,
|
||||
bannerHidden: false,
|
||||
bannerHeight: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -313,6 +317,7 @@ export default {
|
||||
const errorMessage = errorData.message || errorData;
|
||||
|
||||
// Check for conditions to reset the user auth
|
||||
// TODO use a specific error like NotificationNotFound instead of checking for the string
|
||||
const invalidUserMessage = [this.$t('invalidCredentials'), 'Missing authentication headers.'];
|
||||
if (invalidUserMessage.indexOf(errorMessage) !== -1) {
|
||||
this.$store.dispatch('auth:logout');
|
||||
@@ -322,12 +327,6 @@ export default {
|
||||
let snackbarTimeout = false;
|
||||
if (error.response.status === 502) snackbarTimeout = true;
|
||||
|
||||
const notificationNotFoundMessage = [
|
||||
this.$t('messageNotificationNotFound'),
|
||||
this.$t('messageNotificationNotFound', 'en'),
|
||||
];
|
||||
if (notificationNotFoundMessage.indexOf(errorMessage) !== -1) snackbarTimeout = true;
|
||||
|
||||
let errorsToShow = [];
|
||||
// show only the first error for each param
|
||||
let paramErrorsFound = {};
|
||||
@@ -341,13 +340,17 @@ export default {
|
||||
} else {
|
||||
errorsToShow.push(errorMessage);
|
||||
}
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
|
||||
// Ignore NotificationNotFound errors, see https://github.com/HabitRPG/habitica/issues/10391
|
||||
if (errorData.error !== 'NotificationNotFound') {
|
||||
// dispatch as one snackbar notification
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: errorsToShow.join(' '),
|
||||
type: 'error',
|
||||
timeout: snackbarTimeout,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
@@ -418,14 +421,6 @@ export default {
|
||||
|
||||
this.hideLoadingScreen();
|
||||
|
||||
window.addEventListener('resize', this.setBannerOffset);
|
||||
// Adjust the positioning of the header banners
|
||||
this.$watch('showRestingBanner', () => {
|
||||
this.$nextTick(() => {
|
||||
this.setBannerOffset();
|
||||
});
|
||||
}, {immediate: true});
|
||||
|
||||
// Adjust the timezone offset
|
||||
if (this.user.preferences.timezoneOffset !== this.browserTimezoneOffset) {
|
||||
this.$store.dispatch('user:set', {
|
||||
@@ -433,6 +428,14 @@ export default {
|
||||
});
|
||||
}
|
||||
|
||||
let appState = getLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
||||
if (appState) {
|
||||
appState = JSON.parse(appState);
|
||||
if (appState.paymentCompleted) {
|
||||
removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE);
|
||||
this.$root.$emit('habitica:payment-success', appState);
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// Load external scripts after the app has been rendered
|
||||
setupPayments();
|
||||
@@ -452,7 +455,6 @@ export default {
|
||||
this.$root.$off('bv::show::modal');
|
||||
this.$root.$off('buyModal::showItem');
|
||||
this.$root.$off('selectMembersModal::showItem');
|
||||
window.removeEventListener('resize', this.setBannerOffset);
|
||||
},
|
||||
mounted () {
|
||||
// Remove the index.html loading screen and now show the inapp loading
|
||||
@@ -611,22 +613,10 @@ export default {
|
||||
},
|
||||
hideBanner () {
|
||||
this.bannerHidden = true;
|
||||
this.setBannerOffset();
|
||||
},
|
||||
resumeDamage () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
setBannerOffset () {
|
||||
let contentPlacement = 0;
|
||||
if (this.showRestingBanner && this.$refs.restingBanner !== undefined) {
|
||||
contentPlacement = this.$refs.restingBanner.clientHeight;
|
||||
}
|
||||
this.bannerHeight = contentPlacement;
|
||||
let smartBanner = document.getElementsByClassName('smartbanner')[0];
|
||||
if (smartBanner !== undefined) {
|
||||
smartBanner.style.top = `${contentPlacement}px`;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -659,5 +649,6 @@ export default {
|
||||
<style src="assets/css/sprites/spritesmith-main-21.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-23.css"></style>
|
||||
<style src="assets/css/sprites/spritesmith-main-24.css"></style>
|
||||
<style src="assets/css/sprites.css"></style>
|
||||
<style src="smartbanner.js/dist/smartbanner.min.css"></style>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
height: 219px;
|
||||
}
|
||||
|
||||
.Pet_HatchingPotion_Veggie {
|
||||
background: url("~assets/images/Pet_HatchingPotion_Veggie.gif") no-repeat;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.Gems {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
|
||||
@@ -1,84 +1,66 @@
|
||||
.achievement-costumeContest6x {
|
||||
.promo_april_fools_2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -169px;
|
||||
width: 144px;
|
||||
height: 156px;
|
||||
}
|
||||
.promo_alligator {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px 0px;
|
||||
width: 480px;
|
||||
height: 360px;
|
||||
}
|
||||
.promo_animal_tails {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -994px 0px;
|
||||
width: 141px;
|
||||
height: 441px;
|
||||
}
|
||||
.promo_armoire_backgrounds_201811 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -568px;
|
||||
background-position: 0px -337px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_frost_potions {
|
||||
.promo_armoire_backgrounds_201904 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -421px -723px;
|
||||
width: 417px;
|
||||
background-position: -424px -337px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_ios {
|
||||
.promo_celestial_rainbow_potions {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -361px;
|
||||
width: 375px;
|
||||
height: 361px;
|
||||
}
|
||||
.promo_mystery_201810 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px 0px;
|
||||
width: 294px;
|
||||
height: 168px;
|
||||
}
|
||||
.promo_oddballs_bundle {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -420px;
|
||||
background-position: -445px -163px;
|
||||
width: 423px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_classes_spring2019 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -445px 0px;
|
||||
width: 432px;
|
||||
height: 162px;
|
||||
}
|
||||
.promo_egg_hunt {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -485px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_mystery_201903 {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -352px -633px;
|
||||
width: 351px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_seasonalshop_spring {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -710px -485px;
|
||||
width: 162px;
|
||||
height: 138px;
|
||||
}
|
||||
.promo_shiny_seeds {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -633px;
|
||||
width: 351px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_spring_avatar_customizations {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -355px -485px;
|
||||
width: 354px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_take_this {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1281px -169px;
|
||||
background-position: -704px -633px;
|
||||
width: 96px;
|
||||
height: 69px;
|
||||
}
|
||||
.promo_turkey_day_2018 {
|
||||
.scene_hat_guild {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -723px;
|
||||
width: 420px;
|
||||
height: 147px;
|
||||
}
|
||||
.promo_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: 0px -871px;
|
||||
width: 363px;
|
||||
height: 141px;
|
||||
}
|
||||
.scene_nametag {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px 0px;
|
||||
width: 512px;
|
||||
height: 208px;
|
||||
}
|
||||
.scene_sleep {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -481px -209px;
|
||||
width: 390px;
|
||||
height: 210px;
|
||||
}
|
||||
.scene_veteran_pets {
|
||||
background-image: url('~assets/images/sprites/spritesmith-largeSprites-0.png');
|
||||
background-position: -1136px -326px;
|
||||
width: 242px;
|
||||
height: 62px;
|
||||
background-position: 0px 0px;
|
||||
width: 444px;
|
||||
height: 336px;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user