From 11de1015ea6639849cac868d479151c571871b41 Mon Sep 17 00:00:00 2001 From: Husman Date: Tue, 12 Apr 2016 16:41:00 -0700 Subject: [PATCH 001/152] Fixes: cursor as pointer on hero-box class in level up dialog that is not clickable by the user. Updates the style of the herobox to use the default cursor on this page instead of the pointer cursor that is defaulted by the hero-box class. This is in relation to: https://github.com/HabitRPG/habitrpg/issues/7066 --- website/views/shared/modals/level-up.jade | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/views/shared/modals/level-up.jade b/website/views/shared/modals/level-up.jade index 2b1d44ebe9..d0f6ab8400 100644 --- a/website/views/shared/modals/level-up.jade +++ b/website/views/shared/modals/level-up.jade @@ -7,11 +7,11 @@ script(type='text/ng-template', id='modals/levelUp.html') h3(style='margin-bottom: 0')=env.t('gainedLevel') .container-fluid .row - .herobox(style='margin:auto 8.3em; width: 6em; height: 9em; padding-top: 0') + .herobox(style='margin:auto 8.3em; width: 6em; height: 9em; padding-top: 0; cursor:default') .character-sprites(style='margin: 0; width: 0') +generatedAvatar .row - .herobox(style='margin:auto 8.9em; width: 6em; height: 0; padding-top: 0') + .herobox(style='margin:auto 8.9em; width: 6em; height: 0; padding-top: 0; cursor:default') .avatar-level(ng-class='userLevelStyle(user)')=env.t('level')+' {{user.stats.lvl}}' h4(style='margin-top: 1em')!=env.t('leveledUp', {level:'{{user.stats.lvl}}'}) p=env.t('fullyHealed') From 55f039892817643fc39fb636978690228cf605a2 Mon Sep 17 00:00:00 2001 From: Nick Bunn Date: Wed, 13 Apr 2016 01:16:50 -0500 Subject: [PATCH 002/152] moved broadcast to task page. changed up key press binding --- website/public/js/controllers/rootCtrl.js | 14 -------------- website/public/js/controllers/tasksCtrl.js | 22 ++++++++++++++++++++++ website/views/index.jade | 2 +- website/views/main/index.jade | 2 +- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/website/public/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js index eacc0d2cce..d9d42d6a22 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -330,19 +330,5 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ }); // error will be handled via $http interceptor } - - // Global Keyevents - var ctrlKeys = [17, 224, 91]; - $scope.$on("habit:keydown", function (e, keyEvent) { - if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) { - $scope.ctrlPressed = true; - } - }); - - $scope.$on("habit:keyup", function (e, keyEvent) { - if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) { - $scope.ctrlPressed = false; - } - }); } ]); diff --git a/website/public/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js index 619b0a8b3f..6073ec7fe7 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -258,4 +258,26 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $rootScope.playSound('Reward'); } } + + var ctrlKeys = [17, 224, 91]; + $scope.hoverIn = function () { + $(document).keydown(function (keyEvent) { + if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) { + $scope.ctrlPressed = true; + $scope.$apply(); + } + }); + $(document).keyup(function (keyEvent) { + console.log('blah'); + if (ctrlKeys.indexOf(keyEvent.keyCode) !== -1) { + $scope.ctrlPressed = false; + $scope.$apply(); + } + }); + } + $scope.hoverout = function () { + $(document).unbind('keydown'); + $(document).unbind('keyup'); + $scope.ctrlPressed = false; + } }]); diff --git a/website/views/index.jade b/website/views/index.jade index b1568d254a..f3d993d1e7 100644 --- a/website/views/index.jade +++ b/website/views/index.jade @@ -1,6 +1,6 @@ doctype html //html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keypress="{27:'castCancel()'}") -html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keyup="{27:'castCancel()'}", ng-keydown="$broadcast('habit:keydown', $event)", ng-keyup="$broadcast('habit:keyup', $event)") +html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keyup="{27:'castCancel()'}") head title(ng-bind="env.t('habitica') + ' | ' + $root.pageTitle") // ?v=1 needed to force refresh diff --git a/website/views/main/index.jade b/website/views/main/index.jade index 586ce3ca60..3a8ac8d03c 100644 --- a/website/views/main/index.jade +++ b/website/views/main/index.jade @@ -1,4 +1,4 @@ script(id='partials/main.html', type="text/ng-template") include ./filters - div(ng-controller='TasksCtrl') + div(ng-controller='TasksCtrl', ng-mouseenter="hoverIn()", ng-mouseleave="hoverout()") habitrpg-tasks(main='true', obj='user') From 5a2616069a50a03fff4d9e240dfcbb9dd748b22f Mon Sep 17 00:00:00 2001 From: Marco Fras Date: Sat, 21 May 2016 22:12:28 +0200 Subject: [PATCH 003/152] Update tasks.json Added a period for constistency with other strings. (hope this is the right way to do it) --- common/locales/en/tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/locales/en/tasks.json b/common/locales/en/tasks.json index 5b32829f81..3ad86fa6a8 100644 --- a/common/locales/en/tasks.json +++ b/common/locales/en/tasks.json @@ -125,6 +125,6 @@ "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.", "positionRequired": "\"position\" is required and must be a number.", "cantMoveCompletedTodo": "Can't move a completed todo.", - "directionUpDown": "\"direction\" is required and must be 'up' or 'down'", + "directionUpDown": "\"direction\" is required and must be 'up' or 'down'.", "alreadyTagged": "The task is already tagged with given tag." } From fa009a19f4dd733ca8069df8068cc2ae5a3909b1 Mon Sep 17 00:00:00 2001 From: Alys Date: Mon, 23 May 2016 06:27:52 -0400 Subject: [PATCH 004/152] adjust text strings - fixes https://github.com/HabitRPG/habitrpg/issues/5631 and also Short Name -> Tag Name --- common/locales/en/challenge.json | 2 +- common/locales/en/groups.json | 4 ++-- common/locales/en/subscriber.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/locales/en/challenge.json b/common/locales/en/challenge.json index 0fb8982246..c014e1f6db 100644 --- a/common/locales/en/challenge.json +++ b/common/locales/en/challenge.json @@ -79,5 +79,5 @@ "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", "userAlreadyInChallenge": "User is already participating in this challenge.", "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", - "shortNameTooShort": "Short Name must have at least 3 characters." + "shortNameTooShort": "Tag Name must have at least 3 characters." } diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json index 4d8ea0716c..2e0f245232 100644 --- a/common/locales/en/groups.json +++ b/common/locales/en/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 197994da08..8c0d453727 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -134,7 +134,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", From 28f2e9c356d7053884107d90d04e28dde75fa81b Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 13:58:31 +0200 Subject: [PATCH 005/152] API v3 [WIP] (#6144) * Fixed more tests * Added tags into user service * Added api-v3 auth urls * v3: fix package.json * v3: fix package.json * Fixed auth tests. Updated Authctrl response * v3: remove newrelic config file in favour of env variables * v3: upgrade some deps * switch from Q to Bluebird * v3 fix tests with deferred * Removed extra consoles.log. Changed data.data to res.data * v3 fix tests and use coroutines instead of regenerator * v3: fix tests * v3: do not await a non promise * v3: q -> bluebird * Changed id param for registration response * Updated party query and create * Ensured login callback happens after user sync * Add challenges to groups. Fixed isMemberOfGuild check * Updated party and group tests * Fixed cron test * return user.id and send analytics event before changing page * fix trailing spaces * disable redirects * Api v3 party tavern fixes (#7191) * Added check if user is in party before query * Cached party query. Prevented party request when user is not in party. Updated Party create with no invites * Update tavern ctrl to use new promise * v3: misc fixes * Api v3 task fixes (#7193) * Update task view to use _id * Added try catch to user service ops calls * v3 client: saving after syncing is complete * Fixed test broken by part sync change (#7195) * v3: fix todo scoring and try to fix production testing problem * revert changes to mongoose config * mongoose: increase keepAlive * test mongoose fix * fix: Only apply captureStackTrace if it exists on the error object * v3: fix reminders with no startDate * mongoose: use options * chore(): rename website/src -> website/server and website/public -> website/client (#7199) * v3 fix GET /groups: return an error only if an invalid type is supplied not when there are 0 results (#7203) * [API v3] Fix calls to user.ops and deleting tags (#7204) * v3: fixes calls to user.ops from views and deleting tags * v3: fix tests that use user._statsComputed * Api v3 fixes continued (#7205) * Added timzeone offset back * Added APIToken back to settings page * Fixed fetch recent messages for party * Fixed returning group description * Fixed check if user is member of challenge * Fixed party members appearing in header * Updated get myGroups param to include public groups. Fixed isMemberOf group * Fixed hourglass purchase * Fixed challenge addding tasks on first creating * Updated tests to accomidate new changes * fix: Correct checklist on client Closes #7207 * fix: Pin eslint to 2.9 * minor improvements to cron code for clarity; fix inaccurate comments; add TODOs for rest-in-inn actions * fix: Add missing type param to equip call closes #7212 * rename and reword pubChalsMinPrize to reflect that it's only for Tavern challenges * allows players to send gems to each other; other minor related changes - fixes https://github.com/HabitRPG/habitrpg/issues/7227 * fix tests for /members/transfer-gems * fix: Set gems sent notification as translatable string * chore: Remove unusued variable * fix: Remove requirement on message paramter in transfer-gems * add a missing variable declaration * chore: clarify comments on cron code * fix: Correct client request from habitrpg -> tavern * update apidoc URL in package.json Closes #7222 * Fixed start party by invites * Updated spell casting to v3 * Fixed adding and removing tags on tasks * Fixed page reload on settings change * Fixed battle monsters with friends button * Loaded completed todos when done is clicked * chore: Reinstate floating version number for eslint babel-eslint regression fixed * Fixed reload tests * change "an user" to "a user" in comments and text (no code changes) (#7257) * fix: Alert user that drops were recieved * remove userServices.js from karma.conf - it's been moved to website/client/js/services * feat: Create debug update user route * fix: Correct set cron debug function * feat: Add make admin button to debug menu * lint: Add missing semicolons in test * fix: Temporarilly comment out udpate user debug route * v3: fix _tmp for crit and streakBonus * v3: execute all actions when leaving a solo party * v3 client: fix group not found when leaving party * v3 migration: fix challenge prize * v3 cron: only save modified tasks * v3: add CHALLENGE_TASK_NOT_FOUND to valid broken reasons * v3: fix tasks chart * v3 client: fix ability to leave challenge * v3 client: fix filtering by tag and correctly show tag tooltip * v3 common: fix tags tests * v3 client: support unlinking not found challenges tasks * v3: disable Bluebird warning for missing return, fixes #7269 * feat: Separate out update-user into set-cron and make-admin debug routes * chore: Disable make admin debug route for v3 prod testing * v3: misc fixes * v3: misc fixes * v3: fix adding multiple tasks * Fixed join/leave button updates * Queried only user groups to be available when creating challenges * Fixed bulk add tasks to challenge * Synced challenge tasks after leave and join. * Fixed default selected group * Fixed challenge member info. Fixed challenge winner selection * Fixed deleting challenge tasks * Fixed particiapting filter * v3 client: fix casting spells * v3: do not log sensitive data * v3: always save user when casting spell * v3: always save user when casting spell * v3: more fixes for spells * fix typos and missing information in apidocs - fixes https://github.com/HabitRPG/habitrpg/issues/7277 (#7282) * v3: add TODO for client side spells * feat: Add modify inventory debug menu * Fixed viewing user progress on challenge * Updated tests * fix: Fix quest progress button * fix incorrect Armoire test; remove unneeded param details from apidocs; disambiguate health potion * v3: fix stealth casting * v3: fix tasks saving and selection for rebirth reroll and reset (server-only) * v3: fix auto allocation * v3 client: misc fixes * rename buyPotion and buy-potion to buyHealthPotion and buy-health-potion; fix apidoc param error * Added delete for saved challenge task * Fixed member modal on front page * adjust text in apidocs for errors / clarity / consistency / standard terminology (no code changes) (#7298) * fix bug in Rebirth test, add new tests, adjust apidocs (#7293) * Updated task model to allow setting streak (#7306) * fix: Correct missing * in apidoc comments * Api v3 challenge fixes (#7287) * Fixed join/leave button updates * Queried only user groups to be available when creating challenges * Fixed bulk add tasks to challenge * Synced challenge tasks after leave and join. * Fixed default selected group * Fixed challenge member info. Fixed challenge winner selection * Fixed deleting challenge tasks * Fixed particiapting filter * Fixed viewing user progress on challenge * Updated tests * Added delete for saved challenge task * v3: fix sorting * [API v3] add CRON_SAFE_MODE (#7286) * add CRON_SAFE_MODE to example config file, fix some bugs, add an unrelated low-priority TODO * create CRON_SAFE_MODE to disable parts of cron for use after extended outage - fixes https://github.com/HabitRPG/habitrpg/issues/7161 * fix a bug with CRON_SAFE_MODE, remove duplicated code, remove completed TODO comment * fix check for CRON_SAFE_MODE * v3 client: fix typo * adjust debug menu Modify Inventory: hungrier pets, fewer Special items, "Hide" buttons * completed To-Dos: return the 30 most recent instead of 30 oldest (#7318) * v3 migration: fix createdAt date * adjust locales text, key names, and files for Rebirth, Reset, and Fortify / ReRoll for consistency with existing strings (#7321) * v3: fix unlinking multiple tasks * v3 fix releasing pets * v3: fix authenticating with apiUrl * v3: fix typo * v3 fix client tests for unlinking * v3 client: do not show start quest button when quest is active * v3 client: fix ability to send cards * v3 client: fix misc challenge issues * v3: fix notifications * v3 client: more user friendly errors * v3 client: only load completed todos once * v3 client: fix tests * v3: move TAVERN_ID to common code * fix: Provide default type and text for new task creation in score route * fix: Provide default history [] for habit in score route * fix: Add _legacyId prop to tasks to support non-uuid identifiers * chore: Change v3 migration to use _legacyId instead of legacyId * fix: check for _legacyId in tasks if id does not exist * refactor: Extract out finding task by id or _legacyId into a function * Api v3 party quest fixes (#7341) * Fix display of add challenge message when group challenges are empty * Fixed forced quest start to update quest without reload * Fixed needing to reload when accepting party invite * Fix group leave and join reload * Fixed leave current party and join another * Updated party tests * v3 client: remove console.log statement * v3: misc fixes * v3 client: fix predicatbale random * v3: info about API v3 * v3: update footer with links to developer resources * v3: support party invitation from email * v3 client: fix chat flagging * fix: Correct get tasks route to properly get todos (#7349) * move locales strings from api-v3.json to other locales files (#7347) * move locales strings from api-v3.json: authentication strings -> front.json * move locales strings from api-v3.json: authentication strings -> tasks.json * move locales strings from api-v3.json: authentication strings -> groups.json * move locales strings from api-v3.json: authentication strings -> challenge.json * move locales strings from api-v3.json: authentication strings -> groups.json (again) * move locales strings from api-v3.json: authentication strings -> quests.json * move locales strings from api-v3.json: authentication strings -> subscriber.json * move locales strings from api-v3.json: authentication strings -> spells.json * move locales strings from api-v3.json: authentication strings -> character.json * move locales strings from api-v3.json: authentication strings -> groups.json (PMs) * move locales strings from api-v3.json: authentication strings -> npc.json * move locales strings from api-v3.json: authentication strings -> pets.json * move locales strings from api-v3.json: authentication strings -> miscellaneous * move locales strings from api-v3.json: authentication strings -> contrib.json and settings.json * move locales strings from api-v3.json: delete unused string (invalidTasksOwner), delete api-v3.json, whitespace cleanup * v3 client: fix sticky header * v3: remove unused code * v3 client: correctly redirect after inviting * Removed v2 calls from views (#7351) * v3: fix tests for challenge export * v3: fallbackto authWithHeaders if wuthWithSession or authWithUrl fails * Added force cache update when fetching new messages (#7360) * v3: fetch whole user when booting from group tto avoid issues with pre save hook expecting all data * v3: misc fixes for payments * v3: limit fields of challenge tasks that can be updated * fix(tests): never connect to NODE_DB_URI for tests * Added new route for setting last cron and updated front end * v3: fix iap url * v3: fix build and ios IAP * Changed route to user set custom day start * v3: iap accessible under /api/v3, fixes to spells and groups invitations * v3: correctly use v3 routes in client * remove XP, GP when unticking a Daily with a completed checklist - fixes https://github.com/HabitRPG/habitrpg/issues/7246 * use natural language for error message about skills on challenge tasks (#7336), fix other gramatical error * Updated ui when user rejects a guild invite (#7368) * feat: complete custom day start route Closes #7363 * fix: Correct spelling of healAll skill fix: Correct sprite name of healAll skill * fix: Change all instances of spookDust -> spookySparkles * add dateCreated to all tasks; add empty challenge object to tasks that don't have one (#7386) * add plumilla to artists for Tangle Tree in Bailey message * Fixed quest drop modal (#7377) * Fixed quest drop modal * Fixed broken party test * [API v3] Maintenance Mode (#7367) * WIP(maintenance): maintenance * WIP(maintenance): working locale features * fix(maintenance): don't translate info page target * WIP(maintenance): start adding info page * fix(maintenance): linting * feat: Add container to maintenance info page * fix(maintenance): add config.json edits Also DRY variables for main vs info pages * fix(maintenance): linting * refactor(maintenance): further slim down variables * refactor: Remove unnecessary variables * fix: Correct string interpolation in maintenace view * feat: Dynamically add time to maintenance pages * maintenance mode: do not connect to mongodb * fix(maintenance): clean up timezones etc. * fix(maintenance): remove unneeded sprite * Tavern party challenges invites fix (#7394) * Added challenges and invitations to party * Loaded tavern challenges * Updated group and quest services tests * v3: implement automatic syncing if user is not up to date * Removed unnecessary fields when updating groups and challenges (#7395) * v3: do not saved populated user * v3: correctly return user subset * Chained party promises together (#7396) * v3: $w -> splitWhitespace * use bluebird * use babel polyfill * migration: fix items * update links for v3 * Updated shortname validation to support multiple browsers * Docs changes (#7401) * chore: Clarify transfer-gems documentation * chore: Clarify api status route documentation * chore: Mark webhooks as BETA * Added tags update route. Added sort to user service (#7381) * Added tags update route. Added sort to user service * Change update tasks route to reorder tasks * Fixed linting issue * Changed params for reorder tags route * Fixed not found tag and added test * Added password confirmation when deleteing account (#7402) * fix production logging * feat(commit): push * empty commit * feat(maintenance): post-downtime news & awards (#7406) * fix exporting avatar * second attempt at fixing exporting avatar * fix production logging * s3: convert moment to date instance * fix avatar sharing and caching (30 minutes) * fix: Correct missing parameter Closes #7433 * fix: Validate challenge shortname on server * adjust text strings - fixes https://github.com/HabitRPG/habitrpg/issues/5631 and also Short Name -> Tag Name --- .babelrc | 7 +- .bowerrc | 2 +- .eslintignore | 18 +- .eslintrc | 5 +- .gitignore | 11 +- .nodemonignore | 2 +- .travis.yml | 2 +- Gruntfile.js | 26 +- Procfile | 2 +- bower.json | 2 +- common/browserify.js | 2 + common/dist/sprites/spritesmith-main-0.css | 4 +- common/dist/sprites/spritesmith-main-11.css | 10 +- common/dist/sprites/spritesmith-main-11.png | Bin 156807 -> 156933 bytes common/dist/sprites/spritesmith-main-12.css | 780 ++++----- common/dist/sprites/spritesmith-main-12.png | Bin 125673 -> 126575 bytes common/dist/sprites/spritesmith-main-5.css | 96 +- common/dist/sprites/spritesmith-main-5.png | Bin 261273 -> 261236 bytes common/dist/sprites/spritesmith-main-6.css | 4 +- ...ust.png => achievement-spookySparkles.png} | Bin ...x.png => achievement-spookySparkles2x.png} | Bin .../misc/{spookman.png => ghost.png} | Bin ...g => inventory_special_spookySparkles.png} | Bin ..._spookDust.png => shop_spookySparkles.png} | Bin .../{shop_heallAll.png => shop_healAll.png} | Bin .../stable/pets/Pet-Lion-Veteran.png | Bin 0 -> 3801 bytes common/index.js | 4 +- common/locales/en/challenge.json | 5 +- common/locales/en/groups.json | 4 +- common/locales/en/limited.json | 2 +- common/locales/en/maintenance.json | 2 +- common/locales/en/pets.json | 1 + common/locales/en/settings.json | 1 + common/locales/en/spells.json | 6 +- common/locales/en/subscriber.json | 2 +- common/locales/en/tasks.json | 1 + common/script/constants.js | 2 + common/script/content/index.js | 9 + common/script/content/spells.js | 79 +- common/script/cron.js | 1 + common/script/fns/autoAllocate.js | 109 +- common/script/fns/crit.js | 15 +- common/script/fns/cron.js | 358 ---- common/script/fns/dotGet.js | 8 +- common/script/fns/dotSet.js | 18 +- common/script/fns/getItem.js | 11 - common/script/fns/handleTwoHanded.js | 19 +- common/script/fns/index.js | 6 - common/script/fns/nullify.js | 6 +- common/script/fns/predictableRandom.js | 28 +- common/script/fns/preenUserHistory.js | 25 - common/script/fns/randomDrop.js | 114 +- common/script/fns/randomVal.js | 14 +- common/script/fns/resetGear.js | 25 + common/script/fns/ultimateGear.js | 30 +- common/script/fns/updateStats.js | 68 +- common/script/index.js | 281 +++- common/script/libs/appliedTags.js | 19 +- common/script/libs/countExists.js | 7 - common/script/libs/dotGet.js | 10 +- common/script/libs/dotSet.js | 15 +- common/script/libs/encodeiCalLink.js | 9 - common/script/libs/errors.js | 42 + common/script/libs/extendableBuiltin.js | 11 + common/script/libs/friendlyTimestamp.js | 9 - common/script/libs/gold.js | 6 +- common/script/libs/index.js | 46 - common/script/libs/newChatMessages.js | 10 - common/script/libs/noTags.js | 6 +- common/script/libs/percent.js | 10 +- common/script/libs/pickDeep.js | 13 + common/script/libs/preenHistory.js | 43 - common/script/libs/preenTodos.js | 12 +- common/script/libs/refPush.js | 11 +- common/script/libs/removeWhitespace.js | 10 - common/script/libs/silver.js | 9 +- common/script/libs/splitWhitespace.js | 3 +- common/script/libs/statsComputed.js | 28 + common/script/libs/taskClasses.js | 63 +- common/script/libs/taskDefaults.js | 83 +- common/script/libs/updateStore.js | 47 +- common/script/libs/uuid.js | 5 +- common/script/ops/addPushDevice.js | 43 +- common/script/ops/addTag.js | 13 +- common/script/ops/addTask.js | 24 +- common/script/ops/addWebhook.js | 34 +- common/script/ops/allocate.js | 26 +- common/script/ops/allocateNow.js | 15 +- common/script/ops/blockUser.js | 28 +- common/script/ops/buy.js | 131 +- common/script/ops/buyArmoire.js | 115 ++ common/script/ops/buyGear.js | 70 + common/script/ops/buyHealthPotion.js | 48 + common/script/ops/buyMysterySet.js | 74 +- common/script/ops/buyQuest.js | 79 +- common/script/ops/buySpecialSpell.js | 44 +- common/script/ops/changeClass.js | 99 +- common/script/ops/clearCompleted.js | 14 +- common/script/ops/clearPMs.js | 10 +- common/script/ops/deletePM.js | 14 +- common/script/ops/deleteTag.js | 38 +- common/script/ops/deleteTask.js | 31 +- common/script/ops/deleteWebhook.js | 15 +- common/script/ops/disableClasses.js | 13 +- common/script/ops/equip.js | 74 +- common/script/ops/feed.js | 124 +- common/script/ops/getTag.js | 23 +- common/script/ops/getTags.js | 6 +- common/script/ops/hatch.js | 53 +- common/script/ops/hourglassPurchase.js | 73 +- common/script/ops/index.js | 13 +- common/script/ops/markPMSRead.js | 14 + common/script/ops/openMysteryItem.js | 58 +- common/script/ops/purchase.js | 158 +- common/script/ops/readCard.js | 36 +- common/script/ops/rebirth.js | 106 +- common/script/ops/releaseBoth.js | 98 +- common/script/ops/releaseMounts.js | 61 +- common/script/ops/releasePets.js | 59 +- common/script/ops/reroll.js | 49 +- common/script/ops/reset.js | 46 +- common/script/ops/revive.js | 113 +- common/script/ops/score.js | 221 --- common/script/ops/scoreTask.js | 260 +++ common/script/ops/sell.js | 46 +- common/script/ops/sleep.js | 9 +- common/script/ops/sortTag.js | 24 +- common/script/ops/sortTask.js | 58 +- common/script/ops/unlock.js | 142 +- common/script/ops/update.js | 12 +- common/script/ops/updateTag.js | 26 +- common/script/ops/updateTask.js | 36 +- common/script/ops/updateWebhook.js | 23 +- common/script/public/config.js | 80 +- common/script/public/userServices.js | 268 --- config.json.example | 8 +- gulpfile.js | 1 + karma.conf.js | 57 +- migrations/20160521_veteran_ladder.js | 76 + migrations/api_v3/challenges.js | 212 +++ migrations/api_v3/challengesMembers.js | 143 ++ migrations/api_v3/coupons.js | 136 ++ migrations/api_v3/emailUnsubscriptions.js | 137 ++ migrations/api_v3/groups.js | 211 +++ migrations/api_v3/indexes.js | 52 + migrations/api_v3/users.js | 262 +++ migrations/manual_password_reset.js | 2 +- newrelic.js | 27 - package.json | 74 +- .../paypalBillingSetup.js | 6 +- tasks/gulp-apidoc.js | 22 + tasks/gulp-build.js | 11 +- tasks/gulp-console.js | 13 +- tasks/gulp-newstuff.js | 2 +- tasks/gulp-start.js | 2 +- tasks/gulp-tests.js | 155 +- tasks/taskHelper.js | 43 +- test/README.md | 5 - test/api-legacy/api-helper.js | 5 +- test/api-legacy/challenges.js | 6 +- test/api-legacy/chat.js | 4 +- test/api-legacy/coupons.js | 4 +- test/api-legacy/inAppPurchases.js | 4 +- test/api-legacy/party.js | 4 +- test/api-legacy/pushNotifications.js | 10 +- test/api-legacy/score.js | 2 +- test/api-legacy/subscriptions.js | 4 +- test/api-legacy/todos.js | 2 +- test/api/README.md | 8 +- test/api/v2/groups/GET-groups.test.js | 13 +- .../v2/groups/POST-groups_id_invite.test.js | 8 +- .../api/v2/groups/POST-groups_id_join.test.js | 13 +- .../v2/groups/POST-groups_id_leave.test.js | 4 +- test/api/v2/user/DELETE-user.test.js | 18 +- .../POST-user_batch-update.test.js | 2 +- .../user/pushDevice/POST-pushDevice.test.js | 2 +- test/api/v2/user/tasks/GET-tasks.test.js | 11 +- .../user/tasks/POST-clear-completed.test.js | 26 + test/api/v2/user/tasks/POST-tasks.test.js | 2 +- test/api/v2/user/tasks/PUT-tasks_id.test.js | 4 +- test/api/v3/README.md | 4 + .../DELETE-challenges_challengeId.test.js | 94 ++ .../GET-challenges_challengeId.test.js | 142 ++ ...-challenges_challengeId_export_csv.test.js | 74 + ...GET-challenges_challengeId_members.test.js | 109 ++ ...enges_challengeId_members_memberId.test.js | 107 ++ .../GET-challenges_group_groupid.test.js | 118 ++ .../challenges/GET-challenges_user.test.js | 132 ++ .../challenges/POST-challenges.test.js | 308 ++++ .../POST-challenges_challengeId_join.test.js | 127 ++ .../POST-challenges_challengeId_leave.test.js | 123 ++ ...lenges_challengeId_winner_winnerId.test.js | 139 ++ .../PUT-challenges_challengeId.test.js | 85 + .../integration/chat/DELETE-chat_id.test.js | 81 + test/api/v3/integration/chat/GET-chat.test.js | 65 + .../integration/chat/POST-chat.flag.test.js | 84 + .../integration/chat/POST-chat.like.test.js | 75 + .../api/v3/integration/chat/POST-chat.test.js | 81 + .../integration/chat/POST-chat_seen.test.js | 63 + ...POST-groups_id_chat_id_clear_flags.test.js | 101 ++ .../integration/content/GET-content.test.js | 25 + .../integration/coupons/GET-coupons.test.js | 39 + .../coupons/POST-coupons_enter_code.test.js | 62 + .../POST-coupons_generate_event.test.js | 66 + .../POST-coupons_validate_code.test.js | 36 + .../GET-export_avatar-memberId.html.test.js | 35 + .../GET-export_avatar-memberId.png.test.js | 3 + .../dataexport/GET-export_history.csv.test.js | 47 + .../GET-export_userdata.json.test.js | 29 + .../GET-export_userdata.xml.test.js | 42 + .../debug/POST-debug_addHourglass.test.js | 35 + .../debug/POST-debug_addTenGems.test.js | 35 + .../debug/POST-debug_make-admin.test.js | 35 + .../debug/POST-debug_modify-inventory.test.js | 160 ++ .../debug/POST-debug_quest-progress.test.js | 63 + .../debug/POST-debug_set-cron.test.js | 39 + .../emails/GET-email-unsubscribe.test.js | 68 + .../v3/integration/groups/GET-groups.test.js | 115 ++ .../groups/GET-groups_groupId_invites.test.js | 102 ++ .../groups/GET-groups_groupId_members.test.js | 96 ++ .../integration/groups/GET-groups_id.test.js | 298 ++++ .../v3/integration/groups/POST-groups.test.js | 228 +++ .../groups/POST-groups_groupId_join.test.js | 286 ++++ .../groups/POST-groups_groupId_leave.js | 210 +++ .../groups/POST-groups_groupId_reject.test.js | 113 ++ .../POST-groups_id_removeMember.test.js | 130 ++ .../groups/POST-groups_invite.test.js | 332 ++++ .../v3/integration/groups/PUT-groups.test.js | 46 + .../integration/hall/GET-hall_heroes.test.js | 29 + .../hall/GET-hall_heroes_heroId.test.js | 56 + .../integration/hall/GET-hall_patrons.test.js | 60 + .../hall/PUT-hall_heores_heroId.test.js | 148 ++ .../members/GET-members_id.test.js | 49 + .../members/POST-send_private_message.test.js | 107 ++ .../members/POST-transfer_gems.test.js | 174 ++ .../models/GET-model_paths.test.js | 32 + test/api/v3/integration/notFound.test.js | 13 + ...T-payments_amazon_subscribe_cancel.test.js | 21 + .../GET-payments_paypal_checkout.test.js | 21 + ...T-payments_paypal_checkout_success.test.js | 21 + .../GET-payments_paypal_subscribe.test.js | 21 + ...T-payments_paypal_subscribe_cancel.test.js | 21 + ...-payments_paypal_subscribe_success.test.js | 21 + ...T-payments_stripe_subscribe_cancel.test.js | 21 + .../POST-payments_amazon_checkout.test.js | 20 + ...ents_amazon_createOrderReferenceId.test.js | 22 + .../POST-payments_amazon_subscribe.test.js | 21 + ...-payments_amazon_verifyAccessToken.test.js | 20 + .../payments/POST-payments_paypal_ipn.test.js | 17 + .../POST-payments_stripe_checkout.test.js | 20 + ...OST-payments_stripe_subscribe_edit.test.js | 21 + .../POST-groups_groupId_quests_accept.test.js | 119 ++ ...-groups_groupId_quests_force-start.test.js | 126 ++ .../POST-groups_groupId_quests_invite.test.js | 192 +++ .../POST-groups_groupid_quests_abort.test.js | 126 ++ .../POST-groups_groupid_quests_cancel.test.js | 138 ++ .../POST-groups_groupid_quests_leave.test.js | 124 ++ .../POST-groups_groupid_quests_reject.test.js | 146 ++ .../v3/integration/status/GET-status.test.js | 12 + .../integration/tags/DELETE-tags_id.test.js | 27 + test/api/v3/integration/tags/GET-tags.test.js | 22 + .../v3/integration/tags/GET-tags_id.test.js | 20 + .../integration/tags/POST-tag-reorder.test.js | 44 + .../api/v3/integration/tags/POST-tags.test.js | 25 + .../v3/integration/tags/PUT-tags_id.test.js | 28 + .../integration/tasks/DELETE-tasks_id.test.js | 59 + .../GET-tasks_challenge_challengeId.test.js | 75 + .../v3/integration/tasks/GET-tasks_id.test.js | 58 + .../integration/tasks/GET-tasks_user.test.js | 84 + .../POST-tasks_clearCompletedTodos.test.js | 43 + .../POST-tasks_id_score_direction.test.js | 298 ++++ ...POST-tasks_move_taskId_to_position.test.js | 84 + .../integration/tasks/POST-tasks_user.test.js | 593 +++++++ .../PUT-tasks_challenge_challengeId.test.js | 323 ++++ .../v3/integration/tasks/PUT-tasks_id.test.js | 396 +++++ ...lenge_challengeId_checklist_itemId.test.js | 115 ++ ...ETE-tasks_id_challenge_challengeId.test.js | 115 ++ .../challenges/GET_tasks_challenge.id.test.js | 86 + ...lenge_challengeId_taskId_checklist.test.js | 119 ++ .../POST-tasks_challenge_id.test.js | 125 ++ ...allengeId_tasks_id_score_direction.test.js | 140 ++ ...allengeId_tasksId_checklist_itemId.test.js | 155 ++ ...LETE-tasks_taskId_checklist_itemId.test.js | 74 + .../POST-tasks_taskId_checklist.test.js | 73 + ...asks_taskId_checklist_itemId_score.test.js | 79 + .../PUT-tasks_taskId_checklist_itemId.test.js | 83 + .../DELETE-tasks_taskId_tags_tagId.test.js | 42 + .../tags/POST-tasks_taskId_tags_tagId.test.js | 55 + .../v3/integration/user/DELETE-user.test.js | 176 ++ .../user/DELETE-user_delete_webhook.test.js | 23 + .../user/DELETE-user_messages.test.js | 27 + test/api/v3/integration/user/GET-user.test.js | 24 + .../user/GET-user_anonymized.test.js | 90 + .../user/GET-user_inventory_buy.test.js | 26 + .../user/POST-user_addPushDevice.test.js | 35 + .../user/POST-user_add_webhook.test.js | 29 + .../user/POST-user_allocate.test.js | 41 + .../user/POST-user_allocate_now.test.js | 28 + .../integration/user/POST-user_block.test.js | 34 + .../v3/integration/user/POST-user_buy.test.js | 62 + .../user/POST-user_buy_armoire.test.js | 41 + .../user/POST-user_buy_gear.test.js | 45 + .../user/POST-user_buy_health_potion.test.js | 42 + .../user/POST-user_buy_mystery_set.test.js | 38 + .../user/POST-user_buy_quest.test.js | 40 + .../user/POST-user_buy_special_spell.test.js | 43 + .../user/POST-user_change-class.test.js | 30 + .../user/POST-user_class_cast_spellId.test.js | 172 ++ .../user/POST-user_custom-day-start.test.js | 47 + .../user/POST-user_disable-classes.test.js | 26 + .../user/POST-user_equip_type_key.test.js | 40 + .../user/POST-user_feed_pet_food.test.js | 45 + ...POST-user_hatch_egg_hatchingPotion.test.js | 31 + .../user/POST-user_mark_pms_read.test.js | 22 + .../user/POST-user_open_mystery_item.test.js | 26 + .../user/POST-user_purchase.test.js | 34 + .../user/POST-user_purchase_hourglass.test.js | 25 + .../user/POST-user_read_card.test.js | 38 + .../user/POST-user_rebirth.test.js | 57 + .../user/POST-user_release_both.test.js | 48 + .../user/POST-user_release_mounts.test.js | 42 + .../user/POST-user_release_pets.test.js | 42 + .../integration/user/POST-user_reroll.test.js | 54 + .../integration/user/POST-user_reset.test.js | 104 ++ .../integration/user/POST-user_revive.test.js | 37 + .../integration/user/POST-user_sell.test.js | 41 + .../integration/user/POST-user_sleep.test.js | 25 + .../v3/integration/user/POST-user_unlock.js | 37 + test/api/v3/integration/user/PUT-user.test.js | 201 +++ .../user/PUT-user_update_webhook.test.js | 32 + .../DELETE-user_auth_social_network.test.js | 40 + .../integration/user/auth/GET-logout.test.js | 3 + .../user/auth/POST-firebase.test.js | 18 + .../user/auth/POST-login-local.test.js | 69 + .../user/auth/POST-register_local.test.js | 349 ++++ .../auth/POST-user_reset_password.test.js | 38 + .../user/auth/PUT-user_update_email.test.js | 79 + .../auth/PUT-user_update_password.test.js | 53 + .../auth/PUT-user_update_username.test.js | 76 + .../api/v3/unit/libs/analyticsService.test.js | 309 ++++ test/api/v3/unit/libs/baseModel.test.js | 97 ++ test/api/v3/unit/libs/buildManifest.test.js | 19 + .../unit/libs/collectionManipulators.test.js | 88 + test/api/v3/unit/libs/cron.test.js | 573 +++++++ test/api/v3/unit/libs/email.test.js | 232 +++ test/api/v3/unit/libs/encryption.test.js | 15 + test/api/v3/unit/libs/errors.test.js | 122 ++ test/api/v3/unit/libs/i18n.test.js | 39 + test/api/v3/unit/libs/logger.js | 57 + test/api/v3/unit/libs/password.test.js | 41 + test/api/v3/unit/libs/payments.test.js | 72 + test/api/v3/unit/libs/preening.test.js | 56 + test/api/v3/unit/libs/setupNconf.test.js | 44 + test/api/v3/unit/libs/webhooks.test.js | 135 ++ .../api/v3/unit/middlewares/analytics.test.js | 49 + test/api/v3/unit/middlewares/cors.test.js | 40 + .../api/v3/unit/middlewares/cronMiddleware.js | 177 ++ .../middlewares/ensureAccessRight.test.js | 57 + .../unit/middlewares/ensureDevelpmentMode.js | 36 + .../v3/unit/middlewares/errorHandler.test.js | 173 ++ test/api/v3/unit/middlewares/language.test.js | 307 ++++ .../unit/middlewares/maintenanceMode.test.js | 58 + test/api/v3/unit/middlewares/response.js | 66 + test/api/v3/unit/models/challenge.test.js | 161 ++ test/api/v3/unit/models/group.test.js | 300 ++++ test/api/v3/unit/models/task.test.js | 74 + test/api/v3/unit/models/user.test.js | 33 + test/common/algos.mocha.js | 1491 ----------------- test/common/dailies.js | 499 ------ test/common/fns/autoAllocate.test.js | 86 + test/common/fns/crit.test.js | 17 + test/common/fns/handleTwoHanded.js | 38 + test/common/fns/predictableRandom.test.js | 51 + test/common/fns/randomDrop.test.js | 165 ++ test/common/fns/randomVal.js | 119 ++ test/common/fns/statsComputed.test.js | 28 + test/common/fns/ultimateGear.js | 33 + test/common/fns/updateStats.test.js | 170 ++ test/common/libs/appliedTags.test.js | 10 + test/common/libs/gold.test.js | 11 + test/common/libs/noTags.test.js | 13 + test/common/libs/percent.test.js | 19 + test/common/libs/pickDeep.js | 34 + test/common/libs/refPush.js | 53 + test/common/libs/silver.test.js | 19 + test/common/libs/splitWhitespace.test.js | 7 + test/common/libs/taskClasses.test.js | 82 + test/common/libs/taskDefaults.test.js | 61 + test/common/libs/updateStore.js | 57 + test/common/ops/addPushDevice.js | 59 + test/common/ops/addTask.js | 139 ++ test/common/ops/addWebhook.test.js | 57 + test/common/ops/allocate.js | 63 + test/common/ops/allocateNow.js | 30 + test/common/ops/blockUser.test.js | 44 + test/common/ops/buy.js | 61 + test/common/ops/buyArmoire.js | 205 +++ test/common/ops/buyGear.js | 142 ++ test/common/ops/buyHealthPotion.js | 65 + test/common/ops/buyMysterySet.js | 76 + test/common/ops/buyQuest.js | 81 + test/common/ops/buySpecialSpell.js | 79 + test/common/ops/changeClass.js | 139 ++ test/common/ops/clearCompleted.js | 37 + test/common/ops/clearPMs.test.js | 20 + test/common/ops/deletePM.test.js | 20 + test/common/ops/deleteWebhook.test.js | 21 + test/common/ops/disableClasses.js | 35 + test/common/ops/equip.js | 87 + test/common/ops/feed.js | 216 +++ test/common/ops/hatch.js | 147 ++ test/common/ops/hourglassPurchase.js | 145 ++ test/common/ops/openMysteryItem.js | 38 + test/common/ops/purchase.js | 194 +++ test/common/ops/readCard.js | 48 + test/common/ops/rebirth.js | 236 +++ test/common/ops/releaseBoth.js | 98 ++ test/common/ops/releaseMounts.js | 57 + test/common/ops/releasePets.js | 57 + test/common/ops/reroll.js | 63 + test/common/ops/reset.js | 79 + test/common/ops/revive.js | 91 + test/common/ops/scoreTask.test.js | 203 +++ test/common/ops/sell.js | 79 + test/common/ops/sleep.js | 18 + test/common/ops/unlock.js | 121 ++ test/common/ops/updateTask.js | 53 + test/common/ops/updateWebhook.test.js | 42 + test/common/preenTodos.test.js | 76 - test/common/shared.spells.test.js | 103 -- test/common/simulations/autoAllocate.js | 161 -- .../simulations/passive_active_attrs.js | 291 ---- test/common/user.fns.buy.test.js | 288 ---- test/common/user.fns.ultimateGear.test.js | 37 - test/common/user.fns.updateStats.test.js | 134 -- test/common/user.ops.buyMysterySet.test.js | 77 - test/common/user.ops.equip.test.js | 102 -- test/common/user.ops.hatch.js | 129 -- .../common/user.ops.hourglassPurchase.test.js | 122 -- test/common/user.ops.test.js | 34 - test/helpers/api-integration/api-classes.js | 25 +- test/helpers/api-integration/mongo.js | 92 - test/helpers/api-integration/requester.js | 79 +- test/helpers/api-integration/translate.js | 3 +- test/helpers/api-integration/v2/index.js | 2 +- .../api-integration/v2/object-generators.js | 43 +- test/helpers/api-integration/v3/index.js | 11 + .../api-integration/v3/object-generators.js | 157 ++ test/helpers/api-unit.helper.js | 104 ++ test/helpers/api-v3-integration.helper.js | 1 + test/helpers/common.helper.js | 4 +- test/helpers/content.helper.js | 3 +- test/helpers/globals.helper.js | 31 +- test/helpers/mongo.js | 110 ++ test/helpers/sleep.js | 7 + ...50605_ultimate_achievement_backfill.coffee | 2 +- test/mocha.opts | 1 - test/server_side/analytics.test.js | 6 +- test/server_side/controllers/groups.test.js | 14 +- test/server_side/controllers/user.test.js | 8 +- test/server_side/webhooks.test.js | 2 +- test/spec/chatServicesSpec.js | 90 - test/spec/controllers/authCtrlSpec.js | 4 +- test/spec/controllers/challengesCtrlSpec.js | 222 ++- .../copyMessageModalControllerSpec.js | 14 +- test/spec/controllers/filtersCtrlSpec.js | 15 +- test/spec/controllers/footerCtrlSpec.js | 19 +- test/spec/controllers/groupCtrlSpec.js | 65 +- test/spec/controllers/headerCtrlSpec.js | 7 +- test/spec/controllers/inventoryCtrlSpec.js | 26 +- .../spec/controllers/inviteToGroupCtrlSpec.js | 165 +- test/spec/controllers/menuCtrlSpec.js | 2 +- test/spec/controllers/partyCtrlSpec.js | 229 ++- test/spec/controllers/settingsCtrlSpec.js | 40 +- test/spec/controllers/tasksCtrlSpec.js | 6 +- test/spec/services/challengeServicesSpec.js | 88 + test/spec/services/chatServicesSpec.js | 73 + test/spec/services/groupServicesSpec.js | 143 +- test/spec/services/memberServicesSpec.js | 78 +- test/spec/services/questServicesSpec.js | 57 +- test/spec/services/statServicesSpec.js | 12 +- test/spec/services/tagServicesSpec.js | 52 + test/spec/services/taskServicesSpec.js | 163 +- test/spec/services/userServicesSpec.js | 4 +- test/spec/specHelper.js | 3 + website/{public => client}/500.html | 0 .../apple-touch-icon-114-precomposed.png | Bin .../apple-touch-icon-144-precomposed.png | Bin .../apple-touch-icon-57-precomposed.png | Bin .../apple-touch-icon-72-precomposed.png | Bin .../apple-touch-icon-precomposed.png | Bin website/{public => client}/cake.png | Bin .../backCorner.png | Bin .../beingHabitican.png | Bin .../consequences.png | Bin .../contributing.png | Bin .../community-guidelines-images/github.gif | Bin .../infractions.png | Bin .../community-guidelines-images/intro.png | Bin .../moderators.png | Bin .../publicGuilds.png | Bin .../publicSpaces.png | Bin .../restoration.png | Bin .../community-guidelines-images/staff.png | Bin .../community-guidelines-images/tavern.png | Bin .../community-guidelines-images/trello.png | Bin .../community-guidelines-images/wiki.png | Bin website/{public => client}/css/README.md | 0 website/{public => client}/css/alerts.styl | 0 website/{public => client}/css/avatar.styl | 0 .../{public => client}/css/challenges.styl | 0 website/{public => client}/css/classes.styl | 0 .../{public => client}/css/customizer.styl | 0 website/{public => client}/css/filters.styl | 0 website/{public => client}/css/footer.styl | 0 website/{public => client}/css/game-pane.styl | 0 .../{public => client}/css/global-colors.styl | 0 .../css/global-modules.styl | 0 website/{public => client}/css/header.styl | 0 website/{public => client}/css/helpers.styl | 0 website/{public => client}/css/index.styl | 0 website/{public => client}/css/inventory.styl | 0 website/{public => client}/css/items.styl | 0 website/{public => client}/css/menu.styl | 0 website/{public => client}/css/no-script.styl | 0 website/{public => client}/css/npcs.styl | 0 website/{public => client}/css/options.styl | 0 website/{public => client}/css/quests.styl | 0 .../{public => client}/css/scrollbars.styl | 0 website/{public => client}/css/shared.styl | 0 website/{public => client}/css/static.styl | 0 website/{public => client}/css/tasks.styl | 0 .../css/variables/screen-size.styl | 0 .../emails/images/10-days-recapture-v1.png | Bin .../images/3-days-1-month-recapture-v1.png | Bin .../images/PROMO-Enchanted-Armoire-v1.png | Bin .../emails/images/android-promo-v1.png | Bin .../emails/images/iphone-promo-v1.png | Bin .../emails/images/one-day-v1.png | Bin .../emails/images/spring-2015-00-v1.png | Bin .../emails/images/spring-2015-01-v1.png | Bin .../subscription-begins-time-travelers-v1.png | Bin .../emails/images/subscription-begins-v1.png | Bin website/{public => client}/favicon.ico | Bin .../{public => client}/favicon_192x192.png | Bin .../{public => client}/fontello/LICENSE.txt | 0 .../{public => client}/fontello/README.txt | 0 .../fontello/css/animation.css | 0 .../fontello/css/fontelico-codes.css | 0 .../fontello/css/fontelico-embedded.css | 0 .../fontello/css/fontelico-ie7-codes.css | 0 .../fontello/css/fontelico-ie7.css | 0 .../fontello/css/fontelico.css | 0 website/{public => client}/fontello/demo.html | 0 .../fontello/font/fontelico.eot | Bin .../fontello/font/fontelico.svg | 0 .../fontello/font/fontelico.ttf | Bin .../fontello/font/fontelico.woff | Bin website/{public => client}/front/README.md | 0 .../front/css/blockScroll.css | 0 .../front/css/bootstrap.min.css | 0 .../front/css/fixed-positioning.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../front/images/Feeding_Time.png | Bin .../front/images/Guilds Sample Screen.png | Bin .../front/images/HabitRPGPromoPostCard6.png | Bin .../front/images/HabitRPGPromoThin.png | Bin .../Habitica_banner_by_uncommoncriminal.png | Bin .../Habitica_map_by_uncommoncriminal.png | Bin .../front/images/Healer.png | Bin .../{public => client}/front/images/Mount.png | Bin .../front/images/Mount_Body_Dragon-Golden.png | Bin .../front/images/Mount_Body_Dragon-Red.png | Bin .../front/images/Mount_Body_Wolf-Base.png | Bin .../front/images/Mount_Head_Dragon-Golden.png | Bin .../front/images/Mount_Head_Dragon-Red.png | Bin .../front/images/Mount_Head_Wolf-Base.png | Bin .../front/images/Party-Header.png | Bin .../front/images/Pet-Dragon-Red.png | Bin .../front/images/Pet-Fox-Red.png | Bin .../front/images/Promo_springclasses2015.png | Bin .../front/images/Quest_dilatory_drag'on.png | Bin .../images/Quest_dilatory_drag'onSmall.png | Bin .../{public => client}/front/images/Rogue.png | Bin .../front/images/SAMPLEadventurers.png | Bin .../front/images/TVreward.png | Bin .../front/images/VICE_by_Baconsaur.png | Bin .../front/images/Warrior.png | Bin .../front/images/Wizard.png | Bin .../front/images/achievement-perfect.png | Bin .../front/images/achievement-triadbingo.png | Bin .../front/images/avatar/Warrior.png | Bin .../front/images/avatar/avatar.png | Bin .../front/images/avatar/avatarstatic.png | Bin .../images/avatar/hair_bangs_1_brown.png | Bin .../front/images/avatar/head_0.png | Bin .../front/images/avatar/head_warrior_3.png | Bin .../front/images/avatar/head_warrior_5.png | Bin .../front/images/avatar/shield_warrior_3.png | Bin .../front/images/avatar/shield_warrior_5.png | Bin .../front/images/avatar/skin_f5a76e.png | Bin .../images/avatar/slim_armor_warrior_3.png | Bin .../images/avatar/slim_armor_warrior_5.png | Bin .../front/images/avatar/slim_shirt_black.png | Bin .../front/images/avatar/weapon_healer_6.png | Bin .../front/images/avatar/weapon_warrior_3.png | Bin .../front/images/avatar/weapon_warrior_5.png | Bin .../blackish_fox_by_kellllly-d7pzd46.png | Bin .../front/images/coding_by_phoneix_faerie.png | Bin .../front/images/devices.png | Bin .../front/images/explosion.jpg | Bin .../front/images/explosion.png | Bin .../front/images/habitrpg_pixel.png | Bin .../front/images/icon175x175.png | Bin .../{public => client}/front/images/intro.jpg | Bin .../{public => client}/front/images/intro.psd | Bin .../front/images/misc/Pet_Food_Cake_Base.png | Bin .../misc/inventory_quest_scroll_harpy.png | Bin .../front/images/misc/rebirth_orb.png | Bin .../front/images/misc/shop_gold.png | Bin .../front/images/misc/shop_potion.png | Bin ...or_habit_by_cosmic_caterpillar-d8mf5mb.png | Bin .../front/images/party/AnnaCosplay.png | Bin .../front/images/party/Ariel_cosplay.png | Bin .../images/party/Big_Daddy_(BioShock).png | Bin .../party/Cosplay_Daenerys_Targaryen.png | Bin .../front/images/party/GrimReaper.png | Bin .../front/images/party/HomeStuckLusus.png | Bin .../front/images/presslogos/Cnetlogo.png | Bin .../images/presslogos/Fast-Company-logo.png | Bin .../front/images/presslogos/Forbes_logo.png | Bin .../front/images/presslogos/GitHub_Logo.png | Bin .../front/images/presslogos/discover_logo.png | Bin .../images/presslogos/ionic-logo-blog.png | Bin .../ionic-logo-horizontal-transparent.png | Bin .../images/presslogos/kickstarter-logo.png | Bin .../landing_slack_hash_wordmark_logo.png | Bin .../front/images/presslogos/lifehacker.png | Bin .../front/images/presslogos/logo_webstorm.png | Bin .../front/images/presslogos/makeuseof.png | Bin .../front/images/presslogos/nyt-logo.png | Bin .../front/images/presslogos/slack.png | Bin .../images/presslogos/trello-logo-blue.png | Bin .../front/images/quest_vice3.png | Bin .../front/images/screenshot.png | Bin .../t_bone_fight_2_by_mortquitue-d8dtxbl.png | Bin .../front/images/testimonial_by_Streak.png | Bin .../front/images/testimonials/16bitFil.png | Bin .../front/images/testimonials/AlexandraSo.png | Bin .../front/images/testimonials/Althaire.png | Bin .../front/images/testimonials/AndeeLiao.png | Bin .../front/images/testimonials/Brenna.png | Bin .../images/testimonials/Drag0nsilver.png | Bin .../front/images/testimonials/Drei-M.png | Bin .../front/images/testimonials/Elmi.png | Bin .../front/images/testimonials/EvaGantz.png | Bin .../front/images/testimonials/Helcura.png | Bin .../front/images/testimonials/InfH.png | Bin .../front/images/testimonials/Kai.png | Bin .../front/images/testimonials/Kazui.png | Bin .../front/images/testimonials/Zelah_Meyer.png | Bin .../images/testimonials/autumnesquirrel.png | Bin .../images/testimonials/frabjabulous.png | Bin .../front/images/testimonials/galarix.png | Bin .../front/images/testimonials/gwyn.blath.png | Bin .../images/testimonials/irishfeet123.png | Bin .../front/images/testimonials/skysailor.png | Bin .../images/testimonials/supermouse35.png | Bin .../images/testimonials/tonitonirocca.png | Bin .../front/images/uses/achievement-bkgd.png | Bin .../uses/clipart-rosemonkeyct-meditation.png | Bin .../uses/clipart-rosemonkeyct-meditation.psd | Bin .../uses/clipart-rosemonkeyct-reading.png | Bin .../front/images/uses/coding.png | Bin .../coding_3_by_phoneix_faerie-d7idtti.png | Bin .../front/images/uses/consequences.png | Bin .../front/images/uses/dusting-bkgd.png | Bin .../front/images/uses/dusting_by_leephon.png | Bin ...ievement_by_cosmic_caterpillar-d7uyv5z.png | Bin .../front/images/uses/meditation-bkgd.png | Bin .../front/images/uses/publicSpaces.png | Bin .../front/images/uses/reading.png | Bin .../front/js/blockScroll.js | 0 .../front/js/bootstrap.min.js | 0 .../front/js/skrollr.min.js | 0 .../front/landingv1Wireframe.jpg | Bin .../{public => client}/front/staticstyle.css | 0 website/{public => client}/front/style.css | 0 .../google280633b772b94345.html | 0 .../google8ca65b6ff3506fb8.html | 0 .../googlef3b1402b0e28338a.html | 0 website/{public => client}/js/.eslintrc | 0 website/{public => client}/js/app.js | 93 +- .../js/controllers/authCtrl.js | 45 +- .../js/controllers/autoCompleteCtrl.js | 0 .../js/controllers/challengesCtrl.js | 228 ++- .../js/controllers/chatCtrl.js | 96 +- .../js/controllers/copyMessageModalCtrl.js | 2 +- .../js/controllers/filtersCtrl.js | 11 +- .../js/controllers/footerCtrl.js | 78 +- .../js/controllers/groupsCtrl.js | 63 +- website/client/js/controllers/guildsCtrl.js | 127 ++ .../js/controllers/hallCtrl.js | 20 +- .../js/controllers/headerCtrl.js | 20 +- .../js/controllers/inventoryCtrl.js | 26 +- .../js/controllers/inviteToGroupCtrl.js | 33 +- .../js/controllers/memberModalCtrl.js | 52 +- .../js/controllers/menuCtrl.js | 2 +- .../js/controllers/notificationCtrl.js | 10 +- website/client/js/controllers/partyCtrl.js | 213 +++ .../js/controllers/rootCtrl.js | 51 +- .../js/controllers/settingsCtrl.js | 47 +- .../js/controllers/sortableInventoryCtrl.js | 0 .../js/controllers/tasksCtrl.js | 128 +- website/client/js/controllers/tavernCtrl.js | 18 + .../js/controllers/userCtrl.js | 10 +- .../js/directives/close-menu.directive.js | 0 .../js/directives/expand-menu.directive.js | 0 .../js/directives/focus-element.directive.js | 0 .../js/directives/from-now.directive.js | 0 .../js/directives/habitrpg-tasks.directive.js | 0 .../hrpg-sort-checklist.directive.js | 0 .../js/directives/hrpg-sort-tags.directive.js | 4 +- .../directives/hrpg-sort-tasks.directive.js | 4 +- .../popover-html-popup.directive.js | 0 .../js/directives/popover-html.directive.js | 0 .../js/directives/when-scrolled.directive.js | 0 website/{public => client}/js/env.js | 0 .../{public => client}/js/filters/money.js | 0 .../js/filters/roundLargeNumbers.js | 0 .../js/filters/taskOrdering.js | 0 .../js/filters/timezoneOffsetToUtc.js | 0 .../js/services/analyticsServices.js | 0 .../client/js/services/challengeServices.js | 99 ++ website/client/js/services/chatServices.js | 87 + website/client/js/services/groupServices.js | 234 +++ .../js/services/guideServices.js | 6 +- website/client/js/services/memberServices.js | 128 ++ .../js/services/notificationServices.js | 8 +- .../js/services/paymentServices.js | 18 +- .../js/services/questServices.js | 48 +- .../js/services/sharedServices.js | 0 .../js/services/socialServices.js | 0 .../js/services/statServices.js | 4 +- website/client/js/services/tagsServices.js | 60 + website/client/js/services/taskServices.js | 205 +++ website/client/js/services/userServices.js | 583 +++++++ website/{public => client}/js/static.js | 21 +- website/{public => client}/logo.png | Bin .../logo/HABITRPG logo version 1.psd | Bin .../logo/HABITRPG-logo-version-1.gif | Bin website/{public => client}/logo/habitrpg.jpg | Bin .../{public => client}/logo/habitrpg_bl.eps | 0 .../logo/habitrpg_pixel.png | Bin website/{public => client}/manifest.json | 14 +- .../marketing/android_iphone.png | Bin .../{public => client}/marketing/animals.png | Bin .../marketing/challenge.png | Bin .../{public => client}/marketing/devices.png | Bin .../{public => client}/marketing/drops.png | Bin .../marketing/education.png | Bin website/{public => client}/marketing/gear.png | Bin .../{public => client}/marketing/guild.png | Bin .../marketing/guild_small.png | Bin .../marketing/integration.png | Bin .../{public => client}/marketing/lefnire.png | Bin .../marketing/promos/201403_Forest_Walker.png | Bin .../marketing/promos/April14SAMPLE2.png | Bin .../marketing/screenshot.png | Bin .../marketing/social_competitve.png | Bin .../{public => client}/marketing/wellness.png | Bin .../merch/stickermule-logo.png | Bin .../merch/stickermule-logo.svg | 0 .../{public => client}/merch/stickermule.png | Bin .../merch/teespring-eu-logo.png | Bin .../{public => client}/merch/teespring-eu.png | Bin .../merch/teespring-logo.png | Bin .../merch/teespring-logo.svg | 0 .../{public => client}/merch/teespring.png | Bin website/{public => client}/page-loader.gif | Bin .../presskit/Boss - Basi-List.png | Bin .../Boss - Battling the Ghost Stag.png | Bin .../presskit/Boss - Laundromancer.png | Bin .../presskit/Boss - Necro-Vice.png | Bin .../presskit/Boss - SnackLess Monster.png | Bin .../presskit/Boss - Stagnant Dishes.png | Bin .../presskit/Habitica Gryphon.png | Bin .../presskit/Habitica Logo - Android.png | Bin .../Habitica Logo - Icon with Text.png | Bin .../presskit/Habitica Logo - Icon.png | Bin .../presskit/Habitica Logo - Text.png | Bin .../presskit/Habitica Logo - iOS.png | Bin .../presskit/Habitica Promo - Thin.png | Bin .../presskit/Habitica Promo.png | Bin .../presskit/Sample Screen - Boss (iOS).png | Bin .../presskit/Sample Screen - Challenges.png | Bin .../presskit/Sample Screen - Equipment.png | Bin .../presskit/Sample Screen - Guilds.png | Bin .../Sample Screen - Level Up (iOS).png | Bin .../presskit/Sample Screen - Market.png | Bin .../presskit/Sample Screen - Party (iOS).png | Bin .../presskit/Sample Screen - Pets (iOS).png | Bin .../Sample Screen - Tasks Page (iOS).png | Bin .../presskit/Sample Screen - Tasks Page.png | Bin ...World Boss - Dread Drag'on of Dilatory.png | Bin .../{public => client}/presskit/presskit.zip | Bin website/{public => client}/refresh.png | Bin website/public/js/controllers/guildsCtrl.js | 94 -- website/public/js/controllers/partyCtrl.js | 173 -- website/public/js/controllers/tavernCtrl.js | 10 - .../public/js/services/challengeServices.js | 26 - website/public/js/services/chatServices.js | 33 - website/public/js/services/groupServices.js | 92 - website/public/js/services/memberServices.js | 60 - website/public/js/services/taskServices.js | 53 - .../controllers/api-v2/auth.js | 42 +- .../server/controllers/api-v2/challenges.js | 428 +++++ .../controllers/api-v2/coupon.js | 6 +- .../controllers/api-v2}/dataexport.js | 18 +- .../controllers/api-v2/groups.js | 653 +++++--- .../controllers/api-v2/hall.js | 8 +- .../controllers/api-v2/members.js | 19 +- .../controllers/api-v2}/pushNotifications.js | 1 + .../controllers/api-v2/unsubscription.js | 12 +- website/server/controllers/api-v2/user.js | 1053 ++++++++++++ website/server/controllers/api-v3/auth.js | 512 ++++++ .../server/controllers/api-v3/challenges.js | 518 ++++++ website/server/controllers/api-v3/chat.js | 398 +++++ website/server/controllers/api-v3/content.js | 110 ++ website/server/controllers/api-v3/coupon.js | 126 ++ website/server/controllers/api-v3/debug.js | 190 +++ website/server/controllers/api-v3/groups.js | 670 ++++++++ website/server/controllers/api-v3/hall.js | 183 ++ website/server/controllers/api-v3/iap.js | 4 + website/server/controllers/api-v3/members.js | 364 ++++ .../server/controllers/api-v3/modelsPaths.js | 40 + website/server/controllers/api-v3/quests.js | 451 +++++ website/server/controllers/api-v3/status.js | 21 + website/server/controllers/api-v3/tags.js | 190 +++ website/server/controllers/api-v3/tasks.js | 971 +++++++++++ website/server/controllers/api-v3/user.js | 1358 +++++++++++++++ website/server/controllers/top-level/auth.js | 16 + .../controllers/top-level/dataexport.js | 247 +++ website/server/controllers/top-level/email.js | 54 + website/server/controllers/top-level/pages.js | 79 + .../controllers/top-level/payments/amazon.js | 256 +++ .../controllers/top-level/payments/iap.js | 191 +++ .../controllers/top-level/payments/paypal.js | 278 +++ .../controllers/top-level/payments/stripe.js | 169 ++ website/server/index.js | 46 + .../libs => server/libs/api-v2}/analytics.js | 2 +- .../libs/api-v2}/buildManifest.js | 8 +- .../libs => server/libs/api-v2}/firebase.js | 16 +- .../{src/libs => server/libs/api-v2}/i18n.js | 15 +- .../libs => server/libs/api-v2}/logging.js | 2 +- .../{src/libs => server/libs/api-v2}/utils.js | 24 +- .../libs => server/libs/api-v2}/webhook.js | 0 website/server/libs/api-v3/amazonPayments.js | 62 + .../server/libs/api-v3/analyticsService.js | 237 +++ website/server/libs/api-v3/baseModel.js | 79 + website/server/libs/api-v3/buildManifest.js | 62 + .../libs/api-v3/collectionManipulators.js | 22 + website/server/libs/api-v3/cron.js | 280 ++++ website/server/libs/api-v3/csvStringify.js | 11 + website/server/libs/api-v3/email.js | 156 ++ website/server/libs/api-v3/encryption.js | 24 + website/server/libs/api-v3/errors.js | 62 + website/server/libs/api-v3/firebase.js | 69 + website/server/libs/api-v3/i18n.js | 105 ++ website/server/libs/api-v3/logger.js | 60 + website/server/libs/api-v3/password.js | 18 + website/server/libs/api-v3/payments.js | 185 ++ website/server/libs/api-v3/preening.js | 82 + .../server/libs/api-v3/pushNotifications.js | 53 + website/server/libs/api-v3/routes.js | 61 + website/server/libs/api-v3/setupMongoose.js | 28 + website/server/libs/api-v3/setupNconf.js | 17 + website/server/libs/api-v3/setupPassport.js | 24 + website/server/libs/api-v3/webhook.js | 31 + .../middlewares/api-v2}/domain.js | 0 .../middlewares/api-v2}/errorHandler.js | 2 +- .../middlewares/api-v2}/locals.js | 6 +- .../server/middlewares/api-v3/analytics.js | 23 + website/server/middlewares/api-v3/auth.js | 87 + website/server/middlewares/api-v3/cors.js | 9 + website/server/middlewares/api-v3/cron.js | 160 ++ website/server/middlewares/api-v3/domain.js | 13 + .../middlewares/api-v3/ensureAccessRight.js | 23 + .../api-v3/ensureDevelpmentMode.js | 12 + .../server/middlewares/api-v3/errorHandler.js | 86 + website/server/middlewares/api-v3/index.js | 87 + website/server/middlewares/api-v3/language.js | 91 + website/server/middlewares/api-v3/locals.js | 61 + .../middlewares/api-v3/maintenanceMode.js | 31 + website/server/middlewares/api-v3/notFound.js | 7 + .../server/middlewares/api-v3/redirects.js | 43 + website/server/middlewares/api-v3/response.js | 25 + .../server/middlewares/api-v3/setupBody.js | 5 + website/server/middlewares/api-v3/static.js | 18 + website/server/middlewares/api-v3/v1.js | 19 + website/server/middlewares/api-v3/v2.js | 27 + website/server/middlewares/api-v3/v3.js | 30 + .../middlewares/apiThrottle.js | 4 +- .../middlewares/forceRefresh.js | 2 + website/server/models/challenge.js | 426 +++++ website/server/models/coupon.js | 57 + website/server/models/emailUnsubscription.js | 24 + website/server/models/group.js | 760 +++++++++ website/server/models/tag.js | 27 + website/server/models/task.js | 222 +++ website/server/models/user.js | 824 +++++++++ website/server/routes/api-v2/auth.js | 21 + website/server/routes/api-v2/coupon.js | 15 + .../{src => server}/routes/api-v2/swagger.js | 83 +- .../server/routes/api-v2/unsubscription.js | 11 + website/{src => server}/routes/pages.js | 4 +- website/server/routes/payments.js | 34 + website/server/server.js | 35 + website/src/controllers/api-v2/challenges.js | 453 ----- website/src/controllers/api-v2/user.js | 707 -------- website/src/controllers/payments/amazon.js | 271 --- website/src/controllers/payments/iap.js | 155 -- website/src/controllers/payments/index.js | 207 --- website/src/controllers/payments/paypal.js | 216 --- website/src/controllers/payments/stripe.js | 123 -- website/src/middlewares/cors.js | 7 - website/src/middlewares/redirects.js | 41 - website/src/models/challenge.js | 120 -- website/src/models/coupon.js | 59 - website/src/models/emailUnsubscription.js | 14 - website/src/models/group.js | 501 ------ website/src/models/task.js | 113 -- website/src/models/user.js | 697 -------- website/src/routes/api-v1.js | 173 -- website/src/routes/api-v2/auth.js | 21 - website/src/routes/api-v2/coupon.js | 12 - website/src/routes/api-v2/unsubscription.js | 8 - website/src/routes/dataexport.js | 16 - website/src/routes/payments.js | 31 - website/src/server.js | 177 -- website/views/avatar-static.jade | 2 +- website/views/main/filters.jade | 2 +- website/views/options/inventory/drops.jade | 6 +- .../options/inventory/time-travelers.jade | 2 +- website/views/options/profile.jade | 4 +- website/views/options/settings.jade | 21 +- .../views/options/social/challenge-box.jade | 2 +- website/views/options/social/challenges.jade | 16 +- website/views/options/social/chat-box.jade | 2 +- .../views/options/social/chat-message.jade | 2 +- website/views/options/social/group.jade | 6 +- website/views/options/social/hall.jade | 2 +- website/views/options/social/index.jade | 4 +- .../party/leave-party-and-join-another.jade | 2 +- .../social/party/party-invitation.jade | 3 +- .../options/social/quests/questActive.jade | 2 +- .../options/social/quests/questNotActive.jade | 4 +- website/views/options/social/tavern.jade | 2 +- website/views/shared/avatar/appearance.jade | 4 +- website/views/shared/footer.jade | 17 +- website/views/shared/header/header.jade | 6 +- website/views/shared/header/menu.jade | 2 +- website/views/shared/modals/buy-gems.jade | 4 +- website/views/shared/modals/classes.jade | 2 +- website/views/shared/modals/death.jade | 2 +- website/views/shared/modals/index.jade | 1 + website/views/shared/modals/limited.jade | 2 +- website/views/shared/modals/members.jade | 8 +- .../views/shared/modals/modify-inventory.jade | 252 +++ website/views/shared/modals/quests.jade | 5 +- website/views/shared/modals/settings.jade | 6 +- website/views/shared/new-stuff.jade | 64 +- .../views/shared/profiles/achievements.jade | 6 +- .../shared/profiles/stats/attributes.jade | 2 +- .../shared/tasks/edit/habits/plus_minus.jade | 8 +- website/views/shared/tasks/edit/index.jade | 4 +- website/views/shared/tasks/edit/tags.jade | 2 +- website/views/shared/tasks/index.jade | 2 +- website/views/shared/tasks/meta_controls.jade | 10 +- website/views/shared/tasks/task.jade | 4 +- .../views/shared/tasks/task_view/add_new.jade | 2 +- .../views/shared/tasks/task_view/graph.jade | 2 +- .../views/shared/tasks/task_view/index.jade | 8 +- .../views/shared/tasks/task_view/mixins.jade | 4 +- .../views/shared/tasks/task_view/skills.jade | 2 +- .../views/static/{api.jade => api-v2.jade} | 6 +- website/views/static/front.jade | 1 + website/views/static/maintenance-info.jade | 2 +- website/views/static/maintenance.jade | 18 + 993 files changed, 44888 insertions(+), 12883 deletions(-) rename common/img/sprites/spritesmith/achievements/{achievement-spookDust.png => achievement-spookySparkles.png} (100%) rename common/img/sprites/spritesmith/achievements/{achievement-spookDust2x.png => achievement-spookySparkles2x.png} (100%) rename common/img/sprites/spritesmith/misc/{spookman.png => ghost.png} (100%) rename common/img/sprites/spritesmith/misc/{inventory_special_spookDust.png => inventory_special_spookySparkles.png} (100%) rename common/img/sprites/spritesmith/shop/{shop_spookDust.png => shop_spookySparkles.png} (100%) rename common/img/sprites/spritesmith/skills/{shop_heallAll.png => shop_healAll.png} (100%) create mode 100644 common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png delete mode 100644 common/script/fns/cron.js delete mode 100644 common/script/fns/getItem.js delete mode 100644 common/script/fns/preenUserHistory.js create mode 100644 common/script/fns/resetGear.js delete mode 100644 common/script/libs/countExists.js delete mode 100644 common/script/libs/encodeiCalLink.js create mode 100644 common/script/libs/errors.js create mode 100644 common/script/libs/extendableBuiltin.js delete mode 100644 common/script/libs/friendlyTimestamp.js delete mode 100644 common/script/libs/index.js delete mode 100644 common/script/libs/newChatMessages.js create mode 100644 common/script/libs/pickDeep.js delete mode 100644 common/script/libs/preenHistory.js delete mode 100644 common/script/libs/removeWhitespace.js create mode 100644 common/script/libs/statsComputed.js create mode 100644 common/script/ops/buyArmoire.js create mode 100644 common/script/ops/buyGear.js create mode 100644 common/script/ops/buyHealthPotion.js create mode 100644 common/script/ops/markPMSRead.js delete mode 100644 common/script/ops/score.js create mode 100644 common/script/ops/scoreTask.js delete mode 100644 common/script/public/userServices.js create mode 100644 migrations/20160521_veteran_ladder.js create mode 100644 migrations/api_v3/challenges.js create mode 100644 migrations/api_v3/challengesMembers.js create mode 100644 migrations/api_v3/coupons.js create mode 100644 migrations/api_v3/emailUnsubscriptions.js create mode 100644 migrations/api_v3/groups.js create mode 100644 migrations/api_v3/indexes.js create mode 100644 migrations/api_v3/users.js delete mode 100644 newrelic.js rename {website/src/controllers/payments => scripts}/paypalBillingSetup.js (99%) create mode 100644 tasks/gulp-apidoc.js delete mode 100644 test/README.md create mode 100644 test/api/v2/user/tasks/POST-clear-completed.test.js create mode 100644 test/api/v3/README.md create mode 100644 test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js create mode 100644 test/api/v3/integration/challenges/GET-challenges_user.test.js create mode 100644 test/api/v3/integration/challenges/POST-challenges.test.js create mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js create mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js create mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js create mode 100644 test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js create mode 100644 test/api/v3/integration/chat/DELETE-chat_id.test.js create mode 100644 test/api/v3/integration/chat/GET-chat.test.js create mode 100644 test/api/v3/integration/chat/POST-chat.flag.test.js create mode 100644 test/api/v3/integration/chat/POST-chat.like.test.js create mode 100644 test/api/v3/integration/chat/POST-chat.test.js create mode 100644 test/api/v3/integration/chat/POST-chat_seen.test.js create mode 100644 test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js create mode 100644 test/api/v3/integration/content/GET-content.test.js create mode 100644 test/api/v3/integration/coupons/GET-coupons.test.js create mode 100644 test/api/v3/integration/coupons/POST-coupons_enter_code.test.js create mode 100644 test/api/v3/integration/coupons/POST-coupons_generate_event.test.js create mode 100644 test/api/v3/integration/coupons/POST-coupons_validate_code.test.js create mode 100644 test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js create mode 100644 test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js create mode 100644 test/api/v3/integration/dataexport/GET-export_history.csv.test.js create mode 100644 test/api/v3/integration/dataexport/GET-export_userdata.json.test.js create mode 100644 test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_addHourglass.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_addTenGems.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_make-admin.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_modify-inventory.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_quest-progress.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_set-cron.test.js create mode 100644 test/api/v3/integration/emails/GET-email-unsubscribe.test.js create mode 100644 test/api/v3/integration/groups/GET-groups.test.js create mode 100644 test/api/v3/integration/groups/GET-groups_groupId_invites.test.js create mode 100644 test/api/v3/integration/groups/GET-groups_groupId_members.test.js create mode 100644 test/api/v3/integration/groups/GET-groups_id.test.js create mode 100644 test/api/v3/integration/groups/POST-groups.test.js create mode 100644 test/api/v3/integration/groups/POST-groups_groupId_join.test.js create mode 100644 test/api/v3/integration/groups/POST-groups_groupId_leave.js create mode 100644 test/api/v3/integration/groups/POST-groups_groupId_reject.test.js create mode 100644 test/api/v3/integration/groups/POST-groups_id_removeMember.test.js create mode 100644 test/api/v3/integration/groups/POST-groups_invite.test.js create mode 100644 test/api/v3/integration/groups/PUT-groups.test.js create mode 100644 test/api/v3/integration/hall/GET-hall_heroes.test.js create mode 100644 test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js create mode 100644 test/api/v3/integration/hall/GET-hall_patrons.test.js create mode 100644 test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js create mode 100644 test/api/v3/integration/members/GET-members_id.test.js create mode 100644 test/api/v3/integration/members/POST-send_private_message.test.js create mode 100644 test/api/v3/integration/members/POST-transfer_gems.test.js create mode 100644 test/api/v3/integration/models/GET-model_paths.test.js create mode 100644 test/api/v3/integration/notFound.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js create mode 100644 test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js create mode 100644 test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js create mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js create mode 100644 test/api/v3/integration/status/GET-status.test.js create mode 100644 test/api/v3/integration/tags/DELETE-tags_id.test.js create mode 100644 test/api/v3/integration/tags/GET-tags.test.js create mode 100644 test/api/v3/integration/tags/GET-tags_id.test.js create mode 100644 test/api/v3/integration/tags/POST-tag-reorder.test.js create mode 100644 test/api/v3/integration/tags/POST-tags.test.js create mode 100644 test/api/v3/integration/tags/PUT-tags_id.test.js create mode 100644 test/api/v3/integration/tasks/DELETE-tasks_id.test.js create mode 100644 test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js create mode 100644 test/api/v3/integration/tasks/GET-tasks_id.test.js create mode 100644 test/api/v3/integration/tasks/GET-tasks_user.test.js create mode 100644 test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js create mode 100644 test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js create mode 100644 test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js create mode 100644 test/api/v3/integration/tasks/POST-tasks_user.test.js create mode 100644 test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js create mode 100644 test/api/v3/integration/tasks/PUT-tasks_id.test.js create mode 100644 test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js create mode 100644 test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js create mode 100644 test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js create mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js create mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js create mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js create mode 100644 test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js create mode 100644 test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js create mode 100644 test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js create mode 100644 test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js create mode 100644 test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js create mode 100644 test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js create mode 100644 test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js create mode 100644 test/api/v3/integration/user/DELETE-user.test.js create mode 100644 test/api/v3/integration/user/DELETE-user_delete_webhook.test.js create mode 100644 test/api/v3/integration/user/DELETE-user_messages.test.js create mode 100644 test/api/v3/integration/user/GET-user.test.js create mode 100644 test/api/v3/integration/user/GET-user_anonymized.test.js create mode 100644 test/api/v3/integration/user/GET-user_inventory_buy.test.js create mode 100644 test/api/v3/integration/user/POST-user_addPushDevice.test.js create mode 100644 test/api/v3/integration/user/POST-user_add_webhook.test.js create mode 100644 test/api/v3/integration/user/POST-user_allocate.test.js create mode 100644 test/api/v3/integration/user/POST-user_allocate_now.test.js create mode 100644 test/api/v3/integration/user/POST-user_block.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_armoire.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_gear.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_health_potion.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_mystery_set.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_quest.test.js create mode 100644 test/api/v3/integration/user/POST-user_buy_special_spell.test.js create mode 100644 test/api/v3/integration/user/POST-user_change-class.test.js create mode 100644 test/api/v3/integration/user/POST-user_class_cast_spellId.test.js create mode 100644 test/api/v3/integration/user/POST-user_custom-day-start.test.js create mode 100644 test/api/v3/integration/user/POST-user_disable-classes.test.js create mode 100644 test/api/v3/integration/user/POST-user_equip_type_key.test.js create mode 100644 test/api/v3/integration/user/POST-user_feed_pet_food.test.js create mode 100644 test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js create mode 100644 test/api/v3/integration/user/POST-user_mark_pms_read.test.js create mode 100644 test/api/v3/integration/user/POST-user_open_mystery_item.test.js create mode 100644 test/api/v3/integration/user/POST-user_purchase.test.js create mode 100644 test/api/v3/integration/user/POST-user_purchase_hourglass.test.js create mode 100644 test/api/v3/integration/user/POST-user_read_card.test.js create mode 100644 test/api/v3/integration/user/POST-user_rebirth.test.js create mode 100644 test/api/v3/integration/user/POST-user_release_both.test.js create mode 100644 test/api/v3/integration/user/POST-user_release_mounts.test.js create mode 100644 test/api/v3/integration/user/POST-user_release_pets.test.js create mode 100644 test/api/v3/integration/user/POST-user_reroll.test.js create mode 100644 test/api/v3/integration/user/POST-user_reset.test.js create mode 100644 test/api/v3/integration/user/POST-user_revive.test.js create mode 100644 test/api/v3/integration/user/POST-user_sell.test.js create mode 100644 test/api/v3/integration/user/POST-user_sleep.test.js create mode 100644 test/api/v3/integration/user/POST-user_unlock.js create mode 100644 test/api/v3/integration/user/PUT-user.test.js create mode 100644 test/api/v3/integration/user/PUT-user_update_webhook.test.js create mode 100644 test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js create mode 100644 test/api/v3/integration/user/auth/GET-logout.test.js create mode 100644 test/api/v3/integration/user/auth/POST-firebase.test.js create mode 100644 test/api/v3/integration/user/auth/POST-login-local.test.js create mode 100644 test/api/v3/integration/user/auth/POST-register_local.test.js create mode 100644 test/api/v3/integration/user/auth/POST-user_reset_password.test.js create mode 100644 test/api/v3/integration/user/auth/PUT-user_update_email.test.js create mode 100644 test/api/v3/integration/user/auth/PUT-user_update_password.test.js create mode 100644 test/api/v3/integration/user/auth/PUT-user_update_username.test.js create mode 100644 test/api/v3/unit/libs/analyticsService.test.js create mode 100644 test/api/v3/unit/libs/baseModel.test.js create mode 100644 test/api/v3/unit/libs/buildManifest.test.js create mode 100644 test/api/v3/unit/libs/collectionManipulators.test.js create mode 100644 test/api/v3/unit/libs/cron.test.js create mode 100644 test/api/v3/unit/libs/email.test.js create mode 100644 test/api/v3/unit/libs/encryption.test.js create mode 100644 test/api/v3/unit/libs/errors.test.js create mode 100644 test/api/v3/unit/libs/i18n.test.js create mode 100644 test/api/v3/unit/libs/logger.js create mode 100644 test/api/v3/unit/libs/password.test.js create mode 100644 test/api/v3/unit/libs/payments.test.js create mode 100644 test/api/v3/unit/libs/preening.test.js create mode 100644 test/api/v3/unit/libs/setupNconf.test.js create mode 100644 test/api/v3/unit/libs/webhooks.test.js create mode 100644 test/api/v3/unit/middlewares/analytics.test.js create mode 100644 test/api/v3/unit/middlewares/cors.test.js create mode 100644 test/api/v3/unit/middlewares/cronMiddleware.js create mode 100644 test/api/v3/unit/middlewares/ensureAccessRight.test.js create mode 100644 test/api/v3/unit/middlewares/ensureDevelpmentMode.js create mode 100644 test/api/v3/unit/middlewares/errorHandler.test.js create mode 100644 test/api/v3/unit/middlewares/language.test.js create mode 100644 test/api/v3/unit/middlewares/maintenanceMode.test.js create mode 100644 test/api/v3/unit/middlewares/response.js create mode 100644 test/api/v3/unit/models/challenge.test.js create mode 100644 test/api/v3/unit/models/group.test.js create mode 100644 test/api/v3/unit/models/task.test.js create mode 100644 test/api/v3/unit/models/user.test.js delete mode 100644 test/common/algos.mocha.js delete mode 100644 test/common/dailies.js create mode 100644 test/common/fns/autoAllocate.test.js create mode 100644 test/common/fns/crit.test.js create mode 100644 test/common/fns/handleTwoHanded.js create mode 100644 test/common/fns/predictableRandom.test.js create mode 100644 test/common/fns/randomDrop.test.js create mode 100644 test/common/fns/randomVal.js create mode 100644 test/common/fns/statsComputed.test.js create mode 100644 test/common/fns/ultimateGear.js create mode 100644 test/common/fns/updateStats.test.js create mode 100644 test/common/libs/appliedTags.test.js create mode 100644 test/common/libs/gold.test.js create mode 100644 test/common/libs/noTags.test.js create mode 100644 test/common/libs/percent.test.js create mode 100644 test/common/libs/pickDeep.js create mode 100644 test/common/libs/refPush.js create mode 100644 test/common/libs/silver.test.js create mode 100644 test/common/libs/splitWhitespace.test.js create mode 100644 test/common/libs/taskClasses.test.js create mode 100644 test/common/libs/taskDefaults.test.js create mode 100644 test/common/libs/updateStore.js create mode 100644 test/common/ops/addPushDevice.js create mode 100644 test/common/ops/addTask.js create mode 100644 test/common/ops/addWebhook.test.js create mode 100644 test/common/ops/allocate.js create mode 100644 test/common/ops/allocateNow.js create mode 100644 test/common/ops/blockUser.test.js create mode 100644 test/common/ops/buy.js create mode 100644 test/common/ops/buyArmoire.js create mode 100644 test/common/ops/buyGear.js create mode 100644 test/common/ops/buyHealthPotion.js create mode 100644 test/common/ops/buyMysterySet.js create mode 100644 test/common/ops/buyQuest.js create mode 100644 test/common/ops/buySpecialSpell.js create mode 100644 test/common/ops/changeClass.js create mode 100644 test/common/ops/clearCompleted.js create mode 100644 test/common/ops/clearPMs.test.js create mode 100644 test/common/ops/deletePM.test.js create mode 100644 test/common/ops/deleteWebhook.test.js create mode 100644 test/common/ops/disableClasses.js create mode 100644 test/common/ops/equip.js create mode 100644 test/common/ops/feed.js create mode 100644 test/common/ops/hatch.js create mode 100644 test/common/ops/hourglassPurchase.js create mode 100644 test/common/ops/openMysteryItem.js create mode 100644 test/common/ops/purchase.js create mode 100644 test/common/ops/readCard.js create mode 100644 test/common/ops/rebirth.js create mode 100644 test/common/ops/releaseBoth.js create mode 100644 test/common/ops/releaseMounts.js create mode 100644 test/common/ops/releasePets.js create mode 100644 test/common/ops/reroll.js create mode 100644 test/common/ops/reset.js create mode 100644 test/common/ops/revive.js create mode 100644 test/common/ops/scoreTask.test.js create mode 100644 test/common/ops/sell.js create mode 100644 test/common/ops/sleep.js create mode 100644 test/common/ops/unlock.js create mode 100644 test/common/ops/updateTask.js create mode 100644 test/common/ops/updateWebhook.test.js delete mode 100644 test/common/preenTodos.test.js delete mode 100644 test/common/shared.spells.test.js delete mode 100644 test/common/simulations/autoAllocate.js delete mode 100644 test/common/simulations/passive_active_attrs.js delete mode 100644 test/common/user.fns.buy.test.js delete mode 100644 test/common/user.fns.ultimateGear.test.js delete mode 100644 test/common/user.fns.updateStats.test.js delete mode 100644 test/common/user.ops.buyMysterySet.test.js delete mode 100644 test/common/user.ops.equip.test.js delete mode 100644 test/common/user.ops.hatch.js delete mode 100644 test/common/user.ops.hourglassPurchase.test.js delete mode 100644 test/common/user.ops.test.js delete mode 100644 test/helpers/api-integration/mongo.js create mode 100644 test/helpers/api-integration/v3/index.js create mode 100644 test/helpers/api-integration/v3/object-generators.js create mode 100644 test/helpers/api-unit.helper.js create mode 100644 test/helpers/api-v3-integration.helper.js create mode 100644 test/helpers/mongo.js create mode 100644 test/helpers/sleep.js delete mode 100644 test/spec/chatServicesSpec.js create mode 100644 test/spec/services/challengeServicesSpec.js create mode 100644 test/spec/services/chatServicesSpec.js create mode 100644 test/spec/services/tagServicesSpec.js rename website/{public => client}/500.html (100%) rename website/{public => client}/apple-touch-icon-114-precomposed.png (100%) rename website/{public => client}/apple-touch-icon-144-precomposed.png (100%) rename website/{public => client}/apple-touch-icon-57-precomposed.png (100%) rename website/{public => client}/apple-touch-icon-72-precomposed.png (100%) rename website/{public => client}/apple-touch-icon-precomposed.png (100%) rename website/{public => client}/cake.png (100%) rename website/{public => client}/community-guidelines-images/backCorner.png (100%) rename website/{public => client}/community-guidelines-images/beingHabitican.png (100%) rename website/{public => client}/community-guidelines-images/consequences.png (100%) rename website/{public => client}/community-guidelines-images/contributing.png (100%) rename website/{public => client}/community-guidelines-images/github.gif (100%) rename website/{public => client}/community-guidelines-images/infractions.png (100%) rename website/{public => client}/community-guidelines-images/intro.png (100%) rename website/{public => client}/community-guidelines-images/moderators.png (100%) rename website/{public => client}/community-guidelines-images/publicGuilds.png (100%) rename website/{public => client}/community-guidelines-images/publicSpaces.png (100%) rename website/{public => client}/community-guidelines-images/restoration.png (100%) rename website/{public => client}/community-guidelines-images/staff.png (100%) rename website/{public => client}/community-guidelines-images/tavern.png (100%) rename website/{public => client}/community-guidelines-images/trello.png (100%) rename website/{public => client}/community-guidelines-images/wiki.png (100%) rename website/{public => client}/css/README.md (100%) rename website/{public => client}/css/alerts.styl (100%) rename website/{public => client}/css/avatar.styl (100%) rename website/{public => client}/css/challenges.styl (100%) rename website/{public => client}/css/classes.styl (100%) rename website/{public => client}/css/customizer.styl (100%) rename website/{public => client}/css/filters.styl (100%) rename website/{public => client}/css/footer.styl (100%) rename website/{public => client}/css/game-pane.styl (100%) rename website/{public => client}/css/global-colors.styl (100%) rename website/{public => client}/css/global-modules.styl (100%) rename website/{public => client}/css/header.styl (100%) rename website/{public => client}/css/helpers.styl (100%) rename website/{public => client}/css/index.styl (100%) rename website/{public => client}/css/inventory.styl (100%) rename website/{public => client}/css/items.styl (100%) rename website/{public => client}/css/menu.styl (100%) rename website/{public => client}/css/no-script.styl (100%) rename website/{public => client}/css/npcs.styl (100%) rename website/{public => client}/css/options.styl (100%) rename website/{public => client}/css/quests.styl (100%) rename website/{public => client}/css/scrollbars.styl (100%) rename website/{public => client}/css/shared.styl (100%) rename website/{public => client}/css/static.styl (100%) rename website/{public => client}/css/tasks.styl (100%) rename website/{public => client}/css/variables/screen-size.styl (100%) rename website/{public => client}/emails/images/10-days-recapture-v1.png (100%) rename website/{public => client}/emails/images/3-days-1-month-recapture-v1.png (100%) rename website/{public => client}/emails/images/PROMO-Enchanted-Armoire-v1.png (100%) rename website/{public => client}/emails/images/android-promo-v1.png (100%) rename website/{public => client}/emails/images/iphone-promo-v1.png (100%) rename website/{public => client}/emails/images/one-day-v1.png (100%) rename website/{public => client}/emails/images/spring-2015-00-v1.png (100%) rename website/{public => client}/emails/images/spring-2015-01-v1.png (100%) rename website/{public => client}/emails/images/subscription-begins-time-travelers-v1.png (100%) rename website/{public => client}/emails/images/subscription-begins-v1.png (100%) rename website/{public => client}/favicon.ico (100%) rename website/{public => client}/favicon_192x192.png (100%) rename website/{public => client}/fontello/LICENSE.txt (100%) rename website/{public => client}/fontello/README.txt (100%) rename website/{public => client}/fontello/css/animation.css (100%) rename website/{public => client}/fontello/css/fontelico-codes.css (100%) rename website/{public => client}/fontello/css/fontelico-embedded.css (100%) rename website/{public => client}/fontello/css/fontelico-ie7-codes.css (100%) rename website/{public => client}/fontello/css/fontelico-ie7.css (100%) rename website/{public => client}/fontello/css/fontelico.css (100%) rename website/{public => client}/fontello/demo.html (100%) rename website/{public => client}/fontello/font/fontelico.eot (100%) rename website/{public => client}/fontello/font/fontelico.svg (100%) rename website/{public => client}/fontello/font/fontelico.ttf (100%) rename website/{public => client}/fontello/font/fontelico.woff (100%) rename website/{public => client}/front/README.md (100%) rename website/{public => client}/front/css/blockScroll.css (100%) rename website/{public => client}/front/css/bootstrap.min.css (100%) rename website/{public => client}/front/css/fixed-positioning.css (100%) rename website/{public => client}/front/fonts/glyphicons-halflings-regular.eot (100%) rename website/{public => client}/front/fonts/glyphicons-halflings-regular.svg (100%) rename website/{public => client}/front/fonts/glyphicons-halflings-regular.ttf (100%) rename website/{public => client}/front/fonts/glyphicons-halflings-regular.woff (100%) rename website/{public => client}/front/fonts/glyphicons-halflings-regular.woff2 (100%) rename website/{public => client}/front/images/Feeding_Time.png (100%) rename website/{public => client}/front/images/Guilds Sample Screen.png (100%) rename website/{public => client}/front/images/HabitRPGPromoPostCard6.png (100%) rename website/{public => client}/front/images/HabitRPGPromoThin.png (100%) rename website/{public => client}/front/images/Habitica_banner_by_uncommoncriminal.png (100%) rename website/{public => client}/front/images/Habitica_map_by_uncommoncriminal.png (100%) rename website/{public => client}/front/images/Healer.png (100%) rename website/{public => client}/front/images/Mount.png (100%) rename website/{public => client}/front/images/Mount_Body_Dragon-Golden.png (100%) rename website/{public => client}/front/images/Mount_Body_Dragon-Red.png (100%) rename website/{public => client}/front/images/Mount_Body_Wolf-Base.png (100%) rename website/{public => client}/front/images/Mount_Head_Dragon-Golden.png (100%) rename website/{public => client}/front/images/Mount_Head_Dragon-Red.png (100%) rename website/{public => client}/front/images/Mount_Head_Wolf-Base.png (100%) rename website/{public => client}/front/images/Party-Header.png (100%) rename website/{public => client}/front/images/Pet-Dragon-Red.png (100%) rename website/{public => client}/front/images/Pet-Fox-Red.png (100%) rename website/{public => client}/front/images/Promo_springclasses2015.png (100%) rename website/{public => client}/front/images/Quest_dilatory_drag'on.png (100%) rename website/{public => client}/front/images/Quest_dilatory_drag'onSmall.png (100%) rename website/{public => client}/front/images/Rogue.png (100%) rename website/{public => client}/front/images/SAMPLEadventurers.png (100%) rename website/{public => client}/front/images/TVreward.png (100%) rename website/{public => client}/front/images/VICE_by_Baconsaur.png (100%) rename website/{public => client}/front/images/Warrior.png (100%) rename website/{public => client}/front/images/Wizard.png (100%) rename website/{public => client}/front/images/achievement-perfect.png (100%) rename website/{public => client}/front/images/achievement-triadbingo.png (100%) rename website/{public => client}/front/images/avatar/Warrior.png (100%) rename website/{public => client}/front/images/avatar/avatar.png (100%) rename website/{public => client}/front/images/avatar/avatarstatic.png (100%) rename website/{public => client}/front/images/avatar/hair_bangs_1_brown.png (100%) rename website/{public => client}/front/images/avatar/head_0.png (100%) rename website/{public => client}/front/images/avatar/head_warrior_3.png (100%) rename website/{public => client}/front/images/avatar/head_warrior_5.png (100%) rename website/{public => client}/front/images/avatar/shield_warrior_3.png (100%) rename website/{public => client}/front/images/avatar/shield_warrior_5.png (100%) rename website/{public => client}/front/images/avatar/skin_f5a76e.png (100%) rename website/{public => client}/front/images/avatar/slim_armor_warrior_3.png (100%) rename website/{public => client}/front/images/avatar/slim_armor_warrior_5.png (100%) rename website/{public => client}/front/images/avatar/slim_shirt_black.png (100%) rename website/{public => client}/front/images/avatar/weapon_healer_6.png (100%) rename website/{public => client}/front/images/avatar/weapon_warrior_3.png (100%) rename website/{public => client}/front/images/avatar/weapon_warrior_5.png (100%) rename website/{public => client}/front/images/blackish_fox_by_kellllly-d7pzd46.png (100%) rename website/{public => client}/front/images/coding_by_phoneix_faerie.png (100%) rename website/{public => client}/front/images/devices.png (100%) rename website/{public => client}/front/images/explosion.jpg (100%) rename website/{public => client}/front/images/explosion.png (100%) rename website/{public => client}/front/images/habitrpg_pixel.png (100%) rename website/{public => client}/front/images/icon175x175.png (100%) rename website/{public => client}/front/images/intro.jpg (100%) rename website/{public => client}/front/images/intro.psd (100%) rename website/{public => client}/front/images/misc/Pet_Food_Cake_Base.png (100%) rename website/{public => client}/front/images/misc/inventory_quest_scroll_harpy.png (100%) rename website/{public => client}/front/images/misc/rebirth_orb.png (100%) rename website/{public => client}/front/images/misc/shop_gold.png (100%) rename website/{public => client}/front/images/misc/shop_potion.png (100%) rename website/{public => client}/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png (100%) rename website/{public => client}/front/images/party/AnnaCosplay.png (100%) rename website/{public => client}/front/images/party/Ariel_cosplay.png (100%) rename website/{public => client}/front/images/party/Big_Daddy_(BioShock).png (100%) rename website/{public => client}/front/images/party/Cosplay_Daenerys_Targaryen.png (100%) rename website/{public => client}/front/images/party/GrimReaper.png (100%) rename website/{public => client}/front/images/party/HomeStuckLusus.png (100%) rename website/{public => client}/front/images/presslogos/Cnetlogo.png (100%) rename website/{public => client}/front/images/presslogos/Fast-Company-logo.png (100%) rename website/{public => client}/front/images/presslogos/Forbes_logo.png (100%) rename website/{public => client}/front/images/presslogos/GitHub_Logo.png (100%) rename website/{public => client}/front/images/presslogos/discover_logo.png (100%) rename website/{public => client}/front/images/presslogos/ionic-logo-blog.png (100%) rename website/{public => client}/front/images/presslogos/ionic-logo-horizontal-transparent.png (100%) rename website/{public => client}/front/images/presslogos/kickstarter-logo.png (100%) rename website/{public => client}/front/images/presslogos/landing_slack_hash_wordmark_logo.png (100%) rename website/{public => client}/front/images/presslogos/lifehacker.png (100%) rename website/{public => client}/front/images/presslogos/logo_webstorm.png (100%) rename website/{public => client}/front/images/presslogos/makeuseof.png (100%) rename website/{public => client}/front/images/presslogos/nyt-logo.png (100%) rename website/{public => client}/front/images/presslogos/slack.png (100%) rename website/{public => client}/front/images/presslogos/trello-logo-blue.png (100%) rename website/{public => client}/front/images/quest_vice3.png (100%) rename website/{public => client}/front/images/screenshot.png (100%) rename website/{public => client}/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png (100%) rename website/{public => client}/front/images/testimonial_by_Streak.png (100%) rename website/{public => client}/front/images/testimonials/16bitFil.png (100%) rename website/{public => client}/front/images/testimonials/AlexandraSo.png (100%) rename website/{public => client}/front/images/testimonials/Althaire.png (100%) rename website/{public => client}/front/images/testimonials/AndeeLiao.png (100%) rename website/{public => client}/front/images/testimonials/Brenna.png (100%) rename website/{public => client}/front/images/testimonials/Drag0nsilver.png (100%) rename website/{public => client}/front/images/testimonials/Drei-M.png (100%) rename website/{public => client}/front/images/testimonials/Elmi.png (100%) rename website/{public => client}/front/images/testimonials/EvaGantz.png (100%) rename website/{public => client}/front/images/testimonials/Helcura.png (100%) rename website/{public => client}/front/images/testimonials/InfH.png (100%) rename website/{public => client}/front/images/testimonials/Kai.png (100%) rename website/{public => client}/front/images/testimonials/Kazui.png (100%) rename website/{public => client}/front/images/testimonials/Zelah_Meyer.png (100%) rename website/{public => client}/front/images/testimonials/autumnesquirrel.png (100%) rename website/{public => client}/front/images/testimonials/frabjabulous.png (100%) rename website/{public => client}/front/images/testimonials/galarix.png (100%) rename website/{public => client}/front/images/testimonials/gwyn.blath.png (100%) rename website/{public => client}/front/images/testimonials/irishfeet123.png (100%) rename website/{public => client}/front/images/testimonials/skysailor.png (100%) rename website/{public => client}/front/images/testimonials/supermouse35.png (100%) rename website/{public => client}/front/images/testimonials/tonitonirocca.png (100%) rename website/{public => client}/front/images/uses/achievement-bkgd.png (100%) rename website/{public => client}/front/images/uses/clipart-rosemonkeyct-meditation.png (100%) rename website/{public => client}/front/images/uses/clipart-rosemonkeyct-meditation.psd (100%) rename website/{public => client}/front/images/uses/clipart-rosemonkeyct-reading.png (100%) rename website/{public => client}/front/images/uses/coding.png (100%) rename website/{public => client}/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png (100%) rename website/{public => client}/front/images/uses/consequences.png (100%) rename website/{public => client}/front/images/uses/dusting-bkgd.png (100%) rename website/{public => client}/front/images/uses/dusting_by_leephon.png (100%) rename website/{public => client}/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png (100%) rename website/{public => client}/front/images/uses/meditation-bkgd.png (100%) rename website/{public => client}/front/images/uses/publicSpaces.png (100%) rename website/{public => client}/front/images/uses/reading.png (100%) rename website/{public => client}/front/js/blockScroll.js (100%) rename website/{public => client}/front/js/bootstrap.min.js (100%) rename website/{public => client}/front/js/skrollr.min.js (100%) rename website/{public => client}/front/landingv1Wireframe.jpg (100%) rename website/{public => client}/front/staticstyle.css (100%) rename website/{public => client}/front/style.css (100%) rename website/{public => client}/google280633b772b94345.html (100%) rename website/{public => client}/google8ca65b6ff3506fb8.html (100%) rename website/{public => client}/googlef3b1402b0e28338a.html (100%) rename website/{public => client}/js/.eslintrc (100%) rename website/{public => client}/js/app.js (73%) rename website/{public => client}/js/controllers/authCtrl.js (71%) rename website/{public => client}/js/controllers/autoCompleteCtrl.js (100%) rename website/{public => client}/js/controllers/challengesCtrl.js (61%) rename website/{public => client}/js/controllers/chatCtrl.js (62%) rename website/{public => client}/js/controllers/copyMessageModalCtrl.js (89%) rename website/{public => client}/js/controllers/filtersCtrl.js (81%) rename website/{public => client}/js/controllers/footerCtrl.js (64%) rename website/{public => client}/js/controllers/groupsCtrl.js (72%) create mode 100644 website/client/js/controllers/guildsCtrl.js rename website/{public => client}/js/controllers/hallCtrl.js (66%) rename website/{public => client}/js/controllers/headerCtrl.js (79%) rename website/{public => client}/js/controllers/inventoryCtrl.js (92%) rename website/{public => client}/js/controllers/inviteToGroupCtrl.js (63%) rename website/{public => client}/js/controllers/memberModalCtrl.js (50%) rename website/{public => client}/js/controllers/menuCtrl.js (97%) rename website/{public => client}/js/controllers/notificationCtrl.js (97%) create mode 100644 website/client/js/controllers/partyCtrl.js rename website/{public => client}/js/controllers/rootCtrl.js (86%) rename website/{public => client}/js/controllers/settingsCtrl.js (89%) rename website/{public => client}/js/controllers/sortableInventoryCtrl.js (100%) rename website/{public => client}/js/controllers/tasksCtrl.js (71%) create mode 100644 website/client/js/controllers/tavernCtrl.js rename website/{public => client}/js/controllers/userCtrl.js (90%) rename website/{public => client}/js/directives/close-menu.directive.js (100%) rename website/{public => client}/js/directives/expand-menu.directive.js (100%) rename website/{public => client}/js/directives/focus-element.directive.js (100%) rename website/{public => client}/js/directives/from-now.directive.js (100%) rename website/{public => client}/js/directives/habitrpg-tasks.directive.js (100%) rename website/{public => client}/js/directives/hrpg-sort-checklist.directive.js (100%) rename website/{public => client}/js/directives/hrpg-sort-tags.directive.js (88%) rename website/{public => client}/js/directives/hrpg-sort-tasks.directive.js (89%) rename website/{public => client}/js/directives/popover-html-popup.directive.js (100%) rename website/{public => client}/js/directives/popover-html.directive.js (100%) rename website/{public => client}/js/directives/when-scrolled.directive.js (100%) rename website/{public => client}/js/env.js (100%) rename website/{public => client}/js/filters/money.js (100%) rename website/{public => client}/js/filters/roundLargeNumbers.js (100%) rename website/{public => client}/js/filters/taskOrdering.js (100%) rename website/{public => client}/js/filters/timezoneOffsetToUtc.js (100%) rename website/{public => client}/js/services/analyticsServices.js (100%) create mode 100644 website/client/js/services/challengeServices.js create mode 100644 website/client/js/services/chatServices.js create mode 100644 website/client/js/services/groupServices.js rename website/{public => client}/js/services/guideServices.js (97%) create mode 100644 website/client/js/services/memberServices.js rename website/{public => client}/js/services/notificationServices.js (95%) rename website/{public => client}/js/services/paymentServices.js (95%) rename website/{public => client}/js/services/questServices.js (80%) rename website/{public => client}/js/services/sharedServices.js (100%) rename website/{public => client}/js/services/socialServices.js (100%) rename website/{public => client}/js/services/statServices.js (94%) create mode 100644 website/client/js/services/tagsServices.js create mode 100644 website/client/js/services/taskServices.js create mode 100644 website/client/js/services/userServices.js rename website/{public => client}/js/static.js (77%) rename website/{public => client}/logo.png (100%) rename website/{public => client}/logo/HABITRPG logo version 1.psd (100%) rename website/{public => client}/logo/HABITRPG-logo-version-1.gif (100%) rename website/{public => client}/logo/habitrpg.jpg (100%) rename website/{public => client}/logo/habitrpg_bl.eps (100%) rename website/{public => client}/logo/habitrpg_pixel.png (100%) rename website/{public => client}/manifest.json (94%) rename website/{public => client}/marketing/android_iphone.png (100%) rename website/{public => client}/marketing/animals.png (100%) rename website/{public => client}/marketing/challenge.png (100%) rename website/{public => client}/marketing/devices.png (100%) rename website/{public => client}/marketing/drops.png (100%) rename website/{public => client}/marketing/education.png (100%) rename website/{public => client}/marketing/gear.png (100%) rename website/{public => client}/marketing/guild.png (100%) rename website/{public => client}/marketing/guild_small.png (100%) rename website/{public => client}/marketing/integration.png (100%) rename website/{public => client}/marketing/lefnire.png (100%) rename website/{public => client}/marketing/promos/201403_Forest_Walker.png (100%) rename website/{public => client}/marketing/promos/April14SAMPLE2.png (100%) rename website/{public => client}/marketing/screenshot.png (100%) rename website/{public => client}/marketing/social_competitve.png (100%) rename website/{public => client}/marketing/wellness.png (100%) rename website/{public => client}/merch/stickermule-logo.png (100%) rename website/{public => client}/merch/stickermule-logo.svg (100%) rename website/{public => client}/merch/stickermule.png (100%) rename website/{public => client}/merch/teespring-eu-logo.png (100%) rename website/{public => client}/merch/teespring-eu.png (100%) rename website/{public => client}/merch/teespring-logo.png (100%) rename website/{public => client}/merch/teespring-logo.svg (100%) rename website/{public => client}/merch/teespring.png (100%) rename website/{public => client}/page-loader.gif (100%) rename website/{public => client}/presskit/Boss - Basi-List.png (100%) rename website/{public => client}/presskit/Boss - Battling the Ghost Stag.png (100%) rename website/{public => client}/presskit/Boss - Laundromancer.png (100%) rename website/{public => client}/presskit/Boss - Necro-Vice.png (100%) rename website/{public => client}/presskit/Boss - SnackLess Monster.png (100%) rename website/{public => client}/presskit/Boss - Stagnant Dishes.png (100%) rename website/{public => client}/presskit/Habitica Gryphon.png (100%) rename website/{public => client}/presskit/Habitica Logo - Android.png (100%) rename website/{public => client}/presskit/Habitica Logo - Icon with Text.png (100%) rename website/{public => client}/presskit/Habitica Logo - Icon.png (100%) rename website/{public => client}/presskit/Habitica Logo - Text.png (100%) rename website/{public => client}/presskit/Habitica Logo - iOS.png (100%) rename website/{public => client}/presskit/Habitica Promo - Thin.png (100%) rename website/{public => client}/presskit/Habitica Promo.png (100%) rename website/{public => client}/presskit/Sample Screen - Boss (iOS).png (100%) rename website/{public => client}/presskit/Sample Screen - Challenges.png (100%) rename website/{public => client}/presskit/Sample Screen - Equipment.png (100%) rename website/{public => client}/presskit/Sample Screen - Guilds.png (100%) rename website/{public => client}/presskit/Sample Screen - Level Up (iOS).png (100%) rename website/{public => client}/presskit/Sample Screen - Market.png (100%) rename website/{public => client}/presskit/Sample Screen - Party (iOS).png (100%) rename website/{public => client}/presskit/Sample Screen - Pets (iOS).png (100%) rename website/{public => client}/presskit/Sample Screen - Tasks Page (iOS).png (100%) rename website/{public => client}/presskit/Sample Screen - Tasks Page.png (100%) rename website/{public => client}/presskit/World Boss - Dread Drag'on of Dilatory.png (100%) rename website/{public => client}/presskit/presskit.zip (100%) rename website/{public => client}/refresh.png (100%) delete mode 100644 website/public/js/controllers/guildsCtrl.js delete mode 100644 website/public/js/controllers/partyCtrl.js delete mode 100644 website/public/js/controllers/tavernCtrl.js delete mode 100644 website/public/js/services/challengeServices.js delete mode 100644 website/public/js/services/chatServices.js delete mode 100644 website/public/js/services/groupServices.js delete mode 100644 website/public/js/services/memberServices.js delete mode 100644 website/public/js/services/taskServices.js rename website/{src => server}/controllers/api-v2/auth.js (94%) create mode 100644 website/server/controllers/api-v2/challenges.js rename website/{src => server}/controllers/api-v2/coupon.js (90%) rename website/{src/controllers => server/controllers/api-v2}/dataexport.js (89%) rename website/{src => server}/controllers/api-v2/groups.js (69%) rename website/{src => server}/controllers/api-v2/hall.js (96%) rename website/{src => server}/controllers/api-v2/members.js (90%) rename website/{src/controllers => server/controllers/api-v2}/pushNotifications.js (98%) rename website/{src => server}/controllers/api-v2/unsubscription.js (80%) create mode 100644 website/server/controllers/api-v2/user.js create mode 100644 website/server/controllers/api-v3/auth.js create mode 100644 website/server/controllers/api-v3/challenges.js create mode 100644 website/server/controllers/api-v3/chat.js create mode 100644 website/server/controllers/api-v3/content.js create mode 100644 website/server/controllers/api-v3/coupon.js create mode 100644 website/server/controllers/api-v3/debug.js create mode 100644 website/server/controllers/api-v3/groups.js create mode 100644 website/server/controllers/api-v3/hall.js create mode 100644 website/server/controllers/api-v3/iap.js create mode 100644 website/server/controllers/api-v3/members.js create mode 100644 website/server/controllers/api-v3/modelsPaths.js create mode 100644 website/server/controllers/api-v3/quests.js create mode 100644 website/server/controllers/api-v3/status.js create mode 100644 website/server/controllers/api-v3/tags.js create mode 100644 website/server/controllers/api-v3/tasks.js create mode 100644 website/server/controllers/api-v3/user.js create mode 100644 website/server/controllers/top-level/auth.js create mode 100644 website/server/controllers/top-level/dataexport.js create mode 100644 website/server/controllers/top-level/email.js create mode 100644 website/server/controllers/top-level/pages.js create mode 100644 website/server/controllers/top-level/payments/amazon.js create mode 100644 website/server/controllers/top-level/payments/iap.js create mode 100644 website/server/controllers/top-level/payments/paypal.js create mode 100644 website/server/controllers/top-level/payments/stripe.js create mode 100644 website/server/index.js rename website/{src/libs => server/libs/api-v2}/analytics.js (98%) rename website/{src/libs => server/libs/api-v2}/buildManifest.js (91%) rename website/{src/libs => server/libs/api-v2}/firebase.js (87%) rename website/{src/libs => server/libs/api-v2}/i18n.js (90%) rename website/{src/libs => server/libs/api-v2}/logging.js (94%) rename website/{src/libs => server/libs/api-v2}/utils.js (90%) rename website/{src/libs => server/libs/api-v2}/webhook.js (100%) create mode 100644 website/server/libs/api-v3/amazonPayments.js create mode 100644 website/server/libs/api-v3/analyticsService.js create mode 100644 website/server/libs/api-v3/baseModel.js create mode 100644 website/server/libs/api-v3/buildManifest.js create mode 100644 website/server/libs/api-v3/collectionManipulators.js create mode 100644 website/server/libs/api-v3/cron.js create mode 100644 website/server/libs/api-v3/csvStringify.js create mode 100644 website/server/libs/api-v3/email.js create mode 100644 website/server/libs/api-v3/encryption.js create mode 100644 website/server/libs/api-v3/errors.js create mode 100644 website/server/libs/api-v3/firebase.js create mode 100644 website/server/libs/api-v3/i18n.js create mode 100644 website/server/libs/api-v3/logger.js create mode 100644 website/server/libs/api-v3/password.js create mode 100644 website/server/libs/api-v3/payments.js create mode 100644 website/server/libs/api-v3/preening.js create mode 100644 website/server/libs/api-v3/pushNotifications.js create mode 100644 website/server/libs/api-v3/routes.js create mode 100644 website/server/libs/api-v3/setupMongoose.js create mode 100644 website/server/libs/api-v3/setupNconf.js create mode 100644 website/server/libs/api-v3/setupPassport.js create mode 100644 website/server/libs/api-v3/webhook.js rename website/{src/middlewares => server/middlewares/api-v2}/domain.js (100%) rename website/{src/middlewares => server/middlewares/api-v2}/errorHandler.js (95%) rename website/{src/middlewares => server/middlewares/api-v2}/locals.js (94%) create mode 100644 website/server/middlewares/api-v3/analytics.js create mode 100644 website/server/middlewares/api-v3/auth.js create mode 100644 website/server/middlewares/api-v3/cors.js create mode 100644 website/server/middlewares/api-v3/cron.js create mode 100644 website/server/middlewares/api-v3/domain.js create mode 100644 website/server/middlewares/api-v3/ensureAccessRight.js create mode 100644 website/server/middlewares/api-v3/ensureDevelpmentMode.js create mode 100644 website/server/middlewares/api-v3/errorHandler.js create mode 100644 website/server/middlewares/api-v3/index.js create mode 100644 website/server/middlewares/api-v3/language.js create mode 100644 website/server/middlewares/api-v3/locals.js create mode 100644 website/server/middlewares/api-v3/maintenanceMode.js create mode 100644 website/server/middlewares/api-v3/notFound.js create mode 100644 website/server/middlewares/api-v3/redirects.js create mode 100644 website/server/middlewares/api-v3/response.js create mode 100644 website/server/middlewares/api-v3/setupBody.js create mode 100644 website/server/middlewares/api-v3/static.js create mode 100644 website/server/middlewares/api-v3/v1.js create mode 100644 website/server/middlewares/api-v3/v2.js create mode 100644 website/server/middlewares/api-v3/v3.js rename website/{src => server}/middlewares/apiThrottle.js (74%) rename website/{src => server}/middlewares/forceRefresh.js (83%) create mode 100644 website/server/models/challenge.js create mode 100644 website/server/models/coupon.js create mode 100644 website/server/models/emailUnsubscription.js create mode 100644 website/server/models/group.js create mode 100644 website/server/models/tag.js create mode 100644 website/server/models/task.js create mode 100644 website/server/models/user.js create mode 100644 website/server/routes/api-v2/auth.js create mode 100644 website/server/routes/api-v2/coupon.js rename website/{src => server}/routes/api-v2/swagger.js (91%) create mode 100644 website/server/routes/api-v2/unsubscription.js rename website/{src => server}/routes/pages.js (94%) create mode 100644 website/server/routes/payments.js create mode 100644 website/server/server.js delete mode 100644 website/src/controllers/api-v2/challenges.js delete mode 100644 website/src/controllers/api-v2/user.js delete mode 100644 website/src/controllers/payments/amazon.js delete mode 100644 website/src/controllers/payments/iap.js delete mode 100644 website/src/controllers/payments/index.js delete mode 100644 website/src/controllers/payments/paypal.js delete mode 100644 website/src/controllers/payments/stripe.js delete mode 100644 website/src/middlewares/cors.js delete mode 100644 website/src/middlewares/redirects.js delete mode 100644 website/src/models/challenge.js delete mode 100644 website/src/models/coupon.js delete mode 100644 website/src/models/emailUnsubscription.js delete mode 100644 website/src/models/group.js delete mode 100644 website/src/models/task.js delete mode 100644 website/src/models/user.js delete mode 100644 website/src/routes/api-v1.js delete mode 100644 website/src/routes/api-v2/auth.js delete mode 100644 website/src/routes/api-v2/coupon.js delete mode 100644 website/src/routes/api-v2/unsubscription.js delete mode 100644 website/src/routes/dataexport.js delete mode 100644 website/src/routes/payments.js delete mode 100644 website/src/server.js create mode 100644 website/views/shared/modals/modify-inventory.jade rename website/views/static/{api.jade => api-v2.jade} (91%) create mode 100644 website/views/static/maintenance.jade diff --git a/.babelrc b/.babelrc index abdb3b030b..988e0d6f03 100644 --- a/.babelrc +++ b/.babelrc @@ -1,4 +1,9 @@ { "presets": ["es2015"], - "plugins": ["syntax-async-functions","transform-regenerator"] + "plugins": [ + ["transform-async-to-module-method", { + "module": "bluebird", + "method": "coroutine" + }] + ] } diff --git a/.bowerrc b/.bowerrc index 4a52096b99..552e7d2622 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "website/public/bower_components" + "directory": "website/client/bower_components" } diff --git a/.eslintignore b/.eslintignore index 595ad011ae..5b7664ac76 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,24 +3,26 @@ common/dist/ common/transpiled-babel/ coverage/ database_reports/ -migrations/ website/build/ website/transpiled-babel/ -# The files in website/public/js should be moved out and browserified -website/public/ +migrations/* + +# The files in website/client/js should be moved out and browserified +website/client/ # Temporarilly disabled. These should be removed when the linting errors are fixed -common/script/index.js common/script/content/index.js -common/script/ops/**/*.js -common/script/fns/**/*.js -common/script/libs/**/*.js common/script/public/**/*.js -website/src/**/*.js +website/server/**/api-v2/**/*.js +website/server/routes/payments.js +website/server/routes/pages.js +website/server/middlewares/apiThrottle.js +website/server/middlewares/forceRefresh.js debug-scripts/* +scripts/* tasks/*.js gulpfile.js Gruntfile.js diff --git a/.eslintrc b/.eslintrc index 111772a5a3..bcccde1ef6 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,5 +2,8 @@ "extends": [ "habitrpg/server", "habitrpg/babel" - ] + ], + "globals": { + "Promise": true + } } diff --git a/.gitignore b/.gitignore index a8e969e620..4e3d867081 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store -website/public/gen -website/public/common +website/client/gen +website/client/common +website/client/apidoc website/transpiled-babel/ common/transpiled-babel/ node_modules @@ -9,8 +10,8 @@ node_modules config.json npm-debug.log* lib -website/public/bower_components -website/public/new-stuff.html +website/client/bower_components +website/client/new-stuff.html website/build newrelic_agent.log .bower-tmp @@ -24,7 +25,7 @@ src/*/*.map src/*/*/*.map test/*.js test/*.map -website/public/docs +website/client/docs *.sublime-workspace coverage coverage.html diff --git a/.nodemonignore b/.nodemonignore index 5aa436cfa3..c698b88598 100644 --- a/.nodemonignore +++ b/.nodemonignore @@ -2,7 +2,7 @@ node_modules/** .bower-cache/** .bower-tmp/** .bower-registry/** -website/public/** +website/client/** website/views/** website/build/** .git/** diff --git a/.travis.yml b/.travis.yml index 86de1b5103..dd5a397986 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js node_js: - '4.3.1' before_install: - - "npm install -g npm@2" + - "npm install -g npm@3" - "npm install -g gulp" - "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10" - "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list" diff --git a/Gruntfile.js b/Gruntfile.js index b5f507c50a..8f716518fe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -43,11 +43,11 @@ module.exports = function(grunt) { options: { compress: false, // AFTER 'include css': true, - paths: ['website/public'] + paths: ['website/client'] }, files: { - 'website/build/app.css': ['website/public/css/index.styl'], - 'website/build/static.css': ['website/public/css/static.styl'] + 'website/build/app.css': ['website/client/css/index.styl'], + 'website/build/static.css': ['website/client/css/static.styl'] } } }, @@ -55,13 +55,13 @@ module.exports = function(grunt) { copy: { build: { files: [ - {expand: true, cwd: 'website/public/', src: 'favicon.ico', dest: 'website/build/'}, - {expand: true, cwd: 'website/public/', src: 'favicon_192x192.png', dest: 'website/build/'}, + {expand: true, cwd: 'website/client/', src: 'favicon.ico', dest: 'website/build/'}, + {expand: true, cwd: 'website/client/', src: 'favicon_192x192.png', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/dist/sprites/spritesmith*.png', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/backer-only/*.gif', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/npc_ian.gif', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/quest_*.gif', dest: 'website/build/'}, - {expand: true, cwd: 'website/public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'} + {expand: true, cwd: 'website/client/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'} ] } }, @@ -88,9 +88,9 @@ module.exports = function(grunt) { } }); - //Load build files from public/manifest.json - grunt.registerTask('loadManifestFiles', 'Load all build files from public/manifest.json', function(){ - var files = grunt.file.readJSON('./website/public/manifest.json'); + //Load build files from client/manifest.json + grunt.registerTask('loadManifestFiles', 'Load all build files from client/manifest.json', function(){ + var files = grunt.file.readJSON('./website/client/manifest.json'); var uglify = {}; var cssmin = {}; @@ -101,7 +101,7 @@ module.exports = function(grunt) { _.each(files[key].js, function(val){ var path = "./"; if( val.indexOf('common/') == -1) - path = './website/public/'; + path = './website/client/'; js.push(path + val); }); @@ -110,7 +110,7 @@ module.exports = function(grunt) { _.each(files[key].css, function(val){ var path = "./"; if( val.indexOf('common/') == -1) { - path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/public/'; + path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client/'; } css.push(path + val) }); @@ -122,7 +122,7 @@ module.exports = function(grunt) { grunt.config.set('cssmin.build.files', cssmin); // Rewrite urls to relative path - grunt.config.set('cssmin.build.options', {'target': 'website/public/css/whatever-css.css'}); + grunt.config.set('cssmin.build.options', {'target': 'website/client/css/whatever-css.css'}); }); // Register tasks. @@ -131,7 +131,7 @@ module.exports = function(grunt) { grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']); grunt.registerTask('test:prepare:translations', function() { - var i18n = require('./website/src/libs/i18n'), + var i18n = require('./website/server/libs/api-v3/i18n'), fs = require('fs'); fs.writeFileSync('test/spec/mocks/translations.js', "if(!window.env) window.env = {};\n" + diff --git a/Procfile b/Procfile index 5283f654f1..72e2be7947 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: node ./website/transpiled-babel/server.js +web: node ./website/transpiled-babel/index.js diff --git a/bower.json b/bower.json index 8788581cd1..7e6e6f6790 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,7 @@ "ignore": [ "**/.*", "node_modules", - "public/bower_components", + "website/client/bower_components", "test", "tests" ], diff --git a/common/browserify.js b/common/browserify.js index 0530144839..3653cb81b0 100644 --- a/common/browserify.js +++ b/common/browserify.js @@ -1,3 +1,5 @@ +require('babel-polyfill'); + var shared = require('./script/index'); var _ = require('lodash'); var moment = require('moment'); diff --git a/common/dist/sprites/spritesmith-main-0.css b/common/dist/sprites/spritesmith-main-0.css index 33de4f76f8..28970a6d42 100644 --- a/common/dist/sprites/spritesmith-main-0.css +++ b/common/dist/sprites/spritesmith-main-0.css @@ -346,13 +346,13 @@ width: 48px; height: 52px; } -.achievement-spookDust { +.achievement-spookySparkles { background-image: url(spritesmith-main-0.png); background-position: -25px -1601px; width: 24px; height: 26px; } -.achievement-spookDust2x { +.achievement-spookySparkles2x { background-image: url(spritesmith-main-0.png); background-position: -980px -1548px; width: 48px; diff --git a/common/dist/sprites/spritesmith-main-11.css b/common/dist/sprites/spritesmith-main-11.css index 0c5b59a361..6c97f196e0 100644 --- a/common/dist/sprites/spritesmith-main-11.css +++ b/common/dist/sprites/spritesmith-main-11.css @@ -1960,31 +1960,31 @@ width: 81px; height: 99px; } -.Pet-LionCub-Base { +.Pet-Lion-Veteran { background-image: url(spritesmith-main-11.png); background-position: -1640px -700px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyBlue { +.Pet-LionCub-Base { background-image: url(spritesmith-main-11.png); background-position: -1640px -800px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyPink { +.Pet-LionCub-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1640px -900px; width: 81px; height: 99px; } -.Pet-LionCub-Desert { +.Pet-LionCub-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1640px -1000px; width: 81px; height: 99px; } -.Pet-LionCub-Floral { +.Pet-LionCub-Desert { background-image: url(spritesmith-main-11.png); background-position: -1640px -1100px; width: 81px; diff --git a/common/dist/sprites/spritesmith-main-11.png b/common/dist/sprites/spritesmith-main-11.png index 1a3e1517449e0dfe18b775cfdd8eb683b3b0d800..8f1f71ebf3f8894b42c38996a821660174f70e54 100644 GIT binary patch delta 84482 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Þ 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 K9@6gPQU3?!@;s#g delta 84161 zcma&O2{_bU_&5HHlA=D7nW0E3Q4~YQ5}`uMQpPq^D5{}}$r4XQWf)sV zGuD*GK2eO4eHi<`kL7>9Lp}BUe((GKughg-zUw*nxwmtFKIa5gZV0K^5WWXyn6>wF zA9mlUTf@{G%~kTK+27c5Bb5wjn>zQiZ>Hg5U3&&XUig)mXGxYRS~s9DSyz;N?B z)}9fk|Av>IJoQ> zbQyF^QiPAWOHxEp37otKo%#9qc{lP_kzScRA31G^Ph=5s58pO$>Wp4^*aYXA4drEA zl=bChVH=V4E6?Y=6?(-{T>5a`T~lhycfFhmDSaI^33Z7L;HvU6+dwq*s@3-p3OB># zw!PHz{s(Hx9}hkXFOAv+6Wn4^msA?n`82oXjzU=)`GYXQl^?dt^bR_1rLD){!hB3F zidTdbr)$uw|LsNqxQg@Z*Hth6kOAmm{cdFNenayB)%BTWbA*eBVNzU@B8244FkNg@ zr#Qdw%)^&$pY4{vxwo*SuTp;`1)1Lc%VNP7H2I|-ms{3RcsOyl))uQgcH^lSS;JJs zKF%Y-Q^`41(e&>}PMx2Q6|U8bXIOe$34nXN*HXYeD-!(tQywBUKZk^K<`^7q{CPJP z2{m6*q`Zs{z07+LG)`QUcTFF3m7(82UVO|>&=O6wd&C{_zAWdGc~(Nmx?W;6zqR0#2fNH1gDTZpE`y@IqbAn*z}C;O1)|`PUYP+jV!uj%1_iLtSeJ9V)$IoV0@0J}-EKk+g@Il`GDuj_#-ngy&S|vD z8t5?8N6ts*2UQUn@T`fFs321+E-_L_7d|jc#YM`#Ck8OsdmcIF^W+%Dj74qHB~Bc| zpzaUbC7&Km?Zj+y+qxp?lV(saQ-=~Cb4BgD>t9#B#w`AG7c-!H1JZEw&#&e7c2Usj zhiLJVart-#eofRqNfGGH0V}N^n}{H2io(ROsk0wEzd5z;5SG5$zm)ktM08~=!62}p zL4N`Cx2zMh0rd3uUURG!o7uxr@wX3jS2VC~lW&1g{ANwk6-Zh17m+Ke{%T{owRa?#ZP#!~U+VDYX4 zPGzo}t2ZJ)U20r(^>T6OJ-9iimEAhykYa%B8B)4Mii-3ZrtDL0JeYqEo#mdJHr1Pi za|mWl_4{_}Dppn>{J^hX^MW=M9Zg1Ntf(JnOe96LB!D;AfV_j2hY1s4rZ7RQF3Bu> zyrl7Mw7WKf#@fX#;?ukJ-w_4ox85zwjMHmK5a7;V-^pn&TmME(j(;ZE;zZAtoq5iNNL@V&+Aj%uM#qC%t zm%wMXX`re~<@w!I3u*q;YUPz5k&7wGO51oxRn7u9(7R7q4Wj)xKBuh|p$)U2ug!UnZgafRyo z!Nt0^AhRp>Wz=*>AA!-ZFb9s?Td^Ub(KYYaSY!p59P&~fON{0r+ro&>qu0D~x-ipk z?G2OSVO(k1?}>d#VJx>95c&CtD$eEWyi`Rgh)f|q=-?Nhw+HlZ$_nRH{-p?6PuH4q z*Wuaj+Fzq6^5GaaaP-h9BY>{_Yp@)wu&hy~0B*99p2Cehhnm84! z;V)WRCeFEjBV_*^?+YRV{^s;HEJ|N-y864#ex`|q%XZsiCWGD!b#~fmc2;gZ4|L4m zQ1$483Vi8lW|FEuZr-2TR=e<(t%Jz4Un~^t3FXRAoq8SqlqcOvYeD(oBW~knmwW?o zkyVx#eha^|d7IdyMRw27L4N1HTXOM>$oB4UubdY;6Gb8K|93bPB(I|VmX2QGa_)v@ zq@Y!tIA5fv%|xIB|Kn2%C14{bTo4nCHV8;Zv^RVdM`oJX$IJu$_}zhV3H3&(V0%18 zoqDRHPsJeEObopi>a7sAVcOb6nATyThGDbK(vP9#EN5-L`jvK zu)52 z(-T>jfNEJ8^krRzK7k%JV2?AuyUY+=*ZuUBfxMQVqMKA5TV#Nong^&&m2+xM<;hp3 zKBgv1QCo8Op;RNaB5h~m>H6yB98Vn&jTJF4wnq>j{N`hT5QzIn`z77%@1W%Ucg|3{ zbb`eu3TCBOOcqjN*#I>j zNe8_$$n!P1f4yx`Xx}}vEh+jlGh$e-w+)R+T;a8EvjZyDoDjZ<39rg&iRnnjDeyZ_ zeMwTysEi?YN4wg;h~%Mo@BdTtbjd zKrshu!1hqI!QFHdk(RjVl-;N=HUqKyc0OPI+L2JGq->x^N_pRyk=G`@`c;!Xh>w;o z+>jSe+>mK6#rUD_t{w9bdP@FCkp^_p?S;jJ5W%4Hn8#R3o1Rs4w6yh0PRDC&!-ApEr>AH>vAd*p#xzJo<>|HK`6?XSkcC$3*Ju05rasb~~8cZHn6>Ch&O$ z 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%0DJ_slSr5%dBKc}S%|9{ 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 literal 125673 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ν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

k5qUoaSjK(Q!%IFBrzBF0VObmEW3wO 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 HQ$iB}NTGIM diff --git a/common/dist/sprites/spritesmith-main-5.css b/common/dist/sprites/spritesmith-main-5.css index e7245d2efd..50f8d8a69a 100644 --- a/common/dist/sprites/spritesmith-main-5.css +++ b/common/dist/sprites/spritesmith-main-5.css @@ -270,7 +270,7 @@ } .shop_armor_special_candycane { background-image: url(spritesmith-main-5.png); - background-position: -1674px -410px; + background-position: -1674px -451px; width: 40px; height: 40px; } @@ -282,19 +282,19 @@ } .shop_armor_special_snowflake { background-image: url(spritesmith-main-5.png); - background-position: -1674px -615px; + background-position: -1674px -656px; width: 40px; height: 40px; } .shop_armor_special_winter2015Healer { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1189px; + background-position: -1674px -1230px; width: 40px; height: 40px; } .shop_armor_special_winter2015Mage { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1230px; + background-position: -1674px -1271px; width: 40px; height: 40px; } @@ -456,13 +456,13 @@ } .shop_shield_special_winter2015Warrior { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1558px; + background-position: -1674px -1517px; width: 40px; height: 40px; } .shop_shield_special_winter2016Healer { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1476px; + background-position: -1599px -1599px; width: 40px; height: 40px; } @@ -1098,103 +1098,103 @@ } .shop_head_rogue_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -984px; + background-position: -1674px -1025px; width: 40px; height: 40px; } .shop_head_rogue_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -943px; + background-position: -1674px -984px; width: 40px; height: 40px; } .shop_head_rogue_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -902px; + background-position: -1674px -943px; width: 40px; height: 40px; } .shop_head_special_0 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -861px; + background-position: -1674px -902px; width: 40px; height: 40px; } .shop_head_special_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -820px; + background-position: -1674px -861px; width: 40px; height: 40px; } .shop_head_special_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -738px; + background-position: -1674px -779px; width: 40px; height: 40px; } .shop_head_special_fireCoralCirclet { background-image: url(spritesmith-main-5.png); - background-position: -1674px -697px; + background-position: -1674px -738px; width: 40px; height: 40px; } .shop_head_warrior_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -656px; + background-position: -1674px -697px; width: 40px; height: 40px; } .shop_head_warrior_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -574px; + background-position: -1674px -615px; width: 40px; height: 40px; } .shop_head_warrior_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -533px; + background-position: -1674px -574px; width: 40px; height: 40px; } .shop_head_warrior_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -492px; + background-position: -1674px -533px; width: 40px; height: 40px; } .shop_head_warrior_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -451px; + background-position: -1674px -492px; width: 40px; height: 40px; } .shop_head_wizard_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -369px; + background-position: -1674px -410px; width: 40px; height: 40px; } .shop_head_wizard_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -328px; + background-position: -1674px -369px; width: 40px; height: 40px; } .shop_head_wizard_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -287px; + background-position: -1674px -328px; width: 40px; height: 40px; } .shop_head_wizard_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -246px; + background-position: -1674px -287px; width: 40px; height: 40px; } .shop_head_wizard_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -205px; + background-position: -1674px -246px; width: 40px; height: 40px; } @@ -1296,7 +1296,7 @@ } .shop_headAccessory_special_bearEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -164px; + background-position: -1674px -205px; width: 40px; height: 40px; } @@ -1314,31 +1314,31 @@ } .shop_headAccessory_special_lionEars { background-image: url(spritesmith-main-5.png); - background-position: -1599px -1599px; + background-position: -1674px 0px; width: 40px; height: 40px; } .shop_headAccessory_special_pandaEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px 0px; + background-position: -1674px -41px; width: 40px; height: 40px; } .shop_headAccessory_special_pigEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -41px; + background-position: -1674px -82px; width: 40px; height: 40px; } .shop_headAccessory_special_tigerEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -82px; + background-position: -1674px -123px; width: 40px; height: 40px; } .shop_headAccessory_special_wolfEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -123px; + background-position: -1674px -164px; width: 40px; height: 40px; } @@ -1464,55 +1464,55 @@ } .shop_shield_healer_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1025px; + background-position: -1674px -1066px; width: 40px; height: 40px; } .shop_shield_healer_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1066px; + background-position: -1674px -1107px; width: 40px; height: 40px; } .shop_shield_healer_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1107px; + background-position: -1674px -1148px; width: 40px; height: 40px; } .shop_shield_healer_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1148px; + background-position: -1674px -1189px; width: 40px; height: 40px; } .shop_shield_healer_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1271px; + background-position: -1674px -1312px; width: 40px; height: 40px; } .shop_shield_rogue_0 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1312px; + background-position: -1674px -1353px; width: 40px; height: 40px; } .shop_shield_rogue_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1353px; + background-position: -1674px -1394px; width: 40px; height: 40px; } .shop_shield_rogue_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1394px; + background-position: -1674px -1435px; width: 40px; height: 40px; } .shop_shield_rogue_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1435px; + background-position: -1674px -1476px; width: 40px; height: 40px; } @@ -2056,6 +2056,12 @@ width: 64px; height: 54px; } +.ghost { + background-image: url(spritesmith-main-5.png); + background-position: -455px -1144px; + width: 90px; + height: 90px; +} .inventory_present { background-image: url(spritesmith-main-5.png); background-position: -1616px -1049px; @@ -2172,7 +2178,7 @@ } .inventory_special_opaquePotion { background-image: url(spritesmith-main-5.png); - background-position: -1674px -779px; + background-position: -1674px -820px; width: 40px; height: 40px; } @@ -2194,7 +2200,7 @@ width: 57px; height: 54px; } -.inventory_special_spookDust { +.inventory_special_spookySparkles { background-image: url(spritesmith-main-5.png); background-position: -1299px -1508px; width: 57px; @@ -2238,23 +2244,17 @@ } .seafoam_star { background-image: url(spritesmith-main-5.png); - background-position: -455px -1144px; + background-position: -364px -1144px; width: 90px; height: 90px; } .shop_armoire { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1517px; + background-position: -1674px -1558px; width: 40px; height: 40px; } .snowman { - background-image: url(spritesmith-main-5.png); - background-position: -364px -1144px; - width: 90px; - height: 90px; -} -.spookman { background-image: url(spritesmith-main-5.png); background-position: -273px -1144px; width: 90px; diff --git a/common/dist/sprites/spritesmith-main-5.png b/common/dist/sprites/spritesmith-main-5.png index cf85f9fe03b0b3943311b1fb93ece7bb2fcea371..80a66cd2abc10bfdb13863b0ecdbe413bbaf5c5a 100644 GIT binary patch literal 261236 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 BC%z4OL%+<<%; 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

gd{uBQCe{D4MxBzuzAStWK-$$SIfZ`JkL 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

3zJ_zTQ-Rci5JM07#@%c8qjqcb?IQ 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 O9}82PA4>l2dFS5{H|(7N literal 261273 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 z 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%0A`6N2Y;eFShAMKc6|`f}zFc=Qk)QTlywdXC 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

cYY8H)>e!Qwq|+yPRDx=O*G{{JZD`SSW-HyGI) zC$xJYJN7S0;f8^v69Mf(9``BqNDKQG;=vUTlEZVG_8`B{x^{hn97e@}j(3%7R0o_@ zwd>2`Crl$ZCi$)vg@kIol|WXdFy^~3!e4E(=Ble7&*Oxihq}uQhw;VUap`_~jb|bx zjg=;IToYUea%Fn&=PPi4=)@yTNMIIl6T80IJM%p|#EWB~QA!|?EK8W&XofUvN)H=2 zoxzd5Duzn+)~?csG6;C@9Z2BHlu$*IetwX}YDo-YUwTfd@e3KnpvZed zgQ{t%P=!k|=PK70lU;<{o>93fHNAq|sXJPFcfn*F+4Keg}l2}jgs4>N(%DI){$Zq#3if?-V z`jkki@ZD99Sg*9bQ$3G1qC>IeJr2ss)xL+2P`&(yU|3D<&0+fVG6k-A$$MAkMiMr%b0wRS7R9p3o}Ww1Si8vB2KZQiwh&csF9=1 zt5Ye&W5&PMjYYH#*F0CUj5svTf98ORKn&+UcBHMpE4hZ|b@3 z?X(gQk7fO^`o^j>b_1CK?F6sc3BBe_wB;~=TwYIL^-Xcel;B3f)q#3cp zM1MWICTS6!^1b9!(@7;MDPIf(&BlS=nXqkuNWi$BSq>TY5?>e1WsX)~ahE{klqL(u zF?TZFLzsM=@hlhyOUZu=p+=!8dwO7^`#>RH80V)XI}Hmtsl5goHvp?A1dj1eeRwTD%CsQdF8nN(e)F}iLBkgy9LUW z+LPecg^_73+QXS=JUjZQj~kg2tPYb!xQS>78swge&5%&64yc$#*<^j_%Xx%RyNYqG@#d+&e=(-_i>@fWFrjV^&DY+1W zA~t+ZN?pfH=b??yd9OqMLE%^3iBVcYF6Db4{4N0rGnA21r}^GQEu}{aCBF`}b#Llt zcmKZab+9mV(nsN?@4hnLO9_OICb;2nQp3`2$n3Ow@5iiLo{s1CcB`Mpkz{PRj)RBY zrx~vr!FlU-&Zok^5@&Z$=8juwrX3A8>N|@#xexYcj%SQLf0gm9dTrs4aN2aL&;+53 zcbxZ{vWFk+Fr6+p(AP?Dba@D@cVw`Ox_(}J02NC1_}F2TB5O5}`D;BBxr)j!?a}wx zlnmB*;Ltwn%TLS|mdp8hf2Db6{3xt-z7RT@1$X=TPI{VmvEt!uNKClS+)wGN1TQ@w z8Xy!yjPNJ@1Y&wBk9 zdF#aKY~<`*Y0p$Z80uivVOm?~)#vWka{U9XjhShu>4TB$F7pYe8G92n^*TVTxLO_b zHpY!M_|~9oBJrCfkV%|ZK7kQ?&t2ba_wn)3g^jE)X2$9y8h0DzV=arSn`VeTCXVwI;+;fKMiDfUf%A}d;?TCzNwt|O2H3I5bU;9 z%S+wB7Ig{OC>;$jSV1>CZO4{^+x3f^ms)GqCf@mU_A^2tcdG_F?ACI7c>;^PzJFWi z3ADEuZ0Rj1O>*dwfOO=~2cbk7R9?QLOvRc{+|&v7qS;zCrF-+=n>s$_(^glP&YHDa z-;lB#EBZuHo-BtL`V}uE8?B+(>msG@g|!lD0Vt>LcC{cx;$xR#1wD7(l7b)hw$3ZAD`FZ=~Gq|_I}vMI>FGjUym7R|i4 zJnTVJb^7zO2BmCLoul0mj)>IBx{;!YW1S&_A`F+5l=RGcfvqO^M||V4#z5&#-iF1W zZ$Wv=o@&q01iO2*xVs=cV#kr>+>>7s?X|mxZpZtpV!MqEM?HqER3mF@57v*YgBR+| zCU%dH)(c0M1NhX}(pr&VC|cMDh+<7~gXiFwwwFgqg_@1t+<^3b4WZwopsvHzEN4r{ zT99hBT^r}Wlfwo^gkvvV5D4V(tLq2H!Ye42DhX}EvUTA-@PEM=>R60c2}a%6=rcHq zj-=O4%Zb;VXNtV`zP37?{xD4X5FBJy>p5RH<&xZ^x!EQ_aPl=oimPFa-T;W2+=25UY)e)2uAcH7%+%_869=WQ3}nG z8j6|fYYxjtTys~d(}v?<*a5mJLgM;Bw-BQV#1L)3<7gIyr22Q#N~-wO+vd zVd>r^Qp<7voba?ks2*L32}kEf zX%Va4LT+gVblX2*3km0QkZbORGPn z43&`Kk1=qpASb4Tb{^}Ao8tq#RZ}>aCmH>C=47~1M*gYhP~&ScumFtcHV$;#H07`R zwrcL8S5j`KFWm{q$ZMwOseO^Lvcs`{^HaB9Ov3g2o)cq(^B|9`lD;vw8E|MfM?DmZVUy_y7!G5!094AT9}o-{u`dQQmeAvixhAniQ;&r8Nth zx-bPi=WGIhC*+H{05(IsUCLvHKpP z3JaA0EB0APmJ-1V=6`pTUl@EdR1qfKbL6kBu*4}Aw4W;@e(hnk6e333Gom`LP3V;3 z>}MgRJFG5mFimmN>SUTc9&Plx0i4Hu4~mZ|FH_*ZQmJyl0Uv0tynHjvW`BGrG{C{I zz-@nsRdFOLN`p`6;h&LUKNTzVSYs;zRPO#4;&OGcv)symr}HiVLoEe{`7KuC8`*Cm z*pie&ikn|Nc@76Z4e6AY7d0GG5o{T8xND~Aoqu(s&QVIm-S!&K#erY<~y6>8$rix0J{}{+WQVGlya@5|J3@Fmy;r!VQ1*`xNGVa!dB~}eWiUK%g zZ-Qz5GsW^`2882Id!c6J{FMThoPz={OX=bG(gw{)H&KCbb(QMqC{;xq4LaO+wt0(* zsy>+f`vm#In?e88qVFm&aF$%IKPZn3<~h{+l#_Wj3QMALwts~1U;P0#2n&0G^!aGz z^9P_z0#KF*fv7Eq?f%z8^Y)N@>i^8_9XEpCE>~JlQQa~SD|J9#Q_o;9&fq&9_{Z(* z&1?dvYGGMqxkOD%7QKhvkxB}ASxx_UaMDzcjh7p=H#gxnx2wMtyozp_C)RQ?{&X|r z?>Lbh=vmkCpXQBAnsa}XWIW)y|DHIi$I>k{|plRh5asq@+#v9LMxrI zBVWmj^59H=fk?d7B8M_x2a+w5RQ&ar2y2P-r3RlTZ{0{WH*fg ztAJ+$*`^bW%InJu%3fl=6jddkd-8A>)3}m;>5oOjN(J}=GwOUT_Qw!>W=(qgG@9M) zq`AOjf0NjAJgO>LDInbhQ7py!zIkKf$GnHi#RoZ!lgyVzwIa?;1y+8+7g)!; zctmF!_dmAu4DJ5iq%3{d5QXM3J*%dN{+g1H6y>Ae1|%S$%Mar^m0){YX#H&wQs)Ta z#gogY|F14Adjc%UFn&1VdKV)c$a(I!9sB!mtokljlR)KeZj(bDan!{C=sN$;4`COU z%J=P=v@Tvdv(H##0r|{bXh2xx6I`)D8K(D9GbiRCZQqY z@gVf%Czk-1D{oLQTK|Jwsz1~Ie!G;yURyH=`T3@ub_$f=+@~Gj;YWDFLEg2m#Qs{x!0{@wc9d17fMuFw! z)~C>@p3!K%ftWW?xeR^>-$xAB=)Jz*j?az*7xUd;kmXtU8fV}yBWV}M4U-v;Ph65P zp(4W_OFF*+x0J)WNa4+;L;qE6v`xyY!qVe{J!o^J)(-rTAAxh3ri`d8vytKVFjID# zyL3A<0zc|9Vb@2f2#d!bVRuLF!4L)XIFcN?xQ*&VOA3#^w|>th4xVea~f-Whyr$QdB^XM?^M9wu@u+8bm;f%i@dKVCT*uG3u!gsc3!TD z3_hLfiM*M0GG|+_5}ClmQ7=$fy0|k4)ze?D%^Hr1(J*`HsVhf^pqL+;Sua(=z7vCM zDtCGo5L0DiRFF3y8+96x~x{eL5aps}VYapVy=1Q@|luk@2gzb^YnYnd0) zM^P3JJIVmg9~$KhWehT0=(VG#V2EV@Zq*x%ehh2 zp8n>Wk%F6_0#P@Mrg!6373?7#z^PN8#05a??BGI%DNa1n29V~k?Cb-2%L!9ifd*yj zt71=xCd}?~x86=Mq@=t0a?FDRZ)1gcPbYU<9WxlMv96<^x-Mc!)1cfjVxe-Udi9Im zT_x4n9wh+ge86tL5Oll{80Sx4F+RX@4e;cZg9kC9We~tza-c1PIj~Q!cmp=hgc0{Z&0Ne}4E#@Ig^n1Jg5Lc1+M4eQ zVn5_;_Jvbo5=8QQ9%!8@VlsDHaYuJ>CiZ~BE{pJecIV&2Uerj%3br?#3Jd2Toi0Q` zsSTaW!_q9Sm06?m3*)pZKE~rB)MPE^FAd%u7TR*DT>2SfdsO_Rl{u___KR=Wzv6dh zA{YWVu}UOZl*bPZoJywM zYDeFg`eCqp;2a%B1*6_oKgK@ke^n4z)n#^w!kJP%U$!W8tl_p4ao|AP-?gN2(2GHv zf80mEJ-P95^1z=JJ-g}i-q>LPyITK>d3p_!sVwUkIvW?P#e6O9Z^J(PAOLkU+NRDr z1)?N-j}+Q>e;go65@=tMC0Jpsh!Js@XL-mJdZx&G_4+GP5+H79c6js?)ttRK&Jw^NBW2N78B zNyk)E86F{ohByv?R)x*O1|PCdsc$hCirHwZL7&RKy5Yq>NxRdZZsYj_?H$y0Qp$ia>N-9 zY;UMhI1t*!;@ZX0#Fy{6lvMOG&&LVk88f~GyY<^PVh`_jM7{bMtQLkWH!n|51l&a1 zD{~CIJeC@E4U)RXdAcB*9ou3OFJExevQ6lf`h1;7tnF>IQ9)XUrEs?T(-%X~@UU*V z%V2SV6Z`LiAv7E_kl!Bb=L7TthF?w#VgwpOB>-iM!?;S4Hyr{w1sDHn`rvOh!%jjS zp7Tf1!DeP$v<@`+lz;EsXRO8leY7OpJ1iV8dt9alBLy+Mt#Yt=01d+%jH-Wo1#qDe ze?@5(trMPI!S@Pc2sPcp4W}W527jw6B6Y#dpI=}7>66wG;>o!_2&w)ItW+|%g;C_? zQ<${4hU_D0Lw?RbJHrB&2fNBZBO9D%cqycZ85e{g!L@m%^drxHFE>B<3HVj%VU7BY zW{CLzw>71=(e@I{+wChut&S839FGEUtPjRx;vXW0wv=A0iDTm?OF^i~^eX@+5j+*{ zLppE)A83ym%qxG~HhnkxBFgf|UV;dXyue^Nfu1#FAG*|JUzQ|kaY36)`qR$Za_+~WGY7GeiGv+-#ljE{_FVhH zeVX**AV)SdRy@REfp2ac-v(NKk`-k^36(Rb+oE!N$%M|5(0j+M@XtODslc|$pDiv# zCGVlR6{Xc(^=;~^w@IOrBv402^p}iu@F38f<~VPM8BObjwG2)7hQJk7L!v?_79%k$ z#Um7<0qflbUP!nohPi3{sQQ4!<2RO+8rGB^=BOCYd;FsqJ0de(5M2F?@YtQI{Nev5 zZhuXKSY)Ly?Yhr{rQpX&A(#QrLbz$D;Hlc$c8bTmZ{(yqbZBJ4o=b&pj|If4&)h~F z4@_+!N_$;zB32lZ-{T5NCxJF>fB#i8=c~Sr@EV$5s@K!icdTA=vf7d*g1nz~SB$By zaJ_rQW&7yVMNuwV1EFyGYdTpf*RlIo5%+#$<}Q*@!IDVArXFSJWF>`+x?08x3=tMu z_<|8(kMaLU*qgvZy|#bAl@paHgceH;QrSvT2qm%=p&7>7GO|qS*b-UVvlI;>vXrfu zK@DSHT9jdgETb&x5Mo015Z>z^I_Esk|9Ri{d_L!Ns+r&KzOVb*?(g-zE>(1LQG=5n zt7V~|$WDG6?py`kMXF?^y6A$oQgg*gJw>g$-%KYF12OqS?4w%xiZ?UJB$O5vnzRkvX?*qz0yq$!sY`6 zMx>5rAjKzLLvgosZpPONY)5C`AIuwKhxNdV7`|B8{%FJM_~eLM%IdJwbHYZgZbdzU zBqh<&fwPHtbGNZ_LG^S4xymZvc!|UxH~m5?%rDM74UgNfY1%Pm(kc&$+r%N}fG~5` z|6*IX!oq}rVHeBref(lbE{kGsk$sf!Q|Z<3y_K9dDFv3O_88j92fg04=9%6G+(>C* zkF-=a#H}hB3`3twJ1UQDVhojV_%>vsCov3K%{YU+yzlkq))~>KGnNntG0+yM&GZdM zw~IB;M6i630w+~oL?&;rC|$ZPyESa1{?{>p-obsuZP}hR&ujvQZinwk#<<3$F)HhV z+jfWb(SF(}uX(1bxGVqHj@}M8o+s;Ka!Y1@;jALtizm(+&X2ub_h2oC21gb`cYZ+! z6H2~v(Ok<}rN1WjN8rPH5C5LXSBd@i%?rI>Kypd9#jqL-KVubl?o67CQb0Rt!b{Y; z2ufk&01RR^D(XVq0o?!<$Z83sdRg~~%QJA>5J}4}4QMlQZSzjX zyjR+#q*P7|MptFtp*t(=)px`$wHt&t_2RlchL<;deSc9PS7B#<%^OxY0) z_(a#0APQFqrOcGI@ax-nc>6C)Fl{&vO!RzTsZ` zQ$U``;=u=5feh+cYZey@0LlZCk%=9T#b{j*!go+W?S6h%P3$Q-2L+S9-nYS{yUN5? z3bUW)zMZNkCmoMpFXlCuyZ?rsoQp~aKpn9qB}Lz39~p8+nsc6slhBo-ac*j7B2mHZ zsI^XM-~fO$y1Yhb0AXdD2TeQ5uNGD8pir}P0Y18uIXCY}pEOm5#v$d5Tnp zx$$acdYq*R=51jnlFqTWJqq5hx((d2E2oZnM_M_=yVM4ygN^4d`ZiLkTo9ywAU32| z64dR|R3Dck+3pSpFJ5{EKNKYcB*6A^1-JjfD8S1h>7cJUX<`^kyI`-YNw?BjM0c&7 z!Rd#ejX_Q`Nqpuhnl9M)HuyR@y#zyz60*b|jU26kL%k-Wz}`mN3@T+#yh)! z%B;iC-0o&9Ciyfyf=*Hj)MLViN^MrB_oOrm{blmhtWM5OH_4IF9|a|mD?QkFPkwwY zi6LsSotBn7rzh9)tP!%1u`_R5Sh6`ErZ2u!L5?Vd-q6C2e{M&Hls?XJyEg7Z0{L_h z<$xV?D@KODuP_*2>f@kl`_3~ct3Cep z6j`F!lpS^=hE-5iAmU1q8_uG2}>$Ozmm77d4Y?-tgl|$CcG`^|bd0t~@CzO&yRG z*Ch8}^6%NADa&tiNGZ~sbYK|nz7mp}>>Ytqh;o-?8#Y8rb3!v8PCKfpe}DALi3sq* z*NLkj4_4au#ItK8`*Yy6l}pz)Ew(UGXkaVHE6?}7fs~@YQ=qQb%O;;!+L%frS8_{R zB#=_$-fVWXN77=b^w9VZ zYa2ueq#@c->=!gDGqHlWBLXPqCV2$EmV`*`85{*Q@ohMV6MN+&1aQKl#aE06YT~hd z?(U<`b;1FqDMo%t--FwI+BBqI*tGgT=ff|$2DTnR=sCbXe{u|c^H*!-V0X1AN)Ek@ zJz0x7!?W}$WNP$=DA=Jr++0BIy4T~gOpr1iaO$3Tde+*`s>ejeXr~Y95jSLbZpb|Q zl%QsBnc3$-K^n&?wO<$)ifpFppdq-E`AQ}$>=QY~<@FJ(+6z|I9t^V6robwml;QFg zTb-_-gZt%z&Nzp4cn#}YZ%3FiC`&@(w^ z^I|GX3h^Y;_m)HHP9$BBb@}}*wdk6htxkrs%_PghOAs2*wj%r=!vOsk`VC`m{$SL3 z5^#qPQVN&P>-(x#lZAa*O$imA%)i)yT=Q3UOQd!>)}aklNr_u@3)}&Z_&XZ@)&_K- zkUUSkWaaqCMW|()XD<&FU|)W5L{A7pMEj8unQYm=DG7^Kh7$GZC+uLx77Ka?`+u%4 zUVfJ>=)F~C)FU++>FPL-Uc<{@Z7)9hQ6H7l7DXLjhKskCFY4l!+P`3BqIe-4s+BTj zVsB5zjSbA^U;GqNaLjjjA8%8B>0DA1Micz*nKnnw79|E;gKZOO#2AlSqrMT33)u+_ zE=4xqJCAC1XGlV%nhP&{V3(@_XKIj1}h|a_BLfm6s+a1 z$zJs#s#tw2aojTb$2KD)vR;IKB6#vl=ATe}Q#zbClX^T@`+3PA!iKDxK z`D_i@Z|akSe%Iv+YQ^F=|NWeP^Qv{A(nVRwLRu0-{oqo5B(r(3H2fZ0iPN?1U)6lr z>h&QlwH~bKo=B^AhiXQ-n%zeSR$HAnrB)6VPf5y0L|8T|p6I+p(z#p{3hpV3Xw6vb z-*Lb9EyY&a_xoN}(Ri^&{W-|1mo%STiXqMp<}0Y4T-lU?B?t3f8G2E9^yEt1#8kgu zvo3#HT}*7qgSe956}$E;;C^8?>)=4r)#QR2ihqz46~J(*CC#O;>(g=RFM3B?OGw}CH~X_cOD-Y?)h(rvk#{3y?rQBDk@5mlB31@S%I%kq8AqQ`fR5QvUHp+x1>Q=mRXl#;Y0=8HtX|P zTjw&yFuCHreU?WbIj#F`__$deoTV35FEw$h!y!?=f1)GrK$4-4`czr&XPixn_UY!6 zrjT|O7Cv_a16M{c>!Qb6-1k~nfXEk7nWLJJ%Er;F_s|_ss0ih0km)P1QCxPWrceyJ+22 z>ZfVWE&?^ZqTL;X!pv`TVYUZT0Puzk?L5WUx~Pb+@HX?sVJHu5)>{v%(2%V)>!eu_ zwao}>7>m+#?l5%j7>yxs;p=4cST$-S3%l8a;YnMJT1?XFwZE*p?n$LnlOTKe$j1o~zVKQjp`K$n0G zxlVIjQm1}>R#~3QH&Ut!Wgthx8=9!juvp?kxy9qf7m+XS%VBYyTT%r`cb6O zmGJ-jOA`=SGp@EG!BRer9>1qIOq8vFBjbkS_y-$JB*r+Y+ZjIsX9$V$Qf)U@iw(vH z-oxseLh6~b&+d1P<2UAsBtbK~U+CY^q6N;%S$z(}s`SbpWVBjk$wS3Pl@)YMi_mkE zId2tLBFPX!+Po4~cQ^X5C?*>H4KICtCDb zCFgr8g80&#SS%qm0InxW*%%oqb=BRK$Lvzp!x>=M3TdPy(v_JO1XNX>Jy_kNJfpVKe81n=s6f37GR{H*s#J1S%Y#t?!s zn^1x{l2Lmw>@v966MFF2>G*uoGu4^HUZ=2C=pjp29}fTw5e)-)7|jvJ{-Y5($kp6! zeg*_nK6|rYQ^Oj=SG#g~E5$5ICQ>AV^W%NBrxrS6_$=Z(rB#RfsXym;R3v?&+6ln> zLLP7hrMs53=>M_wP&(Wj+I@o9$45($2fW=V_3j!B`Hk9sp9fz)Yzd;&x`~EY4Ezef z-cMb~x2wyd7yUJ0wV5PnK!WAXlCr>_DwIH3nf1R7QAWle3 zx?sL@-KunL-~#h9KC*T=R|W2tUE)$FT0_y5?&K3yCDafx)FA2xVh zik)&b`G@1{@rz!tX>^j`AR4lH0VEaih>hbT{^&H39)8O9oDd-qn8d_lbNy+(SCXj- zQ&+zPd)duh(D87Q&EZ|kYQK_Pw`E*parFm+t}fCk_+s2iN};fHbgyJpg-|4;ZLidP zw)v|`5I^$bYIAW;f^cvBfTu9-vRw6x%a97$|H4_P9I8x{075GKgM!^z?RU=LBW#L| zJAZ~!ID8oC12*Q(0p>#=4ZP0A7abifq=1vwDn% zdR2KXgDrfZ3vK)vwqDk zkL@HiTb+sD%mg0FB(Y`l|M+~($;_#MnzO;SA|(s+>Q!>^N#e|BlHl7WH*LITIvncE z`v3VwFg4tzP)e$H<1-yxiOezaq;)Yg;=SC9=EJyS*~NOly%0t_YWREWo0mJl1C!rk z7^3)@^}v`~vvkrRU|)w8G0vlVCTLj_k7L)6O$;;hT2!T>E@~bzBkLEak;2om8GD*G ztT8LA?_1JGt%wqB(0ReO-@sL_U24Uy8#s~?v@s)!Z&snc+ez(G{&obgYS5_P?gi7Z z`<=u)Y-}T025x7Kt+2 zgfn|WwXzV(#lXk}NdmOM9F3U#%O$Tc*U-qZ=MS8ZIAVe=eR^}{0YvZAj?(20H6rk^ za>lm}3gPpu{f_9Hr32vWm0g-6=?|{??{D<(>K4g9;L-R#q=FJ!^>zN8YfJ0aagPO_ z8>`4BaGTEXG?KqJ10_BT5vPqSBD?lUmzT6cuql!;Q?Y8hPvA#Gud(T%7{@8{R1xf19W*Ti8eRc=B3}Yi)0HfAvfV&*v3WU zfp1P9-*VGfG|f*<8c)1Ye}a`Ha({UDnshYhUJWd-_kKf8GLKUhoL(xu8e zf5fb+x2Q(xxutPb?I~Y}FD0sWIA1>%H%g0$b-xmZxNmei8= zHj9+J2s*2oh6Fi>_(|5K5K5x8=UIuagXVn~+z(ZCs?X_2jZCDm#A$ScSug$*H?qR$ zZvXi{MS#nsX#@0B$O9WE)+su)=ltc@`LL-2|0dy$ z^9EMj)SRkxwyj5VJgPBN$t4196;w+!V)B(mtHX;MHp++(hdFR*83+y(4x#FBPkUK* zaXq*?3|S6C9!g$0`*!gh4iQxT*9WzkfjZ2hDccYK6tjim;Ml+yFkbw9A~bElAzHjc z0<t&AB;4Dt!&8XH0?K zwTP>euXgJ&-|nAxui{^5uBliVeU=N94p}Zcy9au9=Q4KY4$0Z-R;2$TYFFKb{t?Ab zc%?Q@;_sCI-k6#`znN!DRmg?|s~yGH<3mN4g{q-Aw2kWKUaO55m7bCoz88%wc!-}L zIq~C1CInkP+eiYEte*)N&f$A3N)AufH9vT9N;>Mr76xr)XS|PoBkORr~r7IWmeQ>0ZYjRHdacDDB(A}%UZGs1p zVQqeV>xA71GUKJz@8N(hY@!X>X8{clt=&#`d)`)g7|2@pcLwk z8zmMkVTxyoT%fxBzhs3n^|Fh|XPQFt->Nois!M3b8w0iFpL6KuMFy{pW27>t#Jx^c z21*LAckxSMv(kV`kK}uT^+^D)(*HmJ z=lQkybx8L2)ubNGO4aRyH-dsG0=08``bN$+IYX1-abGedXi0EIfU{pzj=Oy5Oh`Sm zoKROlTM-q=T?gb1-lhGq&!uPc$EhU}EH~O8_|1PET(|J9|Jmd8x#W0h+u64?ujrDl z-YY)*(VK2PcjNg!3;jJQ^xy|>JA)~4O`G*>OMh11;zuErtr;8eD(Qlo022j$gF`>0 zU9pjmt+!=Fi7~n3^tAn#%2WAhufN!yn38{D>C5j$?;39Uz zadht;-KBeBi1HKfx`mUpr_skosGPfUI{U)HhcJWr>9)=x1kZYHaKDcf;z*{0F`@KZ zhMFN<_ufGj&5g7IIhBGo=`~5JWA@E4y5w{~vjC7>GLJn+wb`EAuk$Lv8z9`&v)3n4 zEUM)F61Q^EfRIvvKhyPLn3qSLu*+4oF>7F#WYA$sfkc~4kBw3Vi! zEkD~y1&XF>5LL$DIZy}xhwA;yv=&vb7LVk!xB+$On`tFW+PThkh_WMXfKCQt zGTd@Qod)n3?)4gtz#K#9dT7&!ChO(m#wogY(D10MQX|4c9+F~o$=5vscY(BWbS$P-ozQ5 z!Y**|3-6RW`{)*cn-TW8AHMc9Lj#>bAZ3#Awg-;L-9bkmSfgs$3$x*>=@K++kL6$` zWj}+#!T$X~V*+B!v1`^*A@VPyMVFxuY?N@bNkgQn+pKA+@OOLHX?%Np?;9{aR>(~G z3-4qXrorw%tb~34?2=(~htJj~%zigNwgSV=2sdXj{m7DKpN;=?O0Hs%)yl_~Y2#*l zx*lV|KB}eGWWnP6#+h4$`AwYP21&a|oM6VpHM~q#6rBXZTv3yy~-?@Fdiy z@;9P}d>1?Tg{z1Tr?amX(5P^u9-$*Fl??y|n2t!@t5CsgebeT?Q067Uq|da@Iwy2b{UWZO20PAn<(BJpxKT($6<&e z(ifPi{XPw|2Z4&dh|Os;@9W^489-~vlL7|#Ju?DU2YaEAm|dD@Sl*+feRBtL^qog| z7F%cbK*joO++%K75Y5t7-TCcKDbL>8hCMW0+m0)p9P9Ce(dNng_~U)GK+MqaVcnB* zos&uY6{6CsMUZkuRmUMMLNfz_&-8ukz7OUx>CATSM7j@)wWIyPZIp;*0TUh<-1! zYoA)u4!`;I@`ADOV`tPF9W*X>dU<(|T#NQ{ija-H?XvGEVX+m52SZfB5Wl=YwYuMX zazrSRRO|NKK*}c}yow3d9O~N`#f#8k>GdWTzX8RurDmY!qVkspXZjDn9;kHiDjc@r z5}Z*UN}VC}Ipg`LFG-tOSL18Ph85YiDc=6N9Hl{V98_+u8_WU#kdhKh&F_*^y~ z^KKQu35jl>n3hiTb>q_)jup1^b&b9u?A6b!Pr%|gzKMrSgYH|Er0inl+L>uHG(96` z)4B^**>kZaNHhOz^uVRBc~|1vVl-GIYNCxj&#_N0sW^w_Uvg?ZyRNO|-n+o1Rqw%& zJX_j|F|2tWj11~$GLy)5W3V2k9kF#vyu=`z1X0=@fF#xvIJ#@QGz};#t1=~@yM>9w z1R838>`|T^Nk9ie!tX`^ATVQy z+df}|*BYMo9Mc?{+RD7}cx>3%u1I$zhA}DI88BF=FvKsuZsSVa8Igviv>z3eX%)8) z8J%*WHREhD$^pn+ohi4jooa|Zk(wCsBR1uwC5>_JZN`sPuR6qD;6MB|v?cU=xWE6Y z>GvVL&~()QS}}-RX;YRk4vi{x+ya*?7HKd!JE=qdom$c2>Q~fVGHm!QR6u{Ac*7yA|a2N4YqE?=m$o@+q3rMo&Sa}}_-VwC}#Iw5l@KrcFN*chRt-|uS+ zLAJ(Dahi`2QuE%grsQ?Rz%!%h15l^Gr8WdGJzi{&#l@@sgQ7ao0kf}s(hfUDdQ}k` zwl5#OrQrL7A(_9^Z&H^jWMex@TKLuwmA9RGTY6nWz>i6r4AZpZZBYWc|CXtbsx~f` zOGck8*1f_;bp`ni*TT;aZ*>C39Z_S|m7d7Z^!U+?89Pm8{0;rgHmU5R%^G!uU-QCG z>t{;!mhV#O8xnsY>wl>VXw98IW8OC5FAcO<-xAd>NW7EEO~V3D5|{c-^%UbCT|M|u z5%1P{nP6Ur#FFFDtlG|GMw?yU@#yhc*U&x>y`%wQ|G$KbBu__HFb@n@#7dExdv()5 z8aS)gy{jq8t1|~)wBbO+Ja&Y^ALCwL`dVZ#InbuHT2Q(IXPh-c-Vv7JepZSUIaH7# zK~doV!KW+_O5-5m#~WTWu*DeB<+RPt4hmC4;4-6g|;+C$HF+nraE!|!85z(to@q4Jo^KOwE!XLT_@jux!L0Udn&zXf@xKp z+aJ0Oe3Y95^E*(biGPx?rS23tcW)pX2l%}eF3R=ZGCcn}&t`k!y)yFVn?OOPStdWB zx9;AL!-Dg~Mw$YZr>~Zx45YJr*z@bnJY7kOM}XtMPeY|)yGfGYZHesdi}}%*oel(@ zX@%_aq?`2EyY!4TnQ<=-20N5N!pHOBx8(eii9wqgb?4o3Q!T;)DTa@F-#I7KV<93A z8|979O!p<0^F^$;gsj9b<Ve65nXZYeGz`RtgmacPAK^x&`!=P}fVc#Gyj zh!rUOFWM1v!?CDMsJzot!6aFUcHE-39sq)s-U?#&t+PePv`$I6Mv4hO)NbYo<8RMn zWW2Sm*BQ$rG4aA=5>ZPTS)J+cwS^xa z(e}>YE0kuh#yWrH(JkDWa4m+H1!O_MZfD_p%gWWKC@-}X*ojiZ9zH2HHgL`W+>B+j zsAu2{%c}Pm64sJx+h=zkg8atf^7FW60;q6zO{Zi|ak*!?bgqjLSFZ@<(^nSTXOX{P=BiQ$gX<*CiAr*eQ12LYG{&PxYp^CS}Jp_fPA*tUWO z{`_#=O6<9PbjI_Hd4K+}&Bs&-3xYa#^2^UZlJ7UU4l>hS7itO7x{L1`c2BdJ<4TfJ zZcR8c4dfJtYBrbFXUGrNQYHf__)oP3nJybFn_c2=){lxZ7qhlldWHfusiLSHla&NY z^%reNRzsc18PA^w;HGVV!rSv#W0uUO)RKlhN)ZyMO*q_@&NrCs7i$_*myyzMZ6UZV z54zyDD*$aDb#A$*-m`@CB}a}r6O`Ewjg8>riLUEDE+Z+ed7>2TGAvEaNE*nj6m75% z_oS@Jgwpsxoy&PvkruJtOMnkQCY9S-b`Q?E`6%x%1*;9mcdv4JDem0C(li*d4yhP< zs5jGbo9IqU;3Wz6@1y}EOOm3hNS2^MHa}OQ{5fbM{oZgzk@t=I0k=;+FCqcCjYm1P zY?AgY-0z9iDA|g?{EJ*XE?^nt4FZevab7yW`Z007tS8F`vbvi%&o2PzUL005 zdJ-A2VpwfD#-?>^ZbsO~W5;spW1DQWwS}HQU<5q}f!fPLFFXqx6@C{A(Q=03EkZnc z-wfObrPB*rTcY_(L|oyaHg=*TbJNXa3mnNEn?KoW;MGp%F7Se93}{H*BKVqmj^)U^m*`E3(s#-1xF|7 zhi5&xwQ&UkGA8i?uJiEW?wD|T|{2z*f|(9Iy- zD{2U>>~7r}JT)8NsWdGWd}LIwbl^d%q<#c@IPL z_lrNeot@S3N#}zXLKpU28q%x@{Tdb4(q;1Jr)(==RvTpY9JKsV_G5S;d1Z1CyRO zPbb6UuoYs$P2>bfG7qIu85yXs*uzoto&M?)7@32n&v(7>>4<3-#3lOth{t1JAm0pm z2gnm)dB>s64Gx^|y8fxu7IIiTGCEQUCX2crubwX#M9z2_d$ZoTXz-%?(0%!vxV?K$ zb||#@IKcZH3)y)@psbsYXp>Jr6q@Valt_?U+N~y8pP*aX_N^}WXW>7Xev!J|7r@gp z#MWErTVq$^W@11cqU30Cf3zwb2d#^Vo=mSHVHLAcYiIa#11*`kxSLG7A=g8~7&5*g znbS%xSNUKt*rk-L9$UMHNPADo_{<@^0`JCiwXYeQ~OaZ za)%aivktDtxFt)jDkz-|o^L~nm_{FXdyPxvM0mtWl`i;MgLmn%awJc?I0+fZOgp@> zf>!+|H+m#iMz6=4`+nKeBWl$@mZd)R_TQEgNI9}y!vXTwRNd9MTg;T~O@Wk6!uU;3 zuo}*Po_@)P~zffC>lf=sk+fp(yL?xNi8x7Uvnw*-E&?9XaMof z{qkMI^2dC&#L8jvD#}1IXR+5gKj(5&+YS-`&@lp5Tn=SJzt2tSER=F`#U!6AH(fC8 z`|Ko$H(r+$3w0Z|)aUuQMyJ3HH?(jGcR{LTzw^74}Fj zgNVPo8!1P|3Sxs6u~i3W0JoP7_#WjUD*i>n`{LZ`@bjnpQK-Qv=1?;Sy}*s;xi4D9 zCgDXu!Vs$Z8NtP$=TDKaF%rZ{Ya*dYfGS(L>w5JGQq?V1qmHkfgz<_8Rhqg@ zuA{Pk%{_W-+F;y-OPmlXGQx;d;42!N)#!!oUhz-&UB4JhiAq= zUT`cvt0sf)d?ip;W%_GG2rsvp((ndEDpDjPSiXA&^i0c85q^d;AXHyPDNtI3{N=jB zSuUHCpp-Y@jsR8dK#G1aB|pLAa@E$PoxtFZpexGncG8#&v_Bp zt`}0m{`28E|bU*0@P1YOX#@cTZ>xT3h>i9Oe)*S8>}MFc!Pb`2b~ zkx>9SAj_Q~Clw3L`CT@rXYTj&&X3wHQ6*~lXIzyD#M=V`-9?)}>dyoTa4Q!SZ zDjT16Ci;EAt&`*TjB+X}Q3HWsfW9B@gJFdth~^LEieX~e|J}5oHjZDPt>w@v^SW81 z9;C^pA0Sa8Wn{Atz5w+=8`lPY<=P*hJn;5pza^>(eP0NrF2UWX>bZst`#kHnrz@>_ zrKCWstx7csfXOWy{`L{aXTl@QNg<&frFu15P=-42mloU8um$s5AK9)FG9&RDhOaZK zYwUn_lj4Vp!)(DLRbyAvQrB&x_FDWVx&#IuV?4Gta^@kOQxYn6T9^ zdd}zmT)sq9#+d!vmS~AepZBjKZ6&C!&?EQDTqWHey2QBuSy0x{2`|q(Xu61Or2w4h zr)+5-U7V4s+|*F|4hlLA&s!lI;t%FtQ@*Nv0@hx1q@!6B)xdxg1>Z;=rrPEAhEjIC z@(H4x9vX(4Jk(x{Y+;p-97~@b$U-A)xI>@n;114j)9Lyl?SgZg63&wg;+XIZgnIMc zMD&=ju_4OonrNi~?-$=&ID#mc5h6LC1wFSg$A~!hq%;8fU1#sQ8!$_M4TPr!7^6WF zP>%hd?^;q{9V(J#^dezQf*Q`sG*-D+sedk(0iRsP&hR11+fEwr-#fdSsncyCbN!3QG@#o*2&G4?@}jAlq@mCJDEL_i%){ zNzM6lRmJ6Vx>6>GpwMK0Qq&tUUltz_y^ZQqP1dyfrNU=dCzm4vQNc3#RBV=vb;`HP z+iOFzigNVULMt>BvuW45m$e@1xbw!TyiA|;Fn^|Z{?cr*|4~>Lz!KZ3ND-l-)Oe}z zhg2J+z6@yu+TJ3qo-Opnvb69{RWXcV-|zvXT4+Dzpj>}Tz41>S+|UG~>V`rGo)MvW zS{9TWxUPXs)j<>%ho=tHe|FIP7zdPK4}x#Kru_0`^W2XcLctXWVRp>^cjX=PS&1gd z-jJBiYKKZ=xN8MKF36d|GPpr2P_yh~_z>77z|xByY|t!(it1?pfwJ-C>^&nf&m-$a z%(lgywo1?jb~}3`)g>#v+xGuD!3{kH#BeocJ$`>@fp?Xxe&)~oigFUYq3+PXwktrg zPs~C6cPM}Ux9Aq-n+fCQP`H3jN%WCIy$>VM|MAil9Lo@ByL|v?5YlV*MxRcu%HsI^ z!b{@lHp;loB$0MhhoBRD`@yh{DZ$U*Mj{DKI=rWidjkW9>PN=;PF(RX*a3A%PVa2x zKSjX7%)-1}xOr6yj3GyF%A3=M~JXpuu{CR!HqT*&C=?ji?)%nJ=@ z_}&7B4>&6+sT*3d7q#baNwUGEcYz|tFga&riAx#~Fw zPy#oz3A`D5S_8697+D$YZmV-T^2$Z)b8%z#pRxyAobQ=5s|F`qm0j|b=(4t8&+u!U z`1)0lbgK|}9)_Yi=ZcJ_@)awJ!*o;-9Fj(HFC~IGZ}mnyRiaEL|cfxT;k6a zth3Q}wjtMUMv0ojdQ^DPxBrpzW>Dtz^6ns1Yeb_bP>E+HL>jiDu$?vPWQpo_8u*G8 zJz6Tpwx5opKxkRlGMO4m&&Wy8YtzP6TP2Pv*q8!~q|Q;^^tNPdo_E6&^-Z)LjnisK zyrn}X^Isivnqa7R!#2-Twga}h7H{o$F&2!vdbjK!u6mxg7tKj9JnxV^O;uI#fBGo+rk}}w3z;1T zc(zjk(GHb|`)T5Tujo@JINZNi1WF(eJ$9(?015cd6--XF>u(NCqVOS5FYYn#gE}Af z1$t~|`moLJ-9d-}3Hl*W&mittf&8??22&Y>j2+oo#batxt}*&mOFIx_B}8btie>0o z`)?S{2yMzZf5aH53`L%uP7}v?XOZxA&MK(D2er*!>@E&$AZ%Li`mm?iT!}fdfS)id3#MGUb$RRtrH}g05W#kAW-yJ*bya3=?Yj7%bMnJ8J;Rq& z#zvqItSwDnZ-2Mzj6B=gA?oPWd{Nj+Pzmma`@XBkb3^0u$>W1AVXOK zxTjlqKKxvYk>?LeV*TbG2~PLi!$q6I+!IbfvB8~sz||E(fSouqybyX+7ssj=|3Cf@EuIu9}4(=qwmLZQ~Z!ul8kFysf9u?#j97cDL^P zM*w5ETd4w>3_GMs8?pBnx8X5QlPI)6gS`(eyhZ*K~-Mmw!eM=PkXwVY%Uyh+T}>2r*g+R;lq=DvNluQ zUPbA86)j%Hh{|pFr@i*4USkc~w_EJjJ@g?7?p*nyFu>EP>Od-UN8T&QT;FzHoPO<6 zd~nkEQhzuUi^W^ay{uXWbNr%*#q%yk>l=Wg)mEXB5!@kyB0$aHqP!9k#`}Ka|K0{? z9C9O|l2GY{p03}A$t@pd&;u+UBE(JP!-uV|8To-mxh)z4SM3Yt*HjG zFv{c)Y7CsOD~Wq(JUekb*!G}z#I5i&)?FA4)VcD*_equGQdrT`krCz#vz^2803#<~ z+~lCSH&{bg^K8Iyr9#P|zD}3%#RH0p4<;%--_=-Q8J1 ziSU4&?bNLDo<~x{Gmnfnlx}1?^e_V!x`z2dHSG`9s^B5#hm%!t<;U;cO3h%b#~ZDI zzd}iPLksG&nOiKlV94qiB8>CnVT&aHrJ`709A&YP7txv@aGge*8RmL_n=~Vw`#>yFH0Bs5m--o-y&C4gkbeX6BdPp8H zbBK4{axRl;vK}S@&enZ;Az4=THa64n(&e`j2Dtf=OF#uL@RiIO!|Hyw=C`vWY}-S` z$gD|yR6aEqKHO=CrsbdugYbg6qO241wq&|H+?m<}$9;yTg8bc_!W%MyY9rf609Sdo z-ZI{aEfzg18VD0dzBT5ip9A5ada|Z>}m9zu=b2q^!xx z6X%NbepX%4QisCcM}J+7K}rH5^Rj%I6xv~<^>3GV=!vWatbV%6Ch?no{8VpL6_4_M z(aV>39d#4k*%9zCv+bm9{QRdj7yvSN*Z-WGb>7r$aK-be#O+WGdmfzB|Bx*9#jIth zA!2xel>p*|#<^O!>*QP#oqlj60S9bj_1UW~hHlqo3^ITE48bU0TzjoIAMl08N1qN` zjLrxIyhu@8z#89{szz$Y`K!6ehJxnJ)yLEwoGUh%>2T{cG-rJh{O|dMNSpibgCjVF zVJyp8ehzfCq3)Z*Zvj&Zh45JXgXE#^h3(sNTP48{Zn}7a1+zyN>wM_;fy{%g-7q-y zzU9|3f9q%Pu z#j~+5Dm`buJx_(J#w$@k2PX+b1N^=ks(ZKO&zBN#&`5k#vvK?9u^;<41YJ>>8w-z1 z3C^zU>GnxWA2RlCIIM$_ew^eOoUNOLy7v&D_}sLrqCpCG=W{E4v*m*DMCl5nD<9BE z10^E#15vwJDtjW6Yp*!q2|R|1_skq=_#zd}Eg+UjZgM8`{O3U1>%@~`BDr??SIW^` z#hO9STcY9ghHZTK)~g_+^H=fZ#-MI9n|}M&MV)S$f9OSuoG%3~A1hQ2d@LM`(bDLT zO{bLquWWBJn_QB|UW{|^ zb8p-}p0zdQ?`eJ&;A;cY%}-Em+s$w};ZTn}h(Zt}@kQ4*DZ9%97o@yI%B~>!qcIn! za@?g1oTLKSs@cIGrQ+L%#oFs<2x$HoqJ8d<6a&Ny~;Ntoyo?~3)jgWPYByD`}OpLOq`^d*?zP7a5QP(-J3rv_5Z5DnQ!!ym& zzv$sRXi4;7_LS0I4X<}PxRebBWbvFHtuuGx#qr}^4pZ%BZbNo9mf=?Zq+8m*D2=wc zEUhz9I?T?4y(2nZ?#8OQl4bsLa-FhO`kC!OdagHr%T z_x8~Y)rF`zpFC}*SAdUk)uW5Et}vJbW&pL0Tn{mvEa|-nbtsN ztp^SN*Et}`MiEccd=$ke3$bfG?<432_=)!6OZtnz>XLPA(vB)(IgIc9`y8Gq*|&Xb zKVNb)p}Ef9c4kP40+GkWmWM#aQ2vksWoRci(4 zq2&HLY9OsVJwtR($AtFJjIa}_Pry&ypfi9Gq@J0dTSMhaq&H9$ih~j z6Lh;~9d3rEZ<+VzsE_i&*ObQK4slZ$?*J1ttc-tAa(QmlMk{4lX1Cf$BneNlKEvQ_ z6;kBc`*i?^-ke$ZSSqeAWYg;!iN291&nmgo zAMaE@6E4bsSN4w)+_~+sk$RG!C)QW`?8@I+)_&O8J17LRtBYskS&*qWGpA){SY>j! z+~Vv)->>XKW^DA-RL|6{D~i3=?g1?puA#get!kCvXU7mPlVgy-F#%hLc;yxMfCi=)5+(jIQ|V{B`{M(_0PWms79G zZslfwCXFL=`*n&_-G_;fAMKcP@H3nJXcZ8%?J1+OllS`0p*MIQjd$8l53c{ASsoCEMVtzP z`ph0=q@0Vv|o*pGN#Ig%;c5jBZPdJHFp8Mx1@5SXuLmtaDH1-T%K(WMj*%!{GJA`K?5y z`hCRvf{AN5kjNOf8dG>@d40JKMXf$3P$TPANZvc6_WKBQ4n^(Ralc1{yk{#z0DlPA za+rCb-t8}s!}cKlQ}0fY@2;C>ywZ+dZorzF?K%9HV&%?md0#I3?U9UQ8|&OVooexO zCR06Gwa@m3aN(tYjV#1|TSakmDOyW)OL$wRz7j9+>SVh{h~Mlgs@n;f{5)*TB%kea zJVAq(zWIhY*?IFb`;ziYeTN#e*0rh>X4M5(%5qfq#(Vx@X3u=>!`v3VcFmN_AceY> zH7P#lh}GR4$$_^I6@VWui5XTq1=9Xs8X^mtB)gS-#F%g|o1|oXU$AaDC=RU2`hd-e zPhMZtmcic()KG>PHt`=i*@uR{p(tK?C~Cx?CtJw+5BjJ!($m5}W1CJRQCtY!?Y#N% zhA@FawfZUikefqcoftuVPCfBh==}ll`94#(cCog6K|+7GEUVYy_#vTuE<(>^W@i3p z)ent_<)o+AEVBd-F=Y?qZkqEsFUL0@!qwUz!tq}33FTOn{67x@@fnIc;5pc5x`L{@ zf*R~Aypr1GwH8@<6!+6d%O^#D@_zK%ie8H!IuxZ*OXZ}}ed9U>?)RuJXZr$ny%7D! zL{QGz^irZt*rp8^X8&-HDQD#3!>-*4s!v~AQcdHc3U#lFW{Uepe=Q0X4^!cGRCZzV z9qPG?^*AqZ&7$-4<(qnZ@;%F%3^k_uzh)2#{|{qt9th?B{ejz+t=vLH+!QxLsUfo7 zZdbPwr5Q%HY7i4blYL9OFy-0{)fLSQqtV#6bY<)W~z5*Y25G0bB(k zlT=QAw~mzgJT;Tq-RXS>CPog1>M(()G5C*Q>OGwW|fxO8b-G8qY=EU%u_N@4S- zw79Y7i97Ld!+)$|a{}RBk>8Tvy2MWh4OPBN0K!>r%^$9&#e^c?n}?G-qqrmK@Ll*NV6JLR$a zsqBa{rCf1LM+yc@L^l=Qn=9O)Temx8CkCjcQa1}l;KI`=_U#e+0s-k1pHXaV{OvH8 zOABt(={=-MM25ewT>ZjXY>gD0Z36_>Vti-J_bb>sd}MbliW__duw!Lwz#1u#Xp&(@ zU97*BR(a}NdvALc`uR!Mj;~lkMjgt9nLkPKFeTSoTDa*AJ*`4Nh$*NxuGaoFLpG={ zsml7YB@b@LE}%w(w8et=E{i4?PAivzW_QuI_a=WNJZ%fVQEuD($DYqY&jaW6==;3p z+qBs3D>buPiqUc>X)LT%BQAEC2yCzR(^=3ZpKB2SRT&~wj5N_M6oh|MR$IGvZdtw5C$Ea}WP5a&HB|7)r zbwPCtQQcGO^+TKWYoypq%PhDCevIXFP2_?ifFTB^-dt*}}U!(d5ev+Tox2GM<+bGjqz;c5tBu5H zZZoae_qnx~2+^%X!WBz*zs(O2n+wcg?GL5!Jjo1f9j)rf@4H&%rt5XULo&UIPH_~b zJ}muuf9ZZ3PkPgpKx;Vl8*P??kr*UgF_+c5wA2IiP_^B>^p!hE*Me;CB3+KiB9Cf(iss@5cZB9o$eE`Nm=QEp zZYnG@VwaM3dAe1ibWydBs3{_Oo7h}lRnD4yS3qq+(^q6XcqbSf3MQD+Bm~@J+kFst z8<6{dQVX@mJNi_=Qs&i3Qt49e&A|HWdiU?0vc-#=l8xc#=CN&)l-4zLpdak?gTq4J z+{m4K+*qo1cUh}oBqtfId#U!Osj`=d3orJRiG03NoKm*i24+oyW2w!?B4>d{XM8bu z-5NW^Y~N5P`T4)h@|=mk-(z`f4(hKOMYGsiStBz1pmQe)>*j zE;m*{h^^e;0$NV&OMkYH`KJ8@F*fPIQ2r=I;zave$67^1jwBg_BGA7?-5RGohqgTu zfyXV(+G{FSYKZ#RNjcACkjXSYdN5@sgu-PPz8QG&cz;^AZ1Q}bV1#P7p|Qz3H3!@$*HMpJU;t9P^DlB+J-K!{j2K+&!F+1S3oXU1f z4YIg^-Z8*{Im&b6^<%&{%+Qv<* zVu<)`ojb@qUN)~19ecF}q&9vfn5&4+bwCrLwNb~*Wz?LH4GT9+?JA@hFIMWR^9 z2_EAH-@ZLj(nOJ!{}-SiCVeJ>f3l-p72`niC!(76D88!-qio za;jV+=lbl-!929Jm2I|Vd)B0Nj9Yk%UIWZ|TRvRH)M(sjwuc;RP{n8)Jm71GBy47$ ziDkBPV2|fd(y9$INUlmnB(7)9oE8Q#T--m{u;mPlO|%t1b%uamHfKoK_}$nc7e3wW zy&N;i;U6rhaV4uqqzsg2yB-d#Ni2<%e%`3uF`01_BAJrWIMF}Rx+bLrxL13qr2)nd zBF1w~Tv@?8c^&xWltUf|wfP&2x!CG&o4G2hw_D3q?XAL=J^b{p8Nzy8h5*&0jHFbg z#7!^Ypg9cX1XH5cXYrkSgr9JSc!5f1p_U6{p zh7}kIs77b`BCdZ)B3pjXAVH=jo>={foAUKN&>=n!6C? zwDV)LIc&8c!?ArEyaUJ>%{CcrZbX!*&B0BRuR8R!v~q5?);zmeS!qIsd`R^5+}_A4-Y4YQX;LFW_qL> zZ?(!ZZq2=@A5w zY3i~pngcJ?*;EBwvz#T2)9N$spdC~wr`GxW{ab%a>Vg4n zBnjf^oHlh<+S~p|w3ZcD?g;anz9WAK2>s86hz#x_rrH9f25+-08>-!`t?gIisGWTSsCC{=k2z0YIi{w6&f4S3NqZ*aRzzjIVxzM!6H@#u=ActuCHdVw z23Q>jG+ayR-uBA8Gyv}*c}T1o40v9bV-qSK??1$6!`#0Zek$2%6dMX;+HAmlyXBdi z2Ra?iXH(JLAE7lA^o|Sm8OYknS_FUIsFZCZ?v2Dft1-s1e!~Ck{p%FwK?(+%*&iVu z3;wA1kL!@9D$IxM1Tw)XgbQLo!u;2n??t7p0|#{Pc2CW@>Ucj^^%Q}Eh5G^-C?ccJ zp9e1TGG)zuk1vY-YwkbvlvCy}^r);d%uugiD0TpeN#bQFJ=&kCqva#zQm5-9MWo00 zKhF4Bl&7xT{v#TaRno5+*gWHI;u`uxd9a!MdDYKunv(Hy%Ev@1=%lT2w_fegJiTc; zyq+FedHbVsu3Fw1|2lk)_F{zIj9+4urtMg7Pl3Wx|AK7sUM-fFTYK>rmuX!k$wrbs zD@}UwT92djZY{}Hhn~1JaJM>{8b~whrrf%J_h`GfNYWfZj&nD)%={Q_5AN35` z>6v`~yaGORgL5<|_G9#!%(8!0o0hfhyTgvjH__0D0?oSxY1hALS{S0)xvZ{`*$%&T zyU$j}TlrjO8yM9YXt4)oR=Lg(X}7O;?A|hGytPXoql0OZ|ht zJ90DR@KUiM)~`Y8W@*1;&?zSg;dk2{W&A!rt#VZgr{;fHyVM*soX~Sbe<{AFuEX2N zqiJpTKFrre^XWsb`x_H9SREe<{5qTYjmmhPo1!MUhuu$%J<>>up1)$OX2M5TaJ*l5 zsbBiR+on?yhdwMdiyYD$gOed3JRja1iIpfC6l6#(1=a@8p2kx`D1)YgMJH;-{^5F8 z9rmg_CP&LD$8r-yKzo8arUJFvW1;u*~dz>A=n{ zx**Pt((^v!fhI$UkEkNLbxrVf%!@lxKCT}Cd4N*V%&qjVx~HgrjU7%v~8e(9KBER8HWXUBtk%BJ9Z z0~I>0$LqlXuKy7=M9sg8AH8L!eUecsJrlekb4xzQDPz9xbUCw1>u=?XH&lqpzD1Tz z9JCMf+4@^%)J$wHaXteM)vWNM&5yY3KwPPU7QHR8TGunh#@9rEQyB7XvyPe$7+$O@ zoPYjdgABxh@jK3a2sYgRRV2RG$m zu0yPq!%rk7in8E@1Ny+$+9v{e9xO8z73v<8hlut|w=}#R- ze2`(M)5mB^_zbSjxK{hvrl66}f>H8~1ykYXim@T)q6HTvaTT9k^E@JZ3Q{n#ADeV$ zQR#GT1`={wd$cWHnX~#6k^`;u$;Ig{8n2%k5AQSadsPCJK?47|{Fu#&RSAiFG=m_> zCBB!Ak(B%{eRnbh-R$y$Aj5e(!*|9c$i9c`T=7>*_V0lmg&n#y#q8jnXZJvG?sc*5 zriov#5WAfAV_alkTM4=Fs-tT9gV^+YO0T6Wn4e)#oL@ky^D0OP7c$*@ss z`!le8Z+a7tH>x4zpcb* z;r(uiuntygKz%BtY3~f0 z()^>>rLx!mEz>d2bD!rT>EV&mm0A>32Dxb%ZcAg|%eC~jeHP3%0;nK!{YhcnE0_c8 z9-v2;1aF}Cl~YK`VB;!9t|c`Gepd>1P#W`G69Ry5IzZ-xQg>EU48p8#USL+gAZ;r} zX;Evft1u@sUac??Cr2*1In>iW?c1L6Wk;p0*nN)1lIu3dtK2)5BfA3TfF~Z^8+^k0 z3u(}CGBwVKNQm{zJW_9%AzcI&d*US1Q*)=7tMBee(_add8fV>KLO{`$PZ%hdR1Ds_ zQ;9oea7-r{{T6>nGClfKt5U8irY-3do-72v(4+Cy0qclw_K6h{21(m(=#mO9f`K3l_XW}zZ+TfAWd4*y(4I&LxivR;biF$w5y+M zF$G)F_hg+MeqJZ>D^L7rhOTl7u1-r(bA$+fF2UebD*M%!8|4OMoVGZ@>iR(Sql;$1>fX3!GZZ(BdJ zeDFoVuWuc!`D~&?cmp_8#^Hc_8$TOSgP|on|2AOEr~K(75v`s>j>7c5wYL*yaS*9V z*V1Ap=M9-(eX&sn%xyRRokmIeZ}$IQ9)fC`wI*YByi$S6fjzyS*mO_vnYd5VOM#cq zX?=PjlspFvXEr9(0&T$D?CLYb;Jq=Mo!uO1bsH5nI{S6gvvcO3&87KQjo6AUgX9A# zjk5hRyii0gQ*w%(*E7^bAgT5Z1nEJA9w5@?VQo#`XE#7Y;Fg^2TKtdBB$9@@!4E3i zotz-qITsl}JChkl<;4YjdYO2)hH?kp1f?NE47&0x7q+PrEuwlb2uZ~HgVC-58K1oG zsVAS3Xgom`F0pUdy)kxUkKXRrOLY3_POHsw_@VrzD#fK=w7AG?=pL$3e)qBFSIyo1 z?wuc2Y9#GpN$-17;AU=k#LW2yMREV-$J3?LSv`4+XpsxiPM+*A&n1}Rnv}Mi))oQW=+pSco!HQ=goEPuP)eJ-_F7t(6y|kg z!N~r*o@J2c%kVQ21_vltb*`qyO+Sh=w^jqcEA~K=NVnjI-{31pjI$H7o{QE9$*M^D z-zX~6ZP9RSI5KaWmMi3Ky7|ddAJP<;X2+9at>(=m4)k+0>liTieo^uo8c}L=K2tiZ zbnXtp4^p+*Xl^S}3+v+E8$DQR82ucvw+hU&b%un6OyIlI(K>4{)D1?5&JsN5CXJ@_ z?RHfqYn^)mY28Y^_N_3GYO_-JJ8_^)49USTCiQPOHSNn2U??pvSsF0^Qp|yhDS;Ok z&1e(3#EV9AU5EXJW@@}MA7<+y$6ft z2$J4tf(qr6_dE#@ffg)4ag!~^sg%Ng>xjUflnYl*isCAUo)%MBoY=H`{$sxZ{kAqU zl)6b#ZT30jpC>tZL3KpsSofktj8&s5_Reu(+qF#2o+J?3L^WL6@^;SfutL2vv0|z@5<^UZXO?I?h4$LtcA>&+{A0Mf%d6_D3aU(%bE?3b z;1lRseZ{KGqU@v!ON-Gst-L?++3ED`Mtt+{*lGS}Jv&+Nf6e$r`a;UU<^~K=ava)U zPTs?{*HhAF_BZUIDv%mX93+*~$@>$g-+4B8DS%&tgh5 zY}HLeG7GQeCn#3U!zs$)#eE3GM!y*JqvYOD!Op%VPU$Yhqv zhWmKgNT(Aou0|Dd(!hIwTzR{?aAFCN{^D^cUKY!Rk8 z&z#D3m2VvUmFdE%LQ8UE^59g)bi&s)HjU1NFKqeRb~96e?g1J&U(lyL!Rmt_Iu1sQ zdD9OO(ZT8W(p-?N@`t)H2*R>I&SY=>+(BC|8d7dwF1ho*ylS@UtpU_|v;sS1GNc$= zNZcvRghJCQhc$mB?$Edr#?3+`9R}seOZjcC()OA&>r) zvb+l(o-~MWNWl=KD0LS=l?eH7LD3;^R3ydoixPj>!x~BuDs23=s%H{_XTC!%zaVvL z!wgr)cHFD2c)74y?l#iY`!frF$@nBmZc=O1Ogm07r%%7!5alU{PY#&+1JFY4oT?Bw zaI`|}XrWDvd)9JAEQagFswHI;6OuvTHdu?}Mm&H)R7_jeYVDN5`E0G(mscl;m;No9 z_XN5X1phz22shdeYhio4gguNgXTxqgIdY;L-t=qPq~lJp2>scogiMSIpZ!!jP#{L2 z($my#<0jWY=39dd>D#11*m_iU1S(tUWA#w$WMDBD4q6Tp6tfa|Ki&#blRHw(MuAge zD2YOrutZTPkm0Q7QVqv6OS0P``+O-l*>Lu^ypv+i=&R;7D(9rz&_vX9sUjo=-#`#R zR*=#(0P>j}$X3??W1$b>*2wwea>{yQeNvBY*=n6y#z0yW*qdS35D{rc?7~5WF5#Tm zB=F*VG;IOe!*y0cFYWGnLQPWZm}`f6<#A+F6>`1T{>T(O(c9J!S;9$U9!wat#qeRq z=lz~E4#{>?C zhYAm{Sdj5%0ND=B;v1&FeDw`T1FoKMBT5Es6@)B-{HWrd*d`$TcKiYx#s}QnrQ8;9 zWcpeSBs|K|y#qQaHg_E#7dUitrwMLg;ycC+crye|3HC#!8x=_z4XOgpPso$|0%g%q z>KkkW*feI83m82x0o?rttFs#iKrz8xqvgWD$c4cIRyzb>&1``Z0!dmH{F|K$NBY+S*fU$)pEM%95%e;AvnIwv#HONQO!jQ!$XgTr08?-AIZ|{IW6ai% zjbH%@d*zm4MubKAo!E}0#cA229;X(`{&F>LGArDGrK~P=btx+*b2VVx8M+p3z(#n$ zB&}|Yk0!pB=Dv%p?TMCshp3pDp7p{Vc>|ot} z2}X-!^I5(FYO{V@^w&xFR#<)OyMX(zK72!G(~h)z>aW8tTN=c4XfM-5TzS`WqlO2O zedS8i#;TtXWLWMho9!Wl-fhi;ojs4@bZzB)ao*(2_O2hNjBd_2?^>j(XkwQ8k|hk@RN6KcBnIJXbrWmiOe} zihAx)I0ksKlmKzlkUB$(-$FCU^F7p}y2}OtavFK{)YW**d5+4*xaiIM`~PlwSux=2 z%jKKUwLf+~tdfQsYs~{?4YAIqw2sn0g>X+PY2ybatoQzM@06PIWlEmza4Qdy{!1`A z2P2;1G``&yibST`zmCddx6?s_6$^2TX=4S=v z75Hg{@6v(@5Yk;D#YlMjH^H?oPP!X!Sq*%l{c`!c7IaJCmp=lfN4&*jO+Do-^jOu*cWASITH^@|89aQ?-%#+M?N> zRStmIRDL%XR^T=wVJmWj@DEW+wr~Zq29?-T%OOc9FIf9lq5X0@AZ002a%X`fvUgR7i%A~b8 zy0Be%b2XQAuT^lmU_*gZPj1mnn7l;FVD!i=Q$V6M>{3M_@0wFX<8Qn=|L-lc?}j)# z=zm#h3i(0bB=~4>mPcNXG|b!ub4Pi;dU7;ThH{lY=)=VZe>6fUIb0MHU7i2lOT(sp zpDcw>X0(hfd|h@L|Kt|pUC)#fR(A)zMBjSnj34WvCvK>ZfxS~UdIR?U3u&u@6V9G;<`<`ze z$mfdL(Ni(AqyKN$yQ(yTH(vm;;@v2hC#hgxLhk(Nbg-T-P@-*ayEjh?Tk=v*Pqod}VqG7}sj)-Vcn-<2 z{xm!GM^e9~1-jv9CH}(TodA3Pw;|=DP2ky8Py`g^#!3>KE?odz9Zbd#v<2MbNa=E4 z6@ucU<{pNrN^MM<+»Wi$g{w7}prl0D<-zi`Ax`4qRUdPa$o7TTSKdX}bN6EZy z$1y(o1WO~7vQ)$hxJ&&IFpV?CPf|QTe!6Mmf5zjyXZc#iZL!$>5d2rm>`6Kx;@r!u zK;o4cf|-Fhl9+rm=1AZ-3+DVA{K$zNR048tt+4t*AwzaTbR~?dLRdgn4GSzr7EH^+|vhfYIm5;U+KnIp`>C|Od zFqDR$9~V*W%>frc3dBePIvVfj(42M9-dNx^*NbHxLySDTgRl+&ca`PvvvyB5G-Dlv zi@v)*$T1M}?!lz8)6T8qwV9`pKBez&z>i1}DVUIa*0`3IIm;;B7HaN%s5v`u;`RP@ z0yPCKdZH(+7c);qb+pl;#yX0~S`3)}7r%C1U#B3+_JrW7*&!n+{D}id z5{O#`hx+2JUu=Q~ppBbWOe&qy8b{z!(x3e>eV5vCVF?HIw)j3~x%Pzx$*L4)7oPFu zkzh^17VpVW{M6y$JqRiY6%>!mBt=XT_K^gQNLK{Y_{A8DOk@yH|12>*6X zKIOJkDX9mP*Z~|{1CcFDAxQVi&cVmj=m39^_g9$+4(bSES0I&xUTzV{czAItYj9%8 zguxfXCFR}y?|6ul(6YT8}@22e0k(*;sw1~dSG2PH`Z}zCaP+w$o_~r5p7bEKa|3pcRnOyfo@l2HFY-i zHILkxcXXth3~z2sK$k{*5olBwaq5VI3d~+yz`PU(lxvs zGNGU`fz`}beH0Jy3rady5M|JmWA*Tl>>1nl&iNrQt&y5)Y}-@yt=gSG=b-cXUShuwZSi-Hm}o0bTY`h<)WK0tsA zw#Win5?XGo_c$=a)`ZJQzo0v!$LH4!_5KuCl+J!)^!T3P0xypagBmt~7AN)7&rQ^l zzQn2k3pM-}JnkTr8{wAD1b1m#LuRFrK(213dQdFZ9=h(G0h%zkw>ue6$VGz1ogQa= zq;X=dKQW~An@2sj=$p&cEXd@p!-*LTTFVx=_GoZWq~VwFs&x-buJjDDv%(*?!KM=I z455--4eB<~3CWST!O%d3;OVlotYXCoegp`eJfqt1p}XcNDlp&kyud4ukzN}qhSIt$ z)%>o6JD$NX3@xI;iPL4d%5K(*=(xUE_+ohX*w?9t0cnPqd&G|3PeuyOgn92;?9s(s zdYSa3S)qu6&+*CuOJ+Wb>+wJ+0tR7^a^+o+`s*myVd^5;OQ8~usp~I*rsc@Fm>G%_ zM*pc#OlA>ujLv! z^vJU^XwtUW^T`EU1Llld;rXnD#0#~mP=)(rsl}z^Sf+37$WoGTTPZc9UM)= zHse2FjSh)*LhdJeDdQ8ImE!Nw40!8pEZ@$?S>HXFUW+#Bqv3o zhRpk&1FIrzyd7w`(w_4TEc;fK{PxZ_QX@=U;U0X5nX~C`{@asB543?*&%ydEsJbOz zkb|uBio|P27sgd={`=FKM=(FF?_Q&_LB>tH8`N-`7Z=W%`q!G2!11c}rkIhkL+9(Z;;xk&6VR)?tZ~$Z7%@z zSL{ajjam>g4!)O*Vv_%+b@MTNe{uc=3>px?+NOIaIwfa1BW-U5%FQRGeYduwcux$Zyb zfKG~b|&2Fvg4FIfXHZFUO z70qM6P*A~?t|fpfjeXDj`2Ovpq{Mc>| zy#L&GbE`dLgpN(O+w4=CTD@Xlc0HYwSN$WR^^yEv1r274<{oXDfZ9PX3$R7>XvL5g zak6RLHul~r31zQ)ZH|+?I1t`VVn3s;g_$^;tO3=kvlpDuM6m_s5k1rlR@TSHqpD$$7ldI)|C&5#(1DKM`T#3=R)NY9 z7!i}SL#@3KPQ8xq%PzPFkl-`XXOL@0rC{QRXqo)Z(eG7R2?ho?hiFNzX9#b$orB>+ z_d0TO$-p&e()Fm^;VI%$4RBT0ROTceQ4P~Q@C)yz`^?{Vz1@2Ke9PX~$OwvTWIUOS z^vJH%*V#^ImB3cJcPk-Y3Bp zv_Ktvq;T|OYPsm|#jZ6sA&rHmF6dn9-tC#(>P{59AN=O&^aqzGU%;Vx?y6!(MdnLQ z`=dOEa($m%n1|mPnJtbHfGN*_j~~`;+PIkZNcN6&ZqVo12F3mqjH_|RCm8Z(U0I50 zi}k(FeV729u6^0FcOlwF!bp!(21ft{Vj0_q)zh1x<#h-2dLIM)r6Y3f)Y7Bi?n`s+L-ib~U=MVWCY{kZ_k|2(+-QhICWnwl zhe%uhVQQ9*mBR1;@FvS|MjK!_;{Y^6I@^$p?tj?uZ%qo5;ti|U8Lz#QDD-SlyAm3& z&wPOMTC7qt#q9?Wmg(VSK+%_X^+{O0!EubnZ8j8#Y8<&b_l_!W1@?-Jbj3c#h&UgderjvEAb(dWSj-&hW2_`y4oQTqAw>62U|D1i*9Qr@HEG?+Bb3uup`u#t>1>nz#`dvg3R)T{Hcna#Aapz5zZTm#~h(e`TgHff1 zdq;u5AJ7K2j#{h%JrnN~v~z9(OyZoQDx9R=M5+Lx)hNrgYdjOp1@wy)M(a)}idFv%9j#7~W8cWF(e+EykO z0?FsVzw0w&p=Hz#qIT`+xR7At@qK6@gJuojuUg{9hvq1~G|; zqh75h`XM{Ff|WU8{zu3Y9$Hs|xv0gE$AtVyPvPQ(3H98DXsrFYPPk2wCI;jacU1&9BKI#BMoqOlgK@FBVKT63sb7>JX}|Ir!G`HfE? z*ELs^5p3xRnFq!xXqSTi-d<&I*SL3Xs<`YZcZvhpO&A3O=BHgZmlP?HtCQ@+X?z+djozVdGY9nH2diJG$2-sOqpDv&*Gb zqDPCztUD5xdxj+qu}$NRRO7s#=rTBa)+J*=`3ceARcOV1)PzU;agJU0f?eB}N3)qf zNcxZYlvMx@+*3=VO|;)&;P`A1Z!yN@A}%4D~(s z_=uO)KVf)}uj07KHeyDOGT8RkVH$-rS#^P|*HyYc2?7PMT$v4y^SBF`lYK~O# zHe~(=t4__l%XOAm8&Io#`LR=qnRVss@0TK22yIy8a{}*QZ`ibOPl6d$VU}#7f17LR zsj%+dj+}LkXaYLqG`Hq*-utfs*dt5@tD8W!Bi5-28D(K%h8SXL^x4;PJB$ayg_u+5U~)s?RJOd|)|w!( z1GDs{TgRa@X9F^Kop0o&cAzI$1UJgXa^_4|EuBZnFZBmmp~mRVfoFQU8Y)Uh)S&_^ zq;RN|&m31bh(f+~$M51LwF7}H195I__?vxYN<28pqr^brOh!Tzo_$jE^rvC_8}rPx zE0%BHNxwcX(~MQh4oD&pP{Ak>K*FI#IdttC>T@w{wy!aQg!XK?-(Evpfdj)&3~|)h zmD`nHjk4Zn|NWWM>c1h+B%)=ZdW+TyM!CxUpO=xfGkwr6`)>5{Q%e>fwMNyP=cn~O zisyvqhOULF_|8936D(%29RT4T*tMC13*-vB4_Id`}?>fEfG_h9o9u3ZUM_9wq zE%1|cjs*yDvIM3Mb9QbILdNTY*kfO4FF@JeT{wA8aZQo%32-<;Zn%WYbz- z1&$*jyW@5pa*#OoT~ykOh3ae##1rK$H#2xVC%wUevTP!-%Qf=u`u0q34&NDIz z^5Ee4LGBUBztoAtev_8>#Ye-~#DO86U%RPNwZ)cM;nbh@#_jr#C&~OotmwPpz)!CR z`4dujXZLFoqTq(<*@ekjkw4Qtp9hB{eTVFE94EQY?Ur@w6&6jHbdsVA0 zi`N6J%=5QFr5(G9KTlkjofHf$_st3%0yWY&^fRIc&a|yr1v81EIMbS9A(y#uoiW>B1nF9U!33W|&YN0ymFK&6 zzkL3jUsh++Qd*nP3852gLq6*lDFWnqV82jtN_F$bh%ZW+KJ;fCo9vU`ON&A{aQj#? z4~|Z?b-IG*d_72Wn(w_hFf}e~% zJlTN{u^hIaeO#&2H3~65&X}9}VbLwr@bq|XU$=!dsye(!!zsC*-Yi4$;23nH3MP8E|u=Xf7)zpuyt_%RH6VkRf4 z8e6hld!k!obA_8<&OVRX5EVAn&9Ya?wY51?bJcTuqmo>stI@E=yP@><-sc;rwb`Z&GVkCV{yQm=+$c}2en|xOkS`tp`%zF5=eR=O(XaT+pR*O4W?u*6V4P< zV)drwt5V=3k+3J^LYWlEVy6av*JL0W``{+RNy4o{x!Yh|QhivwyeGVJ5f z&vr#-vGZ-icYyX)eBLACKUygH;0vI=Tfdku`()fxu zORAg_Y2)9~^ZKpDqcr-Iv~nP$rZCs3w}zb+iqv#@&3e&w zcN7(>pFJ_?5?J@~=Ob!TI^F}Cpd}yShzGv)u8Y-%8M%urM@4OZ%Hf=}wJAGdtwk)N zozyz3{(t>>%)aG_xB9tiM@^rv=*J6``a&TF91TS8^P=*?Sw$|T`kUd92`w1Kv7SEt zF}k3d%_C0ieQxajbs9p&OX>_Ioy+L87DIGlkD>USilGA%m7BJ#V<=5u#BiobPR7RY+Xj`jKcE%UF{+pLUCD-=O&`pLZ3GeHu9((>!7@Z3GU!WXi~m3 z&i`M@@-j-p-Uxa#W42tPglHuJr(L8)8Vm&8TX@q0zGc?OvFI1A=04+~*&M)u-zf7m zhcllkF>?=_SOm=f?ZUEik+PyUjSf(RTREC8baXX^z6cU$IS98_D(2lHWM5lq%NzP5 z;{g;U6*7XvY?b%Tdva2*>EQB;0}M-{D+H23=HeVD&2s*LjPS(}%2kQ+pARXeycvU^C@cwR z#dBTf7$muvoE6r+Y;oWmz3EXvZb(+14TkgSwG!PTk6kiBtsAgc3mY41k{qub?`Bzx zwQivep)bQsR#Nq?EXZ7gFN{0VjYM}c`apd%7waFTF)suslH{lVS=X_Q`sz}T3T@7{af*}Ox-(Z1G1mh zvuVu8p4om^qoJJ7@?M2+@%&*+$t~vzP0d1>1vqu=(zp@$u0|8%>_zl>PpJKE3$@HH zZswx5)vRyY0j;f+fgpVY41Zv{m((XNY`9XNsDVo8ljyR+DIQ9WTWLVer80DpL)N~R z>u+&lqij|3glJPXoU}{&&eQud9-u#Ofb&SK48#j())Zz2@uhjiXl*KpGQK7_qPB2$$WX_kcd9k%FE=g`!KjSD!vDGA~$-t$>U@Y1@GH z0;7MJNPpBUgv=#;BX|-JJb0&basqUMZJ09Vw(N6Dz?utkpo1yyDNUDxQHzD%%(aQo z;ejC=GXLU2sWc8+rZoVvmG>~&>t)zoPZ;<&8;`S{kI|| z#&&z&=Z6bZsHZWl8$Ytjk0(}9B|pF6ee&7oB|QVvv$+ftFMVb%kBM>r>;&-zIx=$h zi8Vhte{CfX4GDYC#c8Kj&1t;~t4z~vNdj%@`tZjVwlTu%hTk$0Gj{d@8 z>YdVwC-(h|R?qlp1`4ZG!W%C5@;sfg>!`b34s3vEB9Lif3B?ZU*#Q8;1kw=$=9%HjV9I;{t49pPnbKvLE_cB-$WU*i5@aHcG;#7)kjpW24vpa+L>${*X`U@!Q*3p;hm6I4sQ|%l$ zm5OGLm!)T|_LJdh3+wHMmi-FC4b;hhJZzkNk~j0^?HE3kxVzpD7iQb zqcBiI{i5`1MrvyKbG|Ax^XM(STeb-&S;D%NZ@GFutRfX->-{2B=MNmuNgLiD#^Tn#K+4m@W>;bAr)o^UgyTzF1$Av z*en7p@dIwq`H4`|L`c+uibU}Fq3M&l-cEL(c9eKB0?9)LQ~Sv;te+`{L7ZTz=6dtWxm04Ppb*iE`bXWh=b|VB^ik_=TCnigC-D6E#GWqDncB&83#K5 z8*j#7nM`Dv*Px^07mEYj*gcZ|=ckX&xlBK5XdT`H<5hafY4+*#&w?f$o;~L037qsx z6A8~tpuHI%f6$xWdb}`gGJET1E}8=jh+mxTq&F+{xBLxrU}xB8hhz$`tl-3zlUn+$ zE5=pUp_b79A!<|67DD+ve>{GOyoJB`;PHeH++mesa?$^3_z~$kk4?a$&``{p}=|Ek4cv4x* z)ZyoO0Y0r`rT8O3YJP!Xv#Ord4dIX_V7XO@YZ-(0+3K61&q$c)iB_ZyE~$p4Wg=&QNwVu5uZ!TcrRfrJ25C`Dl@t40 zG?Gm@QsbUht`Gqgmg#@@9V(6h3{4!rr4&U3H!-ErTdOT_#Kb4x9@HXv(u)@Vz zNo8&dCi6aBFRyUH_jt=W7K#tSko4jpH@(klR6Wk@a2}anjE>9Am+6Til681vKLvQ%%@3#Aa3=RT~U^* z_g1xid?O%!%<6^{Vo+lmckg?8(N{(Z?!gHeEHxHg%kz=m2atD&lYVF%~h?6hyRAZP$ z%weBTwoDbmanYmJ9u=r6I60&w@=0p6sT#{v$4CoSAw)?RLnFYr-GlRtan9 z!&@kE%6&UoaNZ|B)!`|1xsvK)wyaS1LQiUy*_Su9!D-Fo9M z(nx_LE{LU7In>&T-tZtsk3W-9@ZJ=kG)H2iAdP(VwMUWqSWNKFt#EdsQbssGWwVg= zyw8ip{p+qRLMhBhb+M>;p*}QxgN@d^mp&Bfve{`_)y)t&H~@#>2r6zY;)Z@BKz$At z45M)Pd|hHHq?3KCpG4O_H}Ij6S}jrsL9ZCoIUe z*UycF;kH;m{WmTnn<&EueycEg&Fm zZL0{_5(rXMPz0n1hF)duOIcVzL5e6A5(oy6Uer|*ks^>pX#oKvAl(pJl7F6nuDkF1 zf3MeG=;~9>bIzPOGxyBg(@L|f5k%Lh_y}S&Jn@&=>GghVW~Q_@#>~IFHEQT1M7-%o z+7(Lr^Om*r>P$ia?Uc~7l-;ple5Hs=;)7P+zgdc`#y?S%k+byqA}vs3g1V)|RdHvx zn%&edc|raj3_{4h2Hm7g=_LqWx%HAzK9~$BqOhH8xHj;yb;;!_)}+YWA=AD1>!_jF zUYZmsD}v~jqCjbmG_qLxtxgQj2 zQm0^ljsdWzKk*WL51+)*$|orENB)t(=(||;6S)N`$;3k4#X{_Up_=)}~dOl}oN<;!vai z(ad}u+-PBoWz@l>qKiMlh?hFwX+h{J)4CSM_bvM#aezRU5e`$N+D2gDxyu0QN@91g zLs&OOtVG_+|J(Mf+7!nouGEdTLqS|nEM{Tq7VknHf@HY&{wzr1ZRVd_iqu=xiCM}l zR5C!MyHU)+i2sU|{u#F?x9afpfq?F3hZ2cbHqW^nAKjDQ&$+I7H82r725ovH*BcC@m)=1-~2ME8u~E2mZhxqHpc z82}XiHEV(zY{9vTl-LPsCF*ABhDqWJboRnzyY6qLwmxylLVZh*T%ds!C%+xg6L-iJ zbC#_cL=L#XmqLcIyJJT~yHwphyAOe2Bz&t7EW0(PG0PA9B$ePa*Wrh=jMS`R0*glkhr2Ulimn9v+oeK%5$p` z&DW^X$hS~k6yL=(0(it?j&7;0{57Q33Qk}yI!2^Xf1s()wObY@p|pl-w(!EUT0^Bk z=i}HTgUiy|HWuWSNm=`vvY0ZvyaZCaJ<7_X4-lV*S`Yrnb%5c7C zuAX5ddl3!;H%UqmIM(s&TT@=WztAf*Bwp7Q*taii^k27jng+1v<*E(zCCFz|aW4sZ zXO%XN>Fs9E_9;?sdYqF!0m+7TEDZ+9rE)d4r665=Q-2#6h+B*a1Grk-HGCO;es@Zx zy20fq(h^DtF&88g>G{uZXJH?XC|vvH8q{Ph7Lo%5Ep_*M`I=y3Ec#2mKO<#>9lPRW z*;Za1u1BUN{2@Tj%^9xhz18kvsX`fS_Y$S}AbRG1EEH6%uZ3;2Bkrta^~0whcM6Kl z?7I6FE2EG|{?%xx%(R6+*I}<71gy$8 zaJ6NS;r696$3>d_=57?4{KpLJ0f7oYPJ!>5>_=fkWKOpVw)ojj9I(Y=Jj?Y%;meu_ z?7#0H_}Xu|kjM~{wy7-YAI?b$a>|E+@Scr+@~iU7M#ICuSVn22vr0E_5n2f0rQL2iFe}}jCW6(kk>#X`Rk#JwpL%|x=ccgaaqn7MFL6Gr z3oYs{aU>O`PD}<;aFC4#Whg&>0P;Dd2a*xac~fDaM;*~aN>nbqP=PsGBm{^c7F{M9 z4C(%JA5^Vtx$7QW2Gxpl(UE}qt6?~uUJPs8BMnlaCEsgRlPt6TgoO429f;^;2O_74M>PU zD#t%;F#0|V|6XgQvUkcz{o|sZjo6Y9f#f#@$q)ODUR%2Q^go6Ai>~3qZ&uE@^*{W@ zVPWzDA;VH`L~+b+jX<$oOSTU7CTpuzPhx0DmBt?#vW;ugPo6Zx zitdft@vQvqlwxbpxC)P}+M5lLM+{rB?JvA%bJbgUbTvC%B{Af=YwW>>=a6+ca`IDw z4~q_&;{5dG$NGWmbGYDkz=&-%uf>Gh(ic8`E&R8I3)}m{?BzDie|p#%UuT*FcN)4! zyvIT|^X249|EwW5=7Bibjhi^T2Z=YjSoc^MpOIa_YJcmk0V5U~Bzz7w8W2gmYiLQE z+}u(HS)$MzqjdIzh0c?{5-&(DPbT`J1~tR0J2f-0V>flzsM3o-nzOd6Y9dKJ(2>HP zWs;7+YZusvV;&5HA}R}@Oi-K_{(Eo~3bL#hs$Bjd7x+g8Ve|cGD@s}?;X$E^IC4w- z_@lK^H>#%ihyC(GcdNsS%7E!(1KA4sFWiL@BH;h-fqy>^^f99AkVH?}dvsvE(VAI5 zrxJG;_?HeRUX-zU)p(P2MAiXlfsF#nt4j-w^^r{RpNPhR!`2C;lEIVUo1p9U3-K>Z z^jtHdto09^N`#**^_kvM{2J&CV67AITnd;zm}-w)PWy-pm^zIA-4$(X%&zz*X5d1?^oXP9F4u zO|dg!-aoj;H-QsGF+Vp5?G!9{#hZura9~O#RHh5>7=&R}3c_C7b1aR+NnGR#kNO}W zz?{;|JJ>ZJ?Z3g1LsMaVM7Z^UMrWMSVHY&$K@FexWggNO}lng1Hzw->J zeC^<52K`y*RcVh@^L5VGCpxSY6!|Wm2mkwTkmOibhLkL1`o!fdg$ONDQvbhS`oqov zQ!YxtEVL!`rB`Jn+@8G}k+l=cPSpEEm?Dn#OnKKY(MPS?L4pZYt$expi?oj5EhQoixnRw2b<50+?tTzEfh z7}~J@W7-w@V-FpzuN-YDd23+nG04!*|5Ie|S^Be^b6q@wl?AVLxk9|}&)E=M7@scf zEtGW*+=!)X}_pV0Uf))$Y@7r#ba=X;J%1b&-lksf)D93+pI?QW;;>O%kLmldsg#){hJbM7B@(3=S!%o!dOALQCE66^|FsrLz07d4Fg*tkH;rjm+cA&LUx3lo{B|!aD zJ>jQ^RWr-1I!82>@7zU_*n!w%R%Uiswf9LiLl;FH2Y)Q$|Jpr53+1{*l#HextE_NXd(9@Lm#sT)0f?vXiF^HlIIU=J^&cetOSV|r-&yk=8vcT2ar ztz4QR?$9}OQ04I}kbB*;_OcG#FAtMjKSGsXDmiZn$=I7@QB(rubbliR90JN-MZ4J# zwI)@oS(&)q*R~%aT!4Zl>Q2=MjhSb=fC`5l^=rZhfu@CTmAr3ePu7+5(OkcE zHEk7vlN9|-8em)UHMT?J2EY{US?7YjxWYi9==I&Uy#Z)(G8XU21TG?9SMIT%02%w# zF`XUb4#_!Tp2-ZLPtM*sGzA4TrFK3Ikx#bI`Q z=LFY@;kIbMyWW3zKgo-np+|a8beBUxA;L*sI8G1Kz8QhI}cZo(zQB>N755&xo%JTSfUqS`EoyCPPFk^mk3N!*079xC2Y=d^+=$G_6?S*dgkMRTg@kS?$8NX zzg@b{yeq49`xe=C<|-=73xC_j9#68(5)WEMidd#P^D*dz=arv+eK>g+cbA#ZzlC#| zO8>BfZ?t1{bch{VdD|$MG&61#JmugX$}5=aVHsaIg@gQ1zxQ)SBFz4Kj>;uVN@^0P zG7d~d$~o$_@+VwO6a((~N&u~vZE&0{Bg2|En@-&!W&^XCyUvGWe6Yk#Ufbe+tTa6TU)_7Gj zv_-6itR)|#aP{P>;g*rDW0g#w6NvTb+nfNW&&vsVh9wD>^0 zw6pYF_+4Gf8~5w|;uh1X|DE8)@*|9bbLD7BVq_!J`&KYRnSPVtfi*$qIbfSQI>mU> zZAA(Bk48$4ND*(C$@AzY+TEX;8dPoG3F&aKl zgL%w8pc6=~QQ19Q7@54yJxgC4M~J|Vplr0lu|ulv7Bz@(KX4dnTDbU?Ps6S+a8|)z zm0n$#C4q`a61wp`7|)S@PsiP0pZOYD)>LekDcYtbNckjO?+* z2n;R3x$?X!;V-P?3Z}sw#kc!+IXS~)B zETKK6joro;IP#NU$$6G5GQwWO?6FS?&>nWgA3p`;DE<3pwS81HWcH?^WC%L45C}WlH~`VtY{ANPX?IT7-$ZXlsOfQq7x)zq_se z&FppSPyBK3V-hugr7tgfyt0|>(BV$m9+gy7;Q-@CB&BM-DIzO~uaEeA$R{Hx0aQt} z2?!$&Hv@~Z2u@94ZJjmE+VOmmz-$rMM3?}J+E$Pre+Fs&_*Ce|f=DlnNbmpv zZ1dIvB>NrFNON9%<|24kKzuF>7r`Y6oF?65YaW4-J6|t-zmt+A#9}XDcbVmanIeCigV$ZbcFvzg;budI6-J`nztA4lD;k zq*-@R=-Z^goG*gdFjS~LLmmd?kBGoqZeLtM+kbFdz1wl=wJd$AE&V=O@CO0?pXd18 z=-iY8v$txQbLn#dKKH|9E=5>d1yc=7f$w1v1-K==4tcdeb{AYD_}0C*mXUc=mZU*8 zx0h47US9$`ZqOCWjohNQ3mMq=X#}Z~+c^{{ss$pj?(%BnPFVKr1R$kGCh+?UkVQls zp(ONje2D9F)Cr-yXKjIveE~+2kh9p)jZnb->juHKi8L6(Yd_0dphbrwa}#g*cd)f3GSy z@o)?yXzV+;2z(bmuzz)@Y$jTUlG6IWJ@AtnwXE(RxdWa`Ve+N;g)qdoM}r7_+wRXx{KEEM;ef%Ka zTin5Cfd5}8*F3ZEf`9hi;vNE=J}by{s4>IO+ovLIa))wC1sz3@j-$c4Dtrg?k%9cP z#}O7$&|V66f7D+8Od7~YQY4mY|D>fT0*R9@{yI-Zl5%Gkzd-r~l z?{MsF{|Sln>aAM&+dslbHD0&QS9*jr;?ZZ@JrBiGY353Hi(^68wK@N^{LfNxjx|Tu zN6R~G^|mR{$$m%6vz`q4b4tv0Lh487Kh;>YKT$sEu@dwj zLcyf-q-uR3> zK3`x-(sF~_mun`oUnH15x@Fxke8qh3@I;4JN@qoH8Gb0p-4*#N)mtDo-?W#s^SfUm zck2PPUK;dMB?RXPKVEU$t*r9a*x;j7?Oi=9Dspl}i*kl{k9>9#7X&PR{2YON!D4>r zbx{A^nqkb(iEM1+zl{V^&~q2VbAQdr4R?Q8(}MKLEHWAms1cI*I?Jm`RgQ;vE6pnC z?-NQHlka+$9Wp+}Epz@~Mz}lt#IkwWf+bVs!FLKeQc#TP8;X2uA$($Dng*&Le z#e!}3->r_)@=L)meo_t%U&nGp?;je=d0^1^&P@bT;^2JShc~6) zXbhb{fouY>8iWthodQUeQ-fEE3z=fc4(T0*T!UI#?(@Lm3c+fOM8jPI%bspqkNzSM zMvaQu!Sqp;!Pw3r4-xBo*rK#RaU5pR_!m4D(zn%z?lKw#s%W?iF(;&!kq<%Agdh?q zgqs|9?_D4J{+?W3L1m7=-BWWNyG{qz8oLF(164-E1a#c1Z}*?y?(7QO@XrZ>VmL?c ztX|MdyDLX&qxj?2?VCgCL^G`HPKzu9(}l*W$aaF-#mK^+8pA$gLJ0ioX5)fgqh+`8 z_#^Y#PFQhm>AUSIM9m_{oYU_bhQ25&4hGI_VGQ+`J5*0kU7LE%#*=2g98ySsAkHe9 zETdMAzp9<@sqyGi&@1xSOm5Smp%(dw-SrqpLK`r$Huh76q-A!(IE>X$pkS8k&FFKn z!N2*S9PNT*Xtbrl#-m=b*h0XnGJ*xe&6p3H~Y^MRSXeW+}7)ZE)wt z;Ub8qp)?5y%P%5R0^Lx}=#7puw;cjj)X*+v9CTAf5?dm+I7Cp(PCshMr_PiC8$FA; zE`oUbajK{@`x~5Iu}PIMP`RA4&h`R2bTxLWQu(nci9YcG*Ak$OP4f#YbFCipCskkU zz9{Gv*)Y)VH@B0ZNTSEt=50EC9#+-2~pZ7@Tw+802BZ*$iBx-Il#`PKLy!U+) zO8MG2W;9Iq#~2IM&`*YP(joJAGL)w`d(GC=Y|Jysm5bpNi<108-fJm4&6%1QP4-0+ z(01dEUUq9fLD!m%66p+T*(2o=Rt+ZeQrms6@rPXO3#Slz4NaGjfyq7n4ob@^=BBUh z4_?u^K#djrC&?B^9y z!<$=XCw3?{@JnxwgDW*k@JPrfMATA|2o9fqi+!)1KXW8}u!$qH`xfi0#SqW-%88SF z`qfB6j%S80xQAugs2$+7Bd4JNUtQP?c}RD;{tCna(m(F{q{cOv&d)e$H2t))Xu%Jz zlNJz{FGynRd(0`!Wn0YkLavB%RWoOcDV-I=jfR;xA!JR%H`oiT{cjBRfXrNoqpfno zR5LR8uf;?dkQ5qWtrmy5Y)KoouyWx3XAy z`gQnBZR8_qat=(o5JWDS;*_$f9SRb8&~4LLjv!N!L(c~<+A_Smi&Mp^@d))Ss(1Z* z@Y6t-&1GBKaB4+!)B5%clU=_pYKvZ`|DncL1}p{--wo=5X;=~R;1Rom2yz{Jm!BbG z-Gjj+eifoL;50`5fHuXpw8$W5#t#5fmNvi;+}DmWA)Wm4(~rGMW)JMKZ>w%QXKhNq zWeGwi7semFSM?=@FF)AKqvfR=5b%iVIypP< z=QS9hRUr02O%kT(m@F`)J~B}%k$IqDBtak( z$eqPgNr4mVV3aU<>h2TYkxmo;eAFy{P^M_nE6X$QX7O z9w<|BNS(i-6LxmZE<5NwIw$5?*7sowKyo`|pfD*$0ZlM`|!}81^HX&{5?bzo3XPA zi2t4(?Q+Qojc+s)AB;(JL23^y6+v=gdfT`URHQ><<-%OM!T3csj|645fLZ;a zG4So$q6$85Ze#V>6&<_&X}+OPi}3;ad6rI0;E@SXneh^=V#$vm@Ev_kTuVrqN49nn zE`vSe-}%M2F;|RyZky>EIcE?5S+?fs1ibqG1$TNe$%+7**?2u+Nl{LFxiINa&Wr4f z`LB;e97BfLBFQx;XoMJ#H3;zvJk-yW;EwuM^c!%`EJ^Y~(VYt)#!qWjjuFMxOLNdC zn0h!?#UlEoS~o3#%!vIWxNH}fM2IJQ7)!a^0(g*?(^#P~+|mk9g)NT%Vm@UV#!gUv zy06-;z;UiGwK9x5i;3Yl+P8QGeCVRWWk}#c%z6BB6w#G&Ea$m%+T?eCu$SZ-NyG(d znbEf_W@n+7I45ReWgR;CPanzHUAC@UO7>Y;yKMqR2g#DAZlPJ(TGvIL^}pwLZs#FZ zNZu2`t*d5Vr-}=lR&Q{yXs=j~m{ZmR?_DjBcJRnx*pv(;zbb~vqh&U8Q+*my_7-`#_wU-fjE37_9O^% z*4fh4w^flZL;ex@odvW{|KlDtF$5_Q{= z3Q<#*LYWjY5SRcpWbBY#Xf!Br)MZ9YalM~UnY;nxUDP&5Y=yF6-)7ZF+DOFd`hi zE)5UCoQj^yni3A;kNYbG6D+_@=!dKVnx%p~5bODqvU8%(g~1|WlwjL@2Ngg%!Iwo; z5CAQL<5^&2x@&{>cfjZz+UPky1eS8SFd!=eKx+rw;fFMBBL<4%vY#Htq`p2aVvQcK zxe9fU?@)A2tCC$acfDd+yQ%>Gyk~}GL>`jdZ?XLy)L+N|3xuWt6_ymk*){(rk^rWc z%6w%Q-(14ecg*F*bJ2JNi_4okoSU;+)=i%T*a9Lr0H+D28X6X1hkd6L0fAZE{!ayX z5+bhOFU^`lVPzihK2|c#J(uKL&b*1HUYxfT(OO(lRdbe zk|4$d>tzcG+|edUA9m)+2ebsZKOoG3)Cufjys~o|5>u+|6e?CIDQvX8Omt!L-bU)t zwzpn@fry!VR85%{ma-8UjJI5z^(bVYz#=#S)cg7*h$L6BOo zR@)>0Ucg3SKYmMzML>qdH8!A136Zs{rawj!#&XJ(DBGW$22cYIT){Snb(e(1_eG&p12w4# z$bY>dC1*$8=F)*_F$9H$MYla3haWBOINNOiLE(|nHMlw{vMv|AU)CBNjSEw7kN#lH z?T#7t_+#3q?#ADv79h&b?X5Xg6Gltr>3g#$Ln*I!U6ij#Zt* z@!=IOD--y05B$}Op-D1PCW`p*2tn$bT~Q~x0tuNwU$S91k6}I#CHdnL`GSSQ@~7HI zT0?k#gJAc@FRUhfi$=8jq4Wo#@Fk{%a+LduIN1md3G3vlZcc(zFe>k01by$y2DQgU zNn<%$k7?o+wtk3vY#~g#6rXCAd$30qSk2su^rv_9oLX3UD=D``R5G&5kd1`^B@0j_ zJrkR}5h6i?U9yN(!l|`5W>~{_>x6S3szU8Bpz5-j8YpaqOuoQLNwS;(2yx$5II?aC zHsRujPX0uG=&`T_Df&n_zU-$Hhrr}`GWbQrxk##LzKHQ9lTKdt;9Ou#@ed;uz{3l> zK5IlMw|PaBy@X9d1aU>vO*Ju*3l_pqVG42f?C_|-yM5pHfdHo!L}q>zxy5||n3-w( zx3JzfNjqdp5G9u_$!|m9^qbyv9%6c|i9D2cE%_NY;<&2&vz0t&)uQvd-*(Ioqgv2K zpJbe8`fpm2Bcb+8Jq`&2EW7|F5I%ZPrYS`(f~HPMabrq@a$Pu$5jgo9=nuAlK&Alw zlZN7|=7Zw!b8$bzGzt!3m@JO$#^qXS3xSq@O)D~D=dPoGyurJFcxDmxXL#MZ z$(UJBxZ>Zx3U^N25=ZA8jphlru#ZN&owzU0l{H$^STgna;>EmJsXH`>;9=TO``K7H zBq=qG5&Mv94jy3~KyGpqB~!RbZda&qD2@>vO9LBr=b{C5BS zaNGL0eIpE|S%+G#{s<#4HZRs(ge-i@KAf%;&scH(l}nTxGPZvn(K~UO_%dUx#Nl}r z10L$W3sI(}#)!)gQ}AbGhR`i}+T;BRndZ9No-F}6K`w)ryxsw=lygP27zb1d$8m|L z-q{N0P7_JofdT5H>bdy7hiIZ>brqx7TyNnq$U~w+7_~DZxNEyVW;!s#!#WhM2HBd{ zacra8d2jXvz1#vMO&!qogtJDVC15VDU-FjDX%*ZEuklqKy589^!X6HW(h-1FIUpsW z;*^x5SfMMH?I*&SjQM$Esy)hfe9gv?A!6Tg?cEz^zK-&bPdt`~De#|(q1p@C9<7B~ z1-8_@#zWa{1FV>m9Wu${Gxk-+#*+h9qaM_g22<0$u~uOd>s9V7b?#hBPUi{H=zOCI*)ZxW2mL(y(KDH#%=Y6thI z%pRGzBImHH^(3T=)2CJ4Tjq)}fB!q_0H8N&DIbmmf zRdzvZGz|y2pj7(~W9l!hY=n?_;g|rie^3EJ^4c%cDu?T+Y7w$U4f17u-k(1#t8CO@!P#qK61Ey$ z^;M5^FzltT%t2%0=Y*3e`cUld4KvwR?+pW2qgTMLkkR(ee7%n_KIva&P8xiefgTyR zaGOtboPXs&vv61BOz#uLsb$Kx6HGNxtK;yRak9!%=h=>&%D1YTC@d=1+;MDS+LZqhx#-VT;R0d0z)2BGwj?vqp6?^>UlWYWkO9v zxy2FjGFQi0W>B5(Z5oS!@`xvT4B;6JT=kF|My~*wQ zA`>GceN&7~KHbgr`7e84u|^6wT~z)2H%fD_E*7A0LJxW83q;O~l`P4ZZTA&a?}~?Y zUqwU*w`_tX?VMOc-#u1_3E4gQTCeIU!@uh9QxU`fr8zB4%95t9S6m$aQY1}|dPKSJ zGstVzjzc^4&X*+DRw85_tL2@C3x;RrBmef8OH zIWGD1sJ-_<|N2;+;eu?xjcca4A1g;_3ZLea*Y`B@knz9QietYzj*WMbMAqHC81KTF zt)j(WE)OL${5yw1`_ufQ^=k~czbhp$XC{W@A4wLfR~Alj-&U8cgFYOZj)%rviVY1) zHO-~8g(({KzzWeR3O&L}Rt?FkI#vj<_KH_Sf9JcLOKj`>RP>1Wl}n3ONQfkgiQ{JW z-P_zWC`?%*_L*8qM`;?5@;K#8jaXq@{TdA=ljtwJv9URI=92S9r!!KOn%pMm#%Gr; zWA4!RT1FXErj*qi#g4E$6a60NDl}>_W}2s(jV(B&7R#PbyN<#&_DlvQod%{ znnKQ!ycWweB;H^ieVn+hVZfg`GDT%|gvRzAG}+O1!|y<-|6prCMyH>-vj4&7VBx*< zOp}*Wa#jkTAE(YtX;79(K;;Icp+u{cIt65VL2`h(bj+kznn>=W@Q>uaybc#(yDJ&v z^x_Sq3zhiopjUFwb_!0HNmOCk60-1#%1@s+s#miU8xO@W9#zfU?tAa`!4w;jJsL7m zPGuSlH20ybxK3T(AIv1r0Cbp?r}WOUP$6R_#3t}7onKW3*$GlqeI?1lyDNvs2=V5q zp_HhRjQO;=;dnb?+jk8+Bj|n-9eJSnI71`FMAe}`4Y`4&(W9e`&SeW)6ZWSv)sIIDMV(jIA)C|kx8*QmoS1vX}H{?NFMHkYmP2FLD#z%EYs_vt`)y5a2 zEh1Y5s@2~bwKT418j4J8h4O{Z<9~9_9egBSGn-){&E^7-n$jVbnFyWPn^HDHrBvi- zXTTnx0dyRCZf-KktzzUFBQNCRE#@zhsn2Ys8dq#gqsK8P$Mj~ujYC#M$lGVCPIdg zK4p+wd&sAPPYdPPi;>$NU+gl&$`lHpvhaQK{9r@vZP9iv&GEPN)lDg14OgPvQ%n(L zE``oblvao@w{Za;t*YOMA;yIK2M;4jrWvZGrEfNkP_7jgeOy2d34apIhFU7FJbnt&tEN4HJ?y)aB(E6b|TcO{y zzfm#IV6OP66U3m53q*;7Uzq!&F-<3C->%5dDF|0UW6h2a>@cs%Y2fABsc2?C`Fcm~ z6vl=Btg5Z2*(J%~9h#$39;Lt~La5lqC*o0ya%h2GC4>1$X6tV;y;*7J{ok2kQNK^4 zEqLx}^VYg*$uUyNnfnvutH1skoHvmumunuwxbL<_NzU{B>*-fkyLizPj>3s{2HLwV zqfP0T%#bWf=b~l#ZFc1I@$!rn&i%$`ioq6IR(0IPtIXl9y|}ghPwi7eIs+I4#`IwP z(ycLH#i`N@j4fvxmAactiPr@2X7tp&sz*uc{>xY4xyeXZ+IOQjgW3M1QiGW?e${4u zhuXP>hEeV16t_^x?q{ahwD9DRYF#s|-AQBKy&g|gmb#k+#cjahn6OQ@S(hy;_b5u! z;eds^SmBxcolEv6CiQA6F+Y(*)l@P5nbrnX(el767eOrF{k4v2W%TiS--^#Sxjsi^ zYZJR1SH~r?l0}Fs^?3LR1O}q2Wn$pz0$XPRDu0E-Jso9@h9qOsb!*<3=#caqpYk0# zvvu2j*acV5mi?yczN+cw@XW+s4^%EhcZBkMl1;Ixjg%DsICNdMc>69D-%RuXd0)$H z!{L>{Y7IH}_^#3M*|ktR8z6KMkA7_722@%b7HYdM)`pJ z(`LQ6B3)HYM_#ekKbpvBZpuZy)~d%hP8KyZ%xP28m%v@LD_l8jDNOc>z_&l@T&qvo zHYF8-5yrsZx2QLzNW>)$^;}bK8!|NB5pe>AB(A&qT!`gr@ z5m4;?q;*TZ@iR2WMotJ$X&wIU-J2;l{j#7qaKEZ%_4Pl>Y~HRoPZP7z-;2}COpJPbQNqHb=(xmQ3)|iY z!(iS*KgO8Zdc&`?GI8-yd4S2ZnHLX4AACYl=6f08Tqa#aMPLM?1rug$d&jx_5O0H2O9E}P>Y z^8OHMRF|ifbJNegMnl#8CiB+3vf-k`aT-HtF8A_|}4Qq!J6PpzlSEkcJKoFoHY zO%)NJ0<+Wul(qZ#7fr;r1eX!5`19S1uh&rZn+yhyo7fHnpO8RL)d^t>%ZaL*jisW` zCOC{UXSR^;iRP7^aFVnuI@((iK^)YnJ;dT~>7X}XzW-X2AJE>uu~^>$XBvlYHN|q} z{ZIP)kw%;2qk3}vx(XQnD56z*FlM5`YNoyKp+HHii`iEDw;rX!K60%uosvGUr4B0i z5*T+74V89QE5kx+?oN+1eOLGBDRRDP|8ynkDw&p1E z3+N=p`@L3;Wvcm1g3>lOO#*_MNGu9ChlEPRO+USmN`T`1{X)4HlNImMIb{~06A|O} z!X@x?9gHM#AvAG6Q_25+Us%|%Vb1`kmkX)BF z;^!^0SBd>;H1~wjU`YtS%zTA5m_7_gu0a*md9QoB-|>=s45 z6s{~xzN)QMaafgL?j)v45W&^2I1gf87>Sk1+#mVORphCwFc1M=L=c6e2{I!{u3Nj!8ONK0bSZU&VR8T0Kq#1@> zZBzR1=F+-CHU%YDVKSLriF1b)1Pk4kH;Fh&{yoXC-9s)DLnLdSk*x6TV&sQXIPB{6SVLKIjZ!2D)iB;a8s%zJw?Xc)Kb&ucHlNCv9{`&A(61HEjLxBF)NR{VxBG(VMK62ilPQ~t+yaY_BCb!L{<}{d3=DhP+q49ib3(Y| z6^^7bD*?)sJ+~9|hY&6Td(pdf)o=Z^7QUrb7bE{t6BB@c-g^YwpEk6m$#z|wY@S%5 zE>|xOtq!?t`TKmyO^q$;gE(-RW>#l$_1CUHEpaXlqR>e+rFBs!C# z+cdb9lA?fDS}KI|h4wo_#G{;r?v<$AHn+0BySF5*+Ctk!=mp>jFi#l-r~witxY<1< zuIS)N5NuO($^1(f4^{+L80+HP5K7E*7*y{iCO@b1TOrE`;mm9^>O{dx%=UZ z*Y&P}wxx=cc-lO~rw}%-rSnT|kk3jH6(g5PkS)Vl?bb7K2NYM0NKf+TC(|UF5ID~5 zlJrL8R)A;XWd)d{bGJ)P)_!!(a!QIYj=@*BO!e=`CicHnFU*?-1Sj6-N4~N`k zDk5q*=N-C(+cxuC)sCtiaNE#&KqA*H{TxF5GlUdMlFk%db2Q^c=xgMfksWTALJ>f4 z`!8F2a#xx+9MG5VBftwUN&8p(85^ij5qx#wlIEHC^)n8JDi%rbLGCAd{mMI*yowKB1|8|PuhUmEf@btS|RX<5h9U7VWMc;2Gm`^U!7l!h?{|uis zgx^xzZ%{8aRp|#{G2jf6hb!?fPe+6Ais-z0MUhiVa}OuuH;UQj^>m7e!@2QVqge81 zfHr_-M(_B2`A$KT82P2*gM)wUX!z_w^;l;4S_pSd$!HNyQ%rn z=a>mKu}4q;R)s~V-$7|}CshW47TE>!bnN~d(z!?o7acS3GG{F?yJ+a6ph&dZD7vV zDsc<+zS;{}@3GhSlx=#`ZoeuHKNhA5leXmiE7pD`SiNj3A!k~tieq@MB~|E*ZWZU8 z)sjL;NR#8%mQsZZjW4y8C&0RT{F-%C-?Ddi@EE;)Do0teUAxpd@c<25%moGL;^2LH z;z9VCt+fe|4;G(kNO)~VFhHWb`EiZ66}2=SKdNvoZs=iU;)%^xW!I+Wqk?3yQp-53GD z*oC-NHyPQ(rgXaqA_k?2qKP<;|K0aqd2t0gC*!Ie55*-~g((6W?ltA4JNJ&z@!I)d zi_^!pKON0qcY3W(m=Jp0g`Y^IHqO2MKphR@zM_uv!rH@klx}WHdGNZ$>its&$I!9L zeuC}Nc^Zm3zdW+qrnu_&QNNn2T4mDt$O#BB$*fp>XE^gOyW&RKxlm3DA)$7Fuoy6h zm3$W;MZB01NV`;hv}*-r(1e*&=6ov6(T2?=Xm2hN`x#>wzm5{P3B#_Zl#MirHDFTi4E;g zN^oS~HdIr=Q%0xO6N}1GyVOTw z`X+!VU|OXEilPn+A~4dftD|rhLKyo9j5XdAJF@(gEA7~2izL-80&`p_Kg_TEeuBBV z^s{Xg(w3RzRV4n1Nm#sM!mDda(`^`V#T=ic0c;g0f+HEuR905`db8iS?A|>SSRGW` zcjWN(zo*`NLC7Xa^zCyDl#}<=j)Y#jmuH%bF_+esho_Cy?XShS>OB*Ajn%#rQ(j&A2q4olKdr}oIftF~1WdF!;$879+p6Qs zG&`7Q*8CtPatZ??D{*dLgW<_x7b*JO=JqTZG@hNI276 z5wy5X+-7FZ!}5>*ZVic}Pj_~EIb8CX*kdv~3_5b|)r0Y$^NYri(9|Y%1F-v7Zq`#O zKkhTywfhug(VjpPojD6@)WECvqj9MKCLX}ln<6+(m(CJP*vEysYaH?u-3g`)BoOh ziA_;yVfXIK!~i3=8?5Fq(0N>yOQ(Fg^lmW%RWu`jNIf8JR4%k6pi`XE7S7}bg|bd* zn4YMRk)UV{%MI@>@9p~~^#12Pi8aSBn7DEb)mfPuOa}vF{@sf{e^i}7J^Ae3-|jR} zb3}kkh0N_V#fEtqerEAE>qlUAJxd;E8H(K+ zK)G-b#N*d26t>ZX|F92*gSzI7ZL8dthIM zZM4g3I)F6T9QMvK)l@TF`?r&x+XIn!=_HEmGqx%;w?Ze8^ZZcw;Z?P-couME3U)OT z;@5)Y8L7u-suAG>VC(cXB`hoXsL0qVk#g^ZZ1Dr!2}CD7l+JIPc3{}pdeer+%s4^k zd{9Kkz{xe#r$)@`){%9W0ll6%hVHS+QkSq1iNk*k^_n`Di4NfAiFi$YV&K&g(@wu+ zc7iIQ&S&9tERFxawyyoH$!l3BM8JZ^7Dd}M6uS!q0wuK)6%dH6-3S2{1q6zKh>9Q( zqKF_QQMZSRA`uKA7sC+|gg_v26A%JbNkGHq8suWAT+%>8@U16n z&6+jyzVFP)JT4oxES!S!Mo9b*$NA>6P@K+Dp;4+aL?D3Z+Y1uyU!mFYF0j&M#GqNs zh_$WJA)TB3h0{#=!GmZ`yK7(X`L#uv zPz_Mg-@lQ#ikf;s_@`~S+T*dtc1>EyiC}+*4bcp6rR-HJ>7QaXc{3N&VY)Y|$m)wDnJ#7q;+LaCrth~5<1hTS ziI(mA#(pb5Gw6stzq_0s?>AF-u;F{_v@IJL`U9v7NxBsi-JcK3o+r_B)S%`BOb>>b z%46j>a)bXI&a!r)e-!KfB2sN?Y)%_amKmFEtOa0i=vT0>j&12@L>~(aE3k8W+V=iM z(14+Rwo%i2sk>usAn3&!aePMDiFmI!Ksy=bXn56Wb@7&hm(@gQ_)h1 z*I3*qO)E7fslr(_PgFnL483f|$bHXg<#z*wi=9`eV|{_i3AnCOG5Y2b3ER!-Ol9Gz z&`gbzT12tC_McYXTV@}G4tP6b{xN5zFUKKcP?M?82Zb5wfx+9AH=|JIlv+LTZOlk% zJ6UvP*a8+oC`Q8}GLhNAJamuYcXm9J5PrT5R)j&T))u-Y?o2HZS{G*n5wWUFZ4 z>CFE&rVW}1mR6U~rTQWd|G3Zd!{Y+GZx-LM9q=)_@(L|pbJo(aRa&nzqNqB2kA*eE z6IynF+l`F5ta4lrI%nED(qs9ayKTYM0U!ug)po_RVetH8u6T4QLz(|Bicw54^-I5< z^>Thkqv(ZKWhq&%n%{@?)?^a*p_A_4AZ}zvJ>Sf(X|?Y2k?}jP4&>0{sV>`JOji2( zP&Sf2XA%MAK9uH5oA(d`y_0DnG?2U4cxwRh!u#q&B6O*md^C)w!&L_CSZo+Wp$<7E z_YrPqL5Cg92O3_Qe47!TIkdX1Q3>>-CeB!rZ-ZPeAZKZFoq@+6DovT<`G%SBsIxD^Y{31j;08z1&9W!EMiawXz zQu(IkuaFcT>j#?J-d&!@zpF9^1dPDRgxZew=W09A;5?F^L=-y8XF+3>I5GiY^LjN` z@(DBUMpGX_;-GyZ(l^KW9Qd2^e+8h+W=77Ju?X}h!4RoYiHp~ocZbt%e z_=$0%K^d(WO9~x+9aFtypRJD7iv=&6@3cK&(T2ML5U(bM(DnYU;_4t|zy<@v`s#{j z4Ul^W;|rsdwQ^@k-4spi*R%WWg`+d-n0awIYtZ5JzeQv=t(w&^08)os>POMe`(2Z^ zBoCoFq$Bm3GNL-CqFvC8`S!*OEvisYVw&5dC1_ptX^#^OrOxKB`&H$ZKzLZ=FKwsS z4I=eWEJ#`PcfWD=(yw$#Ws6i>_8H5{?h zG*`cIVFO8j7>^w|+31ESo_pGZL0PdFN6*p4lZ~ly6F*y$(TC^LHu>MlChYheu0dXZ zyM&A4J)oBYI>B@{MzQKFFdSh3b4t5)ebwksDHG%N?D#tCpsy<@ZLBl^w zj@2Mhuq&{L(a50dv&I{$EAD;`_Z~{%b~F)P86L{GTlpFZjKYT*>ux)ZP)luS!7FiI*087PxD$2=B|f}?HqWmi`+Z2%5TjMz(Yjp)njnthS_!bRX<-R z{kZp7fgQ8wIMX8&Yvvj|IvnC|>uPcix(or5ZpO#%C2D%*0gr!r6K^ITF>8z*=}$3C zSFjt|sFEYgzlLyh%Pzj1xFb{!jaQ7WR(%}Dw=B3R9Bren0Mj~&a7E^t$iLZdn}_J- zET4X&;jL_xtft_?2=GZz_iB50RJW!_15vzIXe#+j-=Kp33g?k?96K_zM^*9C^Am%` zPF_r*Punn#){FM_sMBFZ!pY0%T4#CMgqIQ1Lu|}v9m;iq8b}{!%3V~z7cSRaB!J^DYqn-Qovl_>wF11Z=>y?6#Z+}+8#D!&ZNZ=hb}sw2 zok;36BALVR<^zfH^8Lf)i?v;cB|x@vsAn64&vZ+_RT~WUa8fM2i>_Rzn0EpFoSxX= zgD!1G);=KYD%!LQyyp9_lbtP*MYYSWK*7X6!n%(!ie$8DYw(_VOI~5~vI*1siVT0; zNg=;;KD?V2Q>RL6Rf<5oBlyc!T%3tF?R4KmU1pB~lh~RZH@7)9Yp9jhrp0TZ3`PoF zXMY({%Z+uUVy0dpW}a4&THTnBL4BUHHbqW3&)hOByFbtnju!#poF|NaWO*s1E(dtj z{Kj1o6B!jKIEqt9Z)t>=)7m?;-Iu!!^=OW1H2N$_t zP~Tfki1qXO4W)6uR!pJ}WsKbKqQjd3nDuPa07vq5j94nMj#=KVsBZ<&bkivn;S+^B zG;R>YS{a$V2+r8sKt1rOmwFYTys$V0SiFw!*fJQ;3Aa=XN+v>d?$F>7Rs2ow}RH|#Ro^b~V)VzF6>2Gnjgu{B5F!Pf-l1X9wxaPJ6oJRkcyXVZ(bc249jC%5oQ65oE9>Tl; zw;as*Pazts9WENRA@*u|87FJ}{B(A;1?gkmx77MS8->;sO!YHx^A1g`j8ta*B;n{<7j<9}>bJ$lpX#f_B&u#IAe5Vf#u{IRjHP=)^Uw0RF6iYkw?^eL5+R)KybDP#XH-4;Eo##h@sv+?~%|GA%6VJ~;{Z3%V1#lIY404M``xFn4$vX!ASjWS-G#`|2Q?-Fq z_3R_q2&nRo7God~iaO@+z~LW|lGU`vIvx}#x$L(%mE`2OcXHGN0E*Jzroq)+NUUPE#JZ%Hw04dmknAjm)h=RCsVBi z$AEGP4txbf$?q!O;_pQT|Ek6cmHmssy!|T91@r$C6;vZwyHd4I;XgN@9Q~8}`1=#V b|5&A;kcF@}bo4kP;OF$y!TqH_1YG?O)-?;7 diff --git a/common/dist/sprites/spritesmith-main-6.css b/common/dist/sprites/spritesmith-main-6.css index 60fa7a0a5f..498fc4512c 100644 --- a/common/dist/sprites/spritesmith-main-6.css +++ b/common/dist/sprites/spritesmith-main-6.css @@ -682,7 +682,7 @@ width: 32px; height: 32px; } -.shop_spookDust { +.shop_spookySparkles { background-image: url(spritesmith-main-6.png); background-position: -790px -1176px; width: 32px; @@ -730,7 +730,7 @@ width: 40px; height: 40px; } -.shop_heallAll { +.shop_healAll { background-image: url(spritesmith-main-6.png); background-position: -791px -1643px; width: 40px; diff --git a/common/img/sprites/spritesmith/achievements/achievement-spookDust.png b/common/img/sprites/spritesmith/achievements/achievement-spookySparkles.png similarity index 100% rename from common/img/sprites/spritesmith/achievements/achievement-spookDust.png rename to common/img/sprites/spritesmith/achievements/achievement-spookySparkles.png diff --git a/common/img/sprites/spritesmith/achievements/achievement-spookDust2x.png b/common/img/sprites/spritesmith/achievements/achievement-spookySparkles2x.png similarity index 100% rename from common/img/sprites/spritesmith/achievements/achievement-spookDust2x.png rename to common/img/sprites/spritesmith/achievements/achievement-spookySparkles2x.png diff --git a/common/img/sprites/spritesmith/misc/spookman.png b/common/img/sprites/spritesmith/misc/ghost.png similarity index 100% rename from common/img/sprites/spritesmith/misc/spookman.png rename to common/img/sprites/spritesmith/misc/ghost.png diff --git a/common/img/sprites/spritesmith/misc/inventory_special_spookDust.png b/common/img/sprites/spritesmith/misc/inventory_special_spookySparkles.png similarity index 100% rename from common/img/sprites/spritesmith/misc/inventory_special_spookDust.png rename to common/img/sprites/spritesmith/misc/inventory_special_spookySparkles.png diff --git a/common/img/sprites/spritesmith/shop/shop_spookDust.png b/common/img/sprites/spritesmith/shop/shop_spookySparkles.png similarity index 100% rename from common/img/sprites/spritesmith/shop/shop_spookDust.png rename to common/img/sprites/spritesmith/shop/shop_spookySparkles.png diff --git a/common/img/sprites/spritesmith/skills/shop_heallAll.png b/common/img/sprites/spritesmith/skills/shop_healAll.png similarity index 100% rename from common/img/sprites/spritesmith/skills/shop_heallAll.png rename to common/img/sprites/spritesmith/skills/shop_healAll.png diff --git a/common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png b/common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png new file mode 100644 index 0000000000000000000000000000000000000000..83e3f90f39cd2eca1cfeb71a6f428764218b6e6c GIT binary patch literal 3801 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 P00000NkvXXu0mjfq_jD? literal 0 HcmV?d00001 diff --git a/common/index.js b/common/index.js index 475d444df3..04189fa8ab 100644 --- a/common/index.js +++ b/common/index.js @@ -1,4 +1,6 @@ -var pathToCommon; +'use strict'; + +let pathToCommon; if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env pathToCommon = './transpiled-babel/index'; diff --git a/common/locales/en/challenge.json b/common/locales/en/challenge.json index cffe8c4a38..c014e1f6db 100644 --- a/common/locales/en/challenge.json +++ b/common/locales/en/challenge.json @@ -74,9 +74,10 @@ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", "onlyLeaderUpdateChal": "Only the challenge leader can update it.", "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", + "shortNameTooShort": "Tag Name must have at least 3 characters." } diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json index 4d8ea0716c..2e0f245232 100644 --- a/common/locales/en/groups.json +++ b/common/locales/en/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } diff --git a/common/locales/en/limited.json b/common/locales/en/limited.json index 2407b788fe..d9f3ff5ba0 100644 --- a/common/locales/en/limited.json +++ b/common/locales/en/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Annoying Friends", "annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.", "alarmingFriends": "Alarming Friends", - "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Agricultural Friends", "agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.", "aquaticFriends": "Aquatic Friends", diff --git a/common/locales/en/maintenance.json b/common/locales/en/maintenance.json index efdb524cd2..20b3410de2 100644 --- a/common/locales/en/maintenance.json +++ b/common/locales/en/maintenance.json @@ -1,6 +1,6 @@ { "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", + "importantMaintenance": "We are doing important maintenance that we estimate will last until 10pm Pacific Time (5am UTC).", "maintenance": "Maintenance", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", diff --git a/common/locales/en/pets.json b/common/locales/en/pets.json index 7cd716b72d..7d13c8bc53 100644 --- a/common/locales/en/pets.json +++ b/common/locales/en/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Ethereal Lion", "veteranWolf": "Veteran Wolf", "veteranTiger": "Veteran Tiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberus Pup", "hydra": "Hydra", "mantisShrimp": "Mantis Shrimp", diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json index daffc88b5a..727c04774c 100644 --- a/common/locales/en/settings.json +++ b/common/locales/en/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Custom Day Start", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Misc", diff --git a/common/locales/en/spells.json b/common/locales/en/spells.json index 37dc7eaad4..5eb5d18be1 100644 --- a/common/locales/en/spells.json +++ b/common/locales/en/spells.json @@ -52,8 +52,8 @@ "spellSpecialSaltText": "Salt", "spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!", - "spellSpecialSpookDustText": "Spooky Sparkles", - "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Opaque Potion", "spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.", @@ -70,7 +70,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 197994da08..8c0d453727 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -134,7 +134,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/en/tasks.json b/common/locales/en/tasks.json index 5b32829f81..b9180af3bf 100644 --- a/common/locales/en/tasks.json +++ b/common/locales/en/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Clear", "hideTags": "Hide", "showTags": "Show", + "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.", diff --git a/common/script/constants.js b/common/script/constants.js index d6e0aa9384..040b968f8f 100644 --- a/common/script/constants.js +++ b/common/script/constants.js @@ -1,3 +1,5 @@ export const MAX_HEALTH = 50; export const MAX_LEVEL = 100; export const MAX_STAT_POINTS = MAX_LEVEL; +export const ATTRIBUTES = ['str', 'int', 'per', 'con']; +export const TAVERN_ID = '00000000-0000-4000-A000-000000000000'; diff --git a/common/script/content/index.js b/common/script/content/index.js index 18b46786bb..8164d4e0e6 100644 --- a/common/script/content/index.js +++ b/common/script/content/index.js @@ -426,6 +426,7 @@ api.specialPets = { 'Phoenix-Base': 'phoenix', 'Turkey-Gilded': 'gildedTurkey', 'MagicalBee-Base': 'magicalBee', + 'Lion-Veteran': 'veteranLion', }; api.specialMounts = { @@ -601,6 +602,14 @@ api.questMounts = _.transform(api.questEggs, function(m, egg) { })); }); +api.premiumMounts = _.transform(api.dropEggs, function(m, egg) { + return _.defaults(m, _.transform(api.hatchingPotions, function(m2, pot) { + if (pot.premium) { + return m2[egg.key + "-" + pot.key] = true; + } + })); +}); + api.food = { Meat: { text: t('foodMeat'), diff --git a/common/script/content/spells.js b/common/script/content/spells.js index 3150bd0da9..9b0514974d 100644 --- a/common/script/content/spells.js +++ b/common/script/content/spells.js @@ -1,6 +1,6 @@ import t from './translation'; import _ from 'lodash'; - +import { NotAuthorized } from '../libs/errors'; /* --------------------------------------------------------------- Spells @@ -15,7 +15,7 @@ import _ from 'lodash'; web, this function can be performed on the client and on the server. `user` param is self (needed for determining your own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells, you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the - spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are + spell is correct. Take a look at habitrpg/website/server/models/user.js and habitrpg/website/server/models/task.js for what attributes are available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users, so you'll want to iterate over them like: `_.each(target,function(member){...})` @@ -40,14 +40,12 @@ spells.wizard = { lvl: 11, target: 'task', notes: t('spellWizardFireballNotes'), - cast (user, target) { + cast (user, target, req) { let bonus = user._statsComputed.int * user.fns.crit('per'); bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075); user.stats.exp += diminishingReturns(bonus, 75); if (!user.party.quest.progress) user.party.quest.progress = 0; user.party.quest.progress.up += Math.ceil(user._statsComputed.int * 0.1); - // TODO change, pass req to spell? - let req = {language: user.preferences.language}; user.fns.updateStats(user.stats, req); }, }, @@ -166,12 +164,11 @@ spells.rogue = { lvl: 12, target: 'task', notes: t('spellRogueBackStabNotes'), - cast (user, target) { + cast (user, target, req) { let _crit = user.fns.crit('str', 0.3); let bonus = calculateBonus(target.value, user._statsComputed.str, _crit); user.stats.exp += diminishingReturns(bonus, 75, 50); user.stats.gp += diminishingReturns(bonus, 18, 75); - let req = {language: user.preferences.language}; user.fns.updateStats(user.stats, req); }, }, @@ -197,7 +194,7 @@ spells.rogue = { notes: t('spellRogueStealthNotes'), cast (user) { if (!user.stats.buffs.stealth) user.stats.buffs.stealth = 0; - user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.dailys.length * 0.64, 55)); + user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.tasksOrder.dailys.length * 0.64, 55)); }, }, }; @@ -218,10 +215,10 @@ spells.healer = { text: t('spellHealerBrightnessText'), mana: 15, lvl: 12, - target: 'self', + target: 'tasks', notes: t('spellHealerBrightnessNotes'), - cast (user) { - _.each(user.tasks, (task) => { + cast (user, tasks) { + _.each(tasks, (task) => { if (task.type !== 'reward') { task.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40)); } @@ -242,7 +239,7 @@ spells.healer = { }); }, }, - heallAll: { // Blessing + healAll: { // Blessing text: t('spellHealerHealAllText'), mana: 25, lvl: 14, @@ -262,11 +259,13 @@ spells.special = { text: t('spellSpecialSnowballAuraText'), mana: 0, value: 15, + previousPurchase: true, target: 'user', notes: t('spellSpecialSnowballAuraNotes'), - cast (user, target) { + cast (user, target, req) { + if (!user.items.special.snowball) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = true; - target.stats.buffs.spookDust = false; + target.stats.buffs.spookySparkles = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; if (!target.achievements.snowball) target.achievements.snowball = 0; @@ -286,20 +285,22 @@ spells.special = { user.stats.gp -= 5; }, }, - spookDust: { - text: t('spellSpecialSpookDustText'), + spookySparkles: { + text: t('spellSpecialSpookySparklesText'), mana: 0, value: 15, + previousPurchase: true, target: 'user', - notes: t('spellSpecialSpookDustNotes'), - cast (user, target) { + notes: t('spellSpecialSpookySparklesNotes'), + cast (user, target, req) { + if (!user.items.special.spookySparkles) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; - target.stats.buffs.spookDust = true; + target.stats.buffs.spookySparkles = true; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; - if (!target.achievements.spookDust) target.achievements.spookDust = 0; - target.achievements.spookDust++; - user.items.special.spookDust--; + if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0; + target.achievements.spookySparkles++; + user.items.special.spookySparkles--; }, }, opaquePotion: { @@ -310,7 +311,7 @@ spells.special = { target: 'self', notes: t('spellSpecialOpaquePotionNotes'), cast (user) { - user.stats.buffs.spookDust = false; + user.stats.buffs.spookySparkles = false; user.stats.gp -= 5; }, }, @@ -318,11 +319,13 @@ spells.special = { text: t('spellSpecialShinySeedText'), mana: 0, value: 15, + previousPurchase: true, target: 'user', notes: t('spellSpecialShinySeedNotes'), - cast (user, target) { + cast (user, target, req) { + if (!user.items.special.shinySeed) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; - target.stats.buffs.spookDust = false; + target.stats.buffs.spookySparkles = false; target.stats.buffs.shinySeed = true; target.stats.buffs.seafoam = false; if (!target.achievements.shinySeed) target.achievements.shinySeed = 0; @@ -346,11 +349,13 @@ spells.special = { text: t('spellSpecialSeafoamText'), mana: 0, value: 15, + previousPurchase: true, target: 'user', notes: t('spellSpecialSeafoamNotes'), - cast (user, target) { + cast (user, target, req) { + if (!user.items.special.seafoam) throw new NotAuthorized(t('spellNotOwned')(req.language)); target.stats.buffs.snowball = false; - target.stats.buffs.spookDust = false; + target.stats.buffs.spookySparkles = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = true; if (!target.achievements.seafoam) target.achievements.seafoam = 0; @@ -391,7 +396,10 @@ spells.special = { if (!target.items.special.nyeReceived) target.items.special.nyeReceived = []; target.items.special.nyeReceived.push(user.profile.name); + + if (!target.flags) target.flags = {}; target.flags.cardReceived = true; + user.stats.gp -= 10; }, }, @@ -416,7 +424,10 @@ spells.special = { if (!target.items.special.valentineReceived) target.items.special.valentineReceived = []; target.items.special.valentineReceived.push(user.profile.name); + + if (!target.flags) target.flags = {}; target.flags.cardReceived = true; + user.stats.gp -= 10; }, }, @@ -440,7 +451,10 @@ spells.special = { if (!target.items.special.greetingReceived) target.items.special.greetingReceived = []; target.items.special.greetingReceived.push(user.profile.name); + + if (!target.flags) target.flags = {}; target.flags.cardReceived = true; + user.stats.gp -= 10; }, }, @@ -465,7 +479,10 @@ spells.special = { if (!target.items.special.thankyouReceived) target.items.special.thankyouReceived = []; target.items.special.thankyouReceived.push(user.profile.name); + + if (!target.flags) target.flags = {}; target.flags.cardReceived = true; + user.stats.gp -= 10; }, }, @@ -487,9 +504,13 @@ spells.special = { u.achievements.birthday++; }); } + if (!target.items.special.birthdayReceived) target.items.special.birthdayReceived = []; target.items.special.birthdayReceived.push(user.profile.name); + + if (!target.flags) target.flags = {}; target.flags.cardReceived = true; + user.stats.gp -= 10; }, }, @@ -499,8 +520,8 @@ _.each(spells, (spellClass) => { _.each(spellClass, (spell, key) => { spell.key = key; let _cast = spell.cast; - spell.cast = function castSpell (user, target) { - _cast(user, target); + spell.cast = function castSpell (user, target, req) { + _cast(user, target, req); user.stats.mp -= spell.mana; }; }); diff --git a/common/script/cron.js b/common/script/cron.js index 2c0760fcc9..a0f28f9d5a 100644 --- a/common/script/cron.js +++ b/common/script/cron.js @@ -1,3 +1,4 @@ +// TODO what can be moved to /website/server? /* ------------------------------------------------------ Cron and time / day functions diff --git a/common/script/fns/autoAllocate.js b/common/script/fns/autoAllocate.js index ab037e628b..71a4898031 100644 --- a/common/script/fns/autoAllocate.js +++ b/common/script/fns/autoAllocate.js @@ -7,50 +7,69 @@ import splitWhitespace from '../libs/splitWhitespace'; {update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately */ -module.exports = function(user) { - return user.stats[(function() { - var diff, ideal, lvlDiv7, preference, stats, suggested; - switch (user.preferences.allocationMode) { - case "flat": - stats = _.pick(user.stats, splitWhitespace('con str per int')); - return _.invert(stats)[_.min(stats)]; - case "classbased": - lvlDiv7 = user.stats.lvl / 7; - ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7]; - preference = (function() { - switch (user.stats["class"]) { - case "wizard": - return ["int", "per", "con", "str"]; - case "rogue": - return ["per", "str", "int", "con"]; - case "healer": - return ["con", "int", "str", "per"]; - default: - return ["str", "con", "per", "int"]; - } - })(); - diff = [user.stats[preference[0]] - ideal[0], user.stats[preference[1]] - ideal[1], user.stats[preference[2]] - ideal[2], user.stats[preference[3]] - ideal[3]]; - suggested = _.findIndex(diff, (function(val) { - if (val === _.min(diff)) { - return true; - } - })); - if (~suggested) { - return preference[suggested]; - } else { - return "str"; - } - case "taskbased": - suggested = _.invert(user.stats.training)[_.max(user.stats.training)]; - _.merge(user.stats.training, { - str: 0, - int: 0, - con: 0, - per: 0 - }); - return suggested || "str"; - default: - return "str"; +function getStatToAllocate (user) { + let suggested; + + let statsObj = user.stats.toObject ? user.stats.toObject() : user.stats; + + switch (user.preferences.allocationMode) { + case 'flat': { + let stats = _.pick(statsObj, splitWhitespace('con str per int')); + return _.invert(stats)[_.min(stats)]; } - })()]++; + case 'classbased': { + let lvlDiv7 = statsObj.lvl / 7; + let ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7]; + + let preference; + switch (statsObj.class) { + case 'wizard': { + preference = ['int', 'per', 'con', 'str']; + break; + } + case 'rogue': { + preference = ['per', 'str', 'int', 'con']; + break; + } + case 'healer': { + preference = ['con', 'int', 'str', 'per']; + break; + } + default: { + preference = ['str', 'con', 'per', 'int']; + } + } + + let diff = [ + statsObj[preference[0]] - ideal[0], + statsObj[preference[1]] - ideal[1], + statsObj[preference[2]] - ideal[2], + statsObj[preference[3]] - ideal[3], + ]; + + suggested = _.findIndex(diff, (val) => { + if (val === _.min(diff)) return true; + }); + + return suggested !== -1 ? preference[suggested] : 'str'; + } + case 'taskbased': { + suggested = _.invert(statsObj.training)[_.max(statsObj.training)]; + + let training = statsObj.training; + training.str = 0; + training.int = 0; + training.con = 0; + training.per = 0; + + return suggested || 'str'; + } + default: { + return 'str'; + } + } +} + +module.exports = function autoAllocate (user) { + return user.stats[getStatToAllocate(user)]++; }; diff --git a/common/script/fns/crit.js b/common/script/fns/crit.js index 69ac9e5b93..65a0cbb062 100644 --- a/common/script/fns/crit.js +++ b/common/script/fns/crit.js @@ -1,13 +1,8 @@ -module.exports = function(user, stat, chance) { - var s; - if (stat == null) { - stat = 'str'; - } - if (chance == null) { - chance = .03; - } - s = user._statsComputed[stat]; - if (user.fns.predictableRandom() <= chance * (1 + s / 100)) { +import predictableRandom from './predictableRandom'; + +module.exports = function crit (user, stat = 'str', chance = 0.03) { + let s = user._statsComputed[stat]; + if (predictableRandom(user) <= chance * (1 + s / 100)) { return 1.5 + 4 * s / (s + 200); } else { return 1; diff --git a/common/script/fns/cron.js b/common/script/fns/cron.js deleted file mode 100644 index 1e3470d7f9..0000000000 --- a/common/script/fns/cron.js +++ /dev/null @@ -1,358 +0,0 @@ -import moment from 'moment'; -import _ from 'lodash'; -import { - daysSince, - shouldDo, -} from '../cron'; -import { - capByLevel, - toNextLevel, -} from '../statHelpers'; -/* - ------------------------------------------------------ - Cron - ------------------------------------------------------ - */ - -/* - At end of day, add value to all incomplete Daily & Todo tasks (further incentive) - For incomplete Dailys, deduct experience - Make sure to run this function once in a while as server will not take care of overnight calculations. - And you have to run it every time client connects. - {user} - */ - -module.exports = function(user, options) { - var _progress, analyticsData, base, base1, base2, base3, base4, clearBuffs, dailyChecked, dailyDueUnchecked, daysMissed, expTally, lvl, lvlDiv2, multiDaysCountAsOneDay, now, perfect, plan, progress, ref, ref1, ref2, ref3, todoTally, timezoneOffsetFromUserPrefs, timezoneOffsetFromBrowser, timezoneOffsetAtLastCron; - if (options == null) { - options = {}; - } - now = +options.now || +(new Date); - - // If the user's timezone has changed (due to travel or daylight savings), - // cron can be triggered twice in one day, so we check for that and use - // both timezones to work out if cron should run. - // CDS = Custom Day Start time. - timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; - timezoneOffsetAtLastCron = (_.isFinite(user.preferences.timezoneOffsetAtLastCron)) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; - timezoneOffsetFromBrowser = (_.isFinite(+options.timezoneOffset)) ? +options.timezoneOffset : timezoneOffsetFromUserPrefs; - // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults - - if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { - // The user's browser has just told Habitica that the user's timezone has - // changed so store and use the new zone. - user.preferences.timezoneOffset = timezoneOffsetFromBrowser; - timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; - } - - // How many days have we missed using the user's current timezone: - daysMissed = daysSince(user.lastCron, _.defaults({ - now: now - }, user.preferences)); - - if (timezoneOffsetAtLastCron != timezoneOffsetFromUserPrefs) { - // Since cron last ran, the user's timezone has changed. - // How many days have we missed using the old timezone: - let daysMissedNewZone = daysMissed; - let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ - now: now, - timezoneOffsetOverride: timezoneOffsetAtLastCron, - }, user.preferences)); - - if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { - // The timezone change was in the unsafe direction. - // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). - // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). - // Local time changed from, for example, 03:00 to 02:00. - - if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { - // Both old and new timezones indicate that we SHOULD run cron, so - // it is safe to do so immediately. - daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); - // use minimum value to be nice to user - } - else if (daysMissedOldZone > 0) { - // The old timezone says that cron should run; the new timezone does not. - // This should be impossible for this direction of timezone change, but - // just in case I'm wrong... - console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens - } - else if (daysMissedNewZone > 0) { - // The old timezone says that cron should NOT run -- i.e., cron has - // already run today, from the old timezone's point of view. - // The new timezone says that cron SHOULD run, but this is almost - // certainly incorrect. - // This happens when cron occurred at a time soon after the CDS. When - // you reinterpret that time in the new timezone, it looks like it - // was before the CDS, because local time has stepped backwards. - // To fix this, rewrite the cron time to a time that the new - // timezone interprets as being in today. - - daysMissed = 0; // prevent cron running now - let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; - // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 - - user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); - // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. - // From now on we can ignore the old timezone: - user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; - } - else { - // Both old and new timezones indicate that cron should - // NOT run. - daysMissed = 0; // prevent cron running now - } - } - else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { - daysMissed = daysMissedNewZone; - // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). - } - } - - if (!(daysMissed > 0)) { - return; - } - user.auth.timestamps.loggedin = new Date(); - user.lastCron = now; - user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; - if (user.items.lastDrop.count > 0) { - user.items.lastDrop.count = 0; - } - perfect = true; - clearBuffs = { - str: 0, - int: 0, - per: 0, - con: 0, - stealth: 0, - streaks: false - }; - plan = (ref = user.purchased) != null ? ref.plan : void 0; - if (plan != null ? plan.customerId : void 0) { - if (typeof plan.dateUpdated === "undefined") { - // partial compensation for bug in subscription creation - https://github.com/HabitRPG/habitrpg/issues/6682 - plan.dateUpdated = new Date(); - } - if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) { - plan.gemsBought = 0; - plan.dateUpdated = new Date(); - _.defaults(plan.consecutive, { - count: 0, - offset: 0, - trinkets: 0, - gemCapExtra: 0 - }); - plan.consecutive.count++; - if (plan.consecutive.offset > 0) { - plan.consecutive.offset--; - } else if (plan.consecutive.count % 3 === 0) { - plan.consecutive.trinkets++; - plan.consecutive.gemCapExtra += 5; - if (plan.consecutive.gemCapExtra > 25) { - plan.consecutive.gemCapExtra = 25; - } - } - } - if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) { - _.merge(plan, { - planId: null, - customerId: null, - paymentMethod: null - }); - _.merge(plan.consecutive, { - count: 0, - offset: 0, - gemCapExtra: 0 - }); - if (typeof user.markModified === "function") { - user.markModified('purchased.plan'); - } - } - } - if (user.preferences.sleep === true) { - user.stats.buffs = clearBuffs; - user.dailys.forEach(function(daily) { - var completed, repeat, thatDay; - completed = daily.completed, repeat = daily.repeat; - thatDay = moment(now).subtract({ - days: 1 - }); - if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { - _.each(daily.checklist, (function(box) { - box.completed = false; - return true; - })); - } - return daily.completed = false; - }); - return; - } - multiDaysCountAsOneDay = true; - todoTally = 0; - user.todos.forEach(function(task) { - var absVal, completed, delta, id; - if (!task) { - return; - } - id = task.id, completed = task.completed; - delta = user.ops.score({ - params: { - id: task.id, - direction: 'down' - }, - query: { - times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : { - 1: daysMissed - }, - cron: true - } - }); - absVal = completed ? Math.abs(task.value) : task.value; - return todoTally += absVal; - }); - dailyChecked = 0; - dailyDueUnchecked = 0; - if ((base = user.party.quest.progress).down == null) { - base.down = 0; - } - user.dailys.forEach(function(task) { - var EvadeTask, completed, delta, fractionChecked, id, j, n, ref1, ref2, scheduleMisses, thatDay; - if (!task) { - return; - } - id = task.id, completed = task.completed; - EvadeTask = 0; - scheduleMisses = daysMissed; - if (completed) { - dailyChecked += 1; - } else { - scheduleMisses = 0; - for (n = j = 0, ref1 = daysMissed; 0 <= ref1 ? j < ref1 : j > ref1; n = 0 <= ref1 ? ++j : --j) { - thatDay = moment(now).subtract({ - days: n + 1 - }); - if (shouldDo(thatDay.toDate(), task, user.preferences)) { - scheduleMisses++; - if (user.stats.buffs.stealth) { - user.stats.buffs.stealth--; - EvadeTask++; - } - if (multiDaysCountAsOneDay) { - break; - } - } - } - if (scheduleMisses > EvadeTask) { - perfect = false; - if (((ref2 = task.checklist) != null ? ref2.length : void 0) > 0) { - fractionChecked = _.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 0) / task.checklist.length; - dailyDueUnchecked += 1 - fractionChecked; - dailyChecked += fractionChecked; - } else { - dailyDueUnchecked += 1; - } - delta = user.ops.score({ - params: { - id: task.id, - direction: 'down' - }, - query: { - times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : { - 1: scheduleMisses - EvadeTask - }, - cron: true - } - }); - user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); - } - } - (task.history != null ? task.history : task.history = []).push({ - date: +(new Date), - value: task.value - }); - task.completed = false; - if (completed || (scheduleMisses > 0)) { - return _.each(task.checklist, (function(i) { - i.completed = false; - return true; - })); - } - }); - user.habits.forEach(function(task) { - if (task.up === false || task.down === false) { - if (Math.abs(task.value) < 0.1) { - return task.value = 0; - } else { - return task.value = task.value / 2; - } - } - }); - ((base1 = (user.history != null ? user.history : user.history = {})).todos != null ? base1.todos : base1.todos = []).push({ - date: now, - value: todoTally - }); - expTally = user.stats.exp; - lvl = 0; - while (lvl < (user.stats.lvl - 1)) { - lvl++; - expTally += toNextLevel(lvl); - } - ((base2 = user.history).exp != null ? base2.exp : base2.exp = []).push({ - date: now, - value: expTally - }); - if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) { - user.fns.preenUserHistory(); - if (typeof user.markModified === "function") { - user.markModified('history'); - } - if (typeof user.markModified === "function") { - user.markModified('dailys'); - } - } - user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(capByLevel(user.stats.lvl) / 2), { - str: lvlDiv2, - int: lvlDiv2, - per: lvlDiv2, - con: lvlDiv2, - stealth: 0, - streaks: false - }) : clearBuffs; - if (dailyDueUnchecked === 0 && dailyChecked === 0) { - dailyChecked = 1; - } - user.stats.mp += _.max([10, .1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked); - if (user.stats.mp > user._statsComputed.maxMP) { - user.stats.mp = user._statsComputed.maxMP; - } - progress = user.party.quest.progress; - _progress = _.cloneDeep(progress); - _.merge(progress, { - down: 0, - up: 0 - }); - progress.collect = _.transform(progress.collect, (function(m, v, k) { - return m[k] = 0; - })); - if ((base4 = user.flags).cronCount == null) { - base4.cronCount = 0; - } - user.flags.cronCount++; - analyticsData = { - category: 'behavior', - gaLabel: 'Cron Count', - gaValue: user.flags.cronCount, - uuid: user._id, - user: user, - resting: user.preferences.sleep, - cronCount: user.flags.cronCount, - progressUp: _.min([_progress.up, 900]), - progressDown: _progress.down - }; - if ((ref3 = options.analytics) != null) { - ref3.track('Cron', analyticsData); - } - return _progress; -}; diff --git a/common/script/fns/dotGet.js b/common/script/fns/dotGet.js index c95ba55656..3b45e54c71 100644 --- a/common/script/fns/dotGet.js +++ b/common/script/fns/dotGet.js @@ -1,5 +1,7 @@ -import dotGet from '../libs/dotGet'; +import _ from 'lodash'; -module.exports = function(user, path) { - return dotGet(user, path); +// TODO remove completely, use _.get, only used in client + +module.exports = function dotGet (user, path) { + return _.get(user, path); }; diff --git a/common/script/fns/dotSet.js b/common/script/fns/dotSet.js index 283e7a50d0..ceb21605af 100644 --- a/common/script/fns/dotSet.js +++ b/common/script/fns/dotSet.js @@ -1,12 +1,14 @@ -import dotSet from '../libs/dotSet'; +import _ from 'lodash'; /* -This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as -user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value}, -so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where -Angular sets object properties directly - in which case, this function will be used. - */ + This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as + user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value}, + so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where + Angular sets object properties directly - in which case, this function will be used. +*/ -module.exports = function(user, path, val) { - return dotSet(user, path, val); +// TODO use directly _.set and remove this fn, only used in client + +module.exports = function dotSet (user, path, val) { + return _.set(user, path, val); }; diff --git a/common/script/fns/getItem.js b/common/script/fns/getItem.js deleted file mode 100644 index b73ecf9073..0000000000 --- a/common/script/fns/getItem.js +++ /dev/null @@ -1,11 +0,0 @@ -import content from '../content/index'; -import i18n from '../i18n'; - -module.exports = function(user, type) { - var item; - item = content.gear.flat[user.items.gear.equipped[type]]; - if (!item) { - return content.gear.flat[type + "_base_0"]; - } - return item; -}; diff --git a/common/script/fns/handleTwoHanded.js b/common/script/fns/handleTwoHanded.js index c700346988..a861a10e68 100644 --- a/common/script/fns/handleTwoHanded.js +++ b/common/script/fns/handleTwoHanded.js @@ -1,24 +1,23 @@ import content from '../content/index'; import i18n from '../i18n'; -module.exports = function(user, item, type, req) { - var message, currentWeapon, currentShield; - if (type == null) { - type = 'equipped'; - } - currentShield = content.gear.flat[user.items.gear[type].shield]; - currentWeapon = content.gear.flat[user.items.gear[type].weapon]; +module.exports = function handleTwoHanded (user, item, type = 'equipped', req = {}) { + let currentShield = content.gear.flat[user.items.gear[type].shield]; + let currentWeapon = content.gear.flat[user.items.gear[type].weapon]; - if (item.type === "shield" && (currentWeapon ? currentWeapon.twoHanded : false)) { + let message; + + if (item.type === 'shield' && (currentWeapon ? currentWeapon.twoHanded : false)) { user.items.gear[type].weapon = 'weapon_base_0'; message = i18n.t('messageTwoHandedUnequip', { twoHandedText: currentWeapon.text(req.language), offHandedText: item.text(req.language), }, req.language); - } else if (item.twoHanded && (currentShield && user.items.gear[type].shield != "shield_base_0")) { - user.items.gear[type].shield = "shield_base_0"; + } else if (item.twoHanded && (currentShield && user.items.gear[type].shield !== 'shield_base_0')) { + user.items.gear[type].shield = 'shield_base_0'; message = i18n.t('messageTwoHandedEquip', { twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language), }, req.language); } + return message; }; diff --git a/common/script/fns/index.js b/common/script/fns/index.js index 24f3ab604b..04fddb2d75 100644 --- a/common/script/fns/index.js +++ b/common/script/fns/index.js @@ -1,4 +1,3 @@ -import getItem from './getItem'; import handleTwoHanded from './handleTwoHanded'; import predictableRandom from './predictableRandom'; import crit from './crit'; @@ -8,13 +7,10 @@ import dotGet from './dotGet'; import randomDrop from './randomDrop'; import autoAllocate from './autoAllocate'; import updateStats from './updateStats'; -import cron from './cron'; -import preenUserHistory from './preenUserHistory'; import ultimateGear from './ultimateGear'; import nullify from './nullify'; module.exports = { - getItem, handleTwoHanded, predictableRandom, crit, @@ -24,8 +20,6 @@ module.exports = { randomDrop, autoAllocate, updateStats, - cron, - preenUserHistory, ultimateGear, nullify, }; diff --git a/common/script/fns/nullify.js b/common/script/fns/nullify.js index b6e30aa3b9..38753071fc 100644 --- a/common/script/fns/nullify.js +++ b/common/script/fns/nullify.js @@ -1,5 +1,7 @@ -module.exports = function(user) { +// TODO remove once v2 is retired + +module.exports = function nullify (user) { user.ops = null; user.fns = null; - return user = null; + user = null; }; diff --git a/common/script/fns/predictableRandom.js b/common/script/fns/predictableRandom.js index 64c8153746..e67daf3b62 100644 --- a/common/script/fns/predictableRandom.js +++ b/common/script/fns/predictableRandom.js @@ -1,20 +1,24 @@ import _ from 'lodash'; -/* -Because the same op needs to be performed on the client and the server (critical hits, item drops, etc), -we need things to be "random", but technically predictable so that they don't go out-of-sync - */ -module.exports = function(user, seed) { - var x; +// Because the same op needs to be performed on the client and the server (critical hits, item drops, etc), +// we need things to be "random", but technically predictable so that they don't go out-of-sync + +module.exports = function predictableRandom (user, seed) { if (!seed || seed === Math.PI) { - seed = _.reduce(user.stats, (function(m, v) { - if (_.isNumber(v)) { - return m + v; + let stats = user.stats.toObject ? user.stats.toObject() : user.stats; + // These items are not part of the stat object but exists on the server (see controllers/user#getUser) + // we remove them in order to use the same user.stats both on server and on client + stats = _.omit(stats, 'toNextLevel', 'maxHealth', 'maxMP'); + + seed = _.reduce(stats, (accumulator, val) => { + if (_.isNumber(val)) { + return accumulator + val; } else { - return m; + return accumulator; } - }), 0); + }, 0); } - x = Math.sin(seed++) * 10000; + + let x = Math.sin(seed++) * 10000; return x - Math.floor(x); }; diff --git a/common/script/fns/preenUserHistory.js b/common/script/fns/preenUserHistory.js deleted file mode 100644 index a45dd82719..0000000000 --- a/common/script/fns/preenUserHistory.js +++ /dev/null @@ -1,25 +0,0 @@ -import _ from 'lodash'; -import preenHistory from '../libs/preenHistory'; - -module.exports = function(user, minHistLen) { - if (minHistLen == null) { - minHistLen = 7; - } - _.each(user.habits.concat(user.dailys), function(task) { - var ref; - if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) { - task.history = preenHistory(task.history); - } - return true; - }); - _.defaults(user.history, { - todos: [], - exp: [] - }); - if (user.history.exp.length > minHistLen) { - user.history.exp = preenHistory(user.history.exp); - } - if (user.history.todos.length > minHistLen) { - return user.history.todos = preenHistory(user.history.todos); - } -}; diff --git a/common/script/fns/randomDrop.js b/common/script/fns/randomDrop.js index b0121b157b..92064e62f1 100644 --- a/common/script/fns/randomDrop.js +++ b/common/script/fns/randomDrop.js @@ -3,80 +3,118 @@ import content from '../content/index'; import i18n from '../i18n'; import { daysSince } from '../cron'; import { diminishingReturns } from '../statHelpers'; +import predictableRandom from './predictableRandom'; +import randomVal from './randomVal'; // Clone a drop object maintaining its functions so that we can change it without affecting the original item function cloneDropItem (drop) { - return _.cloneDeep(drop, function (val) { + return _.cloneDeep(drop, (val) => { return _.isFunction(val) ? val : undefined; // undefined will be handled by lodash }); } -module.exports = function(user, modifiers, req) { - var acceptableDrops, base, base1, base2, chance, drop, dropK, dropMultiplier, name, name1, name2, quest, rarity, ref, ref1, ref2, ref3, task; +module.exports = function randomDrop (user, modifiers, req = {}) { + let acceptableDrops; + let chance; + let drop; + let dropK; + let dropMultiplier; + let quest; + let rarity; + let task; + task = modifiers.task; - chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + .02; - chance *= task.priority * (1 + (task.streak / 100 || 0)) * (1 + (user._statsComputed.per / 100)) * (1 + (user.contributor.level / 40 || 0)) * (1 + (user.achievements.rebirths / 20 || 0)) * (1 + (user.achievements.streak / 200 || 0)) * (user._tmp.crit || 1) * (1 + .5 * (_.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 0) || 0)); + + chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + 0.02; + chance *= task.priority * // Task priority: +50% for Medium, +100% for Hard + (1 + (task.streak / 100 || 0)) * // Streak bonus: +1% per streak + (1 + user._statsComputed.per / 100) * // PERception: +1% per point + (1 + (user.contributor.level / 40 || 0)) * // Contrib levels: +2.5% per level + (1 + (user.achievements.rebirths / 20 || 0)) * // Rebirths: +5% per achievement + (1 + (user.achievements.streak / 200 || 0)) * // Streak achievements: +0.5% per achievement + (user._tmp.crit || 1) * (1 + 0.5 * (_.reduce(task.checklist, (m, i) => { + return m + (i.completed ? 1 : 0); // +50% per checklist item complete. TODO: make this into X individual drop chances instead + }, 0) || 0)); chance = diminishingReturns(chance, 0.75); - quest = content.quests[(ref = user.party.quest) != null ? ref.key : void 0]; - if ((quest != null ? quest.collect : void 0) && user.fns.predictableRandom(user.stats.gp) < chance) { - dropK = user.fns.randomVal(quest.collect, { - key: true + + if (user.party.quest.key) + quest = content.quests[user.party.quest.key]; + if (quest && quest.collect && predictableRandom(user, user.stats.gp) < chance) { + dropK = randomVal(user, quest.collect, { + key: true, }); + if (!user.party.quest.progress.collect[dropK]) + user.party.quest.progress.collect[dropK] = 0; user.party.quest.progress.collect[dropK]++; - if (typeof user.markModified === "function") { - user.markModified('party.quest.progress'); - } + user.markModified('party.quest.progress'); } - dropMultiplier = ((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0) ? 2 : 1; - if ((daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) { + + if (user.purchased && user.purchased.plan && user.purchased.plan.customerId) { + dropMultiplier = 2; + } else { + dropMultiplier = 1; + } + + if (daysSince(user.items.lastDrop.date, user.preferences) === 0 && + user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0))) { return; } - if (((ref3 = user.flags) != null ? ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) { - rarity = user.fns.predictableRandom(user.stats.gp); - if (rarity > .6) { - drop = cloneDropItem(user.fns.randomVal(_.where(content.food, { - canDrop: true + + if (user.flags && user.flags.dropsEnabled && predictableRandom(user, user.stats.exp) < chance) { + rarity = predictableRandom(user, user.stats.gp); + + if (rarity > 0.6) { // food 40% chance + drop = cloneDropItem(randomVal(user, _.where(content.food, { + canDrop: true, }))); - if ((base = user.items.food)[name = drop.key] == null) { - base[name] = 0; + + if (!user.items.food[drop.key]) { + user.items.food[drop.key] = 0; } user.items.food[drop.key] += 1; drop.type = 'Food'; drop.dialog = i18n.t('messageDropFood', { dropArticle: drop.article, dropText: drop.text(req.language), - dropNotes: drop.notes(req.language) + dropNotes: drop.notes(req.language), }, req.language); - } else if (rarity > .3) { - drop = cloneDropItem(user.fns.randomVal(content.dropEggs)); - if ((base1 = user.items.eggs)[name1 = drop.key] == null) { - base1[name1] = 0; + } else if (rarity > 0.3) { // eggs 30% chance + drop = cloneDropItem(randomVal(user, content.dropEggs)); + if (!user.items.eggs[drop.key]) { + user.items.eggs[drop.key] = 0; } user.items.eggs[drop.key]++; drop.type = 'Egg'; drop.dialog = i18n.t('messageDropEgg', { dropText: drop.text(req.language), - dropNotes: drop.notes(req.language) + dropNotes: drop.notes(req.language), }, req.language); - } else { - acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert']; - drop = cloneDropItem(user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) { + } else { // Hatching Potion, 30% chance - break down by rarity. + if (rarity < 0.02) { // Very Rare: 10% (of 30%) + acceptableDrops = ['Golden']; + } else if (rarity < 0.09) { // Rare: 20% of 30% + acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']; + } else if (rarity < 0.18) { // uncommon: 30% of 30% + acceptableDrops = ['Red', 'Shade', 'Skeleton']; + } else { // common, 40% of 30% + acceptableDrops = ['Base', 'White', 'Desert']; + } + drop = cloneDropItem(randomVal(user, _.pick(content.hatchingPotions, (v, k) => { return acceptableDrops.indexOf(k) >= 0; - })))); - if ((base2 = user.items.hatchingPotions)[name2 = drop.key] == null) { - base2[name2] = 0; + }))); + if (!user.items.hatchingPotions[drop.key]) { + user.items.hatchingPotions[drop.key] = 0; } user.items.hatchingPotions[drop.key]++; drop.type = 'HatchingPotion'; drop.dialog = i18n.t('messageDropPotion', { dropText: drop.text(req.language), - dropNotes: drop.notes(req.language) + dropNotes: drop.notes(req.language), }, req.language); } + user._tmp.drop = drop; - user.items.lastDrop.date = +(new Date); - return user.items.lastDrop.count++; + user.items.lastDrop.date = Number(new Date()); + user.items.lastDrop.count++; } }; diff --git a/common/script/fns/randomVal.js b/common/script/fns/randomVal.js index 3c2b5b82e8..2244d04558 100644 --- a/common/script/fns/randomVal.js +++ b/common/script/fns/randomVal.js @@ -1,14 +1,12 @@ import _ from 'lodash'; +import predictableRandom from './predictableRandom'; -/* - Get a random property from an object - returns random property (the value) - */ +// Get a random property from an object +// returns random property (the value) -module.exports = function(user, obj, options) { - var array, rand; - array = (options != null ? options.key : void 0) ? _.keys(obj) : _.values(obj); - rand = user.fns.predictableRandom(options != null ? options.seed : void 0); +module.exports = function randomVal (user, obj, options = {}) { + let array = options.key ? _.keys(obj) : _.values(obj); + let rand = predictableRandom(user, options.seed); array.sort(); return array[Math.floor(rand * array.length)]; }; diff --git a/common/script/fns/resetGear.js b/common/script/fns/resetGear.js new file mode 100644 index 0000000000..2625f5c9b8 --- /dev/null +++ b/common/script/fns/resetGear.js @@ -0,0 +1,25 @@ +import _ from 'lodash'; +import content from '../content/index'; + +module.exports = function resetGear (user) { + let gear = user.items.gear; + + _.each(['equipped', 'costume'], function resetUserGear (type) { + gear[type] = {}; + gear[type].armor = 'armor_base_0'; + gear[type].weapon = 'weapon_warrior_0'; + gear[type].head = 'head_base_0'; + gear[type].shield = 'shield_base_0'; + }); + + // Gear.owned is a Mongo object so the _.each function iterates over hidden properties. + // The content.gear.flat[k] check should prevent this causing an error + _.each(gear.owned, function resetOwnedGear (v, k) { + if (gear.owned[k] && content.gear.flat[k] && content.gear.flat[k].value) { + gear.owned[k] = false; + } + }); + + gear.owned.weapon_warrior_0 = true; // eslint-disable-line camelcase + user.preferences.costume = false; +}; diff --git a/common/script/fns/ultimateGear.js b/common/script/fns/ultimateGear.js index 1333e8cc38..be5553201d 100644 --- a/common/script/fns/ultimateGear.js +++ b/common/script/fns/ultimateGear.js @@ -1,33 +1,23 @@ import content from '../content/index'; import _ from 'lodash'; -module.exports = function(user) { - var base, owned; - owned = typeof window !== "undefined" && window !== null ? user.items.gear.owned : user.items.gear.owned.toObject(); - if ((base = user.achievements).ultimateGearSets == null) { - base.ultimateGearSets = { - healer: false, - wizard: false, - rogue: false, - warrior: false - }; - } - content.classes.forEach(function(klass) { +module.exports = function ultimateGear (user) { + let owned = typeof window !== 'undefined' ? user.items.gear.owned : user.items.gear.owned.toObject(); + + content.classes.forEach((klass) => { if (user.achievements.ultimateGearSets[klass] !== true) { - return user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], function(soFarGood, type) { - var found; - found = _.find(content.gear.tree[type][klass], { - last: true + user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], (soFarGood, type) => { + let found = _.find(content.gear.tree[type][klass], { + last: true, }); return soFarGood && (!found || owned[found.key] === true); }, true); } }); - if (typeof user.markModified === "function") { - user.markModified('achievements.ultimateGearSets'); - } + if (_.contains(user.achievements.ultimateGearSets, true) && user.flags.armoireEnabled !== true) { user.flags.armoireEnabled = true; - return typeof user.markModified === "function" ? user.markModified('flags') : void 0; } + + return; }; diff --git a/common/script/fns/updateStats.js b/common/script/fns/updateStats.js index 3ce83ed664..6061f717ab 100644 --- a/common/script/fns/updateStats.js +++ b/common/script/fns/updateStats.js @@ -1,21 +1,19 @@ import _ from 'lodash'; import { MAX_HEALTH, - MAX_STAT_POINTS + MAX_STAT_POINTS, } from '../constants'; import { toNextLevel } from '../statHelpers'; -module.exports = function (user, stats, req, analytics) { +import autoAllocate from './autoAllocate'; + +module.exports = function updateStats (user, stats, req = {}, analytics) { let allocatedStatPoints; let totalStatPoints; let experienceToNextLevel; - if (stats.hp <= 0) { - user.stats.hp = 0; - return user.stats.hp; - } - - user.stats.hp = stats.hp; - user.stats.gp = stats.gp >= 0 ? stats.gp : 0; + user.stats.hp = stats.hp > 0 ? stats.hp : 0; + user.stats.gp = stats.gp > 0 ? stats.gp : 0; + if (!user._tmp) user._tmp = {}; experienceToNextLevel = toNextLevel(user.stats.lvl); @@ -35,7 +33,7 @@ module.exports = function (user, stats, req, analytics) { continue; // eslint-disable-line no-continue } if (user.preferences.automaticAllocation) { - user.fns.autoAllocate(); + autoAllocate(user); } else { user.stats.points = user.stats.lvl - allocatedStatPoints; totalStatPoints = user.stats.points + allocatedStatPoints; @@ -52,7 +50,6 @@ module.exports = function (user, stats, req, analytics) { } user.stats.exp = stats.exp; - user.flags = user.flags || {}; if (!user.flags.customizationsNotification && (user.stats.exp > 5 || user.stats.lvl > 1)) { user.flags.customizationsNotification = true; @@ -62,48 +59,39 @@ module.exports = function (user, stats, req, analytics) { } if (!user.flags.dropsEnabled && user.stats.lvl >= 3) { user.flags.dropsEnabled = true; - if (user.items.eggs["Wolf"] > 0) { - user.items.eggs["Wolf"]++; + if (user.items.eggs.Wolf > 0) { + user.items.eggs.Wolf++; } else { - user.items.eggs["Wolf"] = 1; + user.items.eggs.Wolf = 1; } } - if (!user.flags.classSelected && user.stats.lvl >= 10) { - user.flags.classSelected; - } _.each({ vice1: 30, atom1: 15, moonstone1: 60, - goldenknight1: 40 - }, function(lvl, k) { - var analyticsData, base, base1, ref; - if (!((ref = user.flags.levelDrops) != null ? ref[k] : void 0) && user.stats.lvl >= lvl) { - if ((base = user.items.quests)[k] == null) { - base[k] = 0; - } + goldenknight1: 40, + }, (lvl, k) => { + if (user.stats.lvl >= lvl && !user.flags.levelDrops[k]) { + user.flags.levelDrops[k] = true; + if (!user.items.quests[k]) + user.items.quests[k] = 0; user.items.quests[k]++; - ((base1 = user.flags).levelDrops != null ? base1.levelDrops : base1.levelDrops = {})[k] = true; - if (typeof user.markModified === "function") { - user.markModified('flags.levelDrops'); + user.markModified('flags.levelDrops'); + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: k, + acquireMethod: 'Level Drop', + category: 'behavior', + }); } - analyticsData = { - uuid: user._id, - itemKey: k, - acquireMethod: 'Level Drop', - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); - } - if (!user._tmp) user._tmp = {} - return user._tmp.drop = { + user._tmp.drop = { type: 'Quest', - key: k + key: k, }; } }); if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) { - return user.flags.rebirthEnabled = true; + user.flags.rebirthEnabled = true; } }; diff --git a/common/script/index.js b/common/script/index.js index ea502e9a7d..50b01bf762 100644 --- a/common/script/index.js +++ b/common/script/index.js @@ -1,85 +1,197 @@ -import moment from 'moment'; import _ from 'lodash'; -import { - daysSince, - shouldDo, -} from './cron'; +// When using a common module from the website or the server NEVER import the module directly +// but access it through `api` (the main common) module, otherwise you would require the non transpiled version of the file in production. +let api = module.exports = {}; + +import content from './content/index'; +api.content = content; + +import * as errors from './libs/errors'; +api.errors = errors; +import i18n from './i18n'; +api.i18n = i18n; + +// TODO under api.libs.cron? +import { shouldDo, daysSince } from './cron'; +api.shouldDo = shouldDo; +api.daysSince = daysSince; + +// TODO under api.constants? and capitalize exported names too import { MAX_HEALTH, MAX_LEVEL, MAX_STAT_POINTS, + TAVERN_ID, } from './constants'; -import * as statHelpers from './statHelpers'; - -import importedLibs from './libs'; - -var $w, preenHistory, sortOrder, - indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - -import content from './content/index'; -import i18n from './i18n'; - -let api = module.exports = {}; - -api.i18n = i18n; -api.shouldDo = shouldDo; - api.maxLevel = MAX_LEVEL; -api.capByLevel = statHelpers.capByLevel; api.maxHealth = MAX_HEALTH; +api.maxStatPoints = MAX_STAT_POINTS; +api.TAVERN_ID = TAVERN_ID; + +// TODO under api.libs.statHelpers? +import * as statHelpers from './statHelpers'; +api.capByLevel = statHelpers.capByLevel; api.tnl = statHelpers.toNextLevel; api.diminishingReturns = statHelpers.diminishingReturns; -$w = api.$w = importedLibs.splitWhitespace; -api.dotSet = importedLibs.dotSet; -api.dotGet = importedLibs.dotGet; -api.refPush = importedLibs.refPush; -api.planGemLimits = importedLibs.planGemLimits; +import splitWhitespace from './libs/splitWhitespace'; +api.$w = splitWhitespace; -preenHistory = importedLibs.preenHistory; +import dotSet from './libs/dotSet'; +api.dotSet = dotSet; -api.preenTodos = importedLibs.preenTodos; -api.updateStore = importedLibs.updateStore; +import dotGet from './libs/dotGet'; +api.dotGet = dotGet; +import refPush from './libs/refPush'; +api.refPush = refPush; -/* ------------------------------------------------------- -Content ------------------------------------------------------- - */ +import planGemLimits from './libs/planGemLimits'; +api.planGemLimits = planGemLimits; -api.content = content; +import preenTodos from './libs/preenTodos'; +api.preenTodos = preenTodos; +import updateStore from './libs/updateStore'; +api.updateStore = updateStore; -/* ------------------------------------------------------- -Misc Helpers ------------------------------------------------------- - */ +import uuid from './libs/uuid'; +api.uuid = uuid; -api.uuid = importedLibs.uuid; -api.countExists = importedLibs.countExists; -api.taskDefaults = importedLibs.taskDefaults; -api.percent = importedLibs.percent; -api.removeWhitespace = importedLibs.removeWhitespace; -api.encodeiCalLink = importedLibs.encodeiCalLink; -api.gold = importedLibs.gold; -api.silver = importedLibs.silver; -api.taskClasses = importedLibs.taskClasses; -api.friendlyTimestamp = importedLibs.friendlyTimestamp; -api.newChatMessages = importedLibs.newChatMessages; -api.noTags = importedLibs.noTags; -api.appliedTags = importedLibs.appliedTags; +import taskDefaults from './libs/taskDefaults'; +api.taskDefaults = taskDefaults; +import percent from './libs/percent'; +api.percent = percent; -/* -Various counting functions - */ +import gold from './libs/gold'; +api.gold = gold; + +import silver from './libs/silver'; +api.silver = silver; + +import taskClasses from './libs/taskClasses'; +api.taskClasses = taskClasses; + +import noTags from './libs/noTags'; +api.noTags = noTags; + +import appliedTags from './libs/appliedTags'; +api.appliedTags = appliedTags; + +import pickDeep from './libs/pickDeep'; +api.pickDeep = pickDeep; import count from './count'; api.count = count; +import statsComputed from './libs/statsComputed'; +api.statsComputed = statsComputed; + +import autoAllocate from './fns/autoAllocate'; +import crit from './fns/crit'; +import handleTwoHanded from './fns/handleTwoHanded'; +import predictableRandom from './fns/predictableRandom'; +import randomDrop from './fns/randomDrop'; +import randomVal from './fns/randomVal'; +import resetGear from './fns/resetGear'; +import ultimateGear from './fns/ultimateGear'; +import updateStats from './fns/updateStats'; + +api.fns = { + autoAllocate, + crit, + handleTwoHanded, + predictableRandom, + randomDrop, + randomVal, + resetGear, + ultimateGear, + updateStats, +}; + +import scoreTask from './ops/scoreTask'; +import sleep from './ops/sleep'; +import allocate from './ops/allocate'; +import buy from './ops/buy'; +import buyGear from './ops/buyGear'; +import buyHealthPotion from './ops/buyHealthPotion'; +import buyArmoire from './ops/buyArmoire'; +import buyMysterySet from './ops/buyMysterySet'; +import buyQuest from './ops/buyQuest'; +import buySpecialSpell from './ops/buySpecialSpell'; +import allocateNow from './ops/allocateNow'; +import hatch from './ops/hatch'; +import feed from './ops/feed'; +import equip from './ops/equip'; +import changeClass from './ops/changeClass'; +import disableClasses from './ops/disableClasses'; +import purchase from './ops/purchase'; +import purchaseHourglass from './ops/hourglassPurchase'; +import readCard from './ops/readCard'; +import openMysteryItem from './ops/openMysteryItem'; +import addWebhook from './ops/addWebhook'; +import updateWebhook from './ops/updateWebhook'; +import deleteWebhook from './ops/deleteWebhook'; +import releasePets from './ops/releasePets'; +import releaseBoth from './ops/releaseBoth'; +import releaseMounts from './ops/releaseMounts'; +import updateTask from './ops/updateTask'; +import clearCompleted from './ops/clearCompleted'; +import sell from './ops/sell'; +import unlock from './ops/unlock'; +import revive from './ops/revive'; +import rebirth from './ops/rebirth'; +import blockUser from './ops/blockUser'; +import clearPMs from './ops/clearPMs'; +import deletePM from './ops/deletePM'; +import reroll from './ops/reroll'; +import addPushDevice from './ops/addPushDevice'; +import reset from './ops/reset'; +import markPmsRead from './ops/markPMSRead'; + +api.ops = { + scoreTask, + sleep, + allocate, + buy, + buyGear, + buyHealthPotion, + buyArmoire, + buyMysterySet, + buySpecialSpell, + buyQuest, + allocateNow, + hatch, + feed, + equip, + changeClass, + disableClasses, + purchase, + purchaseHourglass, + readCard, + openMysteryItem, + addWebhook, + updateWebhook, + deleteWebhook, + releasePets, + releaseBoth, + releaseMounts, + updateTask, + clearCompleted, + sell, + unlock, + revive, + rebirth, + blockUser, + clearPMs, + deletePM, + reroll, + addPushDevice, + reset, + markPmsRead, +}; /* ------------------------------------------------------ @@ -87,10 +199,9 @@ User (prototype wrapper to give it ops, helper funcs, and virtuals ------------------------------------------------------ */ - /* User is now wrapped (both on client and server), adding a few new properties: - * getters (_statsComputed, tasks, etc) + * getters (_statsComputed) * user.fns, which is a bunch of helper functions These were originally up above, but they make more sense belonging to the user object so we don't have to pass the user object all over the place. In fact, we should pull in more functions such as cron(), updateStats(), etc. @@ -121,14 +232,16 @@ TODO import importedOps from './ops'; import importedFns from './fns'; -api.wrap = function(user, main) { - if (main == null) { - main = true; - } - if (user._wrapped) { - return; - } +// TODO Kept for the client side +api.wrap = function wrapUser (user, main = true) { + if (user._wrapped) return; user._wrapped = true; + + // Make markModified available on the client side as a noop function + if (!user.markModified) { + user.markModified = function noopMarkModified () {}; + } + if (main) { user.ops = { update: _.partial(importedOps.update, user), @@ -163,6 +276,9 @@ api.wrap = function(user, main) { releaseMounts: _.partial(importedOps.releaseMounts, user), releaseBoth: _.partial(importedOps.releaseBoth, user), buy: _.partial(importedOps.buy, user), + buyHealthPotion: _.partial(importedOps.buyHealthPotion, user), + buyArmoire: _.partial(importedOps.buyArmoire, user), + buyGear: _.partial(importedOps.buyGear, user), buyQuest: _.partial(importedOps.buyQuest, user), buyMysterySet: _.partial(importedOps.buyMysterySet, user), hourglassPurchase: _.partial(importedOps.hourglassPurchase, user), @@ -175,11 +291,12 @@ api.wrap = function(user, main) { allocate: _.partial(importedOps.allocate, user), readCard: _.partial(importedOps.readCard, user), openMysteryItem: _.partial(importedOps.openMysteryItem, user), - score: _.partial(importedOps.score, user), + score: _.partial(importedOps.scoreTask, user), + markPmsRead: _.partial(importedOps.markPmsRead, user), }; } + user.fns = { - getItem: _.partial(importedFns.getItem, user), handleTwoHanded: _.partial(importedFns.handleTwoHanded, user), predictableRandom: _.partial(importedFns.predictableRandom, user), crit: _.partial(importedFns.crit, user), @@ -189,34 +306,14 @@ api.wrap = function(user, main) { randomDrop: _.partial(importedFns.randomDrop, user), autoAllocate: _.partial(importedFns.autoAllocate, user), updateStats: _.partial(importedFns.updateStats, user), - cron: _.partial(importedFns.cron, user), - preenUserHistory: _.partial(importedFns.preenUserHistory, user), + statsComputed: _.partial(statsComputed, user), ultimateGear: _.partial(importedFns.ultimateGear, user), nullify: _.partial(importedFns.nullify, user), }; + Object.defineProperty(user, '_statsComputed', { - get: function() { - var computed; - computed = _.reduce(['per', 'con', 'str', 'int'], (function(_this) { - return function(m, stat) { - m[stat] = _.reduce($w('stats stats.buffs items.gear.equipped.weapon items.gear.equipped.armor items.gear.equipped.head items.gear.equipped.shield'), function(m2, path) { - var item, val; - val = user.fns.dotGet(path); - return m2 + (~path.indexOf('items.gear') ? (item = content.gear.flat[val], (+(item != null ? item[stat] : void 0) || 0) * ((item != null ? item.klass : void 0) === user.stats["class"] || (item != null ? item.specialClass : void 0) === user.stats["class"] ? 1.5 : 1)) : +val[stat] || 0); - }, 0); - m[stat] += Math.floor(api.capByLevel(user.stats.lvl) / 2); - return m; - }; - })(this), {}); - computed.maxMP = computed.int * 2 + 30; - return computed; - } - }); - return Object.defineProperty(user, 'tasks', { - get: function() { - var tasks; - tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards); - return _.object(_.pluck(tasks, "id"), tasks); - } + get () { + return statsComputed(user); + }, }); }; diff --git a/common/script/libs/appliedTags.js b/common/script/libs/appliedTags.js index 31302fbe54..ded2a9de23 100644 --- a/common/script/libs/appliedTags.js +++ b/common/script/libs/appliedTags.js @@ -1,19 +1,14 @@ -import _ from 'lodash'; - /* Are there tags applied? */ -module.exports = function(userTags, taskTags) { - var arr; - arr = []; - _.each(userTags, function(t) { - if (t == null) { - return; - } - if (taskTags != null ? taskTags[t.id] : void 0) { - return arr.push(t.name); - } +// TODO move to client + +module.exports = function appliedTags (userTags, taskTags = []) { + let arr = userTags.filter(tag => { + return taskTags.indexOf(tag.id) !== -1; + }).map(tag => { + return tag.name; }); return arr.join(', '); }; diff --git a/common/script/libs/countExists.js b/common/script/libs/countExists.js deleted file mode 100644 index 964e4e286f..0000000000 --- a/common/script/libs/countExists.js +++ /dev/null @@ -1,7 +0,0 @@ -import _ from 'lodash'; - -module.exports = function(items) { - return _.reduce(items, (function(m, v) { - return m + (v ? 1 : 0); - }), 0); -}; diff --git a/common/script/libs/dotGet.js b/common/script/libs/dotGet.js index 4585d8fd53..d8ce026b82 100644 --- a/common/script/libs/dotGet.js +++ b/common/script/libs/dotGet.js @@ -1,9 +1,5 @@ import _ from 'lodash'; -module.exports = function(obj, path) { - return _.reduce(path.split('.'), ((function(_this) { - return function(curr, next) { - return curr != null ? curr[next] : void 0; - }; - })(this)), obj); -}; +// TODO remove completely, only used in client + +module.exports = _.get; diff --git a/common/script/libs/dotSet.js b/common/script/libs/dotSet.js index 40165078aa..b664c54d05 100644 --- a/common/script/libs/dotSet.js +++ b/common/script/libs/dotSet.js @@ -1,14 +1,5 @@ import _ from 'lodash'; -module.exports = function(obj, path, val) { - var arr; - arr = path.split('.'); - return _.reduce(arr, (function(_this) { - return function(curr, next, index) { - if ((arr.length - 1) === index) { - curr[next] = val; - } - return curr[next] != null ? curr[next] : curr[next] = {}; - }; - })(this), obj); -}; +// TODO remove completely, only used in client + +module.exports = _.set; diff --git a/common/script/libs/encodeiCalLink.js b/common/script/libs/encodeiCalLink.js deleted file mode 100644 index 4a85badd59..0000000000 --- a/common/script/libs/encodeiCalLink.js +++ /dev/null @@ -1,9 +0,0 @@ -/* -Encode the download link for .ics iCal file - */ - -module.exports = function(uid, apiToken) { - var loc, ref; - loc = (typeof window !== "undefined" && window !== null ? window.location.host : void 0) || (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.BASE_URL : void 0 : void 0) || ''; - return encodeURIComponent("http://" + loc + "/v1/users/" + uid + "/calendar.ics?apiToken=" + apiToken); -}; diff --git a/common/script/libs/errors.js b/common/script/libs/errors.js new file mode 100644 index 0000000000..cb780d215b --- /dev/null +++ b/common/script/libs/errors.js @@ -0,0 +1,42 @@ +import extendableBuiltin from './extendableBuiltin'; + +// Base class for custom application errors +// It extends Error and capture the stack trace +export class CustomError extends extendableBuiltin(Error) { + constructor () { + super(); + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor); + } + } +} + +// We specify an httpCode for all errors so that they can be used in the API too + +export class NotAuthorized extends CustomError { + constructor (customMessage) { + super(); + this.name = this.constructor.name; + this.httpCode = 401; + this.message = customMessage || 'Not authorized.'; + } +} + +export class BadRequest extends CustomError { + constructor (customMessage) { + super(); + this.name = this.constructor.name; + this.httpCode = 400; + this.message = customMessage || 'Bad request.'; + } +} + +export class NotFound extends CustomError { + constructor (customMessage) { + super(); + this.name = this.constructor.name; + this.httpCode = 404; + this.message = customMessage || 'Not found.'; + } +} diff --git a/common/script/libs/extendableBuiltin.js b/common/script/libs/extendableBuiltin.js new file mode 100644 index 0000000000..56186301c4 --- /dev/null +++ b/common/script/libs/extendableBuiltin.js @@ -0,0 +1,11 @@ +// Babel 6 doesn't support extending native class (Error, Array, ...) +// This function makes it possible to extend native classes with the same results as Babel 5 +module.exports = function extendableBuiltin (klass) { + function ExtendableBuiltin () { + klass.apply(this, arguments); + } + ExtendableBuiltin.prototype = Object.create(klass.prototype); + Object.setPrototypeOf(ExtendableBuiltin, klass); + + return ExtendableBuiltin; +}; diff --git a/common/script/libs/friendlyTimestamp.js b/common/script/libs/friendlyTimestamp.js deleted file mode 100644 index dfda6fbed7..0000000000 --- a/common/script/libs/friendlyTimestamp.js +++ /dev/null @@ -1,9 +0,0 @@ -import moment from 'moment'; - -/* -Friendly timestamp - */ - -module.exports = function(timestamp) { - return moment(timestamp).format('MM/DD h:mm:ss a'); -}; diff --git a/common/script/libs/gold.js b/common/script/libs/gold.js index 8016e2cff9..83d9531d5e 100644 --- a/common/script/libs/gold.js +++ b/common/script/libs/gold.js @@ -1,7 +1,9 @@ -module.exports = function(num) { +// TODO move to client + +module.exports = function gold (num) { if (num) { return Math.floor(num); } else { - return "0"; + return '0'; } }; diff --git a/common/script/libs/index.js b/common/script/libs/index.js deleted file mode 100644 index dd0c420d8d..0000000000 --- a/common/script/libs/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import uuid from './uuid'; -import taskDefaults from './taskDefaults'; -import refPush from './refPush'; -import splitWhitespace from './splitWhitespace'; -import planGemLimits from './planGemLimits'; -import preenTodos from './preenTodos'; -import dotSet from './dotSet'; -import dotGet from './dotGet'; -import preenHistory from './preenHistory'; -import countExists from './countExists'; -import updateStore from './updateStore'; - -import appliedTags from './appliedTags'; -import encodeiCalLink from './encodeiCalLink'; -import friendlyTimestamp from './friendlyTimestamp'; -import gold from './gold'; -import newChatMessages from './newChatMessages'; -import noTags from './noTags'; -import percent from './percent'; -import removeWhitespace from './removeWhitespace'; -import silver from './silver'; -import taskClasses from './taskClasses'; - -module.exports = { - uuid, - taskDefaults, - refPush, - splitWhitespace, - planGemLimits, - preenTodos, - dotSet, - dotGet, - preenHistory, - countExists, - updateStore, - appliedTags, - encodeiCalLink, - friendlyTimestamp, - gold, - newChatMessages, - noTags, - percent, - removeWhitespace, - silver, - taskClasses, -}; diff --git a/common/script/libs/newChatMessages.js b/common/script/libs/newChatMessages.js deleted file mode 100644 index abe7680edf..0000000000 --- a/common/script/libs/newChatMessages.js +++ /dev/null @@ -1,10 +0,0 @@ -/* -Does user have new chat messages? - */ - -module.exports = function(messages, lastMessageSeen) { - if (!((messages != null ? messages.length : void 0) > 0)) { - return false; - } - return (messages != null ? messages[0] : void 0) && (messages[0].id !== lastMessageSeen); -}; diff --git a/common/script/libs/noTags.js b/common/script/libs/noTags.js index c3abb9054e..16a59d1d31 100644 --- a/common/script/libs/noTags.js +++ b/common/script/libs/noTags.js @@ -4,8 +4,10 @@ import _ from 'lodash'; are any tags active? */ -module.exports = function(tags) { - return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) { +// TODO move to client + +module.exports = function noTags (tags) { + return _.isEmpty(tags) || _.isEmpty(_.filter(tags, (t) => { return t; })); }; diff --git a/common/script/libs/percent.js b/common/script/libs/percent.js index 7439b22285..d7622474dd 100644 --- a/common/script/libs/percent.js +++ b/common/script/libs/percent.js @@ -1,10 +1,12 @@ -module.exports = function(x, y, dir) { - var roundFn; +// TODO move to client + +module.exports = function percent (x, y, dir) { + let roundFn; switch (dir) { - case "up": + case 'up': roundFn = Math.ceil; break; - case "down": + case 'down': roundFn = Math.floor; break; default: diff --git a/common/script/libs/pickDeep.js b/common/script/libs/pickDeep.js new file mode 100644 index 0000000000..919d926854 --- /dev/null +++ b/common/script/libs/pickDeep.js @@ -0,0 +1,13 @@ +// An utility to pick deep properties from an object. +// Works like _.pick but supports nested props (ie pickDeep(obj, ['deep.property'])) + +import _ from 'lodash'; + +module.exports = function pickDeep (obj, properties) { + if (!_.isArray(properties)) throw new Error('"properties" must be an array'); + + let result = {}; + _.each(properties, (prop) => _.set(result, prop, _.get(obj, prop))); + + return result; +}; diff --git a/common/script/libs/preenHistory.js b/common/script/libs/preenHistory.js deleted file mode 100644 index 0d354c238c..0000000000 --- a/common/script/libs/preenHistory.js +++ /dev/null @@ -1,43 +0,0 @@ -import moment from 'moment'; -import _ from 'lodash'; - -/* -Preen history for users with > 7 history entries -This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array -of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week -of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite] - */ - -module.exports = function(history) { - var newHistory, preen, thisMonth; - history = _.filter(history, function(h) { - return !!h; - }); - newHistory = []; - preen = function(amount, groupBy) { - var groups; - groups = _.chain(history).groupBy(function(h) { - return moment(h.date).format(groupBy); - }).sortBy(function(h, k) { - return k; - }).value(); - groups = groups.slice(-amount); - groups.pop(); - return _.each(groups, function(group) { - newHistory.push({ - date: moment(group[0].date).toDate(), - value: _.reduce(group, (function(m, obj) { - return m + obj.value; - }), 0) / group.length - }); - return true; - }); - }; - preen(50, "YYYY"); - preen(moment().format('MM'), "YYYYMM"); - thisMonth = moment().format('YYYYMM'); - newHistory = newHistory.concat(_.filter(history, function(h) { - return moment(h.date).format('YYYYMM') === thisMonth; - })); - return newHistory; -}; diff --git a/common/script/libs/preenTodos.js b/common/script/libs/preenTodos.js index f07a49c9d0..fcf8775d5f 100644 --- a/common/script/libs/preenTodos.js +++ b/common/script/libs/preenTodos.js @@ -1,14 +1,12 @@ import moment from 'moment'; import _ from 'lodash'; -/* - Preen 3-day past-completed To-Dos from Angular & mobile app - */ +// TODO used only in v2 -module.exports = function(tasks) { - return _.filter(tasks, function(t) { - return !t.completed || (t.challenge && t.challenge.id) || moment(t.dateCompleted).isAfter(moment().subtract({ - days: 3 +module.exports = function preenTodos (tasks) { + return _.filter(tasks, (t) => { + return !t.completed || t.challenge && t.challenge.id || moment(t.dateCompleted).isAfter(moment().subtract({ + days: 3, })); }); }; diff --git a/common/script/libs/refPush.js b/common/script/libs/refPush.js index 06b3617014..1bdddc9edf 100644 --- a/common/script/libs/refPush.js +++ b/common/script/libs/refPush.js @@ -7,13 +7,14 @@ import uuid from './uuid'; no problem. To maintain sorting, we use these helper functions: */ -module.exports = function(reflist, item, prune) { - if (prune == null) { - prune = 0; - } +module.exports = function refPush (reflist, item) { item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1; + if (!(item.id && !reflist[item.id])) { item.id = uuid(); } - return reflist[item.id] = item; + + reflist[item.id] = item; + + return reflist[item.id]; }; diff --git a/common/script/libs/removeWhitespace.js b/common/script/libs/removeWhitespace.js deleted file mode 100644 index 1015beda54..0000000000 --- a/common/script/libs/removeWhitespace.js +++ /dev/null @@ -1,10 +0,0 @@ -/* -Remove whitespace #FIXME are we using this anywwhere? Should we be? - */ - -module.exports = function(str) { - if (!str) { - return ''; - } - return str.replace(/\s/g, ''); -}; diff --git a/common/script/libs/silver.js b/common/script/libs/silver.js index 0dbae97b05..1d3620f602 100644 --- a/common/script/libs/silver.js +++ b/common/script/libs/silver.js @@ -2,10 +2,13 @@ Silver amount from their money */ -module.exports = function(num) { +// TODO move to client + +module.exports = function silver (num) { if (num) { - return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2); + let centCount = Math.floor((num - Math.floor(num)) * 100); + return `0${centCount}`.slice(-2); } else { - return "00"; + return '00'; } }; diff --git a/common/script/libs/splitWhitespace.js b/common/script/libs/splitWhitespace.js index 1ef3d513aa..2f8276bcb3 100644 --- a/common/script/libs/splitWhitespace.js +++ b/common/script/libs/splitWhitespace.js @@ -1,3 +1,4 @@ -module.exports = function(s) { + +module.exports = function splitWhitespace (s) { return s.split(' '); }; diff --git a/common/script/libs/statsComputed.js b/common/script/libs/statsComputed.js new file mode 100644 index 0000000000..a239b2039c --- /dev/null +++ b/common/script/libs/statsComputed.js @@ -0,0 +1,28 @@ +import _ from 'lodash'; +import content from '../content/index'; +import * as statHelpers from '../statHelpers'; + +module.exports = function statsComputed (user) { + let paths = ['stats', 'stats.buffs', 'items.gear.equipped.weapon', 'items.gear.equipped.armor', + 'items.gear.equipped.head', 'items.gear.equipped.shield']; + let computed = _.reduce(['per', 'con', 'str', 'int'], (m, stat) => { + m[stat] = _.reduce(paths, (m2, path) => { + let val = _.get(user, path); + let item = content.gear.flat[val]; + if (!item) item = {}; + if (!item[stat]) { + item[stat] = 0; + } else { + item[stat] = Number(item[stat]); + } + let thisMultiplier = item.klass === user.stats.class || item.specialClass === user.stats.class ? 1.5 : 1; + let thisReturn = path.indexOf('items.gear') !== -1 ? item[stat] * thisMultiplier : Number(val[stat]); + return m2 + thisReturn || 0; + }, 0); + m[stat] += Math.floor(statHelpers.capByLevel(user.stats.lvl) / 2); + return m; + }, {}); + + computed.maxMP = computed.int * 2 + 30; + return computed; +}; diff --git a/common/script/libs/taskClasses.js b/common/script/libs/taskClasses.js index 21bed1ad63..9ef223d9f7 100644 --- a/common/script/libs/taskClasses.js +++ b/common/script/libs/taskClasses.js @@ -1,51 +1,45 @@ import { - shouldDo + shouldDo, } from '../cron'; + /* Task classes given everything about the class */ -module.exports = function(task, filters, dayStart, lastCron, showCompleted, main) { - var classes, completed, enabled, filter, priority, ref, repeat, type, value; - if (filters == null) { - filters = []; - } - if (dayStart == null) { - dayStart = 0; - } - if (lastCron == null) { - lastCron = +(new Date); - } - if (showCompleted == null) { - showCompleted = false; - } - if (main == null) { - main = false; - } + +// TODO move to the client + +module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCron = Number(new Date()), showCompleted = false, main = false) { if (!task) { - return; + return ''; } - type = task.type, completed = task.completed, value = task.value, repeat = task.repeat, priority = task.priority; - if (main) { - if (!task._editing) { - for (filter in filters) { - enabled = filters[filter]; - if (enabled && !((ref = task.tags) != null ? ref[filter] : void 0)) { - return 'hidden'; - } + let type = task.type; + let classes = task.type; + let completed = task.completed; + let value = task.value; + let priority = task.priority; + + if (main && !task._editing) { + for (let filter in filters) { + let enabled = filters[filter]; + if (!task.tags) task.tags = []; + if (enabled && task.tags.indexOf(filter) === -1) { + return 'hidden'; } } } - classes = type; + + classes = task.type; if (task._editing) { - classes += " beingEdited"; + classes += ' beingEdited'; } + if (type === 'todo' || type === 'daily') { - if (completed || (type === 'daily' && !shouldDo(+(new Date), task, { - dayStart: dayStart + if (completed || (type === 'daily' && !shouldDo(Number(new Date()), task, { // eslint-disable-line no-extra-parens + dayStart, }))) { - classes += " completed"; + classes += ' completed'; } else { - classes += " uncompleted"; + classes += ' uncompleted'; } } else if (type === 'habit') { if (task.down && task.up) { @@ -55,6 +49,7 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main classes += ' habit-narrow'; } } + if (priority === 0.1) { classes += ' difficulty-trivial'; } else if (priority === 1) { @@ -64,6 +59,7 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main } else if (priority === 2) { classes += ' difficulty-hard'; } + if (value < -20) { classes += ' color-worst'; } else if (value < -10) { @@ -79,5 +75,6 @@ module.exports = function(task, filters, dayStart, lastCron, showCompleted, main } else { classes += ' color-best'; } + return classes; }; diff --git a/common/script/libs/taskDefaults.js b/common/script/libs/taskDefaults.js index 69c815e4fd..e6bdba3def 100644 --- a/common/script/libs/taskDefaults.js +++ b/common/script/libs/taskDefaults.js @@ -1,71 +1,74 @@ -import uuid from './uuid'; +import { v4 as uuid } from 'uuid'; import _ from 'lodash'; +import moment from 'moment'; -/* -Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before -sending up to the server for performance - */ +// Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before +// sending up to the server for performance -// TODO revisit +// TODO move to client code? -module.exports = function(task) { - var defaults, ref, ref1, ref2; - if (task == null) { - task = {}; - } - if (!(task.type && ((ref = task.type) === 'habit' || ref === 'daily' || ref === 'todo' || ref === 'reward'))) { +const tasksTypes = ['habit', 'daily', 'todo', 'reward']; + +module.exports = function taskDefaults (task = {}) { + if (!task.type || tasksTypes.indexOf(task.type) === -1) { task.type = 'habit'; } - defaults = { - id: uuid(), - text: task.id != null ? task.id : '', + + let defaultId = uuid(); + let defaults = { + _id: defaultId, + text: task._id || defaultId, notes: '', + tags: [], + value: task.type === 'reward' ? 10 : 0, priority: 1, challenge: {}, + reminders: [], attribute: 'str', - dateCreated: new Date() + createdAt: new Date(), // TODO these are going to be overwritten by the server... + updatedAt: new Date(), }; + _.defaults(task, defaults); + + if (task.type === 'habit' || task.type === 'daily') { + _.defaults(task, { + history: [], + }); + } + + if (task.type === 'todo' || task.type === 'daily') { + _.defaults(task, { + completed: false, + collapseChecklist: false, + checklist: [], + }); + } + if (task.type === 'habit') { _.defaults(task, { up: true, - down: true - }); - } - if ((ref1 = task.type) === 'habit' || ref1 === 'daily') { - _.defaults(task, { - history: [] - }); - } - if ((ref2 = task.type) === 'daily' || ref2 === 'todo') { - _.defaults(task, { - completed: false + down: true, }); } + if (task.type === 'daily') { _.defaults(task, { streak: 0, repeat: { - su: true, m: true, t: true, w: true, th: true, f: true, - s: true - } - }, { - startDate: new Date(), + s: true, + su: true, + }, + startDate: moment().startOf('day').toDate(), everyX: 1, - frequency: 'weekly' + frequency: 'weekly', }); } - task._id = task.id; - if (task.value == null) { - task.value = task.type === 'reward' ? 10 : 0; - } - if (!_.isNumber(task.priority)) { - task.priority = 1; - } + return task; }; diff --git a/common/script/libs/updateStore.js b/common/script/libs/updateStore.js index 05d009fd9d..f6593de8fd 100644 --- a/common/script/libs/updateStore.js +++ b/common/script/libs/updateStore.js @@ -1,36 +1,31 @@ import _ from 'lodash'; import content from '../content/index'; -/* - Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there - */ +// Return the list of gear items available for purchase -var sortOrder = _.reduce(content.gearTypes, (function(m, v, k) { - m[v] = k; - return m; -}), {}); +let sortOrder = _.reduce(content.gearTypes, (accumulator, val, key) => { + accumulator[val] = key; + return accumulator; +}, {}); -module.exports = function(user) { - var changes; - if (!user) { - return; - } - changes = []; - _.each(content.gearTypes, function(type) { - var found; - found = _.find(content.gear.tree[type][user.stats["class"]], function(item) { +module.exports = function updateStore (user) { + let changes = []; + + _.each(content.gearTypes, (type) => { + let found = _.find(content.gear.tree[type][user.stats.class], (item) => { return !user.items.gear.owned[item.key]; }); - if (found) { - changes.push(found); + + if (found) changes.push(found); + }); + + changes = changes.concat(_.filter(content.gear.flat, (val) => { + if (['special', 'mystery', 'armoire'].indexOf(val.klass) !== -1 && !user.items.gear.owned[val.key] && (val.canOwn ? val.canOwn(user) : false)) { + return true; + } else { + return false; } - return true; - }); - changes = changes.concat(_.filter(content.gear.flat, function(v) { - var ref; - return ((ref = v.klass) === 'special' || ref === 'mystery' || ref === 'armoire') && !user.items.gear.owned[v.key] && (typeof v.canOwn === "function" ? v.canOwn(user) : void 0); })); - return _.sortBy(changes, function(c) { - return sortOrder[c.type]; - }); + + return _.sortBy(changes, (change) => sortOrder[change.type]); }; diff --git a/common/script/libs/uuid.js b/common/script/libs/uuid.js index ca20dcec4e..63f75cf398 100644 --- a/common/script/libs/uuid.js +++ b/common/script/libs/uuid.js @@ -1 +1,4 @@ -module.exports = require('uuid').v4; +import uuid from 'uuid'; + +// TODO remove this file completely +module.exports = uuid.v4; diff --git a/common/script/ops/addPushDevice.js b/common/script/ops/addPushDevice.js index d96cf249cf..a909fe9feb 100644 --- a/common/script/ops/addPushDevice.js +++ b/common/script/ops/addPushDevice.js @@ -1,20 +1,41 @@ import _ from 'lodash'; +import i18n from '../i18n'; +import { + BadRequest, + NotAuthorized, +} from '../libs/errors'; + +// TODO move to server code +module.exports = function addPushDevice (user, req = {}) { + let regId = _.get(req, 'body.regId'); + if (!regId) throw new BadRequest(i18n.t('regIdRequired', req.language)); + + let type = _.get(req, 'body.type'); + if (!type) throw new BadRequest(i18n.t('typeRequired', req.language)); -module.exports = function(user, req, cb) { - var i, item, pd; if (!user.pushDevices) { user.pushDevices = []; } - pd = user.pushDevices; - item = { - regId: req.body.regId, - type: req.body.type + + let pushDevices = user.pushDevices; + + let item = { + regId, + type, }; - i = _.findIndex(pd, { - regId: item.regId + + let indexOfPushDevice = _.findIndex(pushDevices, { + regId: item.regId, }); - if (i === -1) { - pd.push(item); + + if (indexOfPushDevice !== -1) { + throw new NotAuthorized(i18n.t('pushDeviceAlreadyAdded', req.language)); } - return typeof cb === "function" ? cb(null, user.pushDevices) : void 0; + + pushDevices.push(item); + + return [ + user.pushDevices, + i18n.t('pushDeviceAdded', req.language), + ]; }; diff --git a/common/script/ops/addTag.js b/common/script/ops/addTag.js index a020a5fbaf..b44ead8d2e 100644 --- a/common/script/ops/addTag.js +++ b/common/script/ops/addTag.js @@ -1,12 +1,17 @@ import uuid from '../libs/uuid'; +import _ from 'lodash'; -module.exports = function(user, req, cb) { - if (user.tags == null) { +// TODO used only in client, move there? + +module.exports = function addTag (user, req = {}) { + if (!user.tags) { user.tags = []; } + user.tags.push({ name: req.body.name, - id: req.body.id || uuid() + id: _.get(req, 'body.id') || uuid(), }); - return typeof cb === "function" ? cb(null, user.tags) : void 0; + + return user.tags; }; diff --git a/common/script/ops/addTask.js b/common/script/ops/addTask.js index a2d1895e1e..592f877248 100644 --- a/common/script/ops/addTask.js +++ b/common/script/ops/addTask.js @@ -1,27 +1,23 @@ import taskDefaults from '../libs/taskDefaults'; -import i18n from '../i18n'; -module.exports = function(user, req, cb) { - var task; - task = taskDefaults(req.body); - if (user.tasks[task.id] != null) { - return typeof cb === "function" ? cb({ - code: 409, - message: i18n.t('messageDuplicateTaskID', req.language) - }) : void 0; - } - user[task.type + "s"].unshift(task); +// TODO move to client since it's only used there? + +module.exports = function addTask (user, req = {body: {}}) { + let task = taskDefaults(req.body); + user.tasksOrder[`${task.type}s`].unshift(task._id); + user[`${task.type}s`].unshift(task); + if (user.preferences.newTaskEdit) { task._editing = true; } + if (user.preferences.tagsCollapsed) { task._tags = true; } + if (!user.preferences.advancedCollapsed) { task._advanced = true; } - if (typeof cb === "function") { - cb(null, task); - } + return task; }; diff --git a/common/script/ops/addWebhook.js b/common/script/ops/addWebhook.js index 99eaf49bba..c308d1b9e5 100644 --- a/common/script/ops/addWebhook.js +++ b/common/script/ops/addWebhook.js @@ -1,15 +1,27 @@ import refPush from '../libs/refPush'; +import validator from 'validator'; +import i18n from '../i18n'; +import { + BadRequest, +} from '../libs/errors'; +import _ from 'lodash'; -module.exports = function(user, req, cb) { - var wh; - wh = user.preferences.webhooks; - refPush(wh, { - url: req.body.url, - enabled: req.body.enabled || true, - id: req.body.id - }); - if (typeof user.markModified === "function") { - user.markModified('preferences.webhooks'); +module.exports = function addWebhook (user, req = {}) { + let wh = user.preferences.webhooks; + + if (!validator.isURL(_.get(req, 'body.url'))) throw new BadRequest(i18n.t('invalidUrl', req.language)); + if (!validator.isBoolean(_.get(req, 'body.enabled'))) throw new BadRequest(i18n.t('invalidEnabled', req.language)); + + user.markModified('preferences.webhooks'); + + if (req.v2 === true) { + return user.preferences.webhooks; + } else { + return [ + refPush(wh, { + url: req.body.url, + enabled: req.body.enabled, + }), + ]; } - return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; }; diff --git a/common/script/ops/allocate.js b/common/script/ops/allocate.js index 92b5ae53fa..8e07e09589 100644 --- a/common/script/ops/allocate.js +++ b/common/script/ops/allocate.js @@ -1,15 +1,31 @@ import _ from 'lodash'; -import splitWhitespace from '../libs/splitWhitespace'; +import { + ATTRIBUTES, +} from '../constants'; +import { + BadRequest, + NotAuthorized, +} from '../libs/errors'; +import i18n from '../i18n'; + +module.exports = function allocate (user, req = {}) { + let stat = _.get(req, 'query.stat', 'str'); + + if (ATTRIBUTES.indexOf(stat) === -1) { + throw new BadRequest(i18n.t('invalidAttribute', {attr: stat}, req.language)); + } -module.exports = function(user, req, cb) { - var stat; - stat = req.query.stat || 'str'; if (user.stats.points > 0) { user.stats[stat]++; user.stats.points--; if (stat === 'int') { user.stats.mp++; } + } else { + throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language)); } - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats'))) : void 0; + + return [ + user.stats, + ]; }; diff --git a/common/script/ops/allocateNow.js b/common/script/ops/allocateNow.js index 815c0b8959..e8ae5d249c 100644 --- a/common/script/ops/allocateNow.js +++ b/common/script/ops/allocateNow.js @@ -1,10 +1,15 @@ import _ from 'lodash'; +import autoAllocate from '../fns/autoAllocate'; -module.exports = function(user, req, cb) { - _.times(user.stats.points, user.fns.autoAllocate); +module.exports = function allocateNow (user, req = {}) { + _.times(user.stats.points, () => autoAllocate(user)); user.stats.points = 0; - if (typeof user.markModified === "function") { - user.markModified('stats'); + + if (req.v2 === true) { + return _.pick(user, 'stats'); + } else { + return [ + user.stats, + ]; } - return typeof cb === "function" ? cb(null, user.stats) : void 0; }; diff --git a/common/script/ops/blockUser.js b/common/script/ops/blockUser.js index dd08925640..5c123735ed 100644 --- a/common/script/ops/blockUser.js +++ b/common/script/ops/blockUser.js @@ -1,13 +1,21 @@ -module.exports = function(user, req, cb) { - var i; - i = user.inbox.blocks.indexOf(req.params.uuid); - if (~i) { - user.inbox.blocks.splice(i, 1); - } else { +import validator from 'validator'; +import i18n from '../i18n'; +import { + BadRequest, +} from '../libs/errors'; + +module.exports = function blockUser (user, req = {}) { + if (!validator.isUUID(req.params.uuid)) throw new BadRequest(i18n.t('invalidUUID', req.language)); + + let i = user.inbox.blocks.indexOf(req.params.uuid); + if (i === -1) { user.inbox.blocks.push(req.params.uuid); + } else { + user.inbox.blocks.splice(i, 1); } - if (typeof user.markModified === "function") { - user.markModified('inbox.blocks'); - } - return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0; + + user.markModified('inbox.blocks'); + return [ + user.inbox.blocks, + ]; }; diff --git a/common/script/ops/buy.js b/common/script/ops/buy.js index 1686316264..ded5b034d2 100644 --- a/common/script/ops/buy.js +++ b/common/script/ops/buy.js @@ -1,119 +1,24 @@ -import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; -import count from '../count'; -import splitWhitespace from '../libs/splitWhitespace'; +import { + BadRequest, +} from '../libs/errors'; +import buyHealthPotion from './buyHealthPotion'; +import buyArmoire from './buyArmoire'; +import buyGear from './buyGear'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, armoireExp, armoireResp, armoireResult, base, buyResp, drop, eligibleEquipment, item, key, message, name; - key = req.params.key; - item = key === 'potion' ? content.potion : key === 'armoire' ? content.armoire : content.gear.flat[key]; - if (!item) { - return typeof cb === "function" ? cb({ - code: 404, - message: "Item '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" - }) : void 0; - } - if (user.stats.gp < item.value) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageNotEnoughGold', req.language) - }) : void 0; - } - if ((item.canOwn != null) && !item.canOwn(user)) { - return typeof cb === "function" ? cb({ - code: 401, - message: "You can't buy this item" - }) : void 0; - } - armoireResp = void 0; - if (item.key === 'potion') { - user.stats.hp += 15; - if (user.stats.hp > 50) { - user.stats.hp = 50; - } - } else if (item.key === 'armoire') { - armoireResult = user.fns.predictableRandom(user.stats.gp); - eligibleEquipment = _.filter(content.gear.flat, (function(i) { - return i.klass === 'armoire' && !user.items.gear.owned[i.key]; - })); - if (!_.isEmpty(eligibleEquipment) && (armoireResult < .6 || !user.flags.armoireOpened)) { - eligibleEquipment.sort(); - drop = user.fns.randomVal(eligibleEquipment); - user.items.gear.owned[drop.key] = true; - user.flags.armoireOpened = true; - message = i18n.t('armoireEquipment', { - image: '', - dropText: drop.text(req.language) - }, req.language); - if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) { - user.flags.armoireEmpty = true; - } - armoireResp = { - type: "gear", - dropKey: drop.key, - dropText: drop.text(req.language) - }; - } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < .8) || armoireResult < .5) { - drop = user.fns.randomVal(_.where(content.food, { - canDrop: true - })); - if ((base = user.items.food)[name = drop.key] == null) { - base[name] = 0; - } - user.items.food[drop.key] += 1; - message = i18n.t('armoireFood', { - image: '', - dropArticle: drop.article, - dropText: drop.text(req.language) - }, req.language); - armoireResp = { - type: "food", - dropKey: drop.key, - dropArticle: drop.article, - dropText: drop.text(req.language) - }; - } else { - armoireExp = Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10); - user.stats.exp += armoireExp; - message = i18n.t('armoireExp', req.language); - armoireResp = { - "type": "experience", - "value": armoireExp - }; - } +module.exports = function buy (user, req = {}, analytics) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); + + let buyRes; + if (key === 'potion') { + buyRes = buyHealthPotion(user, req, analytics); + } else if (key === 'armoire') { + buyRes = buyArmoire(user, req, analytics); } else { - if (user.preferences.autoEquip) { - user.items.gear.equipped[item.type] = item.key; - message = user.fns.handleTwoHanded(item, null, req); - } - user.items.gear.owned[item.key] = true; - if (message == null) { - message = i18n.t('messageBought', { - itemText: item.text(req.language) - }, req.language); - } - if (item.last) { - user.fns.ultimateGear(); - } + buyRes = buyGear(user, req, analytics); } - user.stats.gp -= item.value; - analyticsData = { - uuid: user._id, - itemKey: key, - acquireMethod: 'Gold', - goldCost: item.value, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); - } - buyResp = _.pick(user, splitWhitespace('items achievements stats flags')); - if (armoireResp) { - buyResp["armoire"] = armoireResp; - } - return typeof cb === "function" ? cb({ - code: 200, - message: message - }, buyResp) : void 0; + + return buyRes; }; diff --git a/common/script/ops/buyArmoire.js b/common/script/ops/buyArmoire.js new file mode 100644 index 0000000000..e183c06984 --- /dev/null +++ b/common/script/ops/buyArmoire.js @@ -0,0 +1,115 @@ +import content from '../content/index'; +import i18n from '../i18n'; +import _ from 'lodash'; +import count from '../count'; +import splitWhitespace from '../libs/splitWhitespace'; +import { + NotAuthorized, +} from '../libs/errors'; +import predictableRandom from '../fns/predictableRandom'; +import randomVal from '../fns/randomVal'; + +module.exports = function buyArmoire (user, req = {}, analytics) { + let item = content.armoire; + + if (user.stats.gp < item.value) { + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + } + + if (item.canOwn && !item.canOwn(user)) { + throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); + } + + let armoireResp; + let armoireResult; + let eligibleEquipment; + let drop; + let message; + + armoireResult = predictableRandom(user, user.stats.gp); + eligibleEquipment = _.filter(content.gear.flat, (eligible) => { + return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]; + }); + + if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) { + eligibleEquipment.sort(); + drop = randomVal(user, eligibleEquipment); + + if (user.items.gear.owned[drop.key]) { + throw new NotAuthorized(i18n.t('equipmentAlradyOwned', req.language)); + } + + user.items.gear.owned[drop.key] = true; + user.flags.armoireOpened = true; + message = i18n.t('armoireEquipment', { + image: ``, + dropText: drop.text(req.language), + }, req.language); + + if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) { + user.flags.armoireEmpty = true; + } + + armoireResp = { + type: 'gear', + dropKey: drop.key, + dropText: drop.text(req.language), + }; + } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens + drop = randomVal(user, _.where(content.food, { + canDrop: true, + })); + user.items.food[drop.key] = user.items.food[drop.key] || 0; + user.items.food[drop.key] += 1; + + message = i18n.t('armoireFood', { + image: ``, + dropArticle: drop.article, + dropText: drop.text(req.language), + }, req.language); + armoireResp = { + type: 'food', + dropKey: drop.key, + dropArticle: drop.article, + dropText: drop.text(req.language), + }; + } else { + let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10); + user.stats.exp += armoireExp; + message = i18n.t('armoireExp', req.language); + armoireResp = { + type: 'experience', + value: armoireExp, + }; + } + + user.stats.gp -= item.value; + + if (!message) { + message = i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language); + } + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: 'Armoire', + acquireMethod: 'Gold', + goldCost: item.value, + category: 'behavior', + }); + } + + let resData = _.pick(user, splitWhitespace('items flags')); + if (armoireResp) resData.armoire = armoireResp; + + if (req.v2 === true) { + return resData; + } else { + return [ + resData, + message, + ]; + } +}; diff --git a/common/script/ops/buyGear.js b/common/script/ops/buyGear.js new file mode 100644 index 0000000000..e4f4eb3b68 --- /dev/null +++ b/common/script/ops/buyGear.js @@ -0,0 +1,70 @@ +import content from '../content/index'; +import i18n from '../i18n'; +import _ from 'lodash'; +import splitWhitespace from '../libs/splitWhitespace'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; +import handleTwoHanded from '../fns/handleTwoHanded'; +import ultimateGear from '../fns/ultimateGear'; + +module.exports = function buyGear (user, req = {}, analytics) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); + + let item = content.gear.flat[key]; + + if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language)); + + if (user.stats.gp < item.value) { + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + } + + if (item.canOwn && !item.canOwn(user)) { + throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); + } + + let message; + + if (user.items.gear.owned[item.key]) { + throw new NotAuthorized(i18n.t('equipmentAlreadyOwned', req.language)); + } + + if (user.preferences.autoEquip) { + user.items.gear.equipped[item.type] = item.key; + message = handleTwoHanded(user, item, undefined, req); + } + + user.items.gear.owned[item.key] = true; + + if (item.last) ultimateGear(user); + + user.stats.gp -= item.value; + + if (!message) { + message = i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language); + } + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: key, + acquireMethod: 'Gold', + goldCost: item.value, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('items achievements stats flags')); + } else { + return [ + _.pick(user, splitWhitespace('items achievements stats flags')), + message, + ]; + } +}; diff --git a/common/script/ops/buyHealthPotion.js b/common/script/ops/buyHealthPotion.js new file mode 100644 index 0000000000..1a6c8b0e18 --- /dev/null +++ b/common/script/ops/buyHealthPotion.js @@ -0,0 +1,48 @@ +import content from '../content/index'; +import i18n from '../i18n'; +import { + NotAuthorized, +} from '../libs/errors'; + +module.exports = function buyHealthPotion (user, req = {}, analytics) { + let item = content.potion; + + if (user.stats.gp < item.value) { + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + } + + if (item.canOwn && !item.canOwn(user)) { + throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); + } + + user.stats.hp += 15; + if (user.stats.hp > 50) { + user.stats.hp = 50; + } + + user.stats.gp -= item.value; + + let message = i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language); + + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: 'Potion', + acquireMethod: 'Gold', + goldCost: item.value, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return user.stats; + } else { + return [ + user.stats, + message, + ]; + } +}; diff --git a/common/script/ops/buyMysterySet.js b/common/script/ops/buyMysterySet.js index 44ccdb9aaf..acf0014279 100644 --- a/common/script/ops/buyMysterySet.js +++ b/common/script/ops/buyMysterySet.js @@ -2,42 +2,54 @@ import i18n from '../i18n'; import content from '../content/index'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; +import pickDeep from '../libs/pickDeep'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; + +module.exports = function buyMysterySet (user, req = {}, analytics) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); -module.exports = function(user, req, cb, analytics) { - var mysterySet, ref; if (!(user.purchased.plan.consecutive.trinkets > 0)) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughHourglasses', req.language) - }) : void 0; - } - mysterySet = (ref = content.timeTravelerStore(user.items.gear.owned)) != null ? ref[req.params.key] : void 0; - if ((typeof window !== "undefined" && window !== null ? window.confirm : void 0) != null) { - if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) { - return; - } + throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); } + + let ref = content.timeTravelerStore(user.items.gear.owned); + let mysterySet = ref ? ref[key] : undefined; + if (!mysterySet) { - return typeof cb === "function" ? cb({ - code: 404, - message: "Mystery set not found, or set already owned" - }) : void 0; + throw new NotFound(i18n.t('mysterySetNotFound', req.language)); } - _.each(mysterySet.items, function(i) { - var analyticsData; - user.items.gear.owned[i.key] = true; - analyticsData = { - uuid: user._id, - itemKey: i.key, - itemType: 'Subscriber Gear', - acquireMethod: 'Hourglass', - category: 'behavior' - }; - return analytics != null ? analytics.track('acquire item', analyticsData) : void 0; + + if (typeof window !== 'undefined' && window.confirm) { // TODO move to client + if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) return; + } + + _.each(mysterySet.items, item => { + user.items.gear.owned[item.key] = true; + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: item.key, + itemType: 'Subscriber Gear', + acquireMethod: 'Hourglass', + category: 'behavior', + }); + } }); + user.purchased.plan.consecutive.trinkets--; - return typeof cb === "function" ? cb({ - code: 200, - message: i18n.t('hourglassPurchaseSet', req.language) - }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0; + + + if (req.v2 === true) { + return pickDeep(user, splitWhitespace('items purchased.plan.consecutive')); + } else { + return [ + { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, + i18n.t('hourglassPurchaseSet', req.language), + ]; + } }; diff --git a/common/script/ops/buyQuest.js b/common/script/ops/buyQuest.js index b7653d43ce..af7c384419 100644 --- a/common/script/ops/buyQuest.js +++ b/common/script/ops/buyQuest.js @@ -1,49 +1,50 @@ import i18n from '../i18n'; import content from '../content/index'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; +import _ from 'lodash'; + +// buy a quest with gold +module.exports = function buyQuest (user, req = {}, analytics) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); + + let item = content.quests[key]; + if (!item) throw new NotFound(i18n.t('questNotFound', {key}, req.language)); -module.exports = function(user, req, cb, analytics) { - var analyticsData, base, item, key, message, name; - key = req.params.key; - item = content.quests[key]; - if (!item) { - return typeof cb === "function" ? cb({ - code: 404, - message: "Quest '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" - }) : void 0; - } if (!(item.category === 'gold' && item.goldValue)) { - return typeof cb === "function" ? cb({ - code: 404, - message: "Quest '" + key + " is not a Gold-purchasable quest (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" - }) : void 0; + throw new NotAuthorized(i18n.t('questNotGoldPurchasable', {key}, req.language)); } if (user.stats.gp < item.goldValue) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageNotEnoughGold', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); } - message = i18n.t('messageBought', { - itemText: item.text(req.language) - }, req.language); - if ((base = user.items.quests)[name = item.key] == null) { - base[name] = 0; - } - user.items.quests[item.key] += 1; + + user.items.quests[item.key] = user.items.quests[item.key] || 0; + user.items.quests[item.key]++; user.stats.gp -= item.goldValue; - analyticsData = { - uuid: user._id, - itemKey: item.key, - itemType: 'Market', - goldCost: item.goldValue, - acquireMethod: 'Gold', - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: item.key, + itemType: 'Market', + goldCost: item.goldValue, + acquireMethod: 'Gold', + category: 'behavior', + }); + } + + if (req.v2 === true) { + return user.items.quests; + } else { + return [ + user.items.quests, + i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language), + ]; } - return typeof cb === "function" ? cb({ - code: 200, - message: message - }, user.items.quests) : void 0; }; diff --git a/common/script/ops/buySpecialSpell.js b/common/script/ops/buySpecialSpell.js index e2a9dc8deb..20ea0251aa 100644 --- a/common/script/ops/buySpecialSpell.js +++ b/common/script/ops/buySpecialSpell.js @@ -2,30 +2,34 @@ import i18n from '../i18n'; import content from '../content/index'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; + +module.exports = function buySpecialSpell (user, req = {}) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); + + let item = content.special[key]; + if (!item) throw new NotFound(i18n.t('spellNotFound', {spellId: key}, req.language)); -module.exports = function(user, req, cb) { - var base, item, key, message; - key = req.params.key; - item = content.special[key]; if (user.stats.gp < item.value) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageNotEnoughGold', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); } user.stats.gp -= item.value; - if ((base = user.items.special)[key] == null) { - base[key] = 0; - } + user.items.special[key]++; - if (typeof user.markModified === "function") { - user.markModified('items.special'); + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('items stats')); + } else { + return [ + _.pick(user, splitWhitespace('items stats')), + i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language), + ]; } - message = i18n.t('messageBought', { - itemText: item.text(req.language) - }, req.language); - return typeof cb === "function" ? cb({ - code: 200, - message: message - }, _.pick(user, splitWhitespace('items stats'))) : void 0; }; diff --git a/common/script/ops/changeClass.js b/common/script/ops/changeClass.js index 98fd8fd58d..4b3eb6b289 100644 --- a/common/script/ops/changeClass.js +++ b/common/script/ops/changeClass.js @@ -2,58 +2,77 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; import { capByLevel } from '../statHelpers'; +import { + NotAuthorized, +} from '../libs/errors'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, klass, ref; - klass = (ref = req.query) != null ? ref["class"] : void 0; - if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') { - analyticsData = { - uuid: user._id, - "class": klass, - acquireMethod: 'Gems', - gemCost: 3, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('change class', analyticsData); - } - user.stats["class"] = klass; +module.exports = function changeClass (user, req = {}, analytics) { + let klass = _.get(req, 'query.class'); + + // user.flags.classSelected is set to false after the user paid the 3 gems + if (user.stats.lvl < 10) { + throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language)); + } else if (!user.flags.classSelected && (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer')) { + user.stats.class = klass; user.flags.classSelected = true; - _.each(["weapon", "armor", "shield", "head"], function(type) { - var foundKey; - foundKey = false; - _.findLast(user.items.gear.owned, function(v, k) { - if (~k.indexOf(type + "_" + klass) && v === true) { - return foundKey = k; + + _.each(['weapon', 'armor', 'shield', 'head'], (type) => { + let foundKey = false; + _.findLast(user.items.gear.owned, (val, key) => { + if (key.indexOf(`${type}_${klass}`) !== -1 && val === true) { + foundKey = key; + return true; } }); - user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : type + "_base_0"; - if (type === "weapon" || (type === "shield" && klass === "rogue")) { - user.items.gear.owned[type + "_" + klass + "_0"] = true; + + if (!foundKey) { + if (type === 'weapon') { + foundKey = `weapon_${klass}_0`; + } else if (type === 'shield' && klass === 'rogue') { + foundKey = 'shield_rogue_0'; + } else { + foundKey = `${type}_base_0`; + } + } + + user.items.gear.equipped[type] = foundKey; + + if (type === 'weapon' || (type === 'shield' && klass === 'rogue')) { // eslint-disable-line no-extra-parens + user.items.gear.owned[`${type}_${klass}_0`] = true; } - return true; }); + + if (analytics) { + analytics.track('change class', { + uuid: user._id, + class: klass, + acquireMethod: 'Gems', + gemCost: 3, + category: 'behavior', + }); + } } else { if (user.preferences.disableClasses) { user.preferences.disableClasses = false; user.preferences.autoAllocate = false; } else { - if (!(user.balance >= .75)) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; - } - user.balance -= .75; + if (user.balance < 0.75) throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + user.balance -= 0.75; } - _.merge(user.stats, { - str: 0, - con: 0, - per: 0, - int: 0, - points: capByLevel(user.stats.lvl) - }); + + user.stats.str = 0; + user.stats.con = 0; + user.stats.per = 0; + user.stats.int = 0; + user.stats.points = capByLevel(user.stats.lvl); user.flags.classSelected = false; } - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags items preferences'))) : void 0; + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('stats flags items preferences')); + } else { + return [ + _.pick(user, splitWhitespace('stats flags items preferences')), + ]; + } }; diff --git a/common/script/ops/clearCompleted.js b/common/script/ops/clearCompleted.js index d60f12704f..26fb1727d9 100644 --- a/common/script/ops/clearCompleted.js +++ b/common/script/ops/clearCompleted.js @@ -1,12 +1,10 @@ import _ from 'lodash'; -module.exports = function(user, req, cb) { - _.remove(user.todos, function(t) { - var ref; - return t.completed && !((ref = t.challenge) != null ? ref.id : void 0); +// TODO move to client since it's only used there? +// TODO rename file to clearCompletedTodos + +module.exports = function clearCompletedTodos (todos) { + _.remove(todos, todo => { + return todo.completed && (!todo.challenge || !todo.challenge.id || todo.challenge.broken); }); - if (typeof user.markModified === "function") { - user.markModified('todos'); - } - return typeof cb === "function" ? cb(null, user.todos) : void 0; }; diff --git a/common/script/ops/clearPMs.js b/common/script/ops/clearPMs.js index 47e0a6ebc6..765ecc3b56 100644 --- a/common/script/ops/clearPMs.js +++ b/common/script/ops/clearPMs.js @@ -1,7 +1,7 @@ -module.exports = function(user, req, cb) { +module.exports = function clearPMs (user) { user.inbox.messages = {}; - if (typeof user.markModified === "function") { - user.markModified('inbox.messages'); - } - return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0; + user.markModified('inbox.messages'); + return [ + user.inbox.messages, + ]; }; diff --git a/common/script/ops/deletePM.js b/common/script/ops/deletePM.js index ad95bc9ae0..84bb7ee33a 100644 --- a/common/script/ops/deletePM.js +++ b/common/script/ops/deletePM.js @@ -1,7 +1,9 @@ -module.exports = function(user, req, cb) { - delete user.inbox.messages[req.params.id]; - if (typeof user.markModified === "function") { - user.markModified('inbox.messages.' + req.params.id); - } - return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0; +import _ from 'lodash'; + +module.exports = function deletePM (user, req = {}) { + delete user.inbox.messages[_.get(req, 'params.id')]; + user.markModified(`inbox.messages.${req.params.id}`); + return [ + user.inbox.messages, + ]; }; diff --git a/common/script/ops/deleteTag.js b/common/script/ops/deleteTag.js index a82af59e86..c40fe79ba5 100644 --- a/common/script/ops/deleteTag.js +++ b/common/script/ops/deleteTag.js @@ -1,26 +1,32 @@ import i18n from '../i18n'; import _ from 'lodash'; +import { NotFound } from '../libs/errors'; -module.exports = function(user, req, cb) { - var i, tag, tid; - tid = req.params.id; - i = _.findIndex(user.tags, { - id: tid +// TODO used only in client, move there? + +module.exports = function deleteTag (user, req = {}) { + let tid = _.get(req, 'params.id'); + + let index = _.findIndex(user.tags, { + id: tid, }); - if (!~i) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTagNotFound', req.language) - }) : void 0; + + if (index === -1) { + throw new NotFound(i18n.t('messageTagNotFound', req.language)); } - tag = user.tags[i]; + + let tag = user.tags[index]; delete user.filters[tag.id]; - user.tags.splice(i, 1); - _.each(user.tasks, function(task) { + + user.tags.splice(index, 1); + + _.each(user.tasks, (task) => { return delete task.tags[tag.id]; }); - _.each(['habits', 'dailys', 'todos', 'rewards'], function(type) { - return typeof user.markModified === "function" ? user.markModified(type) : void 0; + + _.each(['habits', 'dailys', 'todos', 'rewards'], (type) => { + user.markModified(type); }); - return typeof cb === "function" ? cb(null, user.tags) : void 0; + + return user.tags; }; diff --git a/common/script/ops/deleteTask.js b/common/script/ops/deleteTask.js index 715b102241..a641763de5 100644 --- a/common/script/ops/deleteTask.js +++ b/common/script/ops/deleteTask.js @@ -1,17 +1,22 @@ import i18n from '../i18n'; +import { NotFound } from '../libs/errors'; +import _ from 'lodash'; -module.exports = function(user, req, cb) { - var i, ref, task; - task = user.tasks[(ref = req.params) != null ? ref.id : void 0]; - if (!task) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTaskNotFound', req.language) - }) : void 0; +// TODO used only in client, move there? + +module.exports = function deleteTask (user, req = {}) { + let tid = _.get(req, 'params.id'); + let taskType = _.get(req, 'params.taskType'); + + let index = _.findIndex(user[`${taskType}s`], function findById (task) { + return task._id === tid; + }); + + if (index === -1) { + throw new NotFound(i18n.t('messageTaskNotFound', req.language)); } - i = user[task.type + "s"].indexOf(task); - if (~i) { - user[task.type + "s"].splice(i, 1); - } - return typeof cb === "function" ? cb(null, {}) : void 0; + + user[`${taskType}s`].splice(index, 1); + + return {}; }; diff --git a/common/script/ops/deleteWebhook.js b/common/script/ops/deleteWebhook.js index a187f08770..9c4f67eba8 100644 --- a/common/script/ops/deleteWebhook.js +++ b/common/script/ops/deleteWebhook.js @@ -1,7 +1,10 @@ -module.exports = function(user, req, cb) { - delete user.preferences.webhooks[req.params.id]; - if (typeof user.markModified === "function") { - user.markModified('preferences.webhooks'); - } - return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; +import _ from 'lodash'; + +module.exports = function deleteWebhook (user, req) { + delete user.preferences.webhooks[_.get(req, 'params.id')]; + user.markModified('preferences.webhooks'); + + return [ + user.preferences.webhooks, + ]; }; diff --git a/common/script/ops/disableClasses.js b/common/script/ops/disableClasses.js index 89636ed52d..e611bb0872 100644 --- a/common/script/ops/disableClasses.js +++ b/common/script/ops/disableClasses.js @@ -2,12 +2,19 @@ import splitWhitespace from '../libs/splitWhitespace'; import { capByLevel } from '../statHelpers'; import _ from 'lodash'; -module.exports = function(user, req, cb) { - user.stats["class"] = 'warrior'; +module.exports = function disableClasses (user, req = {}) { + user.stats.class = 'warrior'; user.flags.classSelected = true; user.preferences.disableClasses = true; user.preferences.autoAllocate = true; user.stats.str = capByLevel(user.stats.lvl); user.stats.points = 0; - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags preferences'))) : void 0; + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('stats flags preferences')); + } else { + return [ + _.pick(user, splitWhitespace('stats flags preferences')), + ]; + } }; diff --git a/common/script/ops/equip.js b/common/script/ops/equip.js index 8c07a364b1..9c614915a7 100644 --- a/common/script/ops/equip.js +++ b/common/script/ops/equip.js @@ -1,52 +1,68 @@ import content from '../content/index'; import i18n from '../i18n'; +import handleTwoHanded from '../fns/handleTwoHanded'; +import { + NotFound, + BadRequest, +} from '../libs/errors'; +import _ from 'lodash'; + +module.exports = function equip (user, req = {}) { + // Being type a parameter followed by another parameter + // when using the API it must be passes specifically in the URL, it's won't default to equipped + let type = _.get(req, 'params.type', 'equipped'); + let key = _.get(req, 'params.key'); + + if (!key || !type) throw new BadRequest(i18n.t('missingTypeKeyEquip', req.language)); + if (['mount', 'pet', 'costume', 'equipped'].indexOf(type) === -1) { + throw new BadRequest(i18n.t('invalidTypeEquip', req.language)); + } + + let message; -module.exports = function(user, req, cb) { - var item, key, message, ref, type; - ref = [req.params.type || 'equipped', req.params.key], type = ref[0], key = ref[1]; switch (type) { - case 'mount': + case 'mount': { if (!user.items.mounts[key]) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":You do not own this mount." - }) : void 0; + throw new NotFound(i18n.t('mountNotOwned', req.language)); } + user.items.currentMount = user.items.currentMount === key ? '' : key; break; - case 'pet': + } + case 'pet': { if (!user.items.pets[key]) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":You do not own this pet." - }) : void 0; + throw new NotFound(i18n.t('petNotOwned', req.language)); } + user.items.currentPet = user.items.currentPet === key ? '' : key; break; + } case 'costume': - case 'equipped': - item = content.gear.flat[key]; + case 'equipped': { if (!user.items.gear.owned[key]) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":You do not own this gear." - }) : void 0; + throw new NotFound(i18n.t('gearNotOwned', req.language)); } + + let item = content.gear.flat[key]; + if (user.items.gear[type][item.type] === key) { - user.items.gear[type][item.type] = item.type + "_base_0"; + user.items.gear[type][item.type] = `${item.type}_base_0`; message = i18n.t('messageUnEquipped', { - itemText: item.text(req.language) + itemText: item.text(req.language), }, req.language); } else { user.items.gear[type][item.type] = item.key; - message = user.fns.handleTwoHanded(item, type, req); - } - if (typeof user.markModified === "function") { - user.markModified("items.gear." + type); + message = handleTwoHanded(user, item, type, req); } + break; + } + } + + if (req.v2 === true) { + return user.items; + } else { + let res = [user.items]; + if (message) res.push(message); + return res; } - return typeof cb === "function" ? cb((message ? { - code: 200, - message: message - } : null), user.items) : void 0; }; diff --git a/common/script/ops/feed.js b/common/script/ops/feed.js index 3c9b0f87fd..637d9b3e1d 100644 --- a/common/script/ops/feed.js +++ b/common/script/ops/feed.js @@ -1,78 +1,102 @@ import content from '../content/index'; import i18n from '../i18n'; +import _ from 'lodash'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; + +function evolve (user, pet, petDisplayName, req) { + user.items.pets[pet] = -1; + user.items.mounts[pet] = true; + + if (pet === user.items.currentPet) { + user.items.currentPet = ''; + } + + return i18n.t('messageEvolve', { + egg: petDisplayName, + }, req.language); +} + +module.exports = function feed (user, req = {}) { + let pet = _.get(req, 'params.pet'); + let foodK = _.get(req, 'params.food'); + + if (!pet || !foodK) throw new BadRequest(i18n.t('missingPetFoodFeed', req.language)); + + if (pet.indexOf('-') === -1) { + throw new BadRequest(i18n.t('invalidPetName', req.language)); + } + + let food = content.food[foodK]; + if (!food) { + throw new NotFound(i18n.t('messageFoodNotFound', req.language)); + } + + let userPets = user.items.pets; -module.exports = function(user, req, cb) { - var egg, eggText, evolve, food, message, pet, petDisplayName, potion, potionText, ref, ref1, ref2, userPets; - ref = req.params, pet = ref.pet, food = ref.food; - food = content.food[food]; - ref1 = pet.split('-'), egg = ref1[0], potion = ref1[1]; - userPets = user.items.pets; - potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - petDisplayName = i18n.t('petName', { - potion: potionText, - egg: eggText - }); if (!userPets[pet]) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messagePetNotFound', req.language) - }) : void 0; + throw new NotFound(i18n.t('messagePetNotFound', req.language)); } - if (!((ref2 = user.items.food) != null ? ref2[food.key] : void 0)) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageFoodNotFound', req.language) - }) : void 0; + + let [egg, potion] = pet.split('-'); + + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text(req.language) : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text(req.language) : egg; + + let petDisplayName = i18n.t('petName', { + potion: potionText, + egg: eggText, + }, req.language); + + if (!user.items.food[food.key]) { + throw new NotFound(i18n.t('messageFoodNotFound', req.language)); } + if (content.specialPets[pet]) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageCannotFeedPet', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageCannotFeedPet', req.language)); } + if (user.items.mounts[pet]) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageAlreadyMount', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageAlreadyMount', req.language)); } - message = ''; - evolve = function() { - userPets[pet] = -1; - user.items.mounts[pet] = true; - if (pet === user.items.currentPet) { - user.items.currentPet = ""; - } - return message = i18n.t('messageEvolve', { - egg: petDisplayName - }, req.language); - }; + + let message; + if (food.key === 'Saddle') { - evolve(); + message = evolve(user, pet, petDisplayName, req); } else { if (food.target === potion || content.hatchingPotions[potion].premium) { userPets[pet] += 5; message = i18n.t('messageLikesFood', { egg: petDisplayName, - foodText: food.text(req.language) + foodText: food.text(req.language), }, req.language); } else { userPets[pet] += 2; message = i18n.t('messageDontEnjoyFood', { egg: petDisplayName, - foodText: food.text(req.language) + foodText: food.text(req.language), }, req.language); } + if (userPets[pet] >= 50 && !user.items.mounts[pet]) { - evolve(); + message = evolve(user, pet, petDisplayName, req); } } + user.items.food[food.key]--; - return typeof cb === "function" ? cb({ - code: 200, - message: message - }, { - value: userPets[pet] - }) : void 0; + + if (req.v2 === true) { + return { + value: userPets[pet], + }; + } else { + return [ + userPets[pet], + message, + ]; + } }; diff --git a/common/script/ops/getTag.js b/common/script/ops/getTag.js index 06f3f24110..4a7db63128 100644 --- a/common/script/ops/getTag.js +++ b/common/script/ops/getTag.js @@ -1,17 +1,18 @@ import _ from 'lodash'; import i18n from '../i18n'; +import { NotFound } from '../libs/errors'; -module.exports = function(user, req, cb) { - var i, tid; - tid = req.params.id; - i = _.findIndex(user.tags, { - id: tid +// TODO used only in client, move there? + +module.exports = function getTag (user, req = {}) { + let tid = _.get(req, 'params.id'); + + let index = _.findIndex(user.tags, { + id: tid, }); - if (!~i) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTagNotFound', req.language) - }) : void 0; + if (index === -1) { + throw new NotFound(i18n.t('messageTagNotFound', req.language)); } - return typeof cb === "function" ? cb(null, user.tags[i]) : void 0; + + return user.tags[index]; }; diff --git a/common/script/ops/getTags.js b/common/script/ops/getTags.js index af9419b050..a96589a831 100644 --- a/common/script/ops/getTags.js +++ b/common/script/ops/getTags.js @@ -1,3 +1,5 @@ -module.exports = function(user, req, cb) { - return typeof cb === "function" ? cb(null, user.tags) : void 0; +// TODO used only in client, move there? + +module.exports = function getTags (user) { + return user.tags; }; diff --git a/common/script/ops/hatch.js b/common/script/ops/hatch.js index 6292e24bcb..87adbf3276 100644 --- a/common/script/ops/hatch.js +++ b/common/script/ops/hatch.js @@ -1,39 +1,44 @@ import content from '../content/index'; import i18n from '../i18n'; +import _ from 'lodash'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../libs/errors'; + +module.exports = function hatch (user, req = {}) { + let egg = _.get(req, 'params.egg'); + let hatchingPotion = _.get(req, 'params.hatchingPotion'); -module.exports = function(user, req, cb) { - var egg, hatchingPotion, pet, ref; - ref = req.params, egg = ref.egg, hatchingPotion = ref.hatchingPotion; if (!(egg && hatchingPotion)) { - return typeof cb === "function" ? cb({ - code: 400, - message: "Please specify query.egg & query.hatchingPotion" - }) : void 0; + throw new BadRequest(i18n.t('missingEggHatchingPotionHatch', req.language)); } + if (!(user.items.eggs[egg] > 0 && user.items.hatchingPotions[hatchingPotion] > 0)) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('messageMissingEggPotion', req.language) - }) : void 0; + throw new NotFound(i18n.t('messageMissingEggPotion', req.language)); } + if (content.hatchingPotions[hatchingPotion].premium && !content.dropEggs[egg]) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('messageInvalidEggPotionCombo', req.language) - }) : void 0; + throw new BadRequest(i18n.t('messageInvalidEggPotionCombo', req.language)); } - pet = egg + "-" + hatchingPotion; + + let pet = `${egg}-${hatchingPotion}`; + if (user.items.pets[pet] && user.items.pets[pet] > 0) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('messageAlreadyPet', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageAlreadyPet', req.language)); } + user.items.pets[pet] = 5; user.items.eggs[egg]--; user.items.hatchingPotions[hatchingPotion]--; - return typeof cb === "function" ? cb({ - code: 200, - message: i18n.t('messageHatched', req.language) - }, user.items) : void 0; + + if (req.v2 === true) { + return user.items; + } else { + return [ + user.items, + i18n.t('messageHatched', req.language), + ]; + } }; diff --git a/common/script/ops/hourglassPurchase.js b/common/script/ops/hourglassPurchase.js index fa0b9e672a..e1d07bb482 100644 --- a/common/script/ops/hourglassPurchase.js +++ b/common/script/ops/hourglassPurchase.js @@ -1,54 +1,61 @@ import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; +import { + BadRequest, + NotAuthorized, +} from '../libs/errors'; import splitWhitespace from '../libs/splitWhitespace'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, key, ref, type; - ref = req.params, type = ref.type, key = ref.key; +module.exports = function purchaseHourglass (user, req = {}, analytics) { + let key = _.get(req, 'params.key'); + if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); + + let type = _.get(req, 'params.type'); + if (!type) throw new BadRequest(i18n.t('missingTypeParam', req.language)); + if (!content.timeTravelStable[type]) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language)); } + if (!_.contains(_.keys(content.timeTravelStable[type]), key)) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t(type + 'NotAllowedHourglass', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('notAllowedHourglass', req.language)); } + if (user.items[type][key]) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t(type + 'AlreadyOwned', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t(`${type}AlreadyOwned`, req.language)); } - if (!(user.purchased.plan.consecutive.trinkets > 0)) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('notEnoughHourglasses', req.language) - }) : void 0; + + if (user.purchased.plan.consecutive.trinkets <= 0) { + throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); } + user.purchased.plan.consecutive.trinkets--; + if (type === 'pets') { user.items.pets[key] = 5; } + if (type === 'mounts') { user.items.mounts[key] = true; } - analyticsData = { - uuid: user._id, - itemKey: key, - itemType: type, - acquireMethod: 'Hourglass', - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: key, + itemType: type, + acquireMethod: 'Hourglass', + category: 'behavior', + }); + } + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('items purchased.plan.consecutive')); + } else { + return [ + { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, + i18n.t('hourglassPurchase', req.language), + ]; } - return typeof cb === "function" ? cb({ - code: 200, - message: i18n.t('hourglassPurchase', req.language) - }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0; }; diff --git a/common/script/ops/index.js b/common/script/ops/index.js index 1bfd3d55f2..2e8bca246d 100644 --- a/common/script/ops/index.js +++ b/common/script/ops/index.js @@ -30,6 +30,9 @@ import releasePets from './releasePets'; import releaseMounts from './releaseMounts'; import releaseBoth from './releaseBoth'; import buy from './buy'; +import buyGear from './buyGear'; +import buyHealthPotion from './buyHealthPotion'; +import buyArmoire from './buyArmoire'; import buyQuest from './buyQuest'; import buyMysterySet from './buyMysterySet'; import hourglassPurchase from './hourglassPurchase'; @@ -42,7 +45,9 @@ import disableClasses from './disableClasses'; import allocate from './allocate'; import readCard from './readCard'; import openMysteryItem from './openMysteryItem'; -import score from './score'; +import scoreTask from './scoreTask'; +import markPmsRead from './markPMSRead'; + module.exports = { update, @@ -77,6 +82,9 @@ module.exports = { releaseMounts, releaseBoth, buy, + buyGear, + buyHealthPotion, + buyArmoire, buyQuest, buyMysterySet, hourglassPurchase, @@ -89,5 +97,6 @@ module.exports = { allocate, readCard, openMysteryItem, - score, + scoreTask, + markPmsRead, }; diff --git a/common/script/ops/markPMSRead.js b/common/script/ops/markPMSRead.js new file mode 100644 index 0000000000..add9f49de5 --- /dev/null +++ b/common/script/ops/markPMSRead.js @@ -0,0 +1,14 @@ +import i18n from '../i18n'; + +module.exports = function markPmsRead (user, req = {}) { + user.inbox.newMessages = 0; + + if (req.v2 === true) { + return user; + } else { + return [ + user.inbox.newMessages, + i18n.t('pmsMarkedRead'), + ]; + } +}; diff --git a/common/script/ops/openMysteryItem.js b/common/script/ops/openMysteryItem.js index eb28c605e0..743104c48b 100644 --- a/common/script/ops/openMysteryItem.js +++ b/common/script/ops/openMysteryItem.js @@ -1,32 +1,44 @@ import content from '../content/index'; +import i18n from '../i18n'; +import { + BadRequest, +} from '../libs/errors'; +import _ from 'lodash'; + +module.exports = function openMysteryItem (user, req = {}, analytics) { + let item = user.purchased.plan.mysteryItems.shift(); -module.exports = function(user, req, cb, analytics) { - var analyticsData, item, ref, ref1; - item = (ref = user.purchased.plan) != null ? (ref1 = ref.mysteryItems) != null ? ref1.shift() : void 0 : void 0; if (!item) { - return typeof cb === "function" ? cb({ - code: 400, - message: "Empty" - }) : void 0; - } - item = content.gear.flat[item]; - user.items.gear.owned[item.key] = true; - if (typeof user.markModified === "function") { - user.markModified('purchased.plan.mysteryItems'); + throw new BadRequest(i18n.t('mysteryItemIsEmpty', req.language)); } + + item = _.cloneDeep(content.gear.flat[item]); item.notificationType = 'Mystery'; - analyticsData = { - uuid: user._id, - itemKey: item, - itemType: 'Subscriber Gear', - acquireMethod: 'Subscriber', - category: 'behavior' - }; - if (analytics != null) { - analytics.track('open mystery item', analyticsData); + user.items.gear.owned[item.key] = true; + + user.markModified('purchased.plan.mysteryItems'); + + if (analytics) { + analytics.track('open mystery item', { + uuid: user._id, + itemKey: item, + itemType: 'Subscriber Gear', + acquireMethod: 'Subscriber', + category: 'behavior', + }); } + if (typeof window !== 'undefined') { - (user._tmp != null ? user._tmp : user._tmp = {}).drop = item; + if (!user._tmp) user._tmp = {}; + user._tmp.drop = item; + } + + if (req.v2 === true) { + return user.items.gear.owned; + } else { + return [ + user.items.gear.owned, + i18n.t('mysteryItemOpened', req.language), + ]; } - return typeof cb === "function" ? cb(null, user.items.gear.owned) : void 0; }; diff --git a/common/script/ops/purchase.js b/common/script/ops/purchase.js index d4d0f78816..79eb0475d4 100644 --- a/common/script/ops/purchase.js +++ b/common/script/ops/purchase.js @@ -3,105 +3,125 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; import planGemLimits from '../libs/planGemLimits'; +import { + NotFound, + NotAuthorized, + BadRequest, +} from '../libs/errors'; + +module.exports = function purchase (user, req = {}, analytics) { + let type = _.get(req.params, 'type'); + let key = _.get(req.params, 'key'); + let item; + let price; + + if (!type) { + throw new BadRequest(i18n.t('typeRequired', req.language)); + } + + if (!key) { + throw new BadRequest(i18n.t('keyRequired', req.language)); + } -module.exports = function(user, req, cb, analytics) { - var analyticsData, convCap, convRate, item, key, price, ref, ref1, ref2, ref3, type; - ref = req.params, type = ref.type, key = ref.key; if (type === 'gems' && key === 'gem') { - ref1 = planGemLimits, convRate = ref1.convRate, convCap = ref1.convCap; + let convRate = planGemLimits.convRate; + let convCap = planGemLimits.convCap; convCap += user.purchased.plan.consecutive.gemCapExtra; - if (!((ref2 = user.purchased) != null ? (ref3 = ref2.plan) != null ? ref3.customerId : void 0 : void 0)) { - return typeof cb === "function" ? cb({ - code: 401, - message: "Must subscribe to purchase gems with GP" - }, req) : void 0; + + if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) { + throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language)); } - if (!(user.stats.gp >= convRate)) { - return typeof cb === "function" ? cb({ - code: 401, - message: "Not enough Gold" - }) : void 0; + + if (user.stats.gp < convRate) { + throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); } + if (user.purchased.plan.gemsBought >= convCap) { - return typeof cb === "function" ? cb({ - code: 401, - message: "You've reached the Gold=>Gem conversion cap (" + convCap + ") for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month." - }) : void 0; + throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language)); } - user.balance += .25; + + user.balance += 0.25; user.purchased.plan.gemsBought++; user.stats.gp -= convRate; - analyticsData = { - uuid: user._id, - itemKey: key, - acquireMethod: 'Gold', - goldCost: convRate, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('purchase gems', analyticsData); + + if (analytics) { + analytics.track('purchase gems', { + uuid: user._id, + itemKey: key, + acquireMethod: 'Gold', + goldCost: convRate, + category: 'behavior', + }); } - return typeof cb === "function" ? cb({ - code: 200, - message: "+1 Gem" - }, _.pick(user, splitWhitespace('stats balance'))) : void 0; + + return [ + _.pick(user, splitWhitespace('stats balance')), + i18n.t('plusOneGem'), + ]; } - if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'gear') { - return typeof cb === "function" ? cb({ - code: 404, - message: ":type must be in [eggs,hatchingPotions,food,quests,gear]" - }, req) : void 0; + + let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear']; + if (acceptedTypes.indexOf(type) === -1) { + throw new NotFound(i18n.t('notAccteptedType', req.language)); } + if (type === 'gear') { item = content.gear.flat[key]; - if (user.items.gear.owned[key]) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('alreadyHave', req.language) - }) : void 0; + + if (!item) { + throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language)); } + + if (user.items.gear.owned[key]) { + throw new NotAuthorized(i18n.t('alreadyHave', req.language)); + } + price = (item.twoHanded || item.gearSet === 'animal' ? 2 : 1) / 4; } else { item = content[type][key]; + + if (!item) { + throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language)); + } + price = item.value / 4; } - if (!item) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":key not found for Content." + type - }, req) : void 0; - } + if (!item.canBuy(user)) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('messageNotAvailable', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } - if ((user.balance < price) || !user.balance) { - return typeof cb === "function" ? cb({ - code: 403, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; + + if (!user.balance || user.balance < price) { + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); } + user.balance -= price; + if (type === 'gear') { user.items.gear.owned[key] = true; } else { - if (!(user.items[type][key] > 0)) { + if (!user.items[type][key] || user.items[type][key] < 0) { user.items[type][key] = 0; } user.items[type][key]++; } - analyticsData = { - uuid: user._id, - itemKey: key, - itemType: 'Market', - acquireMethod: 'Gems', - gemCost: item.value, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: key, + itemType: 'Market', + acquireMethod: 'Gems', + gemCost: item.value, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('items balance')); + } else { + return [ + _.pick(user, splitWhitespace('items balance')), + ]; } - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('items balance'))) : void 0; }; diff --git a/common/script/ops/readCard.js b/common/script/ops/readCard.js index a6eb5c05f1..57b0da4b00 100644 --- a/common/script/ops/readCard.js +++ b/common/script/ops/readCard.js @@ -1,10 +1,32 @@ -module.exports = function(user, req, cb) { - var cardType; - cardType = req.params.cardType; - user.items.special[cardType + "Received"].shift(); - if (typeof user.markModified === "function") { - user.markModified("items.special." + cardType + "Received"); +import splitWhitespace from '../libs/splitWhitespace'; +import _ from 'lodash'; +import i18n from '../i18n'; +import { + BadRequest, + NotAuthorized, +} from '../libs/errors'; +import content from '../content/index'; + +module.exports = function readCard (user, req = {}) { + let cardType = _.get(req.params, 'cardType'); + + if (!cardType) { + throw new BadRequest(i18n.t('cardTypeRequired', req.language)); } + + if (_.keys(content.cardTypes).indexOf(cardType) === -1) { + throw new NotAuthorized(i18n.t('cardTypeNotAllowed', req.language)); + } + + user.items.special[`${cardType}Received`].shift(); user.flags.cardReceived = false; - return typeof cb === "function" ? cb(null, 'items.special flags.cardReceived') : void 0; + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('items.special flags.cardReceived')); + } else { + return [ + { specialItems: user.items.special, cardReceived: user.flags.cardReceived }, + i18n.t('readCard', {cardType}, req.language), + ]; + } }; diff --git a/common/script/ops/rebirth.js b/common/script/ops/rebirth.js index 1ddb75aba1..54f9533fc5 100644 --- a/common/script/ops/rebirth.js +++ b/common/script/ops/rebirth.js @@ -1,21 +1,25 @@ -import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; import { capByLevel } from '../statHelpers'; import { MAX_LEVEL } from '../constants'; +import { + NotAuthorized, +} from '../libs/errors'; +import resetGear from '../fns/resetGear'; +import equip from './equip'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, flags, gear, lvl, stats; +const USERSTATSLIST = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp']; + +module.exports = function rebirth (user, tasks = [], req = {}, analytics) { if (user.balance < 2 && user.stats.lvl < MAX_LEVEL) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); } - analyticsData = { + + let analyticsData = { uuid: user._id, - category: 'behavior' + category: 'behavior', }; + if (user.stats.lvl < MAX_LEVEL) { user.balance -= 2; analyticsData.acquireMethod = 'Gems'; @@ -24,63 +28,55 @@ module.exports = function(user, req, cb, analytics) { analyticsData.gemCost = 0; analyticsData.acquireMethod = '> 100'; } - if (analytics != null) { + + if (analytics) { analytics.track('Rebirth', analyticsData); } - lvl = capByLevel(user.stats.lvl); - _.each(user.tasks, function(task) { - if (task.type !== 'reward') { - task.value = 0; - } - if (task.type === 'daily') { - return task.streak = 0; + + let lvl = capByLevel(user.stats.lvl); + + _.each(tasks, function resetTasks (task) { + if (!task.challenge || !task.challenge.id || task.challenge.broken) { + if (task.type !== 'reward') { + task.value = 0; + } + if (task.type === 'daily') { + task.streak = 0; + } } }); - stats = user.stats; + + let stats = user.stats; stats.buffs = {}; stats.hp = 50; stats.lvl = 1; - stats["class"] = 'warrior'; - _.each(['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'], function(value) { - return stats[value] = 0; - }); - // TODO during refactoring: move all gear code from rebirth() to its own function and then call it in reset() as well - gear = user.items.gear; - _.each(['equipped', 'costume'], function(type) { - gear[type] = {}; - gear[type].armor = 'armor_base_0'; - gear[type].weapon = 'weapon_warrior_0'; - gear[type].head = 'head_base_0'; - return gear[type].shield = 'shield_base_0'; + stats.class = 'warrior'; + + _.each(USERSTATSLIST, function resetStats (value) { + stats[value] = 0; }); + + resetGear(user); + if (user.items.currentPet) { - user.ops.equip({ + equip(user, { params: { type: 'pet', - key: user.items.currentPet - } + key: user.items.currentPet, + }, }); } + if (user.items.currentMount) { - user.ops.equip({ + equip(user, { params: { type: 'mount', - key: user.items.currentMount - } + key: user.items.currentMount, + }, }); } - _.each(gear.owned, function(v, k) { - if (gear.owned[k] && content.gear.flat[k].value) { - gear.owned[k] = false; - return true; - } - }); - gear.owned.weapon_warrior_0 = true; - if (typeof user.markModified === "function") { - user.markModified('items.gear.owned'); - } - user.preferences.costume = false; - flags = user.flags; + + let flags = user.flags; if (!user.achievements.beastMaster) { flags.rebirthEnabled = false; } @@ -88,13 +84,23 @@ module.exports = function(user, req, cb, analytics) { flags.dropsEnabled = false; flags.classSelected = false; flags.levelDrops = {}; + if (!user.achievements.rebirths) { user.achievements.rebirths = 1; user.achievements.rebirthLevel = lvl; - } else if (lvl > user.achievements.rebirthLevel || lvl === 100) { + } else if (lvl > user.achievements.rebirthLevel || lvl === MAX_LEVEL) { user.achievements.rebirths++; user.achievements.rebirthLevel = lvl; } + user.stats.buffs = {}; - return typeof cb === "function" ? cb(null, user) : void 0; + + if (req.v2 === true) { + return user; + } else { + return [ + {user, tasks}, + i18n.t('rebirthComplete'), + ]; + } }; diff --git a/common/script/ops/releaseBoth.js b/common/script/ops/releaseBoth.js index a782581f85..cf7d2267ca 100644 --- a/common/script/ops/releaseBoth.js +++ b/common/script/ops/releaseBoth.js @@ -1,50 +1,68 @@ import content from '../content/index'; import i18n from '../i18n'; +import { + NotAuthorized, +} from '../libs/errors'; +import splitWhitespace from '../libs/splitWhitespace'; +import _ from 'lodash'; + +module.exports = function releaseBoth (user, req = {}, analytics) { + let animal; -module.exports = function(user, req, cb, analytics) { - var analyticsData, animal, giveTriadBingo; if (user.balance < 1.5 && !user.achievements.triadBingo) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; - } else { - giveTriadBingo = true; - if (!user.achievements.triadBingo) { - analyticsData = { + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + } + + let giveTriadBingo = true; + + if (!user.achievements.triadBingo) { + if (analytics) { + analytics.track('release pets & mounts', { uuid: user._id, acquireMethod: 'Gems', gemCost: 6, - category: 'behavior' - }; - if (typeof analytics !== "undefined" && analytics !== null) { - analytics.track('release pets & mounts', analyticsData); - } - user.balance -= 1.5; - } - user.items.currentMount = ""; - user.items.currentPet = ""; - for (animal in content.pets) { - if (user.items.pets[animal] === -1) { - giveTriadBingo = false; - } - user.items.pets[animal] = 0; - user.items.mounts[animal] = null; - } - if (!user.achievements.beastMasterCount) { - user.achievements.beastMasterCount = 0; - } - user.achievements.beastMasterCount++; - if (!user.achievements.mountMasterCount) { - user.achievements.mountMasterCount = 0; - } - user.achievements.mountMasterCount++; - if (giveTriadBingo) { - if (!user.achievements.triadBingoCount) { - user.achievements.triadBingoCount = 0; - } - user.achievements.triadBingoCount++; + category: 'behavior', + }); } + + user.balance -= 1.5; + } + + user.items.currentMount = ''; + user.items.currentPet = ''; + + for (animal in content.pets) { + if (user.items.pets[animal] === -1) { + giveTriadBingo = false; + } + + user.items.pets[animal] = 0; + user.items.mounts[animal] = null; + } + + if (!user.achievements.beastMasterCount) { + user.achievements.beastMasterCount = 0; + } + user.achievements.beastMasterCount++; + + if (!user.achievements.mountMasterCount) { + user.achievements.mountMasterCount = 0; + } + user.achievements.mountMasterCount++; + + if (giveTriadBingo) { + if (!user.achievements.triadBingoCount) { + user.achievements.triadBingoCount = 0; + } + user.achievements.triadBingoCount++; + } + + if (req.v2 === true) { + return user; + } else { + return [ + _.pick(user, splitWhitespace('achievements items balance')), + i18n.t('mountsAndPetsReleased'), + ]; } - return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/releaseMounts.js b/common/script/ops/releaseMounts.js index 4aefab6b40..d8b8dde659 100644 --- a/common/script/ops/releaseMounts.js +++ b/common/script/ops/releaseMounts.js @@ -1,32 +1,43 @@ import content from '../content/index'; import i18n from '../i18n'; +import { + NotAuthorized, +} from '../libs/errors'; + +module.exports = function releaseMounts (user, req = {}, analytics) { + let mount; -module.exports = function(user, req, cb, analytics) { - var analyticsData, mount; if (user.balance < 1) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + } + + user.balance -= 1; + user.items.currentMount = ''; + + for (mount in content.pets) { + user.items.mounts[mount] = null; + } + + if (!user.achievements.mountMasterCount) { + user.achievements.mountMasterCount = 0; + } + user.achievements.mountMasterCount++; + + if (analytics) { + analytics.track('release mounts', { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return user; } else { - user.balance -= 1; - user.items.currentMount = ""; - for (mount in content.pets) { - user.items.mounts[mount] = null; - } - if (!user.achievements.mountMasterCount) { - user.achievements.mountMasterCount = 0; - } - user.achievements.mountMasterCount++; + return [ + user.items.mounts, + i18n.t('mountsReleased'), + ]; } - analyticsData = { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('release mounts', analyticsData); - } - return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/releasePets.js b/common/script/ops/releasePets.js index a4b452cd86..9466e1ccda 100644 --- a/common/script/ops/releasePets.js +++ b/common/script/ops/releasePets.js @@ -1,32 +1,41 @@ import content from '../content/index'; import i18n from '../i18n'; +import { + NotAuthorized, +} from '../libs/errors'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, pet; +module.exports = function releasePets (user, req = {}, analytics) { if (user.balance < 1) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + } + + user.balance -= 1; + user.items.currentPet = ''; + + for (let pet in content.pets) { + user.items.pets[pet] = 0; + } + + if (!user.achievements.beastMasterCount) { + user.achievements.beastMasterCount = 0; + } + user.achievements.beastMasterCount++; + + if (analytics) { + analytics.track('release pets', { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return user; } else { - user.balance -= 1; - for (pet in content.pets) { - user.items.pets[pet] = 0; - } - if (!user.achievements.beastMasterCount) { - user.achievements.beastMasterCount = 0; - } - user.achievements.beastMasterCount++; - user.items.currentPet = ""; + return [ + user.items.pets, + i18n.t('petsReleased'), + ]; } - analyticsData = { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('release pets', analyticsData); - } - return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/reroll.js b/common/script/ops/reroll.js index f6a0862f1d..3087845509 100644 --- a/common/script/ops/reroll.js +++ b/common/script/ops/reroll.js @@ -1,29 +1,40 @@ import i18n from '../i18n'; import _ from 'lodash'; +import { + NotAuthorized, +} from '../libs/errors'; -module.exports = function(user, req, cb, analytics) { - var analyticsData; +module.exports = function reroll (user, tasks = [], req = {}, analytics) { if (user.balance < 1) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); } + user.balance--; - _.each(user.tasks, function(task) { - if (task.type !== 'reward') { - return task.value = 0; + user.stats.hp = 50; + + _.each(tasks, function resetTaskValues (task) { + if (!task.challenge || !task.challenge.id || task.challenge.broken) { + if (task.type !== 'reward') { + task.value = 0; + } } }); - user.stats.hp = 50; - analyticsData = { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('Fortify Potion', analyticsData); + + if (analytics) { + analytics.track('Fortify Potion', { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior', + }); + } + + if (req.v2 === true) { + return user; + } else { + return [ + {user, tasks}, + i18n.t('fortifyComplete'), + ]; } - return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/reset.js b/common/script/ops/reset.js index fbe87729e3..3e48fa4f2d 100644 --- a/common/script/ops/reset.js +++ b/common/script/ops/reset.js @@ -1,35 +1,29 @@ -import _ from 'lodash'; +import resetGear from '../fns/resetGear'; +import i18n from '../i18n'; -module.exports = function(user, req, cb) { - var gear; - user.habits = []; - user.dailys = []; - user.todos = []; - user.rewards = []; +module.exports = function reset (user, tasks = [], req = {}) { user.stats.hp = 50; user.stats.lvl = 1; user.stats.gp = 0; user.stats.exp = 0; - gear = user.items.gear; - _.each(['equipped', 'costume'], function(type) { - gear[type].armor = 'armor_base_0'; - gear[type].weapon = 'weapon_base_0'; - gear[type].head = 'head_base_0'; - return gear[type].shield = 'shield_base_0'; - }); - if (typeof gear.owned === 'undefined') { - gear.owned = {}; - } - _.each(gear.owned, function(v, k) { - if (gear.owned[k]) { - gear.owned[k] = false; + + let tasksToRemove = []; + tasks.forEach(task => { + if (!task.challenge || !task.challenge.id || task.challenge.broken) { + tasksToRemove.push(task._id); + let i = user.tasksOrder[`${task.type}s`].indexOf(task._id); + if (i !== -1) user.tasksOrder[`${task.type}s`].splice(i, 1); } - return true; }); - gear.owned.weapon_warrior_0 = true; - if (typeof user.markModified === "function") { - user.markModified('items.gear.owned'); + + resetGear(user); + + if (req.v2 === true) { + return user; + } else { + return [ + {user, tasksToRemove}, + i18n.t('resetComplete'), + ]; } - user.preferences.costume = false; - return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/revive.js b/common/script/ops/revive.js index 7b6fe8445f..30b29a78fc 100644 --- a/common/script/ops/revive.js +++ b/common/script/ops/revive.js @@ -1,72 +1,107 @@ import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; +import { + NotAuthorized, +} from '../libs/errors'; +import randomVal from '../fns/randomVal'; -module.exports = function(user, req, cb, analytics) { - var analyticsData, base, cl, gearOwned, item, losableItems, lostItem, lostStat; - if (!(user.stats.hp <= 0)) { - return typeof cb === "function" ? cb({ - code: 400, - message: "Cannot revive if not dead" - }) : void 0; +module.exports = function revive (user, req = {}, analytics) { + if (user.stats.hp > 0) { + throw new NotAuthorized(i18n.t('cannotRevive', req.language)); } + _.merge(user.stats, { hp: 50, exp: 0, - gp: 0 + gp: 0, }); + if (user.stats.lvl > 1) { user.stats.lvl--; } - lostStat = user.fns.randomVal(_.reduce(['str', 'con', 'per', 'int'], (function(m, k) { + + let lostStat = randomVal(user, _.reduce(['str', 'con', 'per', 'int'], function findRandomStat (m, k) { if (user.stats[k]) { m[k] = k; } return m; - }), {})); + }, {})); + if (lostStat) { user.stats[lostStat]--; } - cl = user.stats["class"]; - gearOwned = (typeof (base = user.items.gear.owned).toObject === "function" ? base.toObject() : void 0) || user.items.gear.owned; - losableItems = {}; - _.each(gearOwned, function(v, k) { - var itm; - if (v) { - itm = content.gear.flat['' + k]; + + let base = user.items.gear.owned; + let gearOwned; + + if (typeof base.toObject === 'function') { + gearOwned = base.toObject(); + } else { + gearOwned = user.items.gear.owned; + } + + let losableItems = {}; + let userClass = user.stats.class; + + _.each(gearOwned, function findLosableItems (value, key) { + let itm; + if (value) { + itm = content.gear.flat[key]; + if (itm) { - if ((itm.value > 0 || k === 'weapon_warrior_0') && (itm.klass === cl || (itm.klass === 'special' && (!itm.specialClass || itm.specialClass === cl)) || itm.klass === 'armoire')) { - return losableItems['' + k] = '' + k; + let itemHasValueOrWarrior0 = itm.value > 0 || key === 'weapon_warrior_0'; + + let itemClassEqualsUserClass = itm.klass === userClass; + + let itemClassSpecial = itm.klass === 'special'; + let itemNotSpecialOrUserClassIsSpecial = !itm.specialClass || itm.specialClass === userClass; + let itemIsSpecial = itemNotSpecialOrUserClassIsSpecial && itemClassSpecial; + + let itemIsArmoire = itm.klass === 'armoire'; + + if (itemHasValueOrWarrior0 && (itemClassEqualsUserClass || itemIsSpecial || itemIsArmoire)) { + losableItems[key] = key; + return losableItems[key]; } } } }); - lostItem = user.fns.randomVal(losableItems); - if (item = content.gear.flat[lostItem]) { + + let lostItem = randomVal(user, losableItems); + + let message = ''; + let item = content.gear.flat[lostItem]; + + if (item) { user.items.gear.owned[lostItem] = false; + if (user.items.gear.equipped[item.type] === lostItem) { - user.items.gear.equipped[item.type] = item.type + "_base_0"; + user.items.gear.equipped[item.type] = `${item.type}_base_0`; } + if (user.items.gear.costume[item.type] === lostItem) { - user.items.gear.costume[item.type] = item.type + "_base_0"; + user.items.gear.costume[item.type] = `${item.type}_base_0`; } + + message = i18n.t('messageLostItem', { itemText: item.text(req.language)}, req.language); } - if (typeof user.markModified === "function") { - user.markModified('items.gear'); + + if (analytics) { + analytics.track('Death', { + uuid: user._id, + lostItem, + gaLabel: lostItem, + category: 'behavior', + }); } - analyticsData = { - uuid: user._id, - lostItem: lostItem, - gaLabel: lostItem, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('Death', analyticsData); + + if (req.v2 === true) { + return user; + } else { + return [ + user.items, + message, + ]; } - return typeof cb === "function" ? cb((item ? { - code: 200, - message: i18n.t('messageLostItem', { - itemText: item.text(req.language) - }, req.language) - } : null), user) : void 0; }; diff --git a/common/script/ops/score.js b/common/script/ops/score.js deleted file mode 100644 index ec0d2a70e2..0000000000 --- a/common/script/ops/score.js +++ /dev/null @@ -1,221 +0,0 @@ -import moment from 'moment'; -import _ from 'lodash'; -import i18n from '../i18n'; - -module.exports = function(user, req, cb) { - var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, gainMP, id, multiplier, num, options, ref, stats, subtractPoints, task, th; - ref = req.params, id = ref.id, direction = ref.direction; - task = user.tasks[id]; - options = req.query || {}; - _.defaults(options, { - times: 1, - cron: false - }); - user._tmp = {}; - stats = { - gp: +user.stats.gp, - hp: +user.stats.hp, - exp: +user.stats.exp - }; - task.value = +task.value; - task.streak = ~~task.streak; - if (task.priority == null) { - task.priority = 1; - } - if (task.value > stats.gp && task.type === 'reward') { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('messageNotEnoughGold', req.language) - }) : void 0; - } - delta = 0; - calculateDelta = function() { - var currVal, nextDelta, ref1; - currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value; - nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); - if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) { - if (direction === 'down' && task.type === 'daily' && options.cron) { - nextDelta *= 1 - _.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 0) / task.checklist.length; - } - if (task.type === 'todo') { - nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 0); - } - } - return nextDelta; - }; - calculateReverseDelta = function() { - var calc, closeEnough, currVal, diff, nextDelta, ref1, testVal; - currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value; - testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); - closeEnough = 0.00001; - while (true) { - calc = testVal + Math.pow(0.9747, testVal); - diff = currVal - calc; - if (Math.abs(diff) < closeEnough) { - break; - } - if (diff > 0) { - testVal -= diff; - } else { - testVal += diff; - } - } - nextDelta = testVal - currVal; - if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) { - if (task.type === 'todo') { - nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 0); - } - } - return nextDelta; - }; - changeTaskValue = function() { - return _.times(options.times, function() { - var nextDelta, ref1; - nextDelta = !options.cron && direction === 'down' ? calculateReverseDelta() : calculateDelta(); - if (task.type !== 'reward') { - if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) { - user.stats.training[task.attribute] += nextDelta; - } - if (direction === 'up') { - user.party.quest.progress.up = user.party.quest.progress.up || 0; - if ((ref1 = task.type) === 'daily' || ref1 === 'todo') { - user.party.quest.progress.up += nextDelta * (1 + (user._statsComputed.str / 200)); - } - if (task.type === 'habit') { - user.party.quest.progress.up += nextDelta * (0.5 + (user._statsComputed.str / 400)); - } - } - task.value += nextDelta; - } - return delta += nextDelta; - }); - }; - addPoints = function() { - var _crit, afterStreak, currStreak, gpMod, intBonus, perBonus, streakBonus; - _crit = (delta > 0 ? user.fns.crit() : 1); - if (_crit > 1) { - user._tmp.crit = _crit; - } - intBonus = 1 + (user._statsComputed.int * .025); - stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6); - perBonus = 1 + user._statsComputed.per * .02; - gpMod = delta * task.priority * _crit * perBonus; - return stats.gp += task.streak ? (currStreak = direction === 'down' ? task.streak - 1 : task.streak, streakBonus = currStreak / 100 + 1, afterStreak = gpMod * streakBonus, currStreak > 0 ? gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0 : void 0, afterStreak) : gpMod; - }; - subtractPoints = function() { - var conBonus, hpMod; - conBonus = 1 - (user._statsComputed.con / 250); - if (conBonus < .1) { - conBonus = 0.1; - } - hpMod = delta * conBonus * task.priority * 2; - return stats.hp += Math.round(hpMod * 10) / 10; - }; - gainMP = function(delta) { - delta *= user._tmp.crit || 1; - user.stats.mp += delta; - if (user.stats.mp >= user._statsComputed.maxMP) { - user.stats.mp = user._statsComputed.maxMP; - } - if (user.stats.mp < 0) { - return user.stats.mp = 0; - } - }; - switch (task.type) { - case 'habit': - changeTaskValue(); - if (delta > 0) { - addPoints(); - } else { - subtractPoints(); - } - gainMP(_.max([0.25, .0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); - th = (task.history != null ? task.history : task.history = []); - if (th[th.length - 1] && moment(th[th.length - 1].date).isSame(new Date, 'day')) { - th[th.length - 1].value = task.value; - } else { - th.push({ - date: +(new Date), - value: task.value - }); - } - if (typeof user.markModified === "function") { - user.markModified("habits." + (_.findIndex(user.habits, { - id: task.id - })) + ".history"); - } - break; - case 'daily': - if (options.cron) { - changeTaskValue(); - subtractPoints(); - if (!user.stats.buffs.streaks) { - task.streak = 0; - } - } else { - changeTaskValue(); - if (direction === 'down') { - delta = calculateDelta(); - } - addPoints(); - gainMP(_.max([1, .01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); - if (direction === 'up') { - task.streak = task.streak ? task.streak + 1 : 1; - if ((task.streak % 21) === 0) { - user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1; - } - } else { - if ((task.streak % 21) === 0) { - user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; - } - task.streak = task.streak ? task.streak - 1 : 0; - } - } - break; - case 'todo': - if (options.cron) { - changeTaskValue(); - } else { - task.dateCompleted = direction === 'up' ? new Date : void 0; - changeTaskValue(); - if (direction === 'down') { - delta = calculateDelta(); - } - addPoints(); - multiplier = _.max([ - _.reduce(task.checklist, (function(m, i) { - return m + (i.completed ? 1 : 0); - }), 1), 1 - ]); - gainMP(_.max([multiplier, .01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1)); - } - break; - case 'reward': - changeTaskValue(); - stats.gp -= Math.abs(task.value); - num = parseFloat(task.value).toFixed(2); - if (stats.gp < 0) { - stats.hp += stats.gp; - stats.gp = 0; - } - } - user.fns.updateStats(stats, req); - if (typeof window === 'undefined') { - if (direction === 'up') { - user.fns.randomDrop({ - task: task, - delta: delta - }, req); - } - } - if (typeof cb === "function") { - cb(null, user); - } - return delta; -}; diff --git a/common/script/ops/scoreTask.js b/common/script/ops/scoreTask.js new file mode 100644 index 0000000000..c366b84624 --- /dev/null +++ b/common/script/ops/scoreTask.js @@ -0,0 +1,260 @@ +import _ from 'lodash'; +import { + NotAuthorized, +} from '../libs/errors'; +import i18n from '../i18n'; +import updateStats from '../fns/updateStats'; +import crit from '../fns/crit'; + +const MAX_TASK_VALUE = 21.27; +const MIN_TASK_VALUE = -47.27; +const CLOSE_ENOUGH = 0.00001; + +function _getTaskValue (taskValue) { + if (taskValue < MIN_TASK_VALUE) { + return MIN_TASK_VALUE; + } else if (taskValue > MAX_TASK_VALUE) { + return MAX_TASK_VALUE; + } else { + return taskValue; + } +} + +// Calculates the next task.value based on direction +// Uses a capped inverse log y=.95^x, y>= -5 +function _calculateDelta (task, direction, cron) { + // Min/max on task redness + let currVal = _getTaskValue(task.value); + let nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); + + // Checklists + if (task.checklist && task.checklist.length > 0) { + // If the Daily, only dock them a portion based on their checklist completion + if (direction === 'down' && task.type === 'daily' && cron) { + nextDelta *= 1 - _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length; + } + + // If To-Do, point-match the TD per checklist item completed + if (task.type === 'todo') { + nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0); + } + } + + return nextDelta; +} + +// Approximates the reverse delta for the task value +// This is meant to return the task value to its original value when unchecking a task. +// First, calculate the value using the normal way for our first guess although +// it will be a bit off +function _calculateReverseDelta (task, direction) { + let currVal = _getTaskValue(task.value); + let testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); + + // Now keep moving closer to the original value until we get "close enough" + // Check how close we are to the original value by computing the delta off our guess + // and looking at the difference between that and our current value. + while (true) { // eslint-disable-line no-constant-condition + let calc = testVal + Math.pow(0.9747, testVal); + let diff = currVal - calc; + + if (Math.abs(diff) < CLOSE_ENOUGH) break; + + if (diff > 0) { + testVal -= diff; + } else { + testVal += diff; + } + } + + // When we get close enough, return the difference between our approximated value + // and the current value. This will be the delta calculated from the original value + // before the task was checked. + let nextDelta = testVal - currVal; + + // Checklists - If To-Do, point-match the TD per checklist item completed + if (task.checklist && task.checklist.length > 0 && task.type === 'todo') { + nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0); + } + + return nextDelta; +} + +function _gainMP (user, val) { + val *= user._tmp.crit || 1; + user.stats.mp += val; + + if (user.stats.mp >= user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP; + if (user.stats.mp < 0) { + user.stats.mp = 0; + } +} + +// HP modifier +// ===== CONSTITUTION ===== +// TODO Decreases HP loss from bad habits / missed dailies by 0.5% per point. +function _subtractPoints (user, task, stats, delta) { + let conBonus = 1 - user._statsComputed.con / 250; + if (conBonus < 0.1) conBonus = 0.1; + + let hpMod = delta * conBonus * task.priority * 2; // constant 2 multiplier for better results + stats.hp += Math.round(hpMod * 10) / 10; // round to 1dp + return stats.hp; +} + +function _addPoints (user, task, stats, direction, delta) { + // ===== CRITICAL HITS ===== + // allow critical hit only when checking off a task, not when unchecking it: + let _crit = delta > 0 ? crit(user) : 1; + // if there was a crit, alert the user via notification + if (_crit > 1) user._tmp.crit = _crit; + + // Exp Modifier + // ===== Intelligence ===== + // TODO Increases Experience gain by .2% per point. + let intBonus = 1 + user._statsComputed.int * 0.025; + stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6); + + // GP modifier + // ===== PERCEPTION ===== + // TODO Increases Gold gained from tasks by .3% per point. + let perBonus = 1 + user._statsComputed.per * 0.02; + let gpMod = delta * task.priority * _crit * perBonus; + + if (task.streak) { + let currStreak = direction === 'down' ? task.streak - 1 : task.streak; + let streakBonus = currStreak / 100 + 1; // eg, 1-day streak is 1.01, 2-day is 1.02, etc + let afterStreak = gpMod * streakBonus; + if (currStreak > 0 && gpMod > 0) { + user._tmp.streakBonus = afterStreak - gpMod; // keep this on-hand for later, so we can notify streak-bonus + } + + stats.gp += afterStreak; + } else { + stats.gp += gpMod; + } +} + +function _changeTaskValue (user, task, direction, times, cron) { + let addToDelta = 0; + + // If multiple days have passed, multiply times days missed + _.times(times, () => { + // Each iteration calculate the nextDelta, which is then accumulated in the total delta. + let nextDelta = !cron && direction === 'down' ? _calculateReverseDelta(task, direction) : _calculateDelta(task, direction, cron); + + if (task.type !== 'reward') { + if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) { + user.stats.training[task.attribute] += nextDelta; + } + + if (direction === 'up') { // Make progress on quest based on STR + user.party.quest.progress.up = user.party.quest.progress.up || 0; + + if (task.type === 'todo' || task.type === 'daily') { + user.party.quest.progress.up += nextDelta * (1 + user._statsComputed.str / 200); + } else if (task.type === 'habit') { + user.party.quest.progress.up += nextDelta * (0.5 + user._statsComputed.str / 400); + } + } + + task.value += nextDelta; + } + + addToDelta += nextDelta; + }); + + return addToDelta; +} + +module.exports = function scoreTask (options = {}, req = {}) { + let {user, task, direction, times = 1, cron = false} = options; + let delta = 0; + let stats = { + gp: user.stats.gp, + hp: user.stats.hp, + exp: user.stats.exp, + }; + + // This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying + // the API consumer, then cleared afterwards + user._tmp = {}; + + // If they're trying to purchase a too-expensive reward, don't allow them to do that. + if (task.value > user.stats.gp && task.type === 'reward') throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + + if (task.type === 'habit') { + delta += _changeTaskValue(user, task, direction, times, cron); + + // Add habit value to habit-history (if different) + if (delta > 0) { + _addPoints(user, task, stats, direction, delta); + } else { + _subtractPoints(user, task, stats, delta); + } + _gainMP(user, _.max([0.25, 0.0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); + + task.history = task.history || []; + // Add history entry, even more than 1 per day + task.history.push({ + date: Number(new Date()), + value: task.value, + }); + } else if (task.type === 'daily') { + if (cron) { + delta += _changeTaskValue(user, task, direction, times, cron); + _subtractPoints(user, task, stats, delta); + if (!user.stats.buffs.streaks) task.streak = 0; + } else { + delta += _changeTaskValue(user, task, direction, times, cron); + if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly + _addPoints(user, task, stats, direction, delta); // obviously for delta>0, but also a trick to undo accidental checkboxes + _gainMP(user, _.max([1, 0.01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); + + if (direction === 'up') { + task.streak += 1; + // Give a streak achievement when the streak is a multiple of 21 + if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1; + task.completed = true; + } else if (direction === 'down') { + // Remove a streak achievement if streak was a multiple of 21 and the daily was undone + if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; + task.streak -= 1; + task.completed = false; + } + } + } else if (task.type === 'todo') { + if (cron) { // don't touch stats on cron + delta += _changeTaskValue(user, task, direction, times, cron); + } else { + if (direction === 'up') { + task.dateCompleted = new Date(); + task.completed = true; + } else if (direction === 'down') { + task.completed = false; + task.dateCompleted = undefined; + } + + delta += _changeTaskValue(user, task, direction, times, cron); + if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly + _addPoints(user, task, stats, direction, delta); + + // MP++ per checklist item in ToDo, bonus per CLI + let multiplier = _.max([_.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 1), 1]); + _gainMP(user, _.max([multiplier, 0.01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1)); + } + } else if (task.type === 'reward') { + // Don't adjust values for rewards + delta += _changeTaskValue(user, task, direction, times, cron); + // purchase item + stats.gp -= Math.abs(task.value); + // hp - gp difference + if (stats.gp < 0) { + stats.hp += stats.gp; + stats.gp = 0; + } + } + + updateStats(user, stats, req); + return [delta]; +}; diff --git a/common/script/ops/sell.js b/common/script/ops/sell.js index 33e5f7d673..10412da222 100644 --- a/common/script/ops/sell.js +++ b/common/script/ops/sell.js @@ -1,23 +1,43 @@ import content from '../content/index'; +import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; +import { + NotFound, + NotAuthorized, + BadRequest, +} from '../libs/errors'; -module.exports = function(user, req, cb) { - var key, ref, type; - ref = req.params, key = ref.key, type = ref.type; - if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') { - return typeof cb === "function" ? cb({ - code: 404, - message: ":type not found. Must bes in [eggs, hatchingPotions, food]" - }) : void 0; +const ACCEPTEDTYPES = ['eggs', 'hatchingPotions', 'food']; + +module.exports = function sell (user, req = {}) { + let key = _.get(req.params, 'key'); + let type = _.get(req.params, 'type'); + + if (!type) { + throw new BadRequest(i18n.t('typeRequired', req.language)); } + + if (!key) { + throw new BadRequest(i18n.t('keyRequired', req.language)); + } + + if (ACCEPTEDTYPES.indexOf(type) === -1) { + throw new NotAuthorized(i18n.t('typeNotSellable', {acceptedTypes: ACCEPTEDTYPES.join(', ')}, req.language)); + } + if (!user.items[type][key]) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":key not found for user.items." + type - }) : void 0; + throw new NotFound(i18n.t('userItemsKeyNotFound', {type}, req.language)); } + user.items[type][key]--; user.stats.gp += content[type][key].value; - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats items'))) : void 0; + + if (req.v2 === true) { + return _.pick(user, splitWhitespace('stats items')); + } else { + return [ + _.pick(user, splitWhitespace('stats items')), + ]; + } }; diff --git a/common/script/ops/sleep.js b/common/script/ops/sleep.js index dec8095ad3..1a531ecb88 100644 --- a/common/script/ops/sleep.js +++ b/common/script/ops/sleep.js @@ -1,4 +1,9 @@ -module.exports = function(user, req, cb) { +module.exports = function sleep (user, req = {}) { user.preferences.sleep = !user.preferences.sleep; - return typeof cb === "function" ? cb(null, {}) : void 0; + + if (req.v2 === true) { + return {}; + } else { + return [user.preferences.sleep]; + } }; diff --git a/common/script/ops/sortTag.js b/common/script/ops/sortTag.js index 85dcda169f..c1fc42f330 100644 --- a/common/script/ops/sortTag.js +++ b/common/script/ops/sortTag.js @@ -1,9 +1,19 @@ -module.exports = function(user, req, cb) { - var from, ref, to; - ref = req.query, to = ref.to, from = ref.from; - if (!((to != null) && (from != null))) { - return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0; +import { BadRequest } from '../libs/errors'; +import _ from 'lodash'; + +// TODO used only in client, move there? + +module.exports = function sortTag (user, req = {}) { + let to = _.get(req, 'query.to'); + let fromParam = _.get(req, 'query.from'); + + let invalidTo = !to && to !== 0; + let invalidFrom = !fromParam && fromParam !== 0; + + if (invalidTo || invalidFrom) { + throw new BadRequest('?to=__&from=__ are required'); } - user.tags.splice(to, 0, user.tags.splice(from, 1)[0]); - return typeof cb === "function" ? cb(null, user.tags) : void 0; + + user.tags.splice(to, 0, user.tags.splice(fromParam, 1)[0]); + return user.tags; }; diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js index b903b692dc..ce002d8dc0 100644 --- a/common/script/ops/sortTask.js +++ b/common/script/ops/sortTask.js @@ -1,39 +1,49 @@ import i18n from '../i18n'; import preenTodos from '../libs/preenTodos'; +import { + NotFound, + BadRequest, +} from '../libs/errors'; +import _ from 'lodash'; -module.exports = function(user, req, cb) { - var from, id, movedTask, preenedTasks, ref, task, tasks, to; - id = req.params.id; - ref = req.query, to = ref.to, from = ref.from; - task = user.tasks[id]; - if (!task) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTaskNotFound', req.language) - }) : void 0; +// TODO used only in client, move there? + +module.exports = function sortTask (user, req = {}) { + let id = _.get(req, 'params.id'); + let to = _.get(req, 'query.to'); + let fromParam = _.get(req, 'query.from'); + let taskType = _.get(req, 'params.taskType'); + + let index = _.findIndex(user[`${taskType}s`], function findById (task) { + return task._id === id; + }); + + if (index === -1) { + throw new NotFound(i18n.t('messageTaskNotFound', req.language)); } - if (!((to != null) && (from != null))) { - return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0; + if (!to && !fromParam) { + throw new BadRequest('?to=__&from=__ are required'); } - tasks = user[task.type + "s"]; - if (task.type === 'todo' && tasks[from] !== task) { - preenedTasks = preenTodos(tasks); + + let tasks = user[`${taskType}s`]; + + if (taskType === 'todo') { + let preenedTasks = preenTodos(tasks); + if (to !== -1) { to = tasks.indexOf(preenedTasks[to]); } - from = tasks.indexOf(preenedTasks[from]); + + fromParam = tasks.indexOf(preenedTasks[fromParam]); } - if (tasks[from] !== task) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTaskNotFound', req.language) - }) : void 0; - } - movedTask = tasks.splice(from, 1)[0]; + + let movedTask = tasks.splice(fromParam, 1)[0]; + if (to === -1) { tasks.push(movedTask); } else { tasks.splice(to, 0, movedTask); } - return typeof cb === "function" ? cb(null, tasks) : void 0; + + return tasks; }; diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js index c53a5997ec..5e0b118af0 100644 --- a/common/script/ops/unlock.js +++ b/common/script/ops/unlock.js @@ -1,63 +1,113 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; +import { + NotAuthorized, + BadRequest, +} from '../libs/errors'; -module.exports = function(user, req, cb, analytics) { - var alreadyOwns, analyticsData, cost, fullSet, k, path, split, v; - path = req.query.path; - fullSet = ~path.indexOf(","); - cost = ~path.indexOf('background.') ? fullSet ? 3.75 : 1.75 : fullSet ? 1.25 : 0.5; - alreadyOwns = !fullSet && user.fns.dotGet("purchased." + path) === true; - if ((user.balance < cost || !user.balance) && !alreadyOwns) { - return typeof cb === "function" ? cb({ - code: 401, - message: i18n.t('notEnoughGems', req.language) - }) : void 0; +// If item is already purchased -> equip it +// Otherwise unlock it +module.exports = function unlock (user, req = {}, analytics) { + let path = _.get(req.query, 'path'); + + if (!path) { + throw new BadRequest(i18n.t('pathRequired', req.language)); } - if (fullSet) { - _.each(path.split(","), function(p) { - if (~path.indexOf('gear.')) { - user.fns.dotSet("" + p, true); - true; - } else { + let isFullSet = path.indexOf(',') !== -1; + let isBackground = path.indexOf('background.') !== -1; + + let cost; + if (isBackground && isFullSet) { + cost = 3.75; + } else if (isBackground) { + cost = 1.75; + } else if (isFullSet) { + cost = 1.25; + } else { + cost = 0.5; + } + + let setPaths; + let alreadyOwns; + + if (isFullSet) { + setPaths = path.split(','); + let alreadyOwnedItems = 0; + + _.each(setPaths, singlePath => { + if (_.get(user, `purchased.${singlePath}`) === true) { + alreadyOwnedItems++; } - user.fns.dotSet("purchased." + p, true); - return true; + }); + + if (alreadyOwnedItems === setPaths.length) { + throw new NotAuthorized(i18n.t('alreadyUnlocked', req.language)); + // TODO write math formula to check if buying the full set is cheaper than the items individually + // (item cost * number of remaining items) < setCost` + } /* else if (alreadyOwnedItems > 0) { + throw new NotAuthorized(i18n.t('alreadyUnlockedPart', req.language)); + } */ + } else { + alreadyOwns = _.get(user, `purchased.${path}`) === true; + } + + if ((!user.balance || user.balance < cost) && !alreadyOwns) { + throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + } + + if (isFullSet) { + _.each(setPaths, function markItemsAsPurchased (pathPart) { + if (path.indexOf('gear.') !== -1) { + _.set(user, pathPart, true); + } + + _.set(user, `purchased.${pathPart}`, true); }); } else { - if (alreadyOwns) { - split = path.split('.'); - v = split.pop(); - k = split.join('.'); - if (k === 'background' && v === user.preferences.background) { - v = ''; + if (alreadyOwns) { // eslint-disable-line no-lonely-if + let split = path.split('.'); + let value = split.pop(); + let key = split.join('.'); + if (key === 'background' && value === user.preferences.background) { + value = ''; } - user.fns.dotSet("preferences." + k, v); - return typeof cb === "function" ? cb(null, req) : void 0; + + _.set(user, `preferences.${key}`, value); + } else { + _.set(user, `purchased.${path}`, true); } - user.fns.dotSet("purchased." + path, true); } - user.balance -= cost; - if (~path.indexOf('gear.')) { - if (typeof user.markModified === "function") { - user.markModified('gear.owned'); - } - } else { - if (typeof user.markModified === "function") { + + if (!alreadyOwns) { + if (path.indexOf('gear.') === -1) { user.markModified('purchased'); } + + user.balance -= cost; + + if (analytics) { + analytics.track('acquire item', { + uuid: user._id, + itemKey: path, + itemType: 'customization', + acquireMethod: 'Gems', + gemCost: cost / 0.25, + category: 'behavior', + }); + } } - analyticsData = { - uuid: user._id, - itemKey: path, - itemType: 'customization', - acquireMethod: 'Gems', - gemCost: cost / .25, - category: 'behavior' - }; - if (analytics != null) { - analytics.track('acquire item', analyticsData); + + let response = [ + _.pick(user, splitWhitespace('purchased preferences items')), + ]; + + if (!alreadyOwns) response.push(i18n.t('unlocked', req.language)); + + if (req.v2 === true) { + return response[0]; + } else { + return response; } - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('purchased preferences items'))) : void 0; }; diff --git a/common/script/ops/update.js b/common/script/ops/update.js index 12a100e372..c41e74180e 100644 --- a/common/script/ops/update.js +++ b/common/script/ops/update.js @@ -1,9 +1,11 @@ import _ from 'lodash'; -module.exports = function(user, req, cb) { - _.each(req.body, function(v, k) { - user.fns.dotSet(k, v); - return true; +// TODO used only in client, move there? + +module.exports = function updateUser (user, req = {}) { + _.each(req.body, (val, key) => { + _.set(user, key, val); }); - return typeof cb === "function" ? cb(null, user) : void 0; + + return user; }; diff --git a/common/script/ops/updateTag.js b/common/script/ops/updateTag.js index 8e4019fa51..ade87f916d 100644 --- a/common/script/ops/updateTag.js +++ b/common/script/ops/updateTag.js @@ -1,18 +1,20 @@ import i18n from '../i18n'; import _ from 'lodash'; +import { NotFound } from '../libs/errors'; -module.exports = function(user, req, cb) { - var i, tid; - tid = req.params.id; - i = _.findIndex(user.tags, { - id: tid +// TODO used only in client, move there? + +module.exports = function updateTag (user, req = {}) { + let tid = _.get(req, 'params.id'); + + let index = _.findIndex(user.tags, { + id: tid, }); - if (!~i) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTagNotFound', req.language) - }) : void 0; + + if (index === -1) { + throw new NotFound(i18n.t('messageTagNotFound', req.language)); } - user.tags[i].name = req.body.name; - return typeof cb === "function" ? cb(null, user.tags[i]) : void 0; + + user.tags[index].name = _.get(req, 'body.name'); + return user.tags[index]; }; diff --git a/common/script/ops/updateTask.js b/common/script/ops/updateTask.js index 427104f15e..2a2b1edba2 100644 --- a/common/script/ops/updateTask.js +++ b/common/script/ops/updateTask.js @@ -1,23 +1,25 @@ -import i18n from '../i18n'; import _ from 'lodash'; -module.exports = function(user, req, cb) { - var ref, task; - if (!(task = user.tasks[(ref = req.params) != null ? ref.id : void 0])) { - return typeof cb === "function" ? cb({ - code: 404, - message: i18n.t('messageTaskNotFound', req.language) - }) : void 0; +// From server pass task.toObject() not the task document directly +module.exports = function updateTask (task, req = {}) { + let body = req.body || {}; + + // If reminders are updated -> replace the original ones + if (body.reminders) { + task.reminders = body.reminders; } - _.merge(task, _.omit(req.body, ['checklist', 'reminders', 'id', 'type'])); - if (req.body.checklist) { - task.checklist = req.body.checklist; + + // If checklist is updated -> replace the original one + if (body.checklist) { + task.checklist = body.checklist; } - if (req.body.reminders) { - task.reminders = req.body.reminders; + + // If tags are updated -> replace the original ones + if (body.tags) { + task.tags = body.tags; } - if (typeof task.markModified === "function") { - task.markModified('tags'); - } - return typeof cb === "function" ? cb(null, task) : void 0; + + _.merge(task, _.omit(body, ['_id', 'id', 'type', 'reminders', 'checklist', 'tags'])); + + return [task]; }; diff --git a/common/script/ops/updateWebhook.js b/common/script/ops/updateWebhook.js index e2775a40b4..63fed89b17 100644 --- a/common/script/ops/updateWebhook.js +++ b/common/script/ops/updateWebhook.js @@ -1,9 +1,20 @@ -import _ from 'lodash'; +import validator from 'validator'; +import i18n from '../i18n'; +import { + BadRequest, +} from '../libs/errors'; -module.exports = function(user, req, cb) { - _.merge(user.preferences.webhooks[req.params.id], req.body); - if (typeof user.markModified === "function") { - user.markModified('preferences.webhooks'); +module.exports = function updateWebhook (user, req) { + if (!validator.isURL(req.body.url)) throw new BadRequest(i18n.t('invalidUrl', req.language)); + if (!validator.isBoolean(req.body.enabled)) throw new BadRequest(i18n.t('invalidEnabled', req.language)); + + user.markModified('preferences.webhooks'); + user.preferences.webhooks[req.params.id].url = req.body.url; + user.preferences.webhooks[req.params.id].enabled = req.body.enabled; + + if (req.v2 === true) { + return user.preferences.webhooks; + } else { + return [user.preferences.webhooks[req.params.id]]; } - return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; }; diff --git a/common/script/public/config.js b/common/script/public/config.js index bb78e244bd..4456909ef4 100644 --- a/common/script/public/config.js +++ b/common/script/public/config.js @@ -1,8 +1,48 @@ 'use strict'; -angular.module('habitrpg').config(['$httpProvider', function($httpProvider){ + +angular.module('habitrpg') +.config(['$httpProvider', function($httpProvider){ $httpProvider.interceptors.push(['$q', '$rootScope', function($q, $rootScope){ + var resyncNumber = 0; + var lastResync = 0; + + // Verify that the user was not updated from another browser/app/client + // If it was, sync + function verifyUserUpdated (response) { + var isApiCall = response.config.url.indexOf('api/v3') !== -1; + var isUserAvailable = $rootScope.User && $rootScope.User.user && $rootScope.User.user._wrapped === true; + var hasUserV = response.data && response.data.userV; + var isNotSync = response.config.url.indexOf('/api/v3/user') !== 0; + + if (isApiCall && isUserAvailable && hasUserV) { + var oldUserV = $rootScope.User.user._v; + $rootScope.User.user._v = response.data.userV; + + // Something has changed on the user object that was not tracked here, sync the user + if (isNotSync && ($rootScope.User.user._v - oldUserV) > 1) { + $rootScope.User.sync(); + } + } + } + return { + request: function (config) { + var url = config.url; + + if (url.indexOf('api/v3') !== -1) { + if ($rootScope.User && $rootScope.User.user) { + if (url.indexOf('?') !== -1) { + config.url += '&userV=' + $rootScope.User.user._v; + } else { + config.url += '?userV=' + $rootScope.User.user._v; + } + } + } + + return config; + }, response: function(response) { + verifyUserUpdated(response); return response; }, responseError: function(response) { @@ -21,25 +61,43 @@ angular.module('habitrpg').config(['$httpProvider', function($httpProvider){ if (!mobileApp) // skip mobile for now $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); - } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { + } else if (response.data && response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { confirm(response.data.err); localStorage.clear(); window.location.href = mobileApp ? '/app/login' : '/logout'; //location.reload() - // 400 range? + // 400 range + } else if (response.status < 400) { + // never triggered because we're in responseError + $rootScope.$broadcast('responseText', response.data && response.data.message); } else if (response.status < 500) { - $rootScope.$broadcast('responseText', response.data.err || response.data); - // Need to reject the prompse so the error is handled correctly - if (response.status === 401) - return $q.reject(response); + if (response.status === 400 && response.data && response.data.errors && _.isArray(response.data.errors)) { // bad requests with more info + response.data.errors.forEach(function (err) { + $rootScope.$broadcast('responseError', err.message); + }); + } else { + $rootScope.$broadcast('responseError', response.data && response.data.message); + } + if ($rootScope.User && $rootScope.User.sync) { + if (resyncNumber < 100 && (Date.now() - lastResync) > 500) { // avoid thousands of requests when user is not found + $rootScope.User.sync(); + resyncNumber++; + lastResync = Date.now(); + } + } + + // Need to reject the prompse so the error is handled correctly + if (response.status === 401) { + return $q.reject(response); + } // Error } else { - var error = window.env.t('requestError') + '

"' + - window.env.t('error') + ' ' + (response.data.err || response.data || 'something went wrong') + + var error = window.env.t('requestError') + '

"' + + window.env.t('error') + ' ' + (response.data.message || response.data.error || response.data || 'something went wrong') + '"

' + window.env.t('seeConsole'); if (mobileApp) error = 'Error contacting the server. Please try again in a few minutes.'; - $rootScope.$broadcast('responseError', error); + $rootScope.$broadcast('responseError500', error); console.error(response); } @@ -47,4 +105,4 @@ angular.module('habitrpg').config(['$httpProvider', function($httpProvider){ } }; }]); -}]); \ No newline at end of file +}]); diff --git a/common/script/public/userServices.js b/common/script/public/userServices.js deleted file mode 100644 index e5b1790086..0000000000 --- a/common/script/public/userServices.js +++ /dev/null @@ -1,268 +0,0 @@ -'use strict'; - -angular.module('habitrpg') - .service('ApiUrl', ['API_URL', function(currentApiUrl){ - this.setApiUrl = function(newUrl){ - currentApiUrl = newUrl; - }; - - this.get = function(){ - return currentApiUrl; - }; - }]) - -/** - * Services that persists and retrieves user from localStorage. - */ - .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', 'ApiUrl', - function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification, ApiUrl) { - var authenticated = false; - var defaultSettings = { - auth: { apiId: '', apiToken: ''}, - sync: { - queue: [], //here OT will be queued up, this is NOT call-back queue! - sent: [] //here will be OT which have been sent, but we have not got reply from server yet. - }, - fetching: false, // whether fetch() was called or no. this is to avoid race conditions - online: false - }; - var settings = {}; //habit mobile settings (like auth etc.) to be stored here - var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate - - var userNotifications = { - // "party.order" : env.t("updatedParty"), - // "party.orderAscending" : env.t("updatedParty") - // party.order notifications are not currently needed because the party avatars are resorted immediately now - }; // this is a list of notifications to send to the user when changes are made, along with the message. - - //first we populate user with schema - user.apiToken = user._id = ''; // we use id / apitoken to determine if registered - - //than we try to load localStorage - if (localStorage.getItem(STORAGE_USER_ID)) { - _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); - } - user._wrapped = false; - - var syncQueue = function (cb) { - if (!authenticated) { - $window.alert("Not authenticated, can't sync, go to settings first."); - return; - } - - var queue = settings.sync.queue; - var sent = settings.sync.sent; - if (queue.length === 0) { - // Sync: Queue is empty - return; - } - if (settings.fetching) { - // Sync: Already fetching - return; - } - if (settings.online!==true) { - // Sync: Not online - return; - } - - settings.fetching = true; - // move all actions from queue array to sent array - _.times(queue.length, function () { - sent.push(queue.shift()); - }); - - // Save the current filters - var current_filters = user.filters; - - $http.post(ApiUrl.get() + '/api/v2/user/batch-update', sent, {params: {data:+new Date, _v:user._v, siteVersion: $window.env && $window.env.siteVersion}}) - .success(function (data, status, heacreatingders, config) { - //make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data. - if (!queue.length) { - //we can't do user=data as it will not update user references in all other angular controllers. - - // the user has been modified from another application, sync up - if(data && data.wasModified) { - delete data.wasModified; - $rootScope.$emit('userUpdated', user); - } - - // Update user - _.extend(user, data); - // Preserve filter selections between syncs - _.extend(user.filters,current_filters); - if (!user._wrapped){ - - // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, - // they update the user in the browser and then send the request to the server, where the same operation is - // replicated. We need to wrap each op to provide a callback to send that operation - $window.habitrpgShared.wrap(user); - _.each(user.ops, function(op,k){ - user.ops[k] = function(req,cb){ - if (cb) return op(req,cb); - op(req,function(err,response) { - for(var updatedItem in req.body) { - var itemUpdateResponse = userNotifications[updatedItem]; - if(itemUpdateResponse) Notification.text(itemUpdateResponse); - } - if (err) { - var message = err.code ? err.message : err; - if (MOBILE_APP) Notification.push({type:'text',text:message}); - else Notification.text(message); - // In the case of 200s, they're friendly alert messages like "Your pet has hatched!" - still send the op - if ((err.code && err.code >= 400) || !err.code) return; - } - userServices.log({op:k, params: req.params, query:req.query, body:req.body}); - }); - } - }); - } - - // Emit event when user is synced - $rootScope.$emit('userSynced'); - } - sent.length = 0; - settings.fetching = false; - save(); - if (cb) { - cb(false) - } - - syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server. - }) - .error(function (data, status, headers, config) { - // (Notifications handled in app.js) - - // If we're offline, queue up offline actions so we can send when we're back online - if (status === 0) { - //move sent actions back to queue - _.times(sent.length, function () { - queue.push(sent.shift()) - }); - settings.fetching = false; - // In the case of errors, discard the corrupt queue - } else { - // Clear the queue. Better if we can hunt down the problem op, but this is the easiest solution - settings.sync.queue = settings.sync.sent = []; - save(); - } - }); - } - - - var save = function () { - localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); - localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); - }; - var userServices = { - user: user, - set: function(updates) { - user.ops.update({body:updates}); - }, - - online: function (status) { - if (status===true) { - settings.online = true; - syncQueue(); - } else { - settings.online = false; - }; - }, - - authenticate: function (uuid, token, cb) { - if (!!uuid && !!token) { - var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) - $http.defaults.headers.common['x-api-user'] = uuid; - $http.defaults.headers.common['x-api-key'] = token; - $http.defaults.headers.common['x-user-timezoneOffset'] = offset; - authenticated = true; - settings.auth.apiId = uuid; - settings.auth.apiToken = token; - settings.online = true; - if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload - userServices.log({}, function(){ - // If they don't have timezone, set it - if (user.preferences.timezoneOffset !== offset) - userServices.set({'preferences.timezoneOffset': offset}); - cb && cb(); - }); - } else { - alert('Please enter your ID and Token in settings.') - } - }, - - authenticated: function(){ - return this.settings.auth.apiId !== ""; - }, - - getBalanceInGems: function() { - var balance = user.balance || 0; - return balance * 4; - }, - - log: function (action, cb) { - //push by one buy one if an array passed in. - if (_.isArray(action)) { - action.forEach(function (a) { - settings.sync.queue.push(a); - }); - } else { - settings.sync.queue.push(action); - } - - save(); - syncQueue(cb); - }, - - sync: function(){ - user._v--; - userServices.log({}); - }, - - save: save, - - settings: settings - }; - - - //load settings if we have them - if (localStorage.getItem(STORAGE_SETTINGS_ID)) { - //use extend here to make sure we keep object reference in other angular controllers - _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID))); - - //if settings were saved while fetch was in process reset the flag. - settings.fetching = false; - //create and load if not - } else { - localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings)); - _.extend(settings, defaultSettings); - } - - //If user does not have ApiID that forward him to settings. - if (!settings.auth.apiId || !settings.auth.apiToken) { - - if (MOBILE_APP) { - $location.path("/login"); - } else { - //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=... - var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead - if (search.err) return alert(search.err); - if (search._id && search.apiToken) { - userServices.authenticate(search._id, search.apiToken, function(){ - $window.location.href='/'; - }); - } else { - var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); - if (!isStaticOrSocial){ - localStorage.clear(); - $window.location.href = '/logout'; - } - } - } - - } else { - userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) - } - - return userServices; - } -]); diff --git a/config.json.example b/config.json.example index 6aeb8ac74a..d70dcac18c 100644 --- a/config.json.example +++ b/config.json.example @@ -1,5 +1,6 @@ { "PORT":3000, + "ENABLE_CONSOLE_LOGS_IN_PROD":"false", "IP":"0.0.0.0", "CORES":1, "BASE_URL":"http://localhost:3000", @@ -8,6 +9,8 @@ "NODE_DB_URI":"mongodb://localhost/habitrpg", "TEST_DB_URI":"mongodb://localhost/habitrpg_test", "NODE_ENV":"development", + "CRON_SAFE_MODE":"false", + "MAINTENANCE_MODE": "false", "SESSION_SECRET":"YOUR SECRET HERE", "ADMIN_EMAIL": "you@example.com", "SMTP_USER":"user@example.com", @@ -19,6 +22,7 @@ "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", @@ -33,7 +37,7 @@ "EMAIL_SERVER": { "url": "http://example.com", "authUser": "user", - "authPassword": "password" + "authPassword": "password" }, "S3":{ "bucket":"bucket", @@ -60,7 +64,7 @@ "subdomain": "subdomain", "token": "token", "username": "username", - "password": "password" + "password": "password" }, "PUSH_CONFIGS": { "GCM_SERVER_API_KEY": "", diff --git a/gulpfile.js b/gulpfile.js index 0c399ce7fa..d7cd0d79c2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,6 +9,7 @@ require('babel-register'); if (process.env.NODE_ENV === 'production') { + require('./tasks/gulp-apidoc'); require('./tasks/gulp-newstuff'); require('./tasks/gulp-build'); require('./tasks/gulp-babelify'); diff --git a/karma.conf.js b/karma.conf.js index 2ae3a52a9d..d0ceffd5c6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -11,41 +11,40 @@ module.exports = function karmaConfig (config) { // list of files / patterns to load in the browser files: [ - 'website/public/bower_components/jquery/dist/jquery.js', - 'website/public/bower_components/pnotify/jquery.pnotify.js', - 'website/public/bower_components/angular/angular.js', - 'website/public/bower_components/angular-loading-bar/build/loading-bar.min.js', - 'website/public/bower_components/angular-resource/angular-resource.min.js', - 'website/public/bower_components/hello/dist/hello.all.min.js', - 'website/public/bower_components/angular-sanitize/angular-sanitize.js', - 'website/public/bower_components/bootstrap/dist/js/bootstrap.js', - 'website/public/bower_components/angular-bootstrap/ui-bootstrap.js', - 'website/public/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', - 'website/public/bower_components/angular-ui-router/release/angular-ui-router.js', - 'website/public/bower_components/angular-filter/dist/angular-filter.js', - 'website/public/bower_components/angular-ui/build/angular-ui.js', - 'website/public/bower_components/angular-ui-utils/ui-utils.min.js', - 'website/public/bower_components/Angular-At-Directive/src/at.js', - 'website/public/bower_components/Angular-At-Directive/src/caret.js', - 'website/public/bower_components/angular-mocks/angular-mocks.js', - 'website/public/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js', - 'website/public/bower_components/select2/select2.js', - 'website/public/bower_components/angular-ui-select2/src/select2.js', - 'website/public/bower_components/habitica-markdown/dist/habitica-markdown.min.js', + 'website/client/bower_components/jquery/dist/jquery.js', + 'website/client/bower_components/pnotify/jquery.pnotify.js', + 'website/client/bower_components/angular/angular.js', + 'website/client/bower_components/angular-loading-bar/build/loading-bar.min.js', + 'website/client/bower_components/angular-resource/angular-resource.min.js', + 'website/client/bower_components/hello/dist/hello.all.min.js', + 'website/client/bower_components/angular-sanitize/angular-sanitize.js', + 'website/client/bower_components/bootstrap/dist/js/bootstrap.js', + 'website/client/bower_components/angular-bootstrap/ui-bootstrap.js', + 'website/client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'website/client/bower_components/angular-ui-router/release/angular-ui-router.js', + 'website/client/bower_components/angular-filter/dist/angular-filter.js', + 'website/client/bower_components/angular-ui/build/angular-ui.js', + 'website/client/bower_components/angular-ui-utils/ui-utils.min.js', + 'website/client/bower_components/Angular-At-Directive/src/at.js', + 'website/client/bower_components/Angular-At-Directive/src/caret.js', + 'website/client/bower_components/angular-mocks/angular-mocks.js', + 'website/client/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js', + 'website/client/bower_components/select2/select2.js', + 'website/client/bower_components/angular-ui-select2/src/select2.js', + 'website/client/bower_components/habitica-markdown/dist/habitica-markdown.min.js', 'common/dist/scripts/habitrpg-shared.js', 'test/spec/mocks/**/*.js', - 'website/public/js/env.js', - 'website/public/js/app.js', + 'website/client/js/env.js', + 'website/client/js/app.js', 'common/script/public/config.js', - 'common/script/public/userServices.js', 'common/script/public/directives.js', - 'website/public/js/services/**/*.js', - 'website/public/js/filters/**/*.js', - 'website/public/js/directives/**/*.js', - 'website/public/js/controllers/**/*.js', + 'website/client/js/services/**/*.js', + 'website/client/js/filters/**/*.js', + 'website/client/js/directives/**/*.js', + 'website/client/js/controllers/**/*.js', 'test/spec/specHelper.js', 'test/spec/**/*.js', @@ -77,7 +76,7 @@ module.exports = function karmaConfig (config) { browsers: ['PhantomJS'], preprocessors: { - 'website/public/js/**/*.js': ['coverage'], + 'website/client/js/**/*.js': ['coverage'], 'test/**/*.js': ['babel'], }, diff --git a/migrations/20160521_veteran_ladder.js b/migrations/20160521_veteran_ladder.js new file mode 100644 index 0000000000..cf92ff5375 --- /dev/null +++ b/migrations/20160521_veteran_ladder.js @@ -0,0 +1,76 @@ +var migrationName = '20160521_veteran_ladder.js'; +var authorName = 'Sabe'; // in case script author needs to know when their ... +var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done + +/* + * Award Gilded Turkey pet to Turkey mount owners, Turkey Mount if they only have Turkey Pet, + * and Turkey Pet otherwise + */ + +var dbserver = 'localhost:27017'; // FOR TEST DATABASE +// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE +var dbname = 'habitrpg'; + +var mongo = require('mongoskin'); +var _ = require('lodash'); + +var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users'); + +// specify a query to limit the affected users (empty for all users): +var query = { + 'auth.timestamps.loggedin':{$gt:new Date('2016-05-01')} // remove when running migration a second time +}; + +// specify fields we are interested in to limit retrieved data (empty if we're not reading data): +var fields = { + 'migration': 1, + 'items.pets.Wolf-Veteran': 1, + 'items.pets.Tiger-Veteran': 1 +}; + +console.warn('Updating users...'); +var progressCount = 1000; +var count = 0; +dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) { + if (err) { return exiting(1, 'ERROR! ' + err); } + if (!user) { + console.warn('All appropriate users found and modified.'); + return displayData(); + } + count++; + + // specify user data to change: + var set = {}; + if (user.migration !== migrationName) { + if (user.items.pets['Tiger-Veteran']) { + set = {'migration':migrationName, 'items.pets.Lion-Veteran':5}; + } else if (user.items.pets['Wolf-Veteran']) { + set = {'migration':migrationName, 'items.pets.Tiger-Veteran':5}; + } else { + set = {'migration':migrationName, 'items.pets.Wolf-Veteran':5}; + } + } + + dbUsers.update({_id:user._id}, {$set:set}); + + if (count%progressCount == 0) console.warn(count + ' ' + user._id); + if (user._id == authorUuid) console.warn(authorName + ' processed'); +}); + + +function displayData() { + console.warn('\n' + count + ' users processed\n'); + return exiting(0); +} + + +function exiting(code, msg) { + code = code || 0; // 0 = success + if (code && !msg) { msg = 'ERROR!'; } + if (msg) { + if (code) { console.error(msg); } + else { console.log( msg); } + } + process.exit(code); +} + diff --git a/migrations/api_v3/challenges.js b/migrations/api_v3/challenges.js new file mode 100644 index 0000000000..009c205e33 --- /dev/null +++ b/migrations/api_v3/challenges.js @@ -0,0 +1,212 @@ +// Migrate challenges collection to new schema (except for members) + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/challenges.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); +var fs = require('fs'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// Load new models +var NewChallenge = require('../../website/server/models/challenge').model; +var Tasks = require('../../website/server/models/task'); + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldChallengeCollection; + +var mongoDbNewInstance; +var newChallengeCollection; +var newTaskCollection; + +var BATCH_SIZE = 1000; + +var processedChallenges = 0; +var totoalProcessedTasks = 0; + +var newTasksIds = {}; // a map of old id -> [new id, challengeId] + +// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000 +var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID'); +var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID'); + +function processChallenges (afterId) { + var processedTasks = 0; + var lastChallenge = null; + var oldChallenges; + + var query = {}; + + if (BEFORE_CHALLENGE_ID) { + query._id = {$lte: BEFORE_CHALLENGE_ID}; + } + + if ((afterId || AFTER_CHALLENGE_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_CHALLENGE_ID) { + query._id.$gt = AFTER_CHALLENGE_ID; + } + + var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp(); + var batchInsertChallenges = newChallengeCollection.initializeUnorderedBulkOp(); + + console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`); + + return oldChallengeCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldChallengesR) { + oldChallenges = oldChallengesR; + + console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges and ${totoalProcessedTasks} tasks.`); + + if (oldChallenges.length === BATCH_SIZE) { + lastChallenge = oldChallenges[oldChallenges.length - 1]._id; + } + + oldChallenges.forEach(function (oldChallenge) { + var oldTasks = oldChallenge.habits.concat(oldChallenge.dailys).concat(oldChallenge.rewards).concat(oldChallenge.todos); + delete oldChallenge.habits; + delete oldChallenge.dailys; + delete oldChallenge.rewards; + delete oldChallenge.todos; + + var createdAt = oldChallenge.timestamp; + + oldChallenge.memberCount = oldChallenge.members.length; + if (oldChallenge.prize <= 0) oldChallenge.prize = 0; + if (!oldChallenge.name) oldChallenge.name = 'challenge name'; + if (!oldChallenge.shortName) oldChallenge.name = 'challenge-name'; + + if (!oldChallenge.group) throw new Error('challenge.group is required'); + if (!oldChallenge.leader) throw new Error('challenge.leader is required'); + + + if (oldChallenge.leader === '9') { + oldChallenge.leader = '00000000-0000-4000-9000-000000000000'; + } + + if (oldChallenge.group === 'habitrpg') { + oldChallenge.group = '00000000-0000-4000-A000-000000000000'; + } + + delete oldChallenge.id; + + var newChallenge = new NewChallenge(oldChallenge); + + newChallenge.createdAt = createdAt; + + oldTasks.forEach(function (oldTask) { + oldTask._id = uuid.v4(); + oldTask._legacyId = oldTask.id; // store the old task id + delete oldTask.id; + + oldTask.challenge = oldTask.challenge || {}; + oldTask.challenge.id = newChallenge._id; + + if (newTasksIds[oldTask._legacyId + '-' + newChallenge._id]) { + throw new Error('duplicate :('); + } else { + newTasksIds[oldTask._legacyId + '-' + newChallenge._id] = oldTask._id; + } + + oldTask.tags = _.map(oldTask.tags || {}, function (tagPresent, tagId) { + return tagPresent && tagId; + }).filter(function (tag) { + return tag !== false; + }); + + if (!oldTask.text) oldTask.text = 'task text'; // required + + oldTask.createdAt = oldTask.dateCreated; + + newChallenge.tasksOrder[`${oldTask.type}s`].push(oldTask._id); + if (oldTask.completed) oldTask.completed = false; + + var newTask = new Tasks[oldTask.type](oldTask); + + batchInsertTasks.insert(newTask.toObject()); + processedTasks++; + }); + + batchInsertChallenges.insert(newChallenge.toObject()); + }); + + console.log(`Saving ${oldChallenges.length} challenges and ${processedTasks} tasks.`); + + return Bluebird.all([ + batchInsertChallenges.execute(), + batchInsertTasks.execute(), + ]); + }) + .then(function () { + totoalProcessedTasks += processedTasks; + processedChallenges += oldChallenges.length; + + console.log(`Saved ${oldChallenges.length} challenges and their tasks.`); + + if (lastChallenge) { + return processChallenges(lastChallenge); + } else { + console.log('Writing newTasksIds.json...') + fs.writeFileSync('newTasksIds.json', JSON.stringify(newTasksIds, null, 4), 'utf8'); + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldChallengeCollection = mongoDbOldInstance.collection('challenges'); + + mongoDbNewInstance = newInstance; + newChallengeCollection = mongoDbNewInstance.collection('challenges'); + newTaskCollection = mongoDbNewInstance.collection('tasks'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + return processChallenges(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/api_v3/challengesMembers.js b/migrations/api_v3/challengesMembers.js new file mode 100644 index 0000000000..971119831a --- /dev/null +++ b/migrations/api_v3/challengesMembers.js @@ -0,0 +1,143 @@ +// Migrate challenges members +// Run AFTER users migration + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/challengesMembers.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldChallengeCollection; + +var mongoDbNewInstance; +var newUserCollection; + +var BATCH_SIZE = 1000; + +var processedChallenges = 0; + +// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000 +var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID'); +var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID'); + +function processChallenges (afterId) { + var processedTasks = 0; + var lastChallenge = null; + var oldChallenges; + + var query = {}; + + if (BEFORE_CHALLENGE_ID) { + query._id = {$lte: BEFORE_CHALLENGE_ID}; + } + + if ((afterId || AFTER_CHALLENGE_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_CHALLENGE_ID) { + query._id.$gt = AFTER_CHALLENGE_ID; + } + + console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`); + + return oldChallengeCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldChallengesR) { + oldChallenges = oldChallengesR; + + var promises = []; + + console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges.`); + + if (oldChallenges.length === BATCH_SIZE) { + lastChallenge = oldChallenges[oldChallenges.length - 1]._id; + } + + oldChallenges.forEach(function (oldChallenge) { + // Tyler Renelle + oldChallenge.members.forEach(function (id, index) { + if (id === '9') { + oldChallenge.members[index] = '00000000-0000-4000-9000-000000000000'; + } + }); + + promises.push(newUserCollection.updateMany({ + _id: {$in: oldChallenge.members || []}, + }, { + $push: {challenges: oldChallenge._id}, + }, {multi: true})); + }); + + console.log(`Migrating members of ${oldChallenges.length} challenges.`); + + return Bluebird.all(promises); + }) + .then(function () { + processedChallenges += oldChallenges.length; + + console.log(`Migrated members of ${oldChallenges.length} challenges.`); + + if (lastChallenge) { + return processChallenges(lastChallenge); + } else { + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldChallengeCollection = mongoDbOldInstance.collection('challenges'); + + mongoDbNewInstance = newInstance; + newUserCollection = mongoDbNewInstance.collection('users'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + return processChallenges(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/api_v3/coupons.js b/migrations/api_v3/coupons.js new file mode 100644 index 0000000000..64071faffe --- /dev/null +++ b/migrations/api_v3/coupons.js @@ -0,0 +1,136 @@ +// Migrate coupons collection to new schema + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/coupons.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// Load new models +var Coupon = require('../../website/server/models/coupon').model; + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldCouponCollection; + +var mongoDbNewInstance; +var newCouponCollection; + +var BATCH_SIZE = 1000; + +var processedCoupons = 0; + +// Only process coupons that fall in a interval ie -> up to 0000-4000-0000-0000 +var AFTER_COUPON_ID = nconf.get('AFTER_COUPON_ID'); +var BEFORE_COUPON_ID = nconf.get('BEFORE_COUPON_ID'); + +function processCoupons (afterId) { + var processedTasks = 0; + var lastCoupon = null; + var oldCoupons; + + var query = {}; + + if (BEFORE_COUPON_ID) { + query._id = {$lte: BEFORE_COUPON_ID}; + } + + if ((afterId || AFTER_COUPON_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_COUPON_ID) { + query._id.$gt = AFTER_COUPON_ID; + } + + var batchInsertCoupons = newCouponCollection.initializeUnorderedBulkOp(); + + console.log(`Executing coupons query.\nMatching coupons after ${afterId ? afterId : AFTER_COUPON_ID} and before ${BEFORE_COUPON_ID} (included).`); + + return oldCouponCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldCouponsR) { + oldCoupons = oldCouponsR; + + console.log(`Processing ${oldCoupons.length} coupons. Already processed ${processedCoupons} coupons.`); + + if (oldCoupons.length === BATCH_SIZE) { + lastCoupon = oldCoupons[oldCoupons.length - 1]._id; + } + + oldCoupons.forEach(function (oldCoupon) { + var newCoupon = new Coupon(oldCoupon); + + batchInsertCoupons.insert(newCoupon.toObject()); + }); + + console.log(`Saving ${oldCoupons.length} coupons.`); + + return batchInsertCoupons.execute(); + }) + .then(function () { + processedCoupons += oldCoupons.length; + + console.log(`Saved ${oldCoupons.length} coupons.`); + + if (lastCoupon) { + return processCoupons(lastCoupon); + } else { + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldCouponCollection = mongoDbOldInstance.collection('coupons'); + + mongoDbNewInstance = newInstance; + newCouponCollection = mongoDbNewInstance.collection('coupons'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + return processCoupons(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/api_v3/emailUnsubscriptions.js b/migrations/api_v3/emailUnsubscriptions.js new file mode 100644 index 0000000000..d90525db16 --- /dev/null +++ b/migrations/api_v3/emailUnsubscriptions.js @@ -0,0 +1,137 @@ +// Migrate unsubscriptions collection to new schema + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/unsubscriptions.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// Load new models +var EmailUnsubscription = require('../../website/server/models/emailUnsubscription').model; + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldUnsubscriptionCollection; + +var mongoDbNewInstance; +var newUnsubscriptionCollection; + +var BATCH_SIZE = 1000; + +var processedUnsubscriptions = 0; + +// Only process unsubscriptions that fall in a interval ie -> up to 0000-4000-0000-0000 +var AFTER_UNSUBSCRIPTION_ID = nconf.get('AFTER_UNSUBSCRIPTION_ID'); +var BEFORE_UNSUBSCRIPTION_ID = nconf.get('BEFORE_UNSUBSCRIPTION_ID'); + +function processUnsubscriptions (afterId) { + var processedTasks = 0; + var lastUnsubscription = null; + var oldUnsubscriptions; + + var query = {}; + + if (BEFORE_UNSUBSCRIPTION_ID) { + query._id = {$lte: BEFORE_UNSUBSCRIPTION_ID}; + } + + if ((afterId || AFTER_UNSUBSCRIPTION_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_UNSUBSCRIPTION_ID) { + query._id.$gt = AFTER_UNSUBSCRIPTION_ID; + } + + var batchInsertUnsubscriptions = newUnsubscriptionCollection.initializeUnorderedBulkOp(); + + console.log(`Executing unsubscriptions query.\nMatching unsubscriptions after ${afterId ? afterId : AFTER_UNSUBSCRIPTION_ID} and before ${BEFORE_UNSUBSCRIPTION_ID} (included).`); + + return oldUnsubscriptionCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldUnsubscriptionsR) { + oldUnsubscriptions = oldUnsubscriptionsR; + + console.log(`Processing ${oldUnsubscriptions.length} unsubscriptions. Already processed ${processedUnsubscriptions} unsubscriptions.`); + + if (oldUnsubscriptions.length === BATCH_SIZE) { + lastUnsubscription = oldUnsubscriptions[oldUnsubscriptions.length - 1]._id; + } + + oldUnsubscriptions.forEach(function (oldUnsubscription) { + oldUnsubscription.email = oldUnsubscription.email.toLowerCase(); + var newUnsubscription = new EmailUnsubscription(oldUnsubscription); + + batchInsertUnsubscriptions.insert(newUnsubscription.toObject()); + }); + + console.log(`Saving ${oldUnsubscriptions.length} unsubscriptions.`); + + return batchInsertUnsubscriptions.execute(); + }) + .then(function () { + processedUnsubscriptions += oldUnsubscriptions.length; + + console.log(`Saved ${oldUnsubscriptions.length} unsubscriptions.`); + + if (lastUnsubscription) { + return processUnsubscriptions(lastUnsubscription); + } else { + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldUnsubscriptionCollection = mongoDbOldInstance.collection('emailunsubscriptions'); + + mongoDbNewInstance = newInstance; + newUnsubscriptionCollection = mongoDbNewInstance.collection('emailunsubscriptions'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + return processUnsubscriptions(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/api_v3/groups.js b/migrations/api_v3/groups.js new file mode 100644 index 0000000000..dfd0616e07 --- /dev/null +++ b/migrations/api_v3/groups.js @@ -0,0 +1,211 @@ +/* + members are not stored anymore + invites are not stored anymore + + tavern id and leader must be updated +*/ + +// Migrate groups collection to new schema +// Run AFTER users migration + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/groups.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// Load new models +var NewGroup = require('../../website/server/models/group').model; + +var TAVERN_ID = require('../../website/server/models/group').TAVERN_ID; + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldGroupCollection; + +var mongoDbNewInstance; +var newGroupCollection; +var newUserCollection; + +var BATCH_SIZE = 1000; + +var processedGroups = 0; + +// Only process groups that fall in a interval ie -> up to 0000-4000-0000-0000 +var AFTER_GROUP_ID = nconf.get('AFTER_GROUP_ID'); +var BEFORE_GROUP_ID = nconf.get('BEFORE_GROUP_ID'); + +function processGroups (afterId) { + var processedTasks = 0; + var lastGroup = null; + var oldGroups; + + var query = {}; + + if (BEFORE_GROUP_ID) { + query._id = {$lte: BEFORE_GROUP_ID}; + } + + if ((afterId || AFTER_GROUP_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_GROUP_ID) { + query._id.$gt = AFTER_GROUP_ID; + } + + var batchInsertGroups = newGroupCollection.initializeUnorderedBulkOp(); + + console.log(`Executing groups query.\nMatching groups after ${afterId ? afterId : AFTER_GROUP_ID} and before ${BEFORE_GROUP_ID} (included).`); + + return oldGroupCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldGroupsR) { + oldGroups = oldGroupsR; + + var promises = []; + + console.log(`Processing ${oldGroups.length} groups. Already processed ${processedGroups} groups.`); + + if (oldGroups.length === BATCH_SIZE) { + lastGroup = oldGroups[oldGroups.length - 1]._id; + } + + oldGroups.forEach(function (oldGroup) { + if ((!oldGroup.privacy || oldGroup.privacy === 'private') && (!oldGroup.members || oldGroup.members.length === 0)) return; // delete empty private groups TODO must also delete challenges or this won't work + + oldGroup.members = oldGroup.members || []; + oldGroup.memberCount = oldGroup.members ? oldGroup.members.length : 0; + oldGroup.challengeCount = oldGroup.challenges ? oldGroup.challenges.length : 0; + + if (!oldGroup.balance <= 0) oldGroup.balance = 0; + if (!oldGroup.name) oldGroup.name = 'group name'; + if (!oldGroup.leaderOnly) oldGroup.leaderOnly = {}; + if (!oldGroup.leaderOnly.challenges) oldGroup.leaderOnly.challenges = false; + + // Tavern + if (oldGroup._id === 'habitrpg') { + oldGroup._id = TAVERN_ID; + oldGroup.leader = '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0'; // Siena Leslie + } + + if (!oldGroup.type) { + // throw new Error('group.type is required'); + oldGroup.type = 'guild'; + } + + if (!oldGroup.leader) { + if (oldGroup.members && oldGroup.members.length > 0) { + oldGroup.leader = oldGroup.members[0]; + } else { + throw new Error('group.leader is required and no member available!'); + } + } + + if (!oldGroup.privacy) { + // throw new Error('group.privacy is required'); + oldGroup.privacy = 'private'; + } + + var updateMembers = {}; + + if (oldGroup.type === 'guild') { + updateMembers.$push = {guilds: oldGroup._id}; + } else if (oldGroup.type === 'party') { + updateMembers.$set = {'party._id': oldGroup._id}; + } + + if (oldGroup.members) { + // Tyler Renelle + oldGroup.members.forEach(function (id, index) { + if (id === '9') { + oldGroup.members[index] = '00000000-0000-4000-9000-000000000000'; + } + }); + + promises.push(newUserCollection.updateMany({ + _id: {$in: oldGroup.members}, + }, updateMembers, {multi: true})); + } + + var newGroup = new NewGroup(oldGroup); + + batchInsertGroups.insert(newGroup.toObject()); + }); + + console.log(`Saving ${oldGroups.length} groups and migrating members to users collection.`); + + promises.push(batchInsertGroups.execute()); + return Bluebird.all(promises); + }) + .then(function () { + processedGroups += oldGroups.length; + + console.log(`Saved ${oldGroups.length} groups and migrated their members to the user collection.`); + + if (lastGroup) { + return processGroups(lastGroup); + } else { + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldGroupCollection = mongoDbOldInstance.collection('groups'); + + mongoDbNewInstance = newInstance; + newGroupCollection = mongoDbNewInstance.collection('groups'); + newUserCollection = mongoDbNewInstance.collection('users'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + // First delete the tavern group created by having required the group model + return newGroupCollection.deleteOne({_id: TAVERN_ID}); +}) +.then(function () { + return processGroups(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/api_v3/indexes.js b/migrations/api_v3/indexes.js new file mode 100644 index 0000000000..07aaa21db8 --- /dev/null +++ b/migrations/api_v3/indexes.js @@ -0,0 +1,52 @@ +/* + DEFINE BEFORE MIGRATING + + tasks: userId OK (sparse?), challenge.id OK (sparse?), challenge.taskId OK (sparse?), type? completed? + users: + id & apiToken, OK + auth.facebook.emails.value OK -> unique and sparse?, + auth.facebook.id - unique and sparse, OK + auth.local.email - unique and sparse, OK + auth.local.lowerCaseUsername, OK + auth.local.username - unique OK + auth.local.username & auth.local.hashed_password?, + auth.timestamps.created?, OK + auth.timestamps.loggedin?, OK + backer.tier -1 OK + { "contributor.admin" : 1 , "contributor.level" : -1 , "backer.npc" : -1 , "profile.name" : 1} + { "contributor.admin" : 1.0} NO, see ^ + { "contributor.level" : 1.0} OK + { "contributor.level" : 1.0 , "purchased.plan.customerId" : 1.0} ? + NO { "flags.lastWeeklyRecap" : 1 , "_id" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1} + { "invitations.guilds.id" : 1} OK + { "invitations.party.id" : 1} OK + OK { "preferences.sleep" : 1 , "_id" : 1 , "flags.lastWeeklyRecap" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1} + OK { "preferences.sleep" : 1 , "_id" : 1 , "lastCron" : 1 , "preferences.emailNotifications.importantAnnouncements" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "flags.recaptureEmailsPhase" : 1} + profile.name ? OK + { "purchased.plan.customerId" : 1.0} OK + { "purchased.plan.paymentMethod" : 1.0} OK + + guilds OK + party.id OK + challenges OK + challenges: + { "_id" : 1.0 , "__v" : 1.0} ? NO + { "_id" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} + { "group" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK + { "leader" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK + { "members" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} ? NO + { "official" : -1 , "timestamp" : -1} ? + { "official" : -1 , "timestamp" : -1, "_id": 1} ? + groups: + { "_id" : 1 , "quest.key" : 1} ? + { "_id" : 1.0 , "__v" : 1.0} ? + { "_id" : 1.0 , "privacy" : 1.0 , "members" : 1.0} ? NO + { "members" : 1.0 , "type" : 1.0 , "memberCount" : -1.0} ? NO + { "members" : 1} ? NO + { "privacy" : 1.0 , "memberCount" : -1.0} ? + { "privacy" : 1.0} OK + { "type" : 1 , "privacy" : 1} ? + { "type" : 1.0 , "members" : 1.0} ? NO + { "type" : 1} ? OK + emailUnsubscriptions: email unique OK +*/ diff --git a/migrations/api_v3/users.js b/migrations/api_v3/users.js new file mode 100644 index 0000000000..656b8f498a --- /dev/null +++ b/migrations/api_v3/users.js @@ -0,0 +1,262 @@ +// Migrate users collection to new schema +// This should run AFTER challenges migration + +// The console-stamp module must be installed (not included in package.json) + +// It requires two environment variables: MONGODB_OLD and MONGODB_NEW + +// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). +// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM +console.log('Starting migrations/api_v3/users.js.'); + +require('babel-register'); +require('babel-polyfill'); + +var Bluebird = require('bluebird'); +var MongoDB = require('mongodb'); +var nconf = require('nconf'); +var mongoose = require('mongoose'); +var _ = require('lodash'); +var uuid = require('uuid'); +var consoleStamp = require('console-stamp'); +var common = require('../../common'); +var moment = require('moment'); + +// Add timestamps to console messages +consoleStamp(console); + +// Initialize configuration +require('../../website/server/libs/api-v3/setupNconf')(); + +var MONGODB_OLD = nconf.get('MONGODB_OLD'); +var MONGODB_NEW = nconf.get('MONGODB_NEW'); + +var taskDefaults = common.taskDefaults; +var MongoClient = MongoDB.MongoClient; + +mongoose.Promise = Bluebird; // otherwise mongoose models won't work + +// Load new models +var NewUser = require('../../website/server/models/user').model; +var NewTasks = require('../../website/server/models/task'); + +// To be defined later when MongoClient connects +var mongoDbOldInstance; +var oldUserCollection; + +var mongoDbNewInstance; +var newUserCollection; +var newTaskCollection; + +var BATCH_SIZE = 1000; + +var processedUsers = 0; +var totoalProcessedTasks = 0; + +var challengeTaskWithMatchingId = 0; +var challengeTaskNoMatchingId = 0; + +// Load the new tasks ids for challenges tasks +var newTasksIds = require('./newTasksIds.json'); + +// Only process users that fall in a interval ie up to -> 0000-4000-0000-0000 +var AFTER_USER_ID = nconf.get('AFTER_USER_ID'); +var BEFORE_USER_ID = nconf.get('BEFORE_USER_ID'); + +function processUsers (afterId) { + var processedTasks = 0; + var lastUser = null; + var oldUsers; + + var now = new Date(); + + var query = {}; + + if (BEFORE_USER_ID) { + query._id = {$lte: BEFORE_USER_ID}; + } + + if ((afterId || AFTER_USER_ID) && !query._id) { + query._id = {}; + } + + if (afterId) { + query._id.$gt = afterId; + } else if (AFTER_USER_ID) { + query._id.$gt = AFTER_USER_ID; + } + + var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp(); + var batchInsertUsers = newUserCollection.initializeUnorderedBulkOp(); + + console.log(`Executing users query.\nMatching users after ${afterId ? afterId : AFTER_USER_ID} and before ${BEFORE_USER_ID} (included).`); + + return oldUserCollection + .find(query) + .sort({_id: 1}) + .limit(BATCH_SIZE) + .toArray() + .then(function (oldUsersR) { + oldUsers = oldUsersR; + + console.log(`Processing ${oldUsers.length} users. Already processed ${processedUsers} users and ${totoalProcessedTasks} tasks.`); + + if (oldUsers.length === BATCH_SIZE) { + lastUser = oldUsers[oldUsers.length - 1]._id; + } + + oldUsers.forEach(function (oldUser) { + var oldTasks = oldUser.habits.concat(oldUser.dailys).concat(oldUser.rewards).concat(oldUser.todos); + delete oldUser.habits; + delete oldUser.dailys; + delete oldUser.rewards; + delete oldUser.todos; + + delete oldUser.id; + + // spookDust -> spookySparkles + + if (oldUser.achievements && oldUser.achievements.spookDust) { + oldUser.achievements.spookySparkles = oldUser.achievements.spookDust; + delete oldUser.achievements.spookDust; + } + + if (oldUser.items && oldUser.items.special && oldUser.items.special.spookDust) { + oldUser.items.special.spookySparkles = oldUser.items.special.spookDust; + delete oldUser.items.special.spookDust; + } + + if (oldUser.stats && oldUser.stats.buffs && oldUser.stats.buffs.spookySparkles) { + oldUser.stats.buffs.spookySparkles = oldUser.stats.buffs.spookDust; + delete oldUser.stats.buffs.spookDust; + } + + // end spookDust -> spookySparkles + + oldUser.tags = oldUser.tags.map(function (tag) { + return { + id: tag.id, + name: tag.name || 'tag name', + challenge: tag.challenge, + }; + }); + + if (oldUser._id === '9') { // Tyler Renelle + oldUser._id = '00000000-0000-4000-9000-000000000000'; + } + + var newUser = new NewUser(oldUser); + var isSubscribed = newUser.isSubscribed(); + + oldTasks.forEach(function (oldTask) { + oldTask._id = uuid.v4(); // create a new unique uuid + oldTask.userId = newUser._id; + oldTask._legacyId = oldTask.id; // store the old task id + delete oldTask.id; + + oldTask.challenge = oldTask.challenge || {}; + if (oldTask.challenge.id) { + if (oldTask.challenge.broken) { + oldTask.challenge.taskId = oldTask._legacyId; + } else { + var newId = newTasksIds[oldTask._legacyId + '-' + oldTask.challenge.id]; + + // Challenges' tasks ids changed + if (!newId && !oldTask.challenge.broken) { + challengeTaskNoMatchingId++; + oldTask.challenge.taskId = oldTask._legacyId; + oldTask.challenge.broken = 'CHALLENGE_TASK_NOT_FOUND'; + } else { + challengeTaskWithMatchingId++; + oldTask.challenge.taskId = newId; + } + } + } + + // Delete old completed todos + if (oldTask.type === 'todo' && oldTask.completed && (!oldTask.challenge.id || oldTask.challenge.broken)) { + if (moment(now).subtract(isSubscribed ? 90 : 30, 'days').toDate() > moment(oldTask.dateCompleted).toDate()) { + return; + } + } + + oldTask.createdAt = oldTask.dateCreated; + + if (!oldTask.text) oldTask.text = 'task text'; // required + oldTask.tags = _.map(oldTask.tags, function (tagPresent, tagId) { + return tagPresent && tagId; + }).filter(function (tag) { + return tag !== false; + }); + + if (oldTask.type !== 'todo' || (oldTask.type === 'todo' && !oldTask.completed)) { + newUser.tasksOrder[`${oldTask.type}s`].push(oldTask._id); + } + + var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', '_legacyId', 'createdAt']; + // using mongoose models is too slow + if (oldTask.type === 'habit') { + oldTask = _.pick(oldTask, allTasksFields.concat(['history', 'up', 'down'])); + } else if (oldTask.type === 'daily') { + oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'history', 'frequency', 'everyX', 'startDate', 'repeat', 'streak'])); + } else if (oldTask.type === 'todo') { + oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'date', 'dateCompleted'])); + } else if (oldTask.type === 'reward') { + oldTask = _.pick(oldTask, allTasksFields); + } else { + throw new Error('Task with no or invalid type!'); + } + + batchInsertTasks.insert(taskDefaults(oldTask)); + processedTasks++; + }); + + batchInsertUsers.insert(newUser.toObject()); + }); + + console.log(`Saving ${oldUsers.length} users and ${processedTasks} tasks.`); + + return Bluebird.all([ + batchInsertUsers.execute(), + batchInsertTasks.execute(), + ]); + }) + .then(function () { + totoalProcessedTasks += processedTasks; + processedUsers += oldUsers.length; + + console.log(`Saved ${oldUsers.length} users and their tasks.`); + console.log('Challenges\' tasks no matching id: ', challengeTaskNoMatchingId); + console.log('Challenges\' tasks with matching id: ', challengeTaskWithMatchingId); + + if (lastUser) { + return processUsers(lastUser); + } else { + return console.log('Done!'); + } + }); +} + +// Connect to the databases +Bluebird.all([ + MongoClient.connect(MONGODB_OLD), + MongoClient.connect(MONGODB_NEW), +]) +.then(function (result) { + var oldInstance = result[0]; + var newInstance = result[1]; + + mongoDbOldInstance = oldInstance; + oldUserCollection = mongoDbOldInstance.collection('users'); + + mongoDbNewInstance = newInstance; + newUserCollection = mongoDbNewInstance.collection('users'); + newTaskCollection = mongoDbNewInstance.collection('tasks'); + + console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); + + return processUsers(); +}) +.catch(function (err) { + console.error(err.stack || err); +}); diff --git a/migrations/manual_password_reset.js b/migrations/manual_password_reset.js index 68b69cbbbe..622e16913b 100644 --- a/migrations/manual_password_reset.js +++ b/migrations/manual_password_reset.js @@ -7,7 +7,7 @@ nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.jso var Users = require('mongoskin').db(nconf.get("PRODUCTION_DB:URL"), nconf.get("PRODUCTION_DB").CREDS).collection('users'), async = require('async'), - utils = require('../website/src/utils'), + utils = require('../website/server/utils'), salt = utils.makeSalt(), newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later) hashed_password = utils.encryptPassword(newPassword, salt); diff --git a/newrelic.js b/newrelic.js deleted file mode 100644 index 0e8a550af7..0000000000 --- a/newrelic.js +++ /dev/null @@ -1,27 +0,0 @@ -var nconf = require('nconf'); - -/** - * New Relic agent configuration. - * - * See lib/config.defaults.js in the agent distribution for a more complete - * description of configuration variables and their potential values. - */ -exports.config = { - /** - * Array of application names. - */ - app_name: nconf.get('NEW_RELIC_APP_NAME'), - /** - * Your New Relic license key. - */ - license_key: nconf.get('NEW_RELIC_LICENSE_KEY'), - ssl: false, - logging: { - /** - * Level at which to log. 'trace' is most useful to New Relic when diagnosing - * issues with the agent, 'info' and higher will impose the least overhead on - * production applications. - */ - level: 'info' - } -} diff --git a/package.json b/package.json index 931b624c0b..fa12cb4dbb 100644 --- a/package.json +++ b/package.json @@ -1,32 +1,39 @@ { - "name": "habitrpg", + "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "0.0.0-152", - "main": "./website/src/server.js", + "version": "3.0.0", + "main": "./website/server/index.js", "dependencies": { + "accepts": "^1.3.2", "amazon-payments": "0.0.4", "amplitude": "^2.0.3", + "apidoc": "^0.16.0", "async": "^1.5.0", "aws-sdk": "^2.0.25", - "babel-plugin-syntax-async-functions": "^6.5.0", - "babel-plugin-transform-regenerator": "^6.6.0", + "babel-plugin-transform-async-to-module-method": "^6.8.0", "babel-polyfill": "^6.6.1", "babel-preset-es2015": "^6.6.0", "babel-register": "^6.6.0", "babelify": "^7.2.0", + "bluebird": "^3.3.5", "body-parser": "^1.15.0", "bower": "~1.3.12", "browserify": "~12.0.1", "compression": "^1.6.1", "connect-ratelimit": "0.0.7", "cookie-session": "^1.2.0", - "coupon-code": "~0.3.0", + "coupon-code": "^0.4.3", "csv-stringify": "^1.0.2", + "cwait": "^1.0.0", "domain-middleware": "~0.1.0", - "express": "^4.13.4", + "estraverse": "^4.1.1", + "express": "~4.13.3", + "express-csv": "~0.6.0", + "express-validator": "^2.18.0", "firebase": "^2.2.9", "firebase-token-generator": "^2.0.0", "glob": "^4.3.5", + "got": "^6.1.1", "grunt": "~0.4.1", "grunt-cli": "~0.1.9", "grunt-contrib-clean": "~0.6.0", @@ -55,32 +62,34 @@ "markdown-it": "^6.0.1", "merge-stream": "^1.0.0", "method-override": "^2.3.5", - "moment": "~2.10.6", - "mongoose": "~3.8.23", + "moment": "^2.13.0", + "mongoose": "^4.4.16", "mongoose-id-autoinc": "~2013.7.14-4", "morgan": "^1.7.0", "nconf": "~0.8.2", - "newrelic": "~1.26.1", - "uuid": "^2.0.1", - "nib": "~1.0.1", - "nodemailer": "^1.9.0", + "newrelic": "^1.27.2", + "nib": "^1.1.0", + "nodemailer": "^2.3.2", + "object-path": "^0.9.2", "pageres": "^4.1.1", "passport": "~0.2.1", "passport-facebook": "2.0.0", - "paypal-ipn": "2.1.0", + "paypal-ipn": "3.0.0", "paypal-rest-sdk": "^1.2.1", "pretty-data": "^0.40.0", "ps-tree": "^1.0.0", "push-notify": "^1.1.1", - "q": "^1.4.1", - "request": "~2.44.0", + "request": "~2.72.0", + "rimraf": "^2.4.3", + "run-sequence": "^1.1.4", "s3-upload-stream": "^1.0.6", "serve-favicon": "^2.3.0", "stripe": "^4.2.0", - "superagent": "~1.4.0", + "superagent": "^1.8.3", "swagger-node-express": "lefnire/swagger-node-express#habitrpg", "universal-analytics": "~0.3.2", - "validator": "~4.2.1", + "uuid": "^2.0.1", + "validator": "^4.9.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "winston": "^2.1.0" @@ -88,16 +97,20 @@ "private": true, "engines": { "node": "^4.3.1", - "npm": "^2.14.9" + "npm": "^3.8.9" }, "scripts": { "lint": "eslint .", "test": "npm run lint && gulp test", "test:api-v2:unit": "mocha test/server_side", "test:api-v2:integration": "mocha test/api/v2 --recursive", - "test:api-legacy": "istanbul cover -i \"website/src/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy", - "test:common": "mocha test/common", - "test:content": "mocha test/content", + "test:api-v3": "gulp test:api-v3", + "test:api-v3:unit": "gulp test:api-v3:unit", + "test:api-v3:integration": "gulp test:api-v3:integration", + "test:api-v3:integration:separate-server": "gulp test:api-v3:integration:separate-server", + "test:api-legacy": "istanbul cover -i \"website/server/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy", + "test:common": "mocha test/common --recursive", + "test:content": "mocha test/content --recursive", "test:karma": "karma start --single-run", "test:karma:watch": "karma start", "test:prepare:webdriver": "webdriver-manager update", @@ -116,7 +129,7 @@ "coveralls": "^2.11.2", "csv": "~0.3.6", "deep-diff": "~0.1.4", - "eslint": "^2.7.0", + "eslint": "^2.10.1", "eslint-config-habitrpg": "^1.0.0", "eslint-plugin-babel": "^3.0.0", "eslint-plugin-mocha": "^2.1.0", @@ -134,16 +147,23 @@ "mocha": "^2.3.3", "mongodb": "^2.0.46", "mongoskin": "~0.6.1", + "nock": "^2.17.0", "phantomjs": "^1.9", "protractor": "^3.1.1", + "require-again": "^1.0.1", "rewire": "^2.3.3", - "rimraf": "^2.4.3", - "run-sequence": "^1.1.4", - "shelljs": "^0.4.0", + "shelljs": "^0.7.0", "sinon": "^1.17.2", "sinon-chai": "^2.8.0", "superagent-defaults": "^0.1.13", "vinyl-source-stream": "^1.0.0", - "vinyl-transform": "^1.0.0" + "vinyl-transform": "^1.0.0", + "xml2js": "^0.4.16" + }, + "apidoc": { + "name": "habitica", + "title": "Habitica", + "version": "3.0.0", + "url": "https://habitica.com" } } diff --git a/website/src/controllers/payments/paypalBillingSetup.js b/scripts/paypalBillingSetup.js similarity index 99% rename from website/src/controllers/payments/paypalBillingSetup.js rename to scripts/paypalBillingSetup.js index 2effcbd81d..d21cd80c1c 100644 --- a/website/src/controllers/payments/paypalBillingSetup.js +++ b/scripts/paypalBillingSetup.js @@ -2,14 +2,16 @@ // payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this // file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json), // and once for any time you need to edit the plan thereafter + var path = require('path'); var nconf = require('nconf'); -_ = require('lodash'); -nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json'))); +var _ = require('lodash'); var paypal = require('paypal-rest-sdk'); var blocks = require('../../../../common').content.subscriptionBlocks; var live = nconf.get('PAYPAL:mode')=='live'; +nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json'))); + var OP = 'create'; // list create update remove paypal.configure({ diff --git a/tasks/gulp-apidoc.js b/tasks/gulp-apidoc.js new file mode 100644 index 0000000000..b8f65d2abd --- /dev/null +++ b/tasks/gulp-apidoc.js @@ -0,0 +1,22 @@ +import gulp from 'gulp'; +import clean from 'rimraf'; +import apidoc from 'apidoc'; + +const APIDOC_DEST_PATH = './website/build/apidoc'; +const APIDOC_SRC_PATH = './website/server'; +gulp.task('apidoc:clean', (done) => { + clean(APIDOC_DEST_PATH, done); +}); + +gulp.task('apidoc', ['apidoc:clean'], (done) => { + let result = apidoc.createDoc({ + src: APIDOC_SRC_PATH, + dest: APIDOC_DEST_PATH, + }); + + if (result === false) { + done(new Error('There was a problem generating apiDoc documentation.')) + } else { + done(); + } +}); diff --git a/tasks/gulp-build.js b/tasks/gulp-build.js index 2fd34d0121..e660145d92 100644 --- a/tasks/gulp-build.js +++ b/tasks/gulp-build.js @@ -1,4 +1,5 @@ import gulp from 'gulp'; +import runSequence from 'run-sequence'; import babel from 'gulp-babel'; require('gulp-grunt')(gulp); @@ -11,7 +12,7 @@ gulp.task('build', () => { }); gulp.task('build:src', () => { - return gulp.src('website/src/**/*.js') + return gulp.src('website/server/**/*.js') .pipe(babel()) .pipe(gulp.dest('website/transpiled-babel/')); }); @@ -29,9 +30,13 @@ gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => { }); gulp.task('build:dev:watch', ['build:dev'], () => { - gulp.watch(['website/public/**/*.styl', 'common/script/*']); + gulp.watch(['website/client/**/*.styl', 'common/script/*']); }); gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff'], (done) => { - gulp.start('grunt-build:prod', done); + runSequence( + 'grunt-build:prod', + 'apidoc', + done + ); }); diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js index 07512c6357..026d646cee 100644 --- a/tasks/gulp-console.js +++ b/tasks/gulp-console.js @@ -1,8 +1,7 @@ import mongoose from 'mongoose'; import autoinc from 'mongoose-id-autoinc'; -import logging from '../website/src/libs/logging'; +import logger from '../website/server/libs/api-v3/logger'; import nconf from 'nconf'; -import utils from '../website/src/libs/utils'; import repl from 'repl'; import gulp from 'gulp'; @@ -19,11 +18,9 @@ let improveRepl = (context) => { process.stdout.write('\u001B[2J\u001B[0;0f'); }}); - utils.setupConfig(); - - context.Challenge = require('../website/src/models/challenge').model; - context.Group = require('../website/src/models/group').model; - context.User = require('../website/src/models/user').model; + context.Challenge = require('../website/server/models/challenge').model; + context.Group = require('../website/server/models/group').model; + context.User = require('../website/server/models/user').model; var isProd = nconf.get('NODE_ENV') === 'production'; var mongooseOptions = !isProd ? {} : { @@ -36,7 +33,7 @@ let improveRepl = (context) => { mongooseOptions, function(err) { if (err) throw err; - logging.info('Connected with Mongoose'); + logger.info('Connected with Mongoose'); } ) ); diff --git a/tasks/gulp-newstuff.js b/tasks/gulp-newstuff.js index 16085e5c1c..b6d8093ee5 100644 --- a/tasks/gulp-newstuff.js +++ b/tasks/gulp-newstuff.js @@ -4,7 +4,7 @@ import {writeFileSync} from 'fs'; gulp.task('prepare:staticNewStuff', () => { writeFileSync( - './website/public/new-stuff.html', + './website/client/new-stuff.html', jade.compileFile('./website/views/shared/new-stuff.jade')() ); }); diff --git a/tasks/gulp-start.js b/tasks/gulp-start.js index 7cb842af00..51825f71ea 100644 --- a/tasks/gulp-start.js +++ b/tasks/gulp-start.js @@ -9,7 +9,7 @@ gulp.task('nodemon', () => { nodemon({ script: pkg.main, ignore: [ - 'website/public/*', + 'website/client/*', 'website/views/*', 'common/dist/script/content/*', ] diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index cd8622f387..7db299a5c4 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -9,17 +9,20 @@ import mongoose from 'mongoose'; import { exec } from 'child_process'; import psTree from 'ps-tree'; import gulp from 'gulp'; -import Q from 'q'; +import Bluebird from 'bluebird'; import runSequence from 'run-sequence'; import os from 'os'; import nconf from 'nconf'; +// TODO rewrite + const TEST_SERVER_PORT = 3003 let server; const TEST_DB_URI = nconf.get('TEST_DB_URI'); const API_V2_TEST_COMMAND = 'npm run test:api-v2:integration'; +const API_V3_TEST_COMMAND = 'npm run test:api-v3'; const LEGACY_API_TEST_COMMAND = 'npm run test:api-legacy'; const COMMON_TEST_COMMAND = 'npm run test:common'; const CONTENT_TEST_COMMAND = 'npm run test:content'; @@ -41,9 +44,9 @@ let testBin = (string, additionalEnvVariables = '') => { additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set '); additionalEnvVariables = 'set ' + additionalEnvVariables + '&&'; } - return `set NODE_ENV=testing&&${additionalEnvVariables}${string}`; + return `set NODE_ENV=test&&${additionalEnvVariables}${string}`; } else { - return `NODE_ENV=testing ${additionalEnvVariables} ${string}`; + return `NODE_ENV=test ${additionalEnvVariables} ${string}`; } }; @@ -65,7 +68,7 @@ gulp.task('test:prepare:mongo', (cb) => { gulp.task('test:prepare:server', ['test:prepare:mongo'], () => { if (!server) { - server = exec(testBin('node ./website/src/server.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT} `), (error, stdout, stderr) => { + server = exec(testBin(`node ./website/server/index.js`, `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => { if (error) { throw `Problem with the server: ${error}`; } if (stderr) { console.error(stderr); } }); @@ -101,7 +104,7 @@ gulp.task('test:common:clean', (cb) => { }); gulp.task('test:common:watch', ['test:common:clean'], () => { - gulp.watch(['common/script/**', 'test/common/**'], ['test:common:clean']); + gulp.watch(['common/script/**/*', 'test/common/**/*'], ['test:common:clean']); }); gulp.task('test:common:safe', ['test:prepare:build'], (cb) => { @@ -216,7 +219,7 @@ gulp.task('test:api-legacy:watch', [ 'test:prepare:mongo', 'test:api-legacy:clean' ], () => { - gulp.watch(['website/src/**', 'test/api-legacy/**'], ['test:api-legacy:clean']); + gulp.watch(['website/server/**', 'test/api-legacy/**'], ['test:api-legacy:clean']); }); gulp.task('test:karma', ['test:prepare:build'], (cb) => { @@ -262,7 +265,7 @@ gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => { ].map(exec); support.push(server); - Q.all([ + Bluebird.all([ awaitPort(TEST_SERVER_PORT), awaitPort(4444) ]).then(() => { @@ -283,7 +286,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => { 'npm run test:e2e:webdriver', ].map(exec); - Q.all([ + Bluebird.all([ awaitPort(TEST_SERVER_PORT), awaitPort(4444) ]).then(() => { @@ -306,16 +309,16 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => { }); }); -gulp.task('test:api-v2', ['test:prepare:server'], (done) => { - +/*gulp.task('test:api-v2', ['test:prepare:server'], (done) => { + process.env.API_VERSION = 'v2'; awaitPort(TEST_SERVER_PORT).then(() => { - runMochaTests('./test/api/v2/**/*.js', server, done) + runMochaTests('./test/api/v2/**//*.js', server, done) }); }); gulp.task('test:api-v2:watch', ['test:prepare:server'], () => { process.env.RUN_INTEGRATION_TEST_FOREVER = true; - gulp.watch(['website/src/**', 'test/api/v2/**'], ['test:api-v2']); + gulp.watch(['website/server/**', 'test/api/v2/**'], ['test:api-v2']); }); gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { @@ -324,7 +327,118 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { testBin(API_V2_TEST_COMMAND), (err, stdout, stderr) => { testResults.push({ - suite: 'API Specs\t', + suite: 'API V2 Specs\t', + pass: testCount(stdout, /(\d+) passing/), + fail: testCount(stderr, /(\d+) failing/), + pend: testCount(stdout, /(\d+) pending/) + }); + done(); + } + ); + pipe(runner); + }); +});*/ + +gulp.task('test:api-v2:integration', (done) => { + let runner = exec( + testBin('mocha test/api/v2 --recursive'), + {maxBuffer: 500*1024}, + (err, stdout, stderr) => done(err) + ) + + pipe(runner); +}); + +gulp.task('test:api-v3:unit', (done) => { + let runner = exec( + testBin('mocha test/api/v3/unit --recursive'), + (err, stdout, stderr) => done(err) + ) + + pipe(runner); +}); + +gulp.task('test:api-v3:unit:watch', () => { + gulp.watch(['website/server/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']); +}); + +gulp.task('test:api-v3:integration', (done) => { + let runner = exec( + testBin('mocha test/api/v3/integration --recursive'), + {maxBuffer: 500*1024}, + (err, stdout, stderr) => done(err) + ) + + pipe(runner); +}); + +gulp.task('test:api-v3:integration:watch', () => { + gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/api-v3/*.js', + 'test/api/v3/integration/**/*'], ['test:api-v3:integration']); +}); + +gulp.task('test:api-v3:integration:separate-server', (done) => { + let runner = exec( + testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'), + {maxBuffer: 500*1024}, + (err, stdout, stderr) => done(err) + ) + + pipe(runner); +}); + +gulp.task('test', (done) => { + runSequence( + 'test:common', + 'test:karma', + 'test:api-v3:unit', + 'test:api-v3:integration', + 'test:api-v2:integration', + done + ); +}); + +gulp.task('test:api-v3', (done) => { + runSequence( + 'test:api-v3:unit', + 'test:api-v3:integration', + done + ); +}); + +// Old tests tasks +/* +gulp.task('test:api-v3', ['test:api-v3:unit', 'test:api-v3:integration']); + +gulp.task('test:api-v3:watch', ['test:api-v3:unit:watch', 'test:api-v3:integration:watch']); + +gulp.task('test:api-v3:unit', (done) => {*/ +// runMochaTests('./test/api/v3/unit/**/*.js', null, done) +/*}); + +gulp.task('test:api-v3:unit:watch', () => { + gulp.watch(['website/server/**', 'test/api/v3/unit/**'], ['test:api-v3:unit']); +}); + +gulp.task('test:api-v3:integration', ['test:prepare:server'], (done) => { + process.env.API_VERSION = 'v3'; + awaitPort(TEST_SERVER_PORT).then(() => {*/ +// runMochaTests('./test/api/v3/integration/**/*.js', server, done) +/* }); +}); + +gulp.task('test:api-v3:integration:watch', ['test:prepare:server'], () => { + process.env.RUN_INTEGRATION_TEST_FOREVER = true; + gulp.watch(['website/server/**', 'test/api/v3/integration/**'], ['test:api-v3:integration']); +}); + +gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => { + awaitPort(TEST_SERVER_PORT).then(() => { + let runner = exec( + testBin(API_V3_TEST_COMMAND), + (err, stdout, stderr) => { + testResults.push({ + suite: 'API V3 Specs\t', pass: testCount(stdout, /(\d+) passing/), fail: testCount(stdout, /(\d+) failing/), pend: testCount(stdout, /(\d+) pending/) @@ -338,13 +452,14 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { gulp.task('test:all', (done) => { runSequence( - 'test:e2e:safe', - 'test:common:safe', - 'test:content:safe', + //'test:e2e:safe', + //'test:common:safe', + //'test:content:safe', // 'test:server_side:safe', - 'test:karma:safe', - 'test:api-legacy:safe', - 'test:api-v2:safe', + //'test:karma:safe', + //'test:api-legacy:safe', + //'test:api-v2:safe', + 'test:api-v3:safe', done); }); @@ -385,4 +500,4 @@ gulp.task('test', ['test:all'], () => { console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m'); process.exit(); } -}); +});*/ diff --git a/tasks/taskHelper.js b/tasks/taskHelper.js index 408978efd4..b83faf6af2 100644 --- a/tasks/taskHelper.js +++ b/tasks/taskHelper.js @@ -1,9 +1,9 @@ -import { exec } from 'child_process'; -import psTree from 'ps-tree'; -import nconf from 'nconf'; -import net from 'net'; -import Q from 'q'; -import { post } from 'superagent'; +import { exec } from 'child_process'; +import psTree from 'ps-tree'; +import nconf from 'nconf'; +import net from 'net'; +import Bluebird from 'bluebird'; +import { post } from 'superagent'; import { sync as glob } from 'glob'; import Mocha from 'mocha'; import { resolve } from 'path'; @@ -43,25 +43,24 @@ export function kill(proc) { * has fully spun up. Optionally provide a maximum number of seconds to wait * before failing. */ -export function awaitPort(port, max=60) { - let socket, timeout, interval; - let deferred = Q.defer(); +export function awaitPort (port, max=60) { + return new Bluebird((reject, resolve) => { + let socket, timeout, interval; - timeout = setTimeout(() => { - clearInterval(interval); - deferred.reject(`Timed out after ${max} seconds`); - }, max * 1000); - - interval = setInterval(() => { - socket = net.connect({port: port}, () => { + timeout = setTimeout(() => { clearInterval(interval); - clearTimeout(timeout); - socket.destroy(); - deferred.resolve(); - }).on('error', () => { socket.destroy }); - }, 1000); + reject(`Timed out after ${max} seconds`); + }, max * 1000); - return deferred.promise + interval = setInterval(() => { + socket = net.connect({port: port}, () => { + clearInterval(interval); + clearTimeout(timeout); + socket.destroy(); + resolve(); + }).on('error', () => { socket.destroy }); + }, 1000); + }); }; /* diff --git a/test/README.md b/test/README.md deleted file mode 100644 index 8115753727..0000000000 --- a/test/README.md +++ /dev/null @@ -1,5 +0,0 @@ -We need to clean up this directory. The *real* tests are in spec/ mock/ e2e/ and api.mocha.coffee. We want to: - -1. Move all old / deprecated tests from casper, test2, etc into spec, mock, e2e -1. Remove dependency of api.mocha.coffee on Derby, port it to Mongoose -1. Add better test-coverage diff --git a/test/api-legacy/api-helper.js b/test/api-legacy/api-helper.js index 9e61773211..1b994a80a8 100644 --- a/test/api-legacy/api-helper.js +++ b/test/api-legacy/api-helper.js @@ -1,3 +1,4 @@ +require('babel-core/register'); var path, superagentDefaults; superagentDefaults = require("superagent-defaults"); @@ -5,6 +6,8 @@ superagentDefaults = require("superagent-defaults"); global.request = superagentDefaults(); global.mongoose = require("mongoose"); +var Bluebird = require('bluebird'); +mongoose.Promise = Bluebird; global.moment = require("moment"); @@ -14,7 +17,7 @@ global._ = require("lodash"); global.shared = require("../../common"); -global.User = require("../../website/src/models/user").model; +global.User = require("../../website/server/models/user").model; global.chai = require("chai"); diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js index 2f264655ef..b09754bc5c 100644 --- a/test/api-legacy/challenges.js +++ b/test/api-legacy/challenges.js @@ -1,10 +1,10 @@ var Challenge, Group, app; -app = require("../../website/src/server"); +app = require("../../website/server/server"); -Group = require("../../website/src/models/group").model; +Group = require("../../website/server/models/group").model; -Challenge = require("../../website/src/models/challenge").model; +Challenge = require("../../website/server/models/challenge").model; describe("Challenges", function() { var challenge, group, updateTodo; diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js index 89b32b0c54..1f2dbab487 100644 --- a/test/api-legacy/chat.js +++ b/test/api-legacy/chat.js @@ -2,9 +2,9 @@ var Group, app, diff; diff = require("deep-diff"); -Group = require("../../website/src/models/group").model; +Group = require("../../website/server/models/group").model; -app = require("../../website/src/server"); +app = require("../../website/server/server"); describe("Chat", function() { var chat, group; diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js index 31d840de61..4d4e366473 100644 --- a/test/api-legacy/coupons.js +++ b/test/api-legacy/coupons.js @@ -1,8 +1,8 @@ var Coupon, app, makeSudoUser; -app = require("../../website/src/server"); +app = require("../../website/server/server"); -Coupon = require("../../website/src/models/coupon").model; +Coupon = require("../../website/server/models/coupon").model; makeSudoUser = function(usr, cb) { return registerNewUser(function() { diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js index d4182e437c..96809a7717 100644 --- a/test/api-legacy/inAppPurchases.js +++ b/test/api-legacy/inAppPurchases.js @@ -1,12 +1,12 @@ var app, iapMock, inApp, rewire, sinon; -app = require('../../website/src/server'); +app = require('../../website/server/server'); rewire = require('rewire'); sinon = require('sinon'); -inApp = rewire('../../website/src/controllers/payments/iap'); +inApp = rewire('../../website/server/controllers/payments/iap'); iapMock = {}; diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js index 8ea8188713..2f98b67bde 100644 --- a/test/api-legacy/party.js +++ b/test/api-legacy/party.js @@ -2,9 +2,9 @@ var Group, app, diff; diff = require("deep-diff"); -Group = require("../../website/src/models/group").model; +Group = require("../../website/server/models/group").model; -app = require("../../website/src/server"); +app = require("../../website/server/server"); describe("Party", function() { return context("Quests", function() { diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js index ce3d9672f8..7f98ddccfa 100644 --- a/test/api-legacy/pushNotifications.js +++ b/test/api-legacy/pushNotifications.js @@ -1,6 +1,6 @@ var app, rewire, sinon; -app = require("../../website/src/server"); +app = require("../../website/server/server"); rewire = require('rewire'); @@ -21,7 +21,7 @@ describe("Push-Notifications", function() { }); context("Challenges", function() { var challengeMock, challenges, userMock; - challenges = rewire("../../website/src/controllers/api-v2/challenges"); + challenges = rewire("../../website/server/controllers/api-v2/challenges"); challenges.__set__('pushNotify', pushSpy); challengeMock = { findById: function(arg, cb) { @@ -76,7 +76,7 @@ describe("Push-Notifications", function() { context("Groups", function() { var groups, recipient; recipient = null; - groups = rewire("../../website/src/controllers/api-v2/groups"); + groups = rewire("../../website/server/controllers/api-v2/groups"); groups.__set__('pushNotify', pushSpy); before(function(done) { return registerNewUser(function(err, _user) { @@ -304,7 +304,7 @@ describe("Push-Notifications", function() { }); context("sending gems from balance", function() { var members; - members = rewire("../../website/src/controllers/api-v2/members"); + members = rewire("../../website/server/controllers/api-v2/members"); members.sendMessage = function() { return true; }; @@ -342,7 +342,7 @@ describe("Push-Notifications", function() { }); return describe("Purchases", function() { var membersMock, payments; - payments = rewire("../../website/src/controllers/payments"); + payments = rewire("../../website/server/controllers/payments"); payments.__set__('pushNotify', pushSpy); membersMock = { sendMessage: function() { diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js index 8c6906acfb..af31a4f334 100644 --- a/test/api-legacy/score.js +++ b/test/api-legacy/score.js @@ -1,4 +1,4 @@ -require("../../website/src/server"); +require("../../website/server/server"); describe("Score", function() { before(function(done) { diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js index 9d8624cb73..73b55039df 100644 --- a/test/api-legacy/subscriptions.js +++ b/test/api-legacy/subscriptions.js @@ -1,8 +1,8 @@ var app, payments; -payments = require("../../website/src/controllers/payments"); +payments = require("../../website/server/controllers/payments"); -app = require("../../website/src/server"); +app = require("../../website/server/server"); describe("Subscriptions", function() { before(function(done) { diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js index 5285847fd1..b72ea57223 100644 --- a/test/api-legacy/todos.js +++ b/test/api-legacy/todos.js @@ -1,4 +1,4 @@ -require("../../website/src/server"); +require("../../website/server/server"); describe("Todos", function() { before(function(done) { diff --git a/test/api/README.md b/test/api/README.md index 81e7c2d54b..1f5c98abb3 100644 --- a/test/api/README.md +++ b/test/api/README.md @@ -1,5 +1,7 @@ # So you want to write API integration tests? +@TODO rewrite + That's great! This README will serve as a quick primer for style conventions and practices for these tests. ## What is this? @@ -73,7 +75,7 @@ POST-groups_id_leave.test.js To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so: ```js -it('does something', () => { +it('does something', () => { let user; return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously @@ -97,7 +99,7 @@ it('does something', () => { If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion. ```js -it('makes the party creator the leader automatically', () => { +it('makes the party creator the leader automatically', () => { return expect(user.post('/groups', { type: 'party', })).to.eventually.have.deep.property('leader._id', user._id); @@ -107,7 +109,7 @@ it('makes the party creator the leader automatically', () => { If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax. ```js -it('returns an error', () => { +it('returns an error', () => { return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to')) .to.eventually.be.rejected.and.eql({ code: 404, diff --git a/test/api/v2/groups/GET-groups.test.js b/test/api/v2/groups/GET-groups.test.js index 203fdd0acc..d941b2533e 100644 --- a/test/api/v2/groups/GET-groups.test.js +++ b/test/api/v2/groups/GET-groups.test.js @@ -3,12 +3,15 @@ import { generateUser, resetHabiticaDB, } from '../../../helpers/api-integration/v2'; +import { + TAVERN_ID, +} from '../../../../website/server/models/group'; describe('GET /groups', () => { const NUMBER_OF_PUBLIC_GUILDS = 3; - const NUMBER_OF_USERS_GUILDS = 2; let user; + let leader; before(async () => { // Set up a world with a mixture of public and private guilds @@ -16,7 +19,7 @@ describe('GET /groups', () => { await resetHabiticaDB(); user = await generateUser(); - let leader = await generateUser({ balance: 10 }); + leader = await generateUser({ balance: 10 }); await generateGroup(leader, { name: 'public guild - is member', @@ -68,7 +71,7 @@ describe('GET /groups', () => { await expect(user.get('/groups', null, {type: 'tavern'})) .to.eventually.have.a.lengthOf(1) .and.to.have.deep.property('[0]') - .and.to.have.property('_id', 'habitrpg'); + .and.to.have.property('_id', TAVERN_ID); }); }); @@ -90,8 +93,8 @@ describe('GET /groups', () => { context('guilds passed in as query', () => { it('returns all guilds user is a part of ', async () => { - await expect(user.get('/groups', null, {type: 'guilds'})) - .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_GUILDS); + await expect(leader.get('/groups', null, {type: 'guilds'})) + .to.eventually.have.a.lengthOf(4); }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_invite.test.js b/test/api/v2/groups/POST-groups_id_invite.test.js index 5acf6e637a..ef768dbf7e 100644 --- a/test/api/v2/groups/POST-groups_id_invite.test.js +++ b/test/api/v2/groups/POST-groups_id_invite.test.js @@ -27,8 +27,8 @@ describe('POST /groups/:id/invite', () => { await inviter.post(`/groups/${group._id}/invite`, { uuids: [invitee._id], }); - await group.sync(); - expect(group.invites).to.include(invitee._id); + group = await inviter.get(`/groups/${group._id}`); + expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; }); }); }); @@ -53,8 +53,8 @@ describe('POST /groups/:id/invite', () => { await inviter.post(`/groups/${group._id}/invite`, { uuids: [invitee._id], }); - await group.sync(); - expect(group.invites).to.include(invitee._id); + group = await inviter.get(`/groups/${group._id}`); + expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_join.test.js b/test/api/v2/groups/POST-groups_id_join.test.js index 250fd4bbf2..cf216354a0 100644 --- a/test/api/v2/groups/POST-groups_id_join.test.js +++ b/test/api/v2/groups/POST-groups_id_join.test.js @@ -30,9 +30,8 @@ describe('POST /groups/:id/join', () => { it(`allows user to join a ${groupType}`, async () => { await invitee.post(`/groups/${group._id}/join`); - await group.sync(); - - expect(group.members).to.include(invitee._id); + group = await invitee.get(`/groups/${group._id}`); + expect(_.find(group.members, {_id: invitee._id})._id).to.exists; }); }); }); @@ -78,9 +77,9 @@ describe('POST /groups/:id/join', () => { it('allows user to join a public guild', async () => { await user.post(`/groups/${group._id}/join`); - await group.sync(); + group = await user.get(`/groups/${group._id}`); - expect(group.members).to.include(user._id); + expect(_.find(group.members, {_id: user._id})._id).to.exists; }); }); @@ -103,9 +102,9 @@ describe('POST /groups/:id/join', () => { it('makes the joining user the leader', async () => { await user.post(`/groups/${group._id}/join`); - await group.sync(); + group = await user.get(`/groups/${group._id}`); - await expect(group.leader).to.eql(user._id); + expect(group.leader._id).to.eql(user._id); }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_leave.test.js b/test/api/v2/groups/POST-groups_id_leave.test.js index ef5c4a8ac2..f6511e941c 100644 --- a/test/api/v2/groups/POST-groups_id_leave.test.js +++ b/test/api/v2/groups/POST-groups_id_leave.test.js @@ -28,9 +28,9 @@ describe('POST /groups/:id/leave', () => { it('leaves the group', async () => { await user.post(`/groups/${group._id}/leave`); - await group.sync(); + await user.sync(); - expect(group.members).to.not.include(user._id); + expect(user.guilds).to.not.include(group._id); }); }); diff --git a/test/api/v2/user/DELETE-user.test.js b/test/api/v2/user/DELETE-user.test.js index db0eeca1c8..8d28a07b78 100644 --- a/test/api/v2/user/DELETE-user.test.js +++ b/test/api/v2/user/DELETE-user.test.js @@ -4,7 +4,11 @@ import { generateGroup, generateUser, } from '../../../helpers/api-integration/v2'; -import { find } from 'lodash'; +import { + find, + map, +} from 'lodash'; +import Bluebird from 'bluebird'; describe('DELETE /user', () => { let user; @@ -19,6 +23,18 @@ describe('DELETE /user', () => { })).to.eventually.eql(false); }); + it('deletes the user\'s tasks', async () => { + // gets the user's todos ids + let ids = user.todos.map(todo => todo._id); + expect(ids.length).to.be.above(0); // make sure the user has some task to delete + + await user.del('/user'); + + await Bluebird.all(map(ids, id => { + return expect(checkExistence('tasks', id)).to.eventually.eql(false); + })); + }); + context('user has active subscription', () => { it('does not delete account'); }); diff --git a/test/api/v2/user/batch-update/POST-user_batch-update.test.js b/test/api/v2/user/batch-update/POST-user_batch-update.test.js index 0b1f244ff7..f64cbc7f7b 100644 --- a/test/api/v2/user/batch-update/POST-user_batch-update.test.js +++ b/test/api/v2/user/batch-update/POST-user_batch-update.test.js @@ -31,7 +31,7 @@ describe('POST /user/batch-update', () => { }); }); - context('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing' + xcontext('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing' let protectedOperations = { 'Add Ten Gems': 'addTenGems', 'Add Hourglass': 'addHourglass', diff --git a/test/api/v2/user/pushDevice/POST-pushDevice.test.js b/test/api/v2/user/pushDevice/POST-pushDevice.test.js index 97cfc4dbb9..c0b5e9be72 100644 --- a/test/api/v2/user/pushDevice/POST-pushDevice.test.js +++ b/test/api/v2/user/pushDevice/POST-pushDevice.test.js @@ -2,7 +2,7 @@ import { generateUser, } from '../../../../helpers/api-integration/v2'; -describe('POST /user/pushDevice', () => { +xdescribe('POST /user/pushDevice', () => { let user; beforeEach(async () => { diff --git a/test/api/v2/user/tasks/GET-tasks.test.js b/test/api/v2/user/tasks/GET-tasks.test.js index 7e6f847cbb..1506f4c2f0 100644 --- a/test/api/v2/user/tasks/GET-tasks.test.js +++ b/test/api/v2/user/tasks/GET-tasks.test.js @@ -6,14 +6,7 @@ describe('GET /user/tasks/', () => { let user; beforeEach(async () => { - return generateUser({ - dailys: [ - {text: 'daily', type: 'daily'}, - {text: 'daily', type: 'daily'}, - {text: 'daily', type: 'daily'}, - {text: 'daily', type: 'daily'}, - ], - }).then((_user) => { + return generateUser().then((_user) => { user = _user; }); }); @@ -21,7 +14,7 @@ describe('GET /user/tasks/', () => { it('gets all tasks', async () => { return user.get('/user/tasks/').then((tasks) => { expect(tasks).to.be.an('array'); - expect(tasks.length).to.be.greaterThan(3); + expect(tasks.length).to.equal(1); let task = tasks[0]; expect(task.id).to.exist; diff --git a/test/api/v2/user/tasks/POST-clear-completed.test.js b/test/api/v2/user/tasks/POST-clear-completed.test.js new file mode 100644 index 0000000000..40e6cbd5cf --- /dev/null +++ b/test/api/v2/user/tasks/POST-clear-completed.test.js @@ -0,0 +1,26 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v2'; + +describe('POST /user/tasks/clear-completed', () => { + let user; + + beforeEach(async () => { + return generateUser().then((_user) => { + user = _user; + }); + }); + + it('removes all completed todos', async () => { + let toComplete = await user.post('/user/tasks', { + type: 'todo', + text: 'done', + }); + + await user.post(`/user/tasks/${toComplete._id}/up`); + + let todos = await user.get('/user/tasks?type=todo'); + let uncomplete = await user.post('/user/tasks/clear-completed'); + expect(todos.length).to.equal(uncomplete.length + 1); + }); +}); diff --git a/test/api/v2/user/tasks/POST-tasks.test.js b/test/api/v2/user/tasks/POST-tasks.test.js index 5f43224803..4fe4c9f5ea 100644 --- a/test/api/v2/user/tasks/POST-tasks.test.js +++ b/test/api/v2/user/tasks/POST-tasks.test.js @@ -35,7 +35,7 @@ describe('POST /user/tasks', () => { }); }); - it('does not create a task with an id that already exists', async () => { + xit('does not create a task with an id that already exists', async () => { let todo = user.todos[0]; return expect(user.post('/user/tasks', { diff --git a/test/api/v2/user/tasks/PUT-tasks_id.test.js b/test/api/v2/user/tasks/PUT-tasks_id.test.js index 037322a6b7..3bb9d8e9c6 100644 --- a/test/api/v2/user/tasks/PUT-tasks_id.test.js +++ b/test/api/v2/user/tasks/PUT-tasks_id.test.js @@ -33,13 +33,13 @@ describe('PUT /user/tasks/:id', () => { text: 'new text', notes: 'new notes', value: 10000, - priority: 0.5, + priority: 0.1, attribute: 'str', }).then((updatedTask) => { expect(updatedTask.text).to.eql('new text'); expect(updatedTask.notes).to.eql('new notes'); expect(updatedTask.value).to.eql(10000); - expect(updatedTask.priority).to.eql(0.5); + expect(updatedTask.priority).to.eql(0.1); expect(updatedTask.attribute).to.eql('str'); }); }); diff --git a/test/api/v3/README.md b/test/api/v3/README.md new file mode 100644 index 0000000000..ad9b55a32c --- /dev/null +++ b/test/api/v3/README.md @@ -0,0 +1,4 @@ +# How to run tests: + +1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command. +2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other diff --git a/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js b/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js new file mode 100644 index 0000000000..45dc91758a --- /dev/null +++ b/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js @@ -0,0 +1,94 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + sleep, + checkExistence, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /challenges/:challengeId', () => { + it('returns error when challengeId is not a valid UUID', async () => { + let user = await generateUser(); + await expect(user.del('/challenges/test')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when challengeId is not for a valid challenge', async () => { + let user = await generateUser(); + + await expect(user.del(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + context('Deleting a valid challenge', () => { + let groupLeader; + let group; + let challenge; + let taskText = 'A challenge task text'; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup(); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + + challenge = await generateChallenge(groupLeader, group); + + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: taskText}, + ]); + + await challenge.sync(); + }); + + it('returns an error when user doesn\'t have permissions to delete the challenge', async () => { + let user = await generateUser(); + + await expect(user.del(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyLeaderDeleteChal'), + }); + }); + + it('deletes challenge', async () => { + await groupLeader.del(`/challenges/${challenge._id}`); + + await sleep(0.5); + + await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false); + }); + + it('refunds gems to group leader', async () => { + let oldBalance = (await groupLeader.sync()).balance; + + await groupLeader.del(`/challenges/${challenge._id}`); + + await sleep(0.5); + + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4); + }); + + it('sets broken and doesn\'t set winner flags for user\'s challenge tasks', async () => { + await groupLeader.del(`/challenges/${challenge._id}`); + + await sleep(0.5); + + let tasks = await groupLeader.get('/tasks/user'); + let testTask = _.find(tasks, (task) => { + return task.text === taskText; + }); + + expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED'); + expect(testTask.challenge.winner).to.be.null; + }); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js new file mode 100644 index 0000000000..33b329300a --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js @@ -0,0 +1,142 @@ +import { + generateUser, + createAndPopulateGroup, + generateChallenge, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /challenges/:challengeId', () => { + it('fails if challenge doesn\'t exists', async () => { + let user = await generateUser(); + await expect(user.get(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + context('public guild', () => { + let groupLeader; + let group; + let challenge; + let user; + + beforeEach(async () => { + user = await generateUser(); + + let populatedGroup = await createAndPopulateGroup({ + groupDetails: {type: 'guild', privacy: 'public'}, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + + challenge = await generateChallenge(groupLeader, group); + }); + + it('should return challenge data', async () => { + let chal = await user.get(`/challenges/${challenge._id}`); + expect(chal.memberCount).to.equal(challenge.memberCount); + expect(chal.name).to.equal(challenge.name); + expect(chal._id).to.equal(challenge._id); + + expect(chal.leader).to.eql({ + _id: groupLeader._id, + id: groupLeader._id, + profile: {name: groupLeader.profile.name}, + }); + expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); + }); + }); + + context('private guild', () => { + let groupLeader; + let group; + let challenge; + let members; + let user; + + beforeEach(async () => { + user = await generateUser(); + + let populatedGroup = await createAndPopulateGroup({ + groupDetails: {type: 'guild', privacy: 'private'}, + members: 1, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + members = populatedGroup.members; + + challenge = await generateChallenge(groupLeader, group); + await members[0].post(`/challenges/${challenge._id}/join`); + }); + + it('fails if user doesn\'t have access to the challenge', async () => { + await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('should return challenge data', async () => { + let chal = await members[0].get(`/challenges/${challenge._id}`); + expect(chal.name).to.equal(challenge.name); + expect(chal._id).to.equal(challenge._id); + + expect(chal.leader).to.eql({ + _id: groupLeader._id, + id: groupLeader._id, + profile: {name: groupLeader.profile.name}, + }); + expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); + }); + }); + + context('party', () => { + let groupLeader; + let group; + let challenge; + let members; + let user; + + beforeEach(async () => { + user = await generateUser(); + + let populatedGroup = await createAndPopulateGroup({ + groupDetails: {type: 'party'}, + members: 1, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + members = populatedGroup.members; + + challenge = await generateChallenge(groupLeader, group); + await members[0].post(`/challenges/${challenge._id}/join`); + }); + + it('fails if user doesn\'t have access to the challenge', async () => { + await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('should return challenge data', async () => { + let chal = await members[0].get(`/challenges/${challenge._id}`); + expect(chal.name).to.equal(challenge.name); + expect(chal._id).to.equal(challenge._id); + + expect(chal.leader).to.eql({ + _id: groupLeader._id, + id: groupLeader.id, + profile: {name: groupLeader.profile.name}, + }); + expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); + }); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js new file mode 100644 index 0000000000..2b98af9579 --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js @@ -0,0 +1,74 @@ +import { + generateUser, + createAndPopulateGroup, + generateChallenge, + translate as t, + sleep, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /challenges/:challengeId/export/csv', () => { + let groupLeader; + let group; + let challenge; + let members; + let user; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 3, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + members = populatedGroup.members; + + challenge = await generateChallenge(groupLeader, group); + await members[0].post(`/challenges/${challenge._id}/join`); + await members[1].post(`/challenges/${challenge._id}/join`); + await members[2].post(`/challenges/${challenge._id}/join`); + + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: 'Task 1'}, + {type: 'todo', text: 'Task 2'}, + ]); + await sleep(0.5); // Make sure tasks are synced to the users + await members[0].sync(); + await members[1].sync(); + await members[2].sync(); + }); + + it('fails if challenge doesn\'t exists', async () => { + user = await generateUser(); + user.get('/user'); + await expect(user.get(`/challenges/${generateUUID()}/export/csv`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('fails if user doesn\'t have access to the challenge', async () => { + user = await generateUser(); + user.get('/user'); + + await expect(user.get(`/challenges/${challenge._id}/export/csv`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('should return a valid CSV file with export data', async () => { + let res = await members[0].get(`/challenges/${challenge._id}/export/csv`); + let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id'); + let splitRes = res.split('\n'); + + expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes'); + expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`); + expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`); + expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`); + expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`); + expect(splitRes[5]).to.equal(''); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js new file mode 100644 index 0000000000..5117dcd556 --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js @@ -0,0 +1,109 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /challenges/:challengeId/members', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('validates optional req.query.lastId to be an UUID', async () => { + await expect(user.get(`/challenges/${generateUUID()}/members?lastId=invalidUUID`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('fails if challenge doesn\'t exists', async () => { + await expect(user.get(`/challenges/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('fails if user doesn\'t have access to the challenge', async () => { + let group = await generateGroup(user); + let challenge = await generateChallenge(user, group); + let anotherUser = await generateUser(); + + await expect(anotherUser.get(`/challenges/${challenge._id}/members`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('works with challenges belonging to public guild', async () => { + let leader = await generateUser({balance: 4}); + let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); + let challenge = await generateChallenge(leader, group); + let res = await user.get(`/challenges/${challenge._id}/members`); + expect(res[0]).to.eql({ + _id: leader._id, + id: leader._id, + profile: {name: leader.profile.name}, + }); + expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); + expect(res[0].profile).to.have.all.keys(['name']); + }); + + it('populates only some fields', async () => { + let anotherUser = await generateUser({balance: 3}); + let group = await generateGroup(anotherUser, {type: 'guild', privacy: 'public', name: generateUUID()}); + let challenge = await generateChallenge(anotherUser, group); + let res = await user.get(`/challenges/${challenge._id}/members`); + expect(res[0]).to.eql({ + _id: anotherUser._id, + id: anotherUser._id, + profile: {name: anotherUser.profile.name}, + }); + expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); + expect(res[0].profile).to.have.all.keys(['name']); + }); + + it('returns only first 30 members', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + + let usersToGenerate = []; + for (let i = 0; i < 31; i++) { + usersToGenerate.push(generateUser({challenges: [challenge._id]})); + } + await Promise.all(usersToGenerate); + + let res = await user.get(`/challenges/${challenge._id}/members`); + expect(res.length).to.equal(30); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + + it('supports using req.query.lastId to get more members', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + + let usersToGenerate = []; + for (let i = 0; i < 57; i++) { + usersToGenerate.push(generateUser({challenges: [challenge._id]})); + } + let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader) + let expectedIds = [user._id].concat(generatedUsers.map(generatedUser => generatedUser._id)); + + let res = await user.get(`/challenges/${challenge._id}/members`); + expect(res.length).to.equal(30); + let res2 = await user.get(`/challenges/${challenge._id}/members?lastId=${res[res.length - 1]._id}`); + expect(res2.length).to.equal(28); + + let resIds = res.concat(res2).map(member => member._id); + expect(resIds).to.eql(expectedIds.sort()); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js new file mode 100644 index 0000000000..c47e1d4ad8 --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js @@ -0,0 +1,107 @@ +import { + generateUser, + generateChallenge, + generateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /challenges/:challengeId/members/:memberId', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('validates req.params.memberId to be an UUID', async () => { + await expect(user.get(`/challenges/invalidUUID/members/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('validates req.params.memberId to be an UUID', async () => { + await expect(user.get(`/challenges/${generateUUID()}/members/invalidUUID`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('fails if member doesn\'t exists', async () => { + let userId = generateUUID(); + await expect(user.get(`/challenges/${generateUUID()}/members/${userId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId}), + }); + }); + + it('fails if challenge doesn\'t exists', async () => { + let member = await generateUser(); + await expect(user.get(`/challenges/${generateUUID()}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('fails if user doesn\'t have access to the challenge', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + let anotherUser = await generateUser(); + let member = await generateUser(); + await expect(anotherUser.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('fails if member is not part of the challenge', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + let member = await generateUser(); + await expect(user.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeMemberNotFound'), + }); + }); + + it('works with challenges belonging to a public guild', async () => { + let groupLeader = await generateUser({balance: 4}); + let group = await generateGroup(groupLeader, {type: 'guild', privacy: 'public', name: generateUUID()}); + let challenge = await generateChallenge(groupLeader, group); + let taskText = 'Test Text'; + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]); + + let memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`); + expect(memberProgress).to.have.all.keys(['_id', 'id', 'profile', 'tasks']); + expect(memberProgress.profile).to.have.all.keys(['name']); + expect(memberProgress.tasks.length).to.equal(1); + }); + + it('returns the member tasks for the challenges', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: 'Test Text'}]); + + let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`); + let chalTasks = await user.get(`/tasks/challenge/${challenge._id}`); + expect(memberProgress.tasks.length).to.equal(chalTasks.length); + expect(memberProgress.tasks[0].challenge.id).to.equal(challenge._id); + expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id); + }); + + it('returns the tasks without the tags', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + let taskText = 'Test Text'; + await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]); + + let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`); + expect(memberProgress.tasks[0]).not.to.have.key('tags'); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js new file mode 100644 index 0000000000..275fe798f5 --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js @@ -0,0 +1,118 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET challenges/group/:groupId', () => { + context('Public Guild', () => { + let publicGuild, user, nonMember, challenge, challenge2; + + before(async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestGuild', + type: 'guild', + privacy: 'public', + }, + }); + + publicGuild = group; + user = groupLeader; + + nonMember = await generateUser(); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); + }); + + it('should return group challenges for non member with populated leader', async () => { + let challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + }); + + it('should return group challenges for member with populated leader', async () => { + let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + }); + }); + + context('Private Guild', () => { + let privateGuild, user, nonMember, challenge, challenge2; + + before(async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestPrivateGuild', + type: 'guild', + privacy: 'private', + }, + }); + + privateGuild = group; + user = groupLeader; + + nonMember = await generateUser(); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); + }); + + it('should prevent non-member from seeing challenges', async () => { + await expect(nonMember.get(`/challenges/groups/${privateGuild._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('should return group challenges for member with populated leader', async () => { + let challenges = await user.get(`/challenges/groups/${privateGuild._id}`); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: privateGuild.leader._id, + id: privateGuild.leader._id, + profile: {name: user.profile.name}, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: privateGuild.leader._id, + id: privateGuild.leader._id, + profile: {name: user.profile.name}, + }); + }); + }); +}); diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js new file mode 100644 index 0000000000..ecb9f55383 --- /dev/null +++ b/test/api/v3/integration/challenges/GET-challenges_user.test.js @@ -0,0 +1,132 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET challenges/user', () => { + let user, member, nonMember, challenge, challenge2, publicGuild; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestGuild', + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + user = groupLeader; + publicGuild = group; + member = members[0]; + nonMember = await generateUser(); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); + }); + + it('should return challenges user has joined', async () => { + await nonMember.post(`/challenges/${challenge._id}/join`); + + let challenges = await nonMember.get('/challenges/user'); + + let foundChallenge = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge).to.exist; + expect(foundChallenge.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + }); + + it('should return challenges user has created', async () => { + let challenges = await user.get('/challenges/user'); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge1.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge2.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + }); + + it('should return challenges in user\'s group', async () => { + let challenges = await member.get('/challenges/user'); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge1.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge2.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + }); + + it('should not return challenges user doesn\'t have access to', async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestPrivateGuild', + type: 'guild', + privacy: 'private', + }, + }); + + let privateChallenge = await generateChallenge(groupLeader, group); + + let challenges = await nonMember.get('/challenges/user'); + + let foundChallenge = _.find(challenges, { _id: privateChallenge._id }); + expect(foundChallenge).to.not.exist; + }); +}); diff --git a/test/api/v3/integration/challenges/POST-challenges.test.js b/test/api/v3/integration/challenges/POST-challenges.test.js new file mode 100644 index 0000000000..a97578eef5 --- /dev/null +++ b/test/api/v3/integration/challenges/POST-challenges.test.js @@ -0,0 +1,308 @@ +import { + generateUser, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /challenges', () => { + it('returns error when group is empty', async () => { + let user = await generateUser(); + + await expect(user.post('/challenges')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when groupId is not for a valid group', async () => { + let user = await generateUser(); + + await expect(user.post('/challenges', { + group: generateUUID(), + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns error when creating a challenge in the tavern with no prize', async () => { + let user = await generateUser(); + + await expect(user.post('/challenges', { + group: 'habitrpg', + prize: 0, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('tavChalsMinPrize'), + }); + }); + + it('returns error when creating a challenge in a public guild and you are not a member of it', async () => { + let user = await generateUser(); + let { group } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + }); + + await expect(user.post('/challenges', { + group: group._id, + prize: 4, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('mustBeGroupMember'), + }); + }); + + context('Creating a challenge for a valid group', () => { + let groupLeader; + let group; + let groupMember; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 1, + leaderDetails: { + balance: 3, + }, + groupDetails: { + type: 'guild', + leaderOnly: { + challenges: true, + }, + }, + }); + + groupLeader = await populatedGroup.groupLeader.sync(); + group = populatedGroup.group; + groupMember = populatedGroup.members[0]; + }); + + it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => { + await expect(groupMember.post('/challenges', { + group: group._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyGroupLeaderChal'), + }); + }); + + it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => { + await expect(groupMember.post('/challenges', { + group: group._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyGroupLeaderChal'), + }); + }); + + it('allows non-leader member to create a challenge', async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 1, + }); + + group = populatedGroup.group; + groupMember = populatedGroup.members[0]; + + let chal = await groupMember.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + }); + + expect(chal.leader).to.eql({ + _id: groupMember._id, + profile: {name: groupMember.profile.name}, + }); + }); + + it('doesn\'t take gems from user or group when challenge has no prize', async () => { + let oldUserBalance = groupLeader.balance; + let oldGroupBalance = group.balance; + + await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + prize: 0, + }); + + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance); + await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance); + }); + + it('returns error when user and group can\'t pay prize', async () => { + await expect(groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + prize: 20, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cantAfford'), + }); + }); + + it('takes prize out of group if it has sufficient funds', async () => { + let oldUserBalance = groupLeader.balance; + let oldGroupBalance = group.balance; + let prize = 4; + + await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + prize, + }); + + await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance - prize / 4); + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance); + }); + + it('takes prize out of both group and user if group doesn\'t have enough', async () => { + let oldUserBalance = groupLeader.balance; + let prize = 8; + + await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + prize, + }); + + await expect(group.sync()).to.eventually.have.property('balance', 0); + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - (prize / 4 - 1)); + }); + + it('takes prize out of user if group has no balance', async () => { + let oldUserBalance = groupLeader.balance; + let prize = 8; + + await group.update({ balance: 0}); + await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + prize, + }); + + await expect(group.sync()).to.eventually.have.property('balance', 0); + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - prize / 4); + }); + + it('increases challenge count of group', async () => { + let oldChallengeCount = group.challengeCount; + + await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + }); + + await expect(group.sync()).to.eventually.have.property('challengeCount', oldChallengeCount + 1); + }); + + it('sets challenge as official if created by admin and official flag is set', async () => { + await groupLeader.update({ + contributor: { + admin: true, + }, + }); + + let challenge = await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + official: true, + }); + + expect(challenge.official).to.eql(true); + }); + + it('doesn\'t set challenge as official if official flag is set by non-admin', async () => { + let challenge = await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + official: true, + }); + + expect(challenge.official).to.eql(false); + }); + + it('returns an error when challenge validation fails; doesn\'s save user or group', async () => { + let oldChallengeCount = group.challengeCount; + let oldUserBalance = groupLeader.balance; + let oldUserChallenges = groupLeader.challenges; + let oldGroupBalance = group.balance; + + await expect(groupLeader.post('/challenges', { + group: group._id, + prize: 8, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Challenge validation failed', + }); + + group = await group.sync(); + groupLeader = await groupLeader.sync(); + + expect(group.challengeCount).to.eql(oldChallengeCount); + expect(group.balance).to.eql(oldGroupBalance); + expect(groupLeader.balance).to.eql(oldUserBalance); + expect(groupLeader.challenges).to.eql(oldUserChallenges); + }); + + it('sets all properites of the challenge as passed', async () => { + let name = 'Test Challenge'; + let shortName = 'TC Label'; + let description = 'Test Description'; + let prize = 4; + + let challenge = await groupLeader.post('/challenges', { + group: group._id, + name, + shortName, + description, + prize, + }); + + expect(challenge.leader).to.eql({ + _id: groupLeader._id, + profile: {name: groupLeader.profile.name}, + }); + expect(challenge.name).to.eql(name); + expect(challenge.shortName).to.eql(shortName); + expect(challenge.description).to.eql(description); + expect(challenge.official).to.eql(false); + expect(challenge.group).to.eql({ + _id: group._id, + privacy: group.privacy, + name: group.name, + type: group.type, + }); + expect(challenge.memberCount).to.eql(1); + expect(challenge.prize).to.eql(prize); + }); + + it('adds challenge to creator\'s challenges', async () => { + let challenge = await groupLeader.post('/challenges', { + group: group._id, + name: 'Test Challenge', + shortName: 'TC Label', + }); + + await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id); + }); + }); +}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js new file mode 100644 index 0000000000..c55c0e0b49 --- /dev/null +++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js @@ -0,0 +1,127 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /challenges/:challengeId/join', () => { + it('returns error when challengeId is not a valid UUID', async () => { + let user = await generateUser({ balance: 1}); + + await expect(user.post('/challenges/test/join')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when challengeId is not for a valid challenge', async () => { + let user = await generateUser({ balance: 1}); + + await expect(user.post(`/challenges/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + context('Joining a valid challenge', () => { + let groupLeader; + let group; + let challenge; + let authorizedUser; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 1, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + authorizedUser = populatedGroup.members[0]; + + challenge = await generateChallenge(groupLeader, group); + }); + + it('returns an error when user doesn\'t have permissions to access the challenge', async () => { + let unauthorizedUser = await generateUser(); + + await expect(unauthorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('returns challenge data', async () => { + let res = await authorizedUser.post(`/challenges/${challenge._id}/join`); + + expect(res.group).to.eql({ + _id: group._id, + privacy: group.privacy, + name: group.name, + type: group.type, + }); + expect(res.leader).to.eql({ + _id: groupLeader._id, + id: groupLeader._id, + profile: {name: groupLeader.profile.name}, + }); + expect(res.name).to.equal(challenge.name); + }); + + it('adds challenge to user challenges', async () => { + await authorizedUser.post(`/challenges/${challenge._id}/join`); + + await authorizedUser.sync(); + + expect(authorizedUser).to.have.property('challenges').to.include(challenge._id); + }); + + it('returns error when user has already joined the challenge', async () => { + await authorizedUser.post(`/challenges/${challenge._id}/join`); + + await expect(authorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyInChallenge'), + }); + }); + + it('increases memberCount of challenge', async () => { + let oldMemberCount = challenge.memberCount; + + await authorizedUser.post(`/challenges/${challenge._id}/join`); + + await challenge.sync(); + + expect(challenge).to.have.property('memberCount', oldMemberCount + 1); + }); + + it('syncs challenge tasks to joining user', async () => { + let taskText = 'A challenge task text'; + + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: taskText}, + ]); + + await authorizedUser.post(`/challenges/${challenge._id}/join`); + let tasks = await authorizedUser.get('/tasks/user'); + let tasksTexts = tasks.map((task) => { + return task.text; + }); + + expect(tasksTexts).to.include(taskText); + }); + + it('adds challenge tag to user tags', async () => { + let userTagsLength = (await authorizedUser.get('/tags')).length; + + await authorizedUser.post(`/challenges/${challenge._id}/join`); + + await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1); + }); + }); +}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js new file mode 100644 index 0000000000..9694b263a9 --- /dev/null +++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js @@ -0,0 +1,123 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /challenges/:challengeId/leave', () => { + it('returns error when challengeId is not a valid UUID', async () => { + let user = await generateUser(); + + await expect(user.post('/challenges/test/leave')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when challengeId is not for a valid challenge', async () => { + let user = await generateUser(); + + await expect(user.post(`/challenges/${generateUUID()}/leave`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + context('Leaving a valid challenge', () => { + let groupLeader; + let group; + let challenge; + let notInChallengeUser; + let leavingUser; + let taskText; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 2, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + leavingUser = populatedGroup.members[0]; + notInChallengeUser = populatedGroup.members[1]; + + challenge = await generateChallenge(groupLeader, group); + + taskText = 'A challenge task text'; + + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: taskText}, + ]); + + await leavingUser.post(`/challenges/${challenge._id}/join`); + + await challenge.sync(); + }); + + it('returns an error when user doesn\'t have permissions to view the challenge', async () => { + let unauthorizedUser = await generateUser(); + + await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('returns an error when user isn\'t a member of the challenge', async () => { + await expect(notInChallengeUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('challengeMemberNotFound'), + }); + }); + + it('removes challenge from user challenges', async () => { + await leavingUser.post(`/challenges/${challenge._id}/leave`); + + await leavingUser.sync(); + + expect(leavingUser).to.have.property('challenges').to.not.include(challenge._id); + }); + + it('decreases memberCount of challenge', async () => { + let oldMemberCount = challenge.memberCount; + + await leavingUser.post(`/challenges/${challenge._id}/leave`); + + await challenge.sync(); + + expect(challenge).to.have.property('memberCount', oldMemberCount - 1); + }); + + it('unlinks challenge tasks from leaving user when remove-all is passed', async () => { + await leavingUser.post(`/challenges/${challenge._id}/leave`, { + keep: 'remove-all', + }); + let tasks = await leavingUser.get('/tasks/user'); + let tasksTexts = tasks.map((task) => { + return task.text; + }); + + expect(tasksTexts).to.not.include(taskText); + }); + + it('doesn\'t unlink challenge tasks from leaving user when remove-all isn\'t passed', async () => { + await leavingUser.post(`/challenges/${challenge._id}/leave`, { + keep: 'test', + }); + + let tasks = await leavingUser.get('/tasks/user'); + let testTask = _.find(tasks, (task) => { + return task.text === taskText; + }); + + expect(testTask).to.not.be.undefined; + expect(testTask.challenge).to.be.undefined; + }); + }); +}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js new file mode 100644 index 0000000000..98dfc20926 --- /dev/null +++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js @@ -0,0 +1,139 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + sleep, + checkExistence, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /challenges/:challengeId/winner/:winnerId', () => { + it('returns error when challengeId is not a valid UUID', async () => { + let user = await generateUser(); + + await expect(user.post(`/challenges/test/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when winnerId is not a valid UUID', async () => { + let user = await generateUser(); + + await expect(user.post(`/challenges/${generateUUID()}/selectWinner/test`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns error when challengeId is not for a valid challenge', async () => { + let user = await generateUser(); + + await expect(user.post(`/challenges/${generateUUID()}/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + context('Selecting winner for a valid challenge', () => { + let groupLeader; + let group; + let challenge; + let winningUser; + let taskText = 'A challenge task text'; + + beforeEach(async () => { + let populatedGroup = await createAndPopulateGroup({ + members: 1, + }); + + groupLeader = populatedGroup.groupLeader; + group = populatedGroup.group; + winningUser = populatedGroup.members[0]; + + challenge = await generateChallenge(groupLeader, group, { + prize: 1, + }); + + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: taskText}, + ]); + + await winningUser.post(`/challenges/${challenge._id}/join`); + + await challenge.sync(); + }); + + it('returns an error when user doesn\'t have permissions to select winner', async () => { + await expect(winningUser.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyLeaderDeleteChal'), + }); + }); + + it('returns an error when winning user isn\'t part of the challenge', async () => { + let notInChallengeUser = await generateUser(); + + await expect(groupLeader.post(`/challenges/${challenge._id}/selectWinner/${notInChallengeUser._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('winnerNotFound', {userId: notInChallengeUser._id}), + }); + }); + + it('deletes challenge after winner is selected', async () => { + await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); + + await sleep(0.5); + + await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false); + }); + + it('adds challenge to winner\'s achievements', async () => { + await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); + + await sleep(0.5); + + await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name); + }); + + it('gives winner gems as reward', async () => { + let oldBalance = winningUser.balance; + + await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); + + await sleep(0.5); + + await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4); + }); + + it('doesn\'t refund gems to group leader', async () => { + let oldBalance = (await groupLeader.sync()).balance; + + await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); + + await sleep(0.5); + + await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance); + }); + + it('sets broken and winner flags for user\'s challenge tasks', async () => { + await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); + + await sleep(0.5); + + let tasks = await winningUser.get('/tasks/user'); + let testTask = _.find(tasks, (task) => { + return task.text === taskText; + }); + + expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED'); + expect(testTask.challenge.winner).to.eql(winningUser.profile.name); + }); + }); +}); diff --git a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js new file mode 100644 index 0000000000..1fdcc7a5a2 --- /dev/null +++ b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js @@ -0,0 +1,85 @@ +import { + generateUser, + generateChallenge, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('PUT /challenges/:challengeId', () => { + let privateGuild, user, nonMember, challenge, member; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestPrivateGuild', + type: 'guild', + privacy: 'private', + }, + members: 1, + }); + + privateGuild = group; + user = groupLeader; + + nonMember = await generateUser(); + member = members[0]; + + challenge = await generateChallenge(user, group); + await member.post(`/challenges/${challenge._id}/join`); + }); + + it('fails if the user can\'t view the challenge', async () => { + await expect(nonMember.put(`/challenges/${challenge._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('should only allow the leader or an admin to update the challenge', async () => { + await expect(member.put(`/challenges/${challenge._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyLeaderUpdateChal'), + }); + }); + + it('only updates allowed fields', async () => { + let res = await user.put(`/challenges/${challenge._id}`, { + // ignored + prize: 33, + group: 'blabla', + memberCount: 33, + tasksOrder: 'new order', + official: true, + shortName: 'new short name', + + // applied + name: 'New Challenge Name', + description: 'New challenge description.', + leader: member._id, + }); + + expect(res.prize).to.equal(0); + expect(res.group).to.eql({ + _id: privateGuild._id, + privacy: privateGuild.privacy, + name: privateGuild.name, + type: privateGuild.type, + }); + expect(res.memberCount).to.equal(2); + expect(res.tasksOrder).not.to.equal('new order'); + expect(res.official).to.equal(false); + expect(res.shortName).not.to.equal('new short name'); + + expect(res.leader).to.eql({ + _id: member._id, + id: member._id, + profile: {name: member.profile.name}, + }); + expect(res.name).to.equal('New Challenge Name'); + expect(res.description).to.equal('New challenge description.'); + }); +}); diff --git a/test/api/v3/integration/chat/DELETE-chat_id.test.js b/test/api/v3/integration/chat/DELETE-chat_id.test.js new file mode 100644 index 0000000000..5d1f73f867 --- /dev/null +++ b/test/api/v3/integration/chat/DELETE-chat_id.test.js @@ -0,0 +1,81 @@ +import { + createAndPopulateGroup, + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /groups/:groupId/chat/:chatId', () => { + let groupWithChat, message, user, userThatDidNotCreateChat, admin; + + before(async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + }); + + groupWithChat = group; + user = groupLeader; + message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); + message = message.message; + userThatDidNotCreateChat = await generateUser(); + admin = await generateUser({'contributor.admin': true}); + }); + + context('Chat errors', () => { + it('returns an error is message does not exist', async () => { + let fakeChatId = generateUUID(); + await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatNotFound'), + }); + }); + + it('returns an error when user does not have permission to delete', async () => { + await expect(userThatDidNotCreateChat.del(`/groups/${groupWithChat._id}/chat/${message.id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyCreatorOrAdminCanDeleteChat'), + }); + }); + }); + + context('Chat success', () => { + let nextMessage; + + beforeEach(async () => { + nextMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some new message' }); + nextMessage = nextMessage.message; + }); + + it('allows creator to delete a their message', async () => { + await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); + let messages = await user.get(`/groups/${groupWithChat._id}/chat/`); + expect(messages).is.an('array'); + expect(messages).to.not.include(nextMessage); + }); + + it('allows admin to delete another user\'s message', async () => { + await admin.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); + let messages = await user.get(`/groups/${groupWithChat._id}/chat/`); + expect(messages).is.an('array'); + expect(messages).to.not.include(nextMessage); + }); + + it('returns empty when previous message parameter is passed and the last message was deleted', async () => { + await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${nextMessage.id}`)) + .to.eventually.be.empty; + }); + + it('returns the update chat when previous message parameter is passed and the chat is updated', async () => { + await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`)) + .eventually + .is.an('array') + .to.include(message) + .to.be.lengthOf(1); + }); + }); +}); diff --git a/test/api/v3/integration/chat/GET-chat.test.js b/test/api/v3/integration/chat/GET-chat.test.js new file mode 100644 index 0000000000..91424d655e --- /dev/null +++ b/test/api/v3/integration/chat/GET-chat.test.js @@ -0,0 +1,65 @@ +import { + generateUser, + generateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /groups/:groupId/chat', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + context('public Guild', () => { + let group; + + before(async () => { + let leader = await generateUser({balance: 2}); + + group = await generateGroup(leader, { + name: 'test group', + type: 'guild', + privacy: 'public', + }, { + chat: [ + {text: 'Hello', flags: {}}, + {text: 'Welcome to the Guild', flags: {}}, + ], + }); + }); + + it('returns Guild chat', async () => { + let chat = await user.get(`/groups/${group._id}/chat`); + + expect(chat).to.eql(group.chat); + }); + }); + + context('private Guild', () => { + let group; + + before(async () => { + let leader = await generateUser({balance: 2}); + + group = await generateGroup(leader, { + name: 'test group', + type: 'guild', + privacy: 'private', + }, { + chat: [ + 'Hello', + 'Welcome to the Guild', + ], + }); + }); + + it('returns error if user is not member of requested private group', async () => { + await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js new file mode 100644 index 0000000000..51a3abb164 --- /dev/null +++ b/test/api/v3/integration/chat/POST-chat.flag.test.js @@ -0,0 +1,84 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { find } from 'lodash'; + +describe('POST /chat/:chatId/flag', () => { + let user, admin, anotherUser, group; + const TEST_MESSAGE = 'Test Message'; + + before(async () => { + user = await generateUser({balance: 1}); + admin = await generateUser({balance: 1, 'contributor.admin': true}); + anotherUser = await generateUser(); + + group = await user.post('/groups', { + name: 'Test Guild', + type: 'guild', + privacy: 'public', + }); + }); + + it('Returns an error when chat message is not found', async () => { + await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatNotFound'), + }); + }); + + it('Returns an error when user tries to flag their own message', async () => { + let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE }); + await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatFlagOwnMessage'), + }); + }); + + it('Flags a chat', async () => { + let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); + message = message.message; + + let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`); + expect(flagResult.flags[user._id]).to.equal(true); + expect(flagResult.flagCount).to.equal(1); + + let groupWithFlags = await admin.get(`/groups/${group._id}`); + + let messageToCheck = find(groupWithFlags.chat, {id: message.id}); + expect(messageToCheck.flags[user._id]).to.equal(true); + }); + + it('Flags a chat with a higher flag acount when an admin flags the message', async () => { + let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); + message = message.message; + + let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`); + expect(flagResult.flags[admin._id]).to.equal(true); + expect(flagResult.flagCount).to.equal(5); + + let groupWithFlags = await admin.get(`/groups/${group._id}`); + + let messageToCheck = find(groupWithFlags.chat, {id: message.id}); + expect(messageToCheck.flags[admin._id]).to.equal(true); + expect(messageToCheck.flagCount).to.equal(5); + }); + + it('Returns an error when user tries to flag a message that is already flagged', async () => { + let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); + message = message.message; + + await user.post(`/groups/${group._id}/chat/${message.id}/flag`); + + await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatFlagAlreadyReported'), + }); + }); +}); diff --git a/test/api/v3/integration/chat/POST-chat.like.test.js b/test/api/v3/integration/chat/POST-chat.like.test.js new file mode 100644 index 0000000000..d7ae6047df --- /dev/null +++ b/test/api/v3/integration/chat/POST-chat.like.test.js @@ -0,0 +1,75 @@ +import { + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { find } from 'lodash'; + +describe('POST /chat/:chatId/like', () => { + let user; + let groupWithChat; + let testMessage = 'Test Message'; + let anotherUser; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + user = groupLeader; + groupWithChat = group; + anotherUser = members[0]; + }); + + it('Returns an error when chat message is not found', async () => { + await expect(user.post(`/groups/${groupWithChat._id}/chat/incorrectMessage/like`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatNotFound'), + }); + }); + + it('Returns an error when user tries to like their own message', async () => { + let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); + + await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatLikeOwnMessage'), + }); + }); + + it('Likes a chat', async () => { + let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); + + let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); + + expect(likeResult.likes[user._id]).to.equal(true); + + let groupWithChatLikes = await user.get(`/groups/${groupWithChat._id}`); + + let messageToCheck = find(groupWithChatLikes.chat, {id: message.message.id}); + expect(messageToCheck.likes[user._id]).to.equal(true); + }); + + it('Unlikes a chat', async () => { + let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); + + let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); + expect(likeResult.likes[user._id]).to.equal(true); + + let unlikeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); + expect(unlikeResult.likes[user._id]).to.equal(false); + + let groupWithoutChatLikes = await user.get(`/groups/${groupWithChat._id}`); + + let messageToCheck = find(groupWithoutChatLikes.chat, {id: message.message.id}); + expect(messageToCheck.likes[user._id]).to.equal(false); + }); +}); diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js new file mode 100644 index 0000000000..99d1469af8 --- /dev/null +++ b/test/api/v3/integration/chat/POST-chat.test.js @@ -0,0 +1,81 @@ +import { + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /chat', () => { + let user, groupWithChat, userWithChatRevoked, member; + let testMessage = 'Test Message'; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'public', + }, + members: 2, + }); + + user = groupLeader; + groupWithChat = group; + userWithChatRevoked = await members[0].update({'flags.chatRevoked': true}); + member = members[0]; + }); + + it('Returns an error when no message is provided', async () => { + await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ''})) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('Returns an error when group is not found', async () => { + await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('Returns an error when chat privileges are revoked', async () => { + await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: 'Your chat privileges have been revoked.', + }); + }); + + it('creates a chat', async () => { + let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); + + expect(message.message.id).to.exist; + }); + + it('notifies other users of new messages for a guild', async () => { + let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); + let memberWithNotification = await member.get('/user'); + + expect(message.message.id).to.exist; + expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist; + }); + + it('notifies other users of new messages for a party', async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + privacy: 'private', + }, + members: 1, + }); + + let message = await groupLeader.post(`/groups/${group._id}/chat`, { message: testMessage}); + let memberWithNotification = await members[0].get('/user'); + + expect(message.message.id).to.exist; + expect(memberWithNotification.newMessages[`${group._id}`]).to.exist; + }); +}); diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js new file mode 100644 index 0000000000..8b22461a04 --- /dev/null +++ b/test/api/v3/integration/chat/POST-chat_seen.test.js @@ -0,0 +1,63 @@ +import { + createAndPopulateGroup, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /groups/:id/chat/seen', () => { + context('Guild', () => { + let guild, guildLeader, guildMember, guildMessage; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + guild = group; + guildLeader = groupLeader; + guildMember = members[0]; + + guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' }); + guildMessage = guildMessage.message; + }); + + it('clears new messages for a guild', async () => { + await guildMember.post(`/groups/${guild._id}/chat/seen`); + + let guildThatHasSeenChat = await guildMember.get('/user'); + + expect(guildThatHasSeenChat.newMessages).to.be.empty; + }); + }); + + context('Party', () => { + let party, partyLeader, partyMember, partyMessage; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + type: 'party', + privacy: 'private', + }, + members: 1, + }); + + party = group; + partyLeader = groupLeader; + partyMember = members[0]; + + partyMessage = await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some party message' }); + partyMessage = partyMessage.message; + }); + + it('clears new messages for a party', async () => { + await partyMember.post(`/groups/${party._id}/chat/seen`); + + let partyMemberThatHasSeenChat = await partyMember.get('/user'); + + expect(partyMemberThatHasSeenChat.newMessages).to.be.empty; + }); + }); +}); diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js new file mode 100644 index 0000000000..87cad5d6f0 --- /dev/null +++ b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js @@ -0,0 +1,101 @@ +import { + createAndPopulateGroup, + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /groups/:id/chat/:id/clearflags', () => { + let groupWithChat, message, author, nonAdmin, admin; + + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + groupWithChat = group; + author = groupLeader; + nonAdmin = members[0]; + admin = await generateUser({'contributor.admin': true}); + + message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); + message = message.message; + admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/flag`); + }); + + context('Single Message', () => { + it('returns error when non-admin attempts to clear flags', async () => { + return expect(nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupChatAdminClearFlagCount'), + }); + }); + + it('returns error if message does not exist', async () => { + let fakeMessageID = generateUUID(); + + await expect(admin.post(`/groups/${groupWithChat._id}/chat/${fakeMessageID}/clearflags`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatNotFound'), + }); + }); + + it('clears flags and leaves old flags on the flag object', async () => { + await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`); + let messages = await admin.get(`/groups/${groupWithChat._id}/chat`); + expect(messages[0].flagCount).to.eql(0); + expect(messages[0].flags).to.have.property(admin._id, true); + }); + }); + + context('admin user, group with multiple messages', () => { + let message2, message3, message4; + + before(async () => { + message2 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 2' }); + message2 = message2.message; + await admin.post(`/groups/${groupWithChat._id}/chat/${message2.id}/flag`); + + message3 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 3' }); + message3 = message3.message; + await admin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`); + await nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`); + + message4 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 4' }); + message4 = message4.message; + }); + + it('changes only the message that is flagged', async () => { + await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`); + let messages = await admin.get(`/groups/${groupWithChat._id}/chat`); + + expect(messages).to.have.lengthOf(4); + + let messageThatWasUnflagged = messages[3]; + let messageWith1Flag = messages[2]; + let messageWith2Flag = messages[1]; + let messageWithoutFlags = messages[0]; + + expect(messageThatWasUnflagged.flagCount).to.eql(0); + expect(messageThatWasUnflagged.flags).to.have.property(admin._id, true); + + expect(messageWith1Flag.flagCount).to.eql(5); + expect(messageWith1Flag.flags).to.have.property(admin._id, true); + + expect(messageWith2Flag.flagCount).to.eql(6); + expect(messageWith2Flag.flags).to.have.property(admin._id, true); + expect(messageWith2Flag.flags).to.have.property(nonAdmin._id, true); + + expect(messageWithoutFlags.flagCount).to.eql(0); + expect(messageWithoutFlags.flags).to.eql({}); + }); + }); +}); diff --git a/test/api/v3/integration/content/GET-content.test.js b/test/api/v3/integration/content/GET-content.test.js new file mode 100644 index 0000000000..9324fce398 --- /dev/null +++ b/test/api/v3/integration/content/GET-content.test.js @@ -0,0 +1,25 @@ +import { + requester, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import i18n from '../../../../../common/script/i18n'; + +describe('GET /content', () => { + it('returns content (and does not require authentication)', async () => { + let res = await requester().get('/content'); + expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); + expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText')); + }); + + it('returns content not in English', async () => { + let res = await requester().get('/content?language=de'); + expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); + expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'de')); + }); + + it('falls back to English if the desired language is not found', async () => { + let res = await requester().get('/content?language=wrong'); + expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); + expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText')); + }); +}); diff --git a/test/api/v3/integration/coupons/GET-coupons.test.js b/test/api/v3/integration/coupons/GET-coupons.test.js new file mode 100644 index 0000000000..6008d2b1df --- /dev/null +++ b/test/api/v3/integration/coupons/GET-coupons.test.js @@ -0,0 +1,39 @@ +import { + generateUser, + translate as t, + resetHabiticaDB, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /coupons/', () => { + let user; + before(async () => { + await resetHabiticaDB(); + }); + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error if user has no sudo permission', async () => { + await user.get('/user'); // needed so the request after this will authenticate with the correct cookie session + await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('noSudoAccess'), + }); + }); + + it('should return the coupons in CSV format ordered by creation date', async () => { + await user.update({ + 'contributor.sudo': true, + }); + + let coupons = await user.post('/coupons/generate/wondercon?count=11'); + let res = await user.get('/coupons'); + let splitRes = res.split('\n'); + + expect(splitRes.length).to.equal(13); + expect(splitRes[0]).to.equal('code,event,date,user'); + expect(splitRes[6].split(',')[1]).to.equal(coupons[5].event); + }); +}); diff --git a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js new file mode 100644 index 0000000000..e1f9db3583 --- /dev/null +++ b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js @@ -0,0 +1,62 @@ +import { + generateUser, + translate as t, + resetHabiticaDB, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /coupons/enter/:code', () => { + let user; + let sudoUser; + + before(async () => { + await resetHabiticaDB(); + }); + + beforeEach(async () => { + user = await generateUser(); + sudoUser = await generateUser({ + 'contributor.sudo': true, + }); + }); + + it('returns an error if code is missing', async () => { + await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); + + it('returns an error if code is invalid', async () => { + await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidCoupon'), + }); + }); + + it('returns an error if coupon has been used', async () => { + let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); + await user.post(`/coupons/enter/${coupon._id}`); // use coupon + + await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('couponUsed'), + }); + }); + + it('should apply the coupon to the user', async () => { + let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); + let userRes = await user.post(`/coupons/enter/${coupon._id}`); + expect(userRes._id).to.equal(user._id); + expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true; + expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true; + expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true; + expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true; + expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true; + expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true; + expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true; + expect(userRes.extra).to.eql({signupEvent: 'wondercon'}); + }); +}); diff --git a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js new file mode 100644 index 0000000000..27bbc5c4f7 --- /dev/null +++ b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js @@ -0,0 +1,66 @@ +import { + generateUser, + translate as t, + resetHabiticaDB, +} from '../../../../helpers/api-v3-integration.helper'; +import couponCode from 'coupon-code'; + +describe('POST /coupons/generate/:event', () => { + let user; + before(async () => { + await resetHabiticaDB(); + }); + + beforeEach(async () => { + user = await generateUser({ + 'contributor.sudo': true, + }); + }); + + it('returns an error if user has no sudo permission', async () => { + await user.update({ + 'contributor.sudo': false, + }); + + await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('noSudoAccess'), + }); + }); + + it('returns an error if event is missing', async () => { + await expect(user.post('/coupons/generate')).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); + + it('returns an error if event is invalid', async () => { + await expect(user.post('/coupons/generate/notValid?count=1')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Coupon validation failed', + }); + }); + + it('returns an error if count is missing', async () => { + await expect(user.post('/coupons/generate/notValid')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('should generate coupons', async () => { + await user.update({ + 'contributor.sudo': true, + }); + + let coupons = await user.post('/coupons/generate/wondercon?count=2'); + expect(coupons.length).to.equal(2); + expect(coupons[0].event).to.equal('wondercon'); + expect(couponCode.validate(coupons[1]._id)).to.not.equal(''); // '' means invalid + }); +}); diff --git a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js new file mode 100644 index 0000000000..9d433813ea --- /dev/null +++ b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js @@ -0,0 +1,36 @@ +import { + generateUser, + requester, + resetHabiticaDB, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /coupons/validate/:code', () => { + let api = requester(); + + before(async () => { + await resetHabiticaDB(); + }); + + it('returns an error if code is missing', async () => { + await expect(api.post('/coupons/validate')).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); + + it('returns true if coupon code is valid', async () => { + let sudoUser = await generateUser({ + 'contributor.sudo': true, + }); + + let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); + let res = await api.post(`/coupons/validate/${coupon._id}`); + expect(res).to.eql({valid: true}); + }); + + it('returns false if coupon code is valid', async () => { + let res = await api.post('/coupons/validate/notValid'); + expect(res).to.eql({valid: false}); + }); +}); diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js new file mode 100644 index 0000000000..a7b97390b6 --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js @@ -0,0 +1,35 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /export/avatar-:memberId.html', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('validates req.params.memberId', async () => { + await expect(user.get('/export/avatar-:memberId.html')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('handles non-existing members', async () => { + let dummyId = generateUUID(); + await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: dummyId}), + }); + }); + + it('returns an html page', async () => { + let res = await user.get(`/export/avatar-${user._id}.html`); + expect(res.substring(0, 100).indexOf('')).to.equal(0); + }); +}); diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js new file mode 100644 index 0000000000..a46ed695c6 --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js @@ -0,0 +1,3 @@ +// TODO how to test this route since it points to a file on AWS s3? + +describe('GET /export/avatar-:memberId.png', () => {}); diff --git a/test/api/v3/integration/dataexport/GET-export_history.csv.test.js b/test/api/v3/integration/dataexport/GET-export_history.csv.test.js new file mode 100644 index 0000000000..2dbc80c49d --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_history.csv.test.js @@ -0,0 +1,47 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import { + updateDocument, +} from '../../../../helpers/mongo'; +import moment from 'moment'; + +describe('GET /export/history.csv', () => { + it('should return a valid CSV file with tasks history data', async () => { + let user = await generateUser(); + let tasks = await user.post('/tasks/user', [ + {type: 'habit', text: 'habit 1'}, + {type: 'daily', text: 'daily 1'}, + {type: 'habit', text: 'habit 2'}, + {type: 'todo', text: 'todo 1'}, + ]); + + // score all the tasks twice + await Promise.all(tasks.map(task => { + return user.post(`/tasks/${task._id}/score/up`); + })); + await Promise.all(tasks.map(task => { + return user.post(`/tasks/${task._id}/score/up`); + })); + + // adding an history entry to daily 1 manually because cron didn't run yet + await updateDocument('tasks', tasks[1], { + history: {value: 3.2, date: Number(new Date())}, + }); + + // get updated tasks + tasks = await Promise.all(tasks.map(task => { + return user.get(`/tasks/${task._id}`); + })); + + let res = await user.get('/export/history.csv'); + let splitRes = res.split('\n'); + expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value'); + expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`); + expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`); + expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`); + expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`); + expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`); + expect(splitRes[6]).to.equal(''); + }); +}); diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js new file mode 100644 index 0000000000..d8152f1209 --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js @@ -0,0 +1,29 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /export/userdata.json', () => { + it('should return a valid JSON file with user data', async () => { + let user = await generateUser(); + let tasks = await user.post('/tasks/user', [ + {type: 'habit', text: 'habit 1'}, + {type: 'daily', text: 'daily 1'}, + {type: 'reward', text: 'reward 1'}, + {type: 'todo', text: 'todo 1'}, + ]); + + let res = await user.get('/export/userdata.json'); + expect(res._id).to.equal(user._id); + expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']); + expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']); + expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']); + expect(res.tasks.habits.length).to.equal(1); + expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id); + expect(res.tasks.dailys.length).to.equal(1); + expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id); + expect(res.tasks.rewards.length).to.equal(1); + expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id); + expect(res.tasks.todos.length).to.equal(2); + expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id); + }); +}); diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js new file mode 100644 index 0000000000..58bf4e6135 --- /dev/null +++ b/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js @@ -0,0 +1,42 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import xml2js from 'xml2js'; +import Bluebird from 'bluebird'; + +let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js}); + +describe('GET /export/userdata.xml', () => { + it('should return a valid XML file with user data', async () => { + let user = await generateUser(); + let tasks = await user.post('/tasks/user', [ + {type: 'habit', text: 'habit 1'}, + {type: 'daily', text: 'daily 1'}, + {type: 'reward', text: 'reward 1'}, + {type: 'todo', text: 'todo 1'}, + // due to how the xml parser works an array is returned only if there's more than one children + // so we create two tasks for each type + {type: 'habit', text: 'habit 2'}, + {type: 'daily', text: 'daily 2'}, + {type: 'reward', text: 'reward 2'}, + {type: 'todo', text: 'todo 2'}, + + ]); + + let response = await user.get('/export/userdata.xml'); + let {user: res} = await parseStringAsync(response, {explicitArray: false}); + + expect(res._id).to.equal(user._id); + expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']); + expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']); + expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']); + expect(res.tasks.habits.length).to.equal(2); + expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id); + expect(res.tasks.dailys.length).to.equal(2); + expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id); + expect(res.tasks.rewards.length).to.equal(2); + expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id); + expect(res.tasks.todos.length).to.equal(3); + expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js new file mode 100644 index 0000000000..767fa840f2 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js @@ -0,0 +1,35 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /debug/add-hourglass', () => { + let userToGetHourGlass; + + before(async () => { + userToGetHourGlass = await generateUser(); + }); + + after(() => { + nconf.set('IS_PROD', false); + }); + + it('adds Hourglass to the current user', async () => { + await userToGetHourGlass.post('/debug/add-hourglass'); + + let userWithHourGlass = await userToGetHourGlass.get('/user'); + + expect(userWithHourGlass.purchased.plan.consecutive.trinkets).to.equal(1); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(userToGetHourGlass.post('/debug/add-hourglass')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js new file mode 100644 index 0000000000..fd01aea5d3 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js @@ -0,0 +1,35 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /debug/add-ten-gems', () => { + let userToGainTenGems; + + before(async () => { + userToGainTenGems = await generateUser(); + }); + + after(() => { + nconf.set('IS_PROD', false); + }); + + it('adds ten gems to the current user', async () => { + await userToGainTenGems.post('/debug/add-ten-gems'); + + let userWithTenGems = await userToGainTenGems.get('/user'); + + expect(userWithTenGems.balance).to.equal(2.5); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(userToGainTenGems.post('/debug/add-ten-gems')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js new file mode 100644 index 0000000000..69628aa8bc --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_make-admin.test.js @@ -0,0 +1,35 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +xdescribe('POST /debug/make-admin (pended for v3 prod testing)', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + afterEach(() => { + nconf.set('IS_PROD', false); + }); + + it('makes user an admine', async () => { + await user.post('/debug/make-admin'); + + await user.sync(); + + expect(user.contributor.admin).to.eql(true); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(user.post('/debug/make-admin')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js new file mode 100644 index 0000000000..93f9081492 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js @@ -0,0 +1,160 @@ +/* eslint-disable camelcase */ + +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /debug/modify-inventory', () => { + let user, originalItems; + + before(async () => { + originalItems = { + gear: { owned: { armor_base_0: true } }, + special: { + snowball: 1, + }, + pets: { + 'Wolf-Desert': 5, + }, + mounts: { + 'Wolf-Desert': true, + }, + eggs: { + Wolf: 5, + }, + hatchingPotions: { + Desert: 5, + }, + food: { + Watermelon: 5, + }, + quests: { + gryphon: 5, + }, + }; + user = await generateUser({ + items: originalItems, + }); + }); + + afterEach(() => { + nconf.set('IS_PROD', false); + }); + + it('sets equipment', async () => { + let gear = { + weapon_healer_2: true, + weapon_wizard_1: true, + weapon_special_critical: true, + }; + + await user.post('/debug/modify-inventory', { + gear, + }); + + await user.sync(); + + expect(user.items.gear.owned).to.eql(gear); + }); + + it('sets special spells', async () => { + let special = { + shinySeed: 3, + }; + + await user.post('/debug/modify-inventory', { + special, + }); + + await user.sync(); + + expect(user.items.special).to.eql(special); + }); + + it('sets mounts', async () => { + let mounts = { + 'Orca-Base': true, + 'Mammoth-Base': true, + }; + + await user.post('/debug/modify-inventory', { + mounts, + }); + + await user.sync(); + + expect(user.items.mounts).to.eql(mounts); + }); + + it('sets eggs', async () => { + let eggs = { + Gryphon: 3, + Hedgehog: 7, + }; + + await user.post('/debug/modify-inventory', { + eggs, + }); + + await user.sync(); + + expect(user.items.eggs).to.eql(eggs); + }); + + it('sets hatching potions', async () => { + let hatchingPotions = { + White: 7, + Spooky: 2, + }; + + await user.post('/debug/modify-inventory', { + hatchingPotions, + }); + + await user.sync(); + + expect(user.items.hatchingPotions).to.eql(hatchingPotions); + }); + + it('sets food', async () => { + let food = { + Meat: 5, + Candy_Red: 7, + }; + + await user.post('/debug/modify-inventory', { + food, + }); + + await user.sync(); + + expect(user.items.food).to.eql(food); + }); + + it('sets quests', async () => { + let quests = { + whale: 5, + cheetah: 10, + }; + + await user.post('/debug/modify-inventory', { + quests, + }); + + await user.sync(); + + expect(user.items.quests).to.eql(quests); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(user.post('/debug/modify-inventory')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js new file mode 100644 index 0000000000..3ae3d48882 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js @@ -0,0 +1,63 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /debug/quest-progress', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + afterEach(() => { + nconf.set('IS_PROD', false); + }); + + it('errors if user is not on a quest', async () => { + await expect(user.post('/debug/quest-progress')) + .to.eventually.be.rejected.and.to.deep.equal({ + code: 400, + error: 'BadRequest', + message: 'User is not on a valid quest.', + }); + }); + + it('increases boss quest progress by 1000', async () => { + await user.update({ + 'party.quest.key': 'whale', + }); + + await user.post('/debug/quest-progress'); + + await user.sync(); + + expect(user.party.quest.progress.up).to.eql(1000); + }); + + it('increases collection quest progress by 300 items', async () => { + await user.update({ + 'party.quest.key': 'evilsanta2', + }); + + await user.post('/debug/quest-progress'); + + await user.sync(); + + expect(user.party.quest.progress.collect).to.eql({ + tracks: 300, + branches: 300, + }); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(user.post('/debug/quest-progress')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_set-cron.test.js b/test/api/v3/integration/debug/POST-debug_set-cron.test.js new file mode 100644 index 0000000000..c737831d95 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_set-cron.test.js @@ -0,0 +1,39 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /debug/set-cron', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + afterEach(() => { + nconf.set('IS_PROD', false); + }); + + it('sets last cron', async () => { + let newCron = new Date(2015, 11, 20); + + await user.post('/debug/set-cron', { + lastCron: newCron, + }); + + await user.sync(); + + expect(user.lastCron).to.eql(newCron); + }); + + it('returns error when not in production mode', async () => { + nconf.set('IS_PROD', true); + + await expect(user.post('/debug/set-cron')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js new file mode 100644 index 0000000000..1bd3a532fa --- /dev/null +++ b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js @@ -0,0 +1,68 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { encrypt } from '../../../../../website/server/libs/api-v3/encryption'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /email/unsubscribe', () => { + let user; + let testEmail = 'test@habitica.com'; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('return error when code is not provided', async () => { + await expect(user.get('/email/unsubscribe')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('return error when user is not found', async () => { + let code = encrypt(JSON.stringify({ + _id: generateUUID(), + })); + + await expect(user.get(`/email/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userNotFound'), + }); + }); + + it('unsubscribes a user from email notifications', async () => { + let code = encrypt(JSON.stringify({ + _id: user._id, + email: user.email, + })); + + await user.get(`/email/unsubscribe?code=${code}`); + + let unsubscribedUser = await user.get('/user'); + + expect(unsubscribedUser.preferences.emailNotifications.unsubscribeFromAll).to.be.true; + }); + + it('unsubscribes an email from notifications', async () => { + let code = encrypt(JSON.stringify({ + email: testEmail, + })); + + let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); + + expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); + }); + + it('returns okay when email is already unsubscribed', async () => { + let code = encrypt(JSON.stringify({ + email: testEmail, + })); + + let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); + + expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); + }); +}); diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js new file mode 100644 index 0000000000..8e1e9dfbfc --- /dev/null +++ b/test/api/v3/integration/groups/GET-groups.test.js @@ -0,0 +1,115 @@ +import { + generateUser, + resetHabiticaDB, + generateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { + TAVERN_ID, +} from '../../../../../website/server/models/group'; + +describe('GET /groups', () => { + let user; + const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern + const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1; + const NUMBER_OF_USERS_PRIVATE_GUILDS = 1; + const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5; + + before(async () => { + await resetHabiticaDB(); + + let leader = await generateUser({ balance: 10 }); + user = await generateUser({balance: 4}); + + let publicGuildUserIsMemberOf = await generateGroup(leader, { + name: 'public guild - is member', + type: 'guild', + privacy: 'public', + }); + await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]}); + await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`); + + await generateGroup(leader, { + name: 'public guild - is not member', + type: 'guild', + privacy: 'public', + }); + + let privateGuildUserIsMemberOf = await generateGroup(leader, { + name: 'private guild - is member', + type: 'guild', + privacy: 'private', + }); + await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]}); + await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`); + + await generateGroup(leader, { + name: 'private guild - is not member', + type: 'guild', + privacy: 'private', + }); + + await generateGroup(leader, { + name: 'party - is not member', + type: 'party', + privacy: 'private', + }); + + await user.post('/groups', { + name: 'party - is member', + type: 'party', + privacy: 'private', + }); + }); + + it('returns error when no query passed in', async () => { + await expect(user.get('/groups')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when an invalid ?type query is passed', async () => { + await expect(user.get('/groups?type=invalid')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('groupTypesRequired'), + }); + }); + + it('returns only the tavern when tavern passed in as query', async () => { + await expect(user.get('/groups?type=tavern')) + .to.eventually.have.a.lengthOf(1) + .and.to.have.deep.property('[0]') + .and.to.have.property('_id', TAVERN_ID); + }); + + it('returns only the user\'s party when party passed in as query', async () => { + await expect(user.get('/groups?type=party')) + .to.eventually.have.a.lengthOf(1) + .and.to.have.deep.property('[0]'); + }); + + it('returns all public guilds when publicGuilds passed in as query', async () => { + await expect(user.get('/groups?type=publicGuilds')) + .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS); + }); + + it('returns all the user\'s guilds when guilds passed in as query', async () => { + await expect(user.get('/groups?type=guilds')) + .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS); + }); + + it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => { + await expect(user.get('/groups?type=privateGuilds')) + .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS); + }); + + it('returns a list of groups user has access to', async () => { + await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern')) + .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW); + }); +}); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js new file mode 100644 index 0000000000..1b7c172d94 --- /dev/null +++ b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js @@ -0,0 +1,102 @@ +import { + generateUser, + generateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /groups/:groupId/invites', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('validates optional req.query.lastId to be an UUID', async () => { + await expect(user.get('/groups/groupId/invites?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('fails if group doesn\'t exists', async () => { + await expect(user.get(`/groups/${generateUUID()}/invites`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('fails if user doesn\'t have access to the group', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let anotherUser = await generateUser(); + await expect(anotherUser.get(`/groups/${group._id}/invites`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('works when passing party as req.params.groupId', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let invited = await generateUser(); + await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]}); + let res = await user.get('/groups/party/invites'); + + expect(res).to.be.an('array'); + expect(res.length).to.equal(1); + expect(res[0]).to.eql({ + _id: invited._id, + id: invited._id, + profile: {name: invited.profile.name}, + }); + }); + + it('populates only some fields', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let invited = await generateUser(); + await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]}); + let res = await user.get('/groups/party/invites'); + expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); + expect(res[0].profile).to.have.all.keys(['name']); + }); + + it('returns only first 30 invites', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let invitesToGenerate = []; + for (let i = 0; i < 31; i++) { + invitesToGenerate.push(generateUser()); + } + let generatedInvites = await Promise.all(invitesToGenerate); + await user.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)}); + + let res = await user.get('/groups/party/invites'); + expect(res.length).to.equal(30); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + + it('supports using req.query.lastId to get more invites', async () => { + let leader = await generateUser({balance: 4}); + let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); + + let invitesToGenerate = []; + for (let i = 0; i < 32; i++) { + invitesToGenerate.push(generateUser()); + } + let generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites + let expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id); + await user.post(`/groups/${group._id}/invite`, {uuids: expectedIds}); + + let res = await user.get(`/groups/${group._id}/invites`); + expect(res.length).to.equal(30); + let res2 = await user.get(`/groups/${group._id}/invites?lastId=${res[res.length - 1]._id}`); + expect(res2.length).to.equal(2); + + let resIds = res.concat(res2).map(invite => invite._id); + expect(resIds).to.eql(expectedIds.sort()); + }); +}); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js new file mode 100644 index 0000000000..857f6fb863 --- /dev/null +++ b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js @@ -0,0 +1,96 @@ +import { + generateUser, + generateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /groups/:groupId/members', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('validates optional req.query.lastId to be an UUID', async () => { + await expect(user.get('/groups/groupId/members?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('fails if group doesn\'t exists', async () => { + await expect(user.get(`/groups/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('fails if user doesn\'t have access to the group', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let anotherUser = await generateUser(); + await expect(anotherUser.get(`/groups/${group._id}/members`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('works when passing party as req.params.groupId', async () => { + await generateGroup(user, {type: 'party', name: generateUUID()}); + let res = await user.get('/groups/party/members'); + expect(res).to.be.an('array'); + expect(res.length).to.equal(1); + expect(res[0]).to.eql({ + _id: user._id, + id: user._id, + profile: {name: user.profile.name}, + }); + }); + + it('populates only some fields', async () => { + await generateGroup(user, {type: 'party', name: generateUUID()}); + let res = await user.get('/groups/party/members'); + expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); + expect(res[0].profile).to.have.all.keys(['name']); + }); + + it('returns only first 30 members', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + + let usersToGenerate = []; + for (let i = 0; i < 31; i++) { + usersToGenerate.push(generateUser({party: {_id: group._id}})); + } + await Promise.all(usersToGenerate); + + let res = await user.get('/groups/party/members'); + expect(res.length).to.equal(30); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + + it('supports using req.query.lastId to get more members', async () => { + let leader = await generateUser({balance: 4}); + let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); + + let usersToGenerate = []; + for (let i = 0; i < 57; i++) { + usersToGenerate.push(generateUser({guilds: [group._id]})); + } + let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader) + let expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id)); + + let res = await user.get(`/groups/${group._id}/members`); + expect(res.length).to.equal(30); + let res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`); + expect(res2.length).to.equal(28); + + let resIds = res.concat(res2).map(member => member._id); + expect(resIds).to.eql(expectedIds.sort()); + }); +}); diff --git a/test/api/v3/integration/groups/GET-groups_id.test.js b/test/api/v3/integration/groups/GET-groups_id.test.js new file mode 100644 index 0000000000..2ecb53a33f --- /dev/null +++ b/test/api/v3/integration/groups/GET-groups_id.test.js @@ -0,0 +1,298 @@ +import { + generateUser, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +import { + each, +} from 'lodash'; + +describe('GET /groups/:id', () => { + let typesOfGroups = {}; + typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' }; + typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' }; + typesOfGroups.party = { type: 'party', privacy: 'private' }; + + each(typesOfGroups, (groupDetails, groupType) => { + context(`Member of a ${groupType}`, () => { + let leader, member, createdGroup; + + before(async () => { + let groupData = await createAndPopulateGroup({ + members: 30, + groupDetails, + }); + + leader = groupData.groupLeader; + member = groupData.members[0]; + createdGroup = groupData.group; + }); + + it('returns the group object', async () => { + let group = await member.get(`/groups/${createdGroup._id}`); + + expect(group._id).to.eql(createdGroup._id); + expect(group.name).to.eql(createdGroup.name); + expect(group.type).to.eql(createdGroup.type); + expect(group.privacy).to.eql(createdGroup.privacy); + }); + + it('transforms leader id to leader object', async () => { + let group = await member.get(`/groups/${createdGroup._id}`); + + expect(group.leader._id).to.eql(leader._id); + expect(group.leader.profile.name).to.eql(leader.profile.name); + }); + }); + }); + + context('Non-member of a public guild', () => { + let nonMember, createdGroup; + + before(async () => { + let groupData = await createAndPopulateGroup({ + members: 1, + groupDetails: { + name: 'test guild', + type: 'guild', + privacy: 'public', + }, + }); + + createdGroup = groupData.group; + nonMember = await generateUser(); + }); + + it('returns the group object for a non-member', async () => { + let group = await nonMember.get(`/groups/${createdGroup._id}`); + + expect(group._id).to.eql(createdGroup._id); + expect(group.name).to.eql(createdGroup.name); + expect(group.type).to.eql(createdGroup.type); + expect(group.privacy).to.eql(createdGroup.privacy); + }); + }); + + context('Non-member of a private guild', () => { + let nonMember, createdGroup; + + before(async () => { + let groupData = await createAndPopulateGroup({ + members: 1, + groupDetails: { + name: 'test guild', + type: 'guild', + privacy: 'private', + }, + }); + + createdGroup = groupData.group; + nonMember = await generateUser(); + }); + + it('does not return the group object for a non-member', async () => { + await expect(nonMember.get(`/groups/${createdGroup._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + }); + + context('Non-member of a party', () => { + let nonMember, createdGroup; + + before(async () => { + let groupData = await createAndPopulateGroup({ + members: 1, + groupDetails: { + name: 'test party', + type: 'party', + privacy: 'private', + }, + }); + + createdGroup = groupData.group; + nonMember = await generateUser(); + }); + + it('does not return the group object for a non-member', async () => { + await expect(nonMember.get(`/groups/${createdGroup._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + }); + + context('Member of a party', () => { + let member, createdGroup; + + before(async () => { + let groupData = await createAndPopulateGroup({ + members: 1, + groupDetails: { + name: 'test party', + type: 'party', + privacy: 'private', + }, + }); + + createdGroup = groupData.group; + member = groupData.members[0]; + }); + + it('returns the user\'s party if an id of "party" is passed in', async () => { + let group = await member.get('/groups/party'); + + expect(group._id).to.eql(createdGroup._id); + expect(group.name).to.eql(createdGroup.name); + expect(group.type).to.eql(createdGroup.type); + expect(group.privacy).to.eql(createdGroup.privacy); + }); + }); + + context('Non-existent group', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns error if group does not exist', async () => { + await expect(user.get('/groups/group-that-does-not-exist')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + }); + + context('Flagged messages', () => { + let group; + + let chat1 = { + id: 'chat1', + text: 'chat 1', + flags: {}, + }; + + let chat2 = { + id: 'chat2', + text: 'chat 2', + flags: {}, + flagCount: 0, + }; + + let chat3 = { + id: 'chat3', + text: 'chat 3', + flags: { + 'user-id': true, + }, + flagCount: 1, + }; + + let chat4 = { + id: 'chat4', + text: 'chat 4', + flags: { + 'user-id': true, + 'other-user-id': true, + }, + flagCount: 2, + }; + + let chat5 = { + id: 'chat5', + text: 'chat 5', + flags: { + 'user-id': true, + 'other-user-id': true, + 'yet-another-user-id': true, + }, + flagCount: 3, + }; + + beforeEach(async () => { + let groupData = await createAndPopulateGroup({ + groupDetails: { + name: 'test guild', + type: 'guild', + privacy: 'public', + chat: [ + chat1, + chat2, + chat3, + chat4, + chat5, + ], + }, + }); + + group = groupData.group; + + await group.addChat([chat1, chat2, chat3, chat4, chat5]); + }); + + context('non-admin', () => { + let nonAdmin; + + beforeEach(async () => { + nonAdmin = await generateUser(); + }); + + it('does not include messages with a flag count of 2 or greater', async () => { + let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); + + expect(fetchedGroup.chat).to.have.lengthOf(3); + expect(fetchedGroup.chat[0].id).to.eql(chat1.id); + expect(fetchedGroup.chat[1].id).to.eql(chat2.id); + expect(fetchedGroup.chat[2].id).to.eql(chat3.id); + }); + + it('does not include user ids in flags object', async () => { + let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); + let chatWithOneFlag = fetchedGroup.chat[2]; + + expect(chatWithOneFlag.id).to.eql(chat3.id); + expect(chat3.flags).to.eql({ 'user-id': true }); + expect(chatWithOneFlag.flags).to.eql({}); + }); + }); + + context('admin', () => { + let admin; + + beforeEach(async () => { + admin = await generateUser({ + 'contributor.admin': true, + }); + }); + + it('includes all messages', async () => { + let fetchedGroup = await admin.get(`/groups/${group._id}`); + + expect(fetchedGroup.chat).to.have.lengthOf(5); + expect(fetchedGroup.chat[0].id).to.eql(chat1.id); + expect(fetchedGroup.chat[1].id).to.eql(chat2.id); + expect(fetchedGroup.chat[2].id).to.eql(chat3.id); + expect(fetchedGroup.chat[3].id).to.eql(chat4.id); + expect(fetchedGroup.chat[4].id).to.eql(chat5.id); + }); + + it('includes user ids in flags object', async () => { + let fetchedGroup = await admin.get(`/groups/${group._id}`); + let chatWithOneFlag = fetchedGroup.chat[2]; + + expect(chatWithOneFlag.id).to.eql(chat3.id); + expect(chat3.flags).to.eql({ 'user-id': true }); + expect(chatWithOneFlag.flags).to.eql(chat3.flags); + }); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups.test.js b/test/api/v3/integration/groups/POST-groups.test.js new file mode 100644 index 0000000000..ad8f7a6c92 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups.test.js @@ -0,0 +1,228 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /group', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ balance: 10 }); + }); + + context('All Groups', () => { + it('it returns validation error when type is not provided', async () => { + await expect( + user.post('/groups', { name: 'Test Group Without Type' }) + ).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Group validation failed', + }); + }); + + it('it returns validation error when type is not supported', async () => { + await expect( + user.post('/groups', { name: 'Group with unsupported type', type: 'foo' }) + ).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Group validation failed', + }); + }); + + it('sets the group leader to the user who created the group', async () => { + let group = await user.post('/groups', { + name: 'Test Public Guild', + type: 'guild', + }); + + expect(group.leader).to.eql({ + _id: user._id, + profile: { + name: user.profile.name, + }, + }); + }); + }); + + context('Guilds', () => { + it('returns an error when a user with insufficient funds attempts to create a guild', async () => { + await user.update({ balance: 0 }); + + await expect( + user.post('/groups', { + name: 'Test Public Guild', + type: 'guild', + }) + ).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageInsufficientGems'), + }); + }); + + it('adds guild to user\'s list of guilds', async () => { + let guild = await user.post('/groups', { + name: 'some guild', + type: 'guild', + privacy: 'public', + }); + + let updatedUser = await user.get('/user'); + + expect(updatedUser.guilds).to.include(guild._id); + }); + + context('public guild', () => { + it('creates a group', async () => { + let groupName = 'Test Public Guild'; + let groupType = 'guild'; + let groupPrivacy = 'public'; + + let publicGuild = await user.post('/groups', { + name: groupName, + type: groupType, + privacy: groupPrivacy, + }); + + expect(publicGuild._id).to.exist; + expect(publicGuild.name).to.equal(groupName); + expect(publicGuild.type).to.equal(groupType); + expect(publicGuild.memberCount).to.equal(1); + expect(publicGuild.privacy).to.equal(groupPrivacy); + expect(publicGuild.leader).to.eql({ + _id: user._id, + profile: { + name: user.profile.name, + }, + }); + }); + }); + + context('private guild', () => { + let groupName = 'Test Private Guild'; + let groupType = 'guild'; + let groupPrivacy = 'private'; + + it('creates a group', async () => { + let privateGuild = await user.post('/groups', { + name: groupName, + type: groupType, + privacy: groupPrivacy, + }); + + expect(privateGuild._id).to.exist; + expect(privateGuild.name).to.equal(groupName); + expect(privateGuild.type).to.equal(groupType); + expect(privateGuild.memberCount).to.equal(1); + expect(privateGuild.privacy).to.equal(groupPrivacy); + expect(privateGuild.leader).to.eql({ + _id: user._id, + profile: { + name: user.profile.name, + }, + }); + }); + + it('deducts gems from user and adds them to guild bank', async () => { + let privateGuild = await user.post('/groups', { + name: groupName, + type: groupType, + privacy: groupPrivacy, + }); + + expect(privateGuild.balance).to.eql(1); + + let updatedUser = await user.get('/user'); + + expect(updatedUser.balance).to.eql(user.balance - 1); + }); + }); + }); + + context('Parties', () => { + let partyName = 'Test Party'; + let partyType = 'party'; + + it('creates a party', async () => { + let party = await user.post('/groups', { + name: partyName, + type: partyType, + }); + + expect(party._id).to.exist; + expect(party.name).to.equal(partyName); + expect(party.type).to.equal(partyType); + expect(party.memberCount).to.equal(1); + expect(party.leader).to.eql({ + _id: user._id, + profile: { + name: user.profile.name, + }, + }); + }); + + it('does not require gems to create a party', async () => { + await user.update({ balance: 0 }); + + let party = await user.post('/groups', { + name: partyName, + type: partyType, + }); + + expect(party._id).to.exist; + + let updatedUser = await user.get('/user'); + + expect(updatedUser.balance).to.eql(user.balance); + }); + + it('sets party id on user object', async () => { + let party = await user.post('/groups', { + name: partyName, + type: partyType, + }); + + let updatedUser = await user.get('/user'); + + expect(updatedUser.party._id).to.eql(party._id); + }); + + it('does not award Party Up achievement to solo partier', async () => { + await user.post('/groups', { + name: partyName, + type: partyType, + }); + + let updatedUser = await user.get('/user'); + + expect(updatedUser.achievements.partyUp).to.not.eql(true); + }); + + it('prevents user in a party from creating another party', async () => { + await user.post('/groups', { + name: partyName, + type: partyType, + }); + + await expect(user.post('/groups')).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupAlreadyInParty'), + }); + }); + + it('prevents creating a public party', async () => { + await expect(user.post('/groups', { + name: partyName, + type: partyType, + privacy: 'public', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('partyMustbePrivate'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js new file mode 100644 index 0000000000..f47a734e89 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js @@ -0,0 +1,286 @@ +import { + generateUser, + createAndPopulateGroup, + checkExistence, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /group/:groupId/join', () => { + const PET_QUEST = 'whale'; + + it('returns error when groupId is not for a valid group', async () => { + let joiningUser = await generateUser(); + + await expect(joiningUser.post(`/groups/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + context('Joining a public guild', () => { + let user, joiningUser, publicGuild; + + beforeEach(async () => { + let {group, groupLeader} = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'public', + }, + }); + + publicGuild = group; + user = groupLeader; + joiningUser = await generateUser(); + }); + + it('allows non-invited users to join public guilds', async () => { + let res = await joiningUser.post(`/groups/${publicGuild._id}/join`); + + await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id); + expect(res.leader._id).to.eql(user._id); + expect(res.leader.profile.name).to.eql(user.profile.name); + }); + + it('returns an error is user was already a member', async () => { + await joiningUser.post(`/groups/${publicGuild._id}/join`); + await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyInGroup'), + }); + }); + + it('promotes joining member in a public empty guild to leader', async () => { + await user.post(`/groups/${publicGuild._id}/leave`); + + await joiningUser.post(`/groups/${publicGuild._id}/join`); + + await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.deep.property('leader._id', joiningUser._id); + }); + + it('increments memberCount when joining guilds', async () => { + let oldMemberCount = publicGuild.memberCount; + + await joiningUser.post(`/groups/${publicGuild._id}/join`); + + await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); + }); + }); + + context('Joining a private guild', () => { + let user, invitedUser, guild; + + beforeEach(async () => { + let { group, groupLeader, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'private', + }, + invites: 1, + }); + + guild = group; + user = groupLeader; + invitedUser = invitees[0]; + }); + + it('returns error when user is not invited to private guild', async () => { + let userWithoutInvite = await generateUser(); + + await expect(userWithoutInvite.post(`/groups/${guild._id}/join`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupRequiresInvite'), + }); + }); + + context('User is invited', () => { + it('allows invited user to join private guilds', async () => { + await invitedUser.post(`/groups/${guild._id}/join`); + + await expect(invitedUser.get('/user')).to.eventually.have.property('guilds').to.include(guild._id); + }); + + it('clears invitation from user when joining guilds', async () => { + await invitedUser.post(`/groups/${guild._id}/join`); + + await expect(invitedUser.get('/user')) + .to.eventually.have.deep.property('invitations.guilds') + .to.not.include({id: guild._id}); + }); + + it('increments memberCount when joining guilds', async () => { + let oldMemberCount = guild.memberCount; + + await invitedUser.post(`/groups/${guild._id}/join`); + + await expect(invitedUser.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); + }); + + it('does not give basilist quest to inviter when joining a guild', async () => { + await invitedUser.post(`/groups/${guild._id}/join`); + + await expect(user.get('/user')).to.eventually.not.have.deep.property('items.quests.basilist'); + }); + + it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => { + await user.update({ 'items.quests.basilist': 1 }); + + await invitedUser.post(`/groups/${guild._id}/join`); + + await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1); + }); + }); + }); + + context('Joining a party', () => { + let user, invitedUser, party; + + beforeEach(async () => { + let { group, groupLeader, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + }, + members: 2, + invites: 1, + }); + + party = group; + user = groupLeader; + invitedUser = invitees[0]; + }); + + it('returns error when user is not invited to party', async () => { + let userWithoutInvite = await generateUser(); + + await expect(userWithoutInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupRequiresInvite'), + }); + }); + + context('User is invited', () => { + it('allows invited user to join party', async () => { + await invitedUser.post(`/groups/${party._id}/join`); + + await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id); + }); + + it('clears invitation from user when joining party', async () => { + await invitedUser.post(`/groups/${party._id}/join`); + + await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id'); + }); + + it('increments memberCount when joining party', async () => { + let oldMemberCount = party.memberCount; + + await invitedUser.post(`/groups/${party._id}/join`); + + await expect(invitedUser.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); + }); + + it('gives basilist quest item to the inviter when joining a party', async () => { + await invitedUser.post(`/groups/${party._id}/join`); + + await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1); + }); + + it('increments basilist quest item count to inviter when joining a party', async () => { + await user.update({'items.quests.basilist': 1 }); + + await invitedUser.post(`/groups/${party._id}/join`); + + await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 2); + }); + + it('deletes previous party where the user was the only member', async () => { + let userToInvite = await generateUser(); + let oldParty = await userToInvite.post('/groups', { // add user to a party + name: 'Another Test Party', + type: 'party', + }); + + await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); + await user.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id], + }); + await userToInvite.post(`/groups/${party._id}/join`); + + await expect(user.get('/user')).to.eventually.have.deep.property('party._id', party._id); + await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false); + }); + + it('invites joining member to active quest', async () => { + await user.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`); + + await invitedUser.post(`/groups/${party._id}/join`); + + await invitedUser.sync(); + await party.sync(); + + expect(invitedUser).to.have.deep.property('party.quest.RSVPNeeded', true); + expect(invitedUser).to.have.deep.property('party.quest.key', party.quest.key); + expect(party.quest.members[invitedUser._id]).to.be.null; + }); + }); + }); + + context('Party incentive achievements', () => { + let leader, member, party; + + beforeEach(async () => { + leader = await generateUser(); + member = await generateUser(); + party = await leader.post('/groups', { + name: 'Testing Party', + type: 'party', + }); + await leader.post(`/groups/${party._id}/invite`, { + uuids: [member._id], + }); + await member.post(`/groups/${party._id}/join`); + }); + + it('awards Party Up achievement to party of size 2', async () => { + await member.sync(); + await leader.sync(); + + expect(member).to.have.deep.property('achievements.partyUp', true); + expect(leader).to.have.deep.property('achievements.partyUp', true); + }); + + it('does not award Party On achievement to party of size 2', async () => { + await member.sync(); + await leader.sync(); + + expect(member).to.not.have.deep.property('achievements.partyOn'); + expect(leader).to.not.have.deep.property('achievements.partyOn'); + }); + + it('awards Party On achievement to party of size 4', async () => { + let addlMemberOne = await generateUser(); + let addlMemberTwo = await generateUser(); + await leader.post(`/groups/${party._id}/invite`, { + uuids: [addlMemberOne._id, addlMemberTwo._id], + }); + await addlMemberOne.post(`/groups/${party._id}/join`); + await addlMemberTwo.post(`/groups/${party._id}/join`); + + await member.sync(); + await leader.sync(); + + expect(member).to.have.deep.property('achievements.partyOn', true); + expect(leader).to.have.deep.property('achievements.partyOn', true); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js new file mode 100644 index 0000000000..3e13634bd8 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups_groupId_leave.js @@ -0,0 +1,210 @@ +import { + generateChallenge, + checkExistence, + createAndPopulateGroup, + sleep, + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { + each, +} from 'lodash'; + +describe('POST /groups/:groupId/leave', () => { + let typesOfGroups = { + 'public guild': { type: 'guild', privacy: 'public' }, + 'private guild': { type: 'guild', privacy: 'private' }, + party: { type: 'party', privacy: 'private' }, + }; + + each(typesOfGroups, (groupDetails, groupType) => { + context(`Leaving a ${groupType}`, () => { + let groupToLeave; + let leader; + let member; + let memberCount; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails, + members: 1, + }); + + groupToLeave = group; + leader = groupLeader; + member = members[0]; + memberCount = group.memberCount; + }); + + it('prevents non members from leaving', async () => { + let user = await generateUser(); + await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it(`lets user leave a ${groupType}`, async () => { + await member.post(`/groups/${groupToLeave._id}/leave`); + + let userThatLeftGroup = await member.get('/user'); + + expect(userThatLeftGroup.guilds).to.be.empty; + expect(userThatLeftGroup.party._id).to.not.exist; + await groupToLeave.sync(); + expect(groupToLeave.memberCount).to.equal(memberCount - 1); + }); + + it(`sets a new group leader when leader leaves a ${groupType}`, async () => { + await leader.post(`/groups/${groupToLeave._id}/leave`); + + await groupToLeave.sync(); + expect(groupToLeave.memberCount).to.equal(memberCount - 1); + expect(groupToLeave.leader).to.equal(member._id); + }); + + context('With challenges', () => { + let challenge; + + beforeEach(async () => { + challenge = await generateChallenge(leader, groupToLeave); + + await leader.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + + await sleep(0.5); + }); + + it('removes all challenge tasks when keep parameter is set to remove', async () => { + await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`); + + let userWithoutChallengeTasks = await leader.get('/user'); + + expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id); + expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty; + }); + + it('keeps all challenge tasks when keep parameter is not set', async () => { + await leader.post(`/groups/${groupToLeave._id}/leave`); + + let userWithChallengeTasks = await leader.get('/user'); + + expect(userWithChallengeTasks.challenges).to.not.include(challenge._id); + // @TODO find elegant way to assert against the task existing + expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty; + }); + }); + + it('prevents quest leader from leaving a groupToLeave'); + it('prevents a user from leaving during an active quest'); + }); + }); + + context('Leaving a group as the last member', () => { + context('private guild', () => { + let privateGuild; + let leader; + let invitedUser; + + beforeEach(async () => { + let { group, groupLeader, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Private Guild', + type: 'guild', + }, + invites: 1, + }); + + privateGuild = group; + leader = groupLeader; + invitedUser = invitees[0]; + }); + + it('removes a group when the last member leaves', async () => { + await leader.post(`/groups/${privateGuild._id}/leave`); + + await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false); + }); + + it('removes invitations when the last member leaves', async () => { + await leader.post(`/groups/${privateGuild._id}/leave`); + + let userWithoutInvitation = await invitedUser.get('/user'); + + expect(userWithoutInvitation.invitations.guilds).to.be.empty; + }); + }); + + context('public guild', () => { + let publicGuild; + let leader; + let invitedUser; + + beforeEach(async () => { + let { group, groupLeader, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Public Guild', + type: 'guild', + privacy: 'public', + }, + invites: 1, + }); + + publicGuild = group; + leader = groupLeader; + invitedUser = invitees[0]; + }); + + it('keeps the group when the last member leaves', async () => { + await leader.post(`/groups/${publicGuild._id}/leave`); + + await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true); + }); + + it('keeps the invitations when the last member leaves a public guild', async () => { + await leader.post(`/groups/${publicGuild._id}/leave`); + + let userWithoutInvitation = await invitedUser.get('/user'); + + expect(userWithoutInvitation.invitations.guilds).to.not.be.empty; + }); + }); + + context('party', () => { + let party; + let leader; + let invitedUser; + + beforeEach(async () => { + let { group, groupLeader, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + }, + invites: 1, + }); + + party = group; + leader = groupLeader; + invitedUser = invitees[0]; + }); + + it('removes a group when the last member leaves a party', async () => { + await leader.post(`/groups/${party._id}/leave`); + + await expect(checkExistence('party', party._id)).to.eventually.equal(false); + }); + + it('removes invitations when the last member leaves a party', async () => { + await leader.post(`/groups/${party._id}/leave`); + + let userWithoutInvitation = await invitedUser.get('/user'); + + expect(userWithoutInvitation.invitations.party).to.be.empty; + }); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js new file mode 100644 index 0000000000..18f83fe9c9 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js @@ -0,0 +1,113 @@ +import { + generateUser, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /group/:groupId/reject-invite', () => { + context('Rejecting a public guild invite', () => { + let publicGuild, invitedUser; + + beforeEach(async () => { + let {group, invitees} = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'public', + }, + invites: 1, + }); + + publicGuild = group; + invitedUser = invitees[0]; + }); + + it('returns error when user is not invited', async () => { + let userWithoutInvite = await generateUser(); + + await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupRequiresInvite'), + }); + }); + + it('clears invitation from user', async () => { + await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`); + + await expect(invitedUser.get('/user')) + .to.eventually.have.deep.property('invitations.guilds') + .to.not.include({id: publicGuild._id}); + }); + }); + + context('Rejecting a private guild invite', () => { + let invitedUser, guild; + + beforeEach(async () => { + let { group, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'private', + }, + invites: 1, + }); + + guild = group; + invitedUser = invitees[0]; + }); + + it('returns error when user is not invited', async () => { + let userWithoutInvite = await generateUser(); + + await expect(userWithoutInvite.post(`/groups/${guild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupRequiresInvite'), + }); + }); + + it('clears invitation from user', async () => { + await invitedUser.post(`/groups/${guild._id}/reject-invite`); + + await expect(invitedUser.get('/user')) + .to.eventually.have.deep.property('invitations.guilds') + .to.not.include({id: guild._id}); + }); + }); + + context('Rejecting a party invite', () => { + let invitedUser, party; + + beforeEach(async () => { + let { group, invitees } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + }, + members: 2, + invites: 1, + }); + + party = group; + invitedUser = invitees[0]; + }); + + it('returns error when user is not invited', async () => { + let userWithoutInvite = await generateUser(); + + await expect(userWithoutInvite.post(`/groups/${party._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupRequiresInvite'), + }); + }); + + it('clears invitation from user', async () => { + await invitedUser.post(`/groups/${party._id}/reject-invite`); + + await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id'); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js new file mode 100644 index 0000000000..0ebde39361 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js @@ -0,0 +1,130 @@ +import { + generateUser, + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /groups/:groupId/removeMember/:memberId', () => { + let leader; + let invitedUser; + let guild; + let member; + let member2; + + beforeEach(async () => { + let { group, groupLeader, invitees, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Guild', + type: 'guild', + privacy: 'private', + }, + invites: 1, + members: 2, + }); + + guild = group; + leader = groupLeader; + invitedUser = invitees[0]; + member = members[0]; + member2 = members[1]; + }); + + context('All Groups', () => { + it('returns an error when user is not member of the group', async () => { + let nonMember = await generateUser(); + + expect(nonMember.post(`/groups/${guild._id}/removeMember/${member._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + type: 'NotAuthorized', + message: t('onlyLeaderCanRemoveMember'), + }); + }); + + it('returns an error when user is a non-leader member of a group', async () => { + expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + type: 'NotAuthorized', + message: t('onlyLeaderCanRemoveMember'), + }); + }); + + it('does not allow leader to remove themselves', async () => { + expect(leader.post(`/groups/${guild._id}/removeMember/${leader._id}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + text: t('messageGroupCannotRemoveSelf'), + }); + }); + }); + + context('Guilds', () => { + it('can remove other members', async () => { + await leader.post(`/groups/${guild._id}/removeMember/${member._id}`); + let memberRemoved = await member.get('/user'); + + expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1); + }); + + it('updates memberCount', async () => { + let oldMemberCount = guild.memberCount; + await leader.post(`/groups/${guild._id}/removeMember/${member._id}`); + await expect(leader.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1); + }); + + it('can remove other invites', async () => { + await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`); + + let invitedUserWithoutInvite = await invitedUser.get('/user'); + + expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1); + }); + }); + + context('Party', () => { + let party; + let partyleader; + let partyInvitedUser; + let partyMember; + + beforeEach(async () => { + let { group, groupLeader, invitees, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + privacy: 'private', + }, + invites: 1, + members: 1, + }); + + party = group; + partyleader = groupLeader; + partyInvitedUser = invitees[0]; + partyMember = members[0]; + }); + + it('can remove other members', async () => { + await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`); + + let memberRemoved = await partyMember.get('/user'); + + expect(memberRemoved.party._id).eql(undefined); + }); + + it('updates memberCount', async () => { + let oldMemberCount = party.memberCount; + await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`); + await expect(partyleader.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1); + }); + + it('can remove other invites', async () => { + await partyleader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`); + + let invitedUserWithoutInvite = await partyInvitedUser.get('/user'); + + expect(_.findIndex(invitedUserWithoutInvite.invitations.party, {id: party._id})).eql(-1); + }); + }); +}); diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js new file mode 100644 index 0000000000..92328463b2 --- /dev/null +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -0,0 +1,332 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +const INVITES_LIMIT = 100; + +describe('Post /groups/:groupId/invite', () => { + let inviter; + let group; + let groupName = 'Test Public Guild'; + + beforeEach(async () => { + inviter = await generateUser({balance: 1}); + group = await inviter.post('/groups', { + name: groupName, + type: 'guild', + }); + }); + + describe('user id invites', () => { + it('returns an error when invited user is not found', async () => { + let fakeID = generateUUID(); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [fakeID], + })) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: fakeID}), + }); + }); + + it('returns an error when uuids is not an array', async () => { + let fakeID = generateUUID(); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: {fakeID}, + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('uuidsMustBeAnArray'), + }); + }); + + it('returns empty when uuids is empty', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [], + })) + .to.eventually.be.empty; + }); + + it('returns an error when there are more than INVITES_LIMIT uuids', async () => { + let uuids = []; + + for (let i = 0; i < 101; i += 1) { + uuids.push(generateUUID()); + } + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids, + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), + }); + }); + + it('invites a user to a group by uuid', async () => { + let userToInvite = await generateUser(); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInvite._id], + })).to.eventually.deep.equal([{ + id: group._id, + name: groupName, + inviter: inviter._id, + }]); + + await expect(userToInvite.get('/user')) + .to.eventually.have.deep.property('invitations.guilds[0].id', group._id); + }); + + it('invites multiple users to a group by uuid', async () => { + let userToInvite = await generateUser(); + let userToInvite2 = await generateUser(); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInvite._id, userToInvite2._id], + })).to.eventually.deep.equal([ + { + id: group._id, + name: groupName, + inviter: inviter._id, + }, + { + id: group._id, + name: groupName, + inviter: inviter._id, + }, + ]); + + await expect(userToInvite.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id); + await expect(userToInvite2.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id); + }); + + it('returns an error when inviting multiple users and a user is not found', async () => { + let userToInvite = await generateUser(); + let fakeID = generateUUID(); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInvite._id, fakeID], + })) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: fakeID}), + }); + }); + }); + + describe('email invites', () => { + let testInvite = {name: 'test', email: 'test@habitica.com'}; + + it('returns an error when invite is missing an email', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`, { + emails: [{name: 'test'}], + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('inviteMissingEmail'), + }); + }); + + it('returns an error when emails is not an array', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`, { + emails: {testInvite}, + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('emailsMustBeAnArray'), + }); + }); + + it('returns empty when emails is an empty array', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`, { + emails: [], + })) + .to.eventually.be.empty; + }); + + it('returns an error when there are more than INVITES_LIMIT emails', async () => { + let emails = []; + + for (let i = 0; i < 101; i += 1) { + emails.push(`${generateUUID()}@habitica.com`); + } + + await expect(inviter.post(`/groups/${group._id}/invite`, { + emails, + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), + }); + }); + + it('invites a user to a group by email', async () => { + let res = await inviter.post(`/groups/${group._id}/invite`, { + emails: [testInvite], + inviter: 'inviter name', + }); + + expect(res).to.exist; + }); + + it('invites multiple users to a group by email', async () => { + let res = await inviter.post(`/groups/${group._id}/invite`, { + emails: [testInvite, {name: 'test2', email: 'test2@habitica.com'}], + }); + + expect(res).to.exist; + }); + }); + + describe('user and email invites', () => { + it('returns an error when emails and uuids are not provided', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`)) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('canOnlyInviteEmailUuid'), + }); + }); + + it('returns an error when there are more than INVITES_LIMIT uuids and emails', async () => { + let emails = []; + let uuids = []; + + for (let i = 0; i < 50; i += 1) { + emails.push(`${generateUUID()}@habitica.com`); + } + + for (let i = 0; i < 51; i += 1) { + uuids.push(generateUUID()); + } + + await expect(inviter.post(`/groups/${group._id}/invite`, { + emails, + uuids, + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), + }); + }); + + it('invites users to a group by uuid and email', async () => { + let newUser = await generateUser(); + let invite = await inviter.post(`/groups/${group._id}/invite`, { + uuids: [newUser._id], + emails: [{name: 'test', email: 'test@habitica.com'}], + }); + let invitedUser = await newUser.get('/user'); + + expect(invitedUser.invitations.guilds[0].id).to.equal(group._id); + expect(invite).to.exist; + }); + }); + + describe('guild invites', () => { + it('returns an error when invited user is already invited to the group', async () => { + let userToInivite = await generateUser(); + await inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInivite._id], + }); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInivite._id], + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyInvitedToGroup'), + }); + }); + + it('returns an error when invited user is already in the group', async () => { + let userToInvite = await generateUser(); + await inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInvite._id], + }); + await userToInvite.post(`/groups/${group._id}/join`); + + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [userToInvite._id], + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyInGroup'), + }); + }); + }); + + describe('party invites', () => { + let party; + + beforeEach(async () => { + party = await inviter.post('/groups', { + name: 'Test Party', + type: 'party', + }); + }); + + it('returns an error when invited user has a pending invitation to the party', async () => { + let userToInvite = await generateUser(); + await inviter.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id], + }); + + await expect(inviter.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id], + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyPendingInvitation'), + }); + }); + + it('returns an error when invited user is already in a party of more than 1 member', async () => { + let userToInvite = await generateUser(); + let userToInvite2 = await generateUser(); + await inviter.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id, userToInvite2._id], + }); + await userToInvite.post(`/groups/${party._id}/join`); + await userToInvite2.post(`/groups/${party._id}/join`); + + await expect(inviter.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id], + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('userAlreadyInAParty'), + }); + }); + + it('allow inviting a user to a party if he\'s partying solo', async () => { + let userToInvite = await generateUser(); + await userToInvite.post('/groups', { // add user to a party + name: 'Another Test Party', + type: 'party', + }); + + await inviter.post(`/groups/${party._id}/invite`, { + uuids: [userToInvite._id], + }); + expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id); + }); + }); +}); diff --git a/test/api/v3/integration/groups/PUT-groups.test.js b/test/api/v3/integration/groups/PUT-groups.test.js new file mode 100644 index 0000000000..8d581d56ca --- /dev/null +++ b/test/api/v3/integration/groups/PUT-groups.test.js @@ -0,0 +1,46 @@ +import { + createAndPopulateGroup, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('PUT /group', () => { + let leader, nonLeader, groupToUpdate; + let groupName = 'Test Public Guild'; + let groupType = 'guild'; + let groupUpdatedName = 'Test Public Guild Updated'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: groupName, + type: groupType, + privacy: 'public', + }, + members: 1, + }); + + groupToUpdate = group; + leader = groupLeader; + nonLeader = members[0]; + }); + + it('returns an error when a non group leader tries to update', async () => { + await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, { + name: groupUpdatedName, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageGroupOnlyLeaderCanUpdate'), + }); + }); + + it('updates a group', async () => { + let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, { + name: groupUpdatedName, + }); + + expect(updatedGroup.leader._id).to.eql(leader._id); + expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name); + expect(updatedGroup.name).to.equal(groupUpdatedName); + }); +}); diff --git a/test/api/v3/integration/hall/GET-hall_heroes.test.js b/test/api/v3/integration/hall/GET-hall_heroes.test.js new file mode 100644 index 0000000000..745bc7739c --- /dev/null +++ b/test/api/v3/integration/hall/GET-hall_heroes.test.js @@ -0,0 +1,29 @@ +import { + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /hall/heroes', () => { + it('returns all heroes sorted by -contributor.level and with correct fields', async () => { + let nonHero = await generateUser(); + let hero1 = await generateUser({ + contributor: {level: 1}, + }); + let hero2 = await generateUser({ + contributor: {level: 3}, + }); + + let heroes = await nonHero.get('/hall/heroes'); + expect(heroes.length).to.equal(2); + expect(heroes[0]._id).to.equal(hero2._id); + expect(heroes[1]._id).to.equal(hero1._id); + + expect(heroes[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); + expect(heroes[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); + + expect(heroes[0].profile).to.have.all.keys(['name']); + expect(heroes[1].profile).to.have.all.keys(['name']); + + expect(heroes[0].profile.name).to.equal(hero2.profile.name); + expect(heroes[1].profile.name).to.equal(hero1.profile.name); + }); +}); diff --git a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js new file mode 100644 index 0000000000..2bf1a0a017 --- /dev/null +++ b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js @@ -0,0 +1,56 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /heroes/:heroId', () => { + let user; + + before(async () => { + user = await generateUser({ + contributor: {admin: true}, + }); + }); + + it('requires the caller to be an admin', async () => { + let nonAdmin = await generateUser(); + + await expect(nonAdmin.get(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('noAdminAccess'), + }); + }); + + 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'), + }); + }); + + it('handles non-existing heroes', async () => { + let dummyId = generateUUID(); + await expect(user.get(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: dummyId}), + }); + }); + + it('returns only necessary hero data', async () => { + let hero = await generateUser({ + contributor: {tier: 23}, + }); + let heroRes = await user.get(`/hall/heroes/${hero._id}`); + + 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']); + }); +}); diff --git a/test/api/v3/integration/hall/GET-hall_patrons.test.js b/test/api/v3/integration/hall/GET-hall_patrons.test.js new file mode 100644 index 0000000000..9599daef89 --- /dev/null +++ b/test/api/v3/integration/hall/GET-hall_patrons.test.js @@ -0,0 +1,60 @@ +import { + generateUser, + translate as t, + resetHabiticaDB, +} from '../../../../helpers/api-v3-integration.helper'; +import { times } from 'lodash'; + +describe('GET /hall/patrons', () => { + let user; + + beforeEach(async () => { + await resetHabiticaDB(); + user = await generateUser(); + }); + + it('fails if req.query.page is not numeric', async () => { + await expect(user.get('/hall/patrons?page=notNumber')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns all patrons sorted by -backer.tier and with correct fields', async () => { + let patron1 = await generateUser({ + backer: {tier: 1}, + }); + let patron2 = await generateUser({ + backer: {tier: 3}, + }); + + let patrons = await user.get('/hall/patrons'); + expect(patrons.length).to.equal(2); + expect(patrons[0]._id).to.equal(patron2._id); + expect(patrons[1]._id).to.equal(patron1._id); + + expect(patrons[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); + expect(patrons[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); + + expect(patrons[0].profile).to.have.all.keys(['name']); + expect(patrons[1].profile).to.have.all.keys(['name']); + + expect(patrons[0].profile.name).to.equal(patron2.profile.name); + expect(patrons[1].profile.name).to.equal(patron1.profile.name); + }); + + it('returns only first 50 patrons per request, more if req.query.page is passed', async () => { + await Promise.all(times(53, n => { + return generateUser({backer: {tier: n}}); + })); + + let patrons = await user.get('/hall/patrons'); + expect(patrons.length).to.equal(50); + + let morePatrons = await user.get('/hall/patrons?page=1'); + expect(morePatrons.length).to.equal(2); + expect(morePatrons[0].backer.tier).to.equal(2); + expect(morePatrons[1].backer.tier).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js new file mode 100644 index 0000000000..9948cbe3c7 --- /dev/null +++ b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js @@ -0,0 +1,148 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('PUT /heroes/:heroId', () => { + let user; + + before(async () => { + user = await generateUser({ + contributor: {admin: true}, + }); + }); + + it('requires the caller to be an admin', async () => { + let nonAdmin = await generateUser(); + + await expect(nonAdmin.put(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('noAdminAccess'), + }); + }); + + it('validates req.params.heroId', async () => { + await expect(user.put('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('handles non-existing heroes', async () => { + let dummyId = generateUUID(); + await expect(user.put(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: dummyId}), + }); + }); + + it('updates contributor level, balance, ads, blocked', async () => { + let hero = await generateUser(); + let heroRes = await user.put(`/hall/heroes/${hero._id}`, { + balance: 3, + contributor: {level: 1}, + purchased: {ads: true}, + auth: {blocked: true}, + }); + + // test response + expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys + '_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']); + + // test response values + expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level + expect(heroRes.contributor.level).to.equal(1); + expect(heroRes.purchased.ads).to.equal(true); + expect(heroRes.auth.blocked).to.equal(true); + // test hero values + await hero.sync(); + expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level + expect(hero.contributor.level).to.equal(1); + expect(hero.flags.contributor).to.equal(true); + expect(hero.purchased.ads).to.equal(true); + expect(hero.auth.blocked).to.equal(true); + }); + + it('updates contributor level', async () => { + let hero = await generateUser({ + contributor: {level: 5}, + }); + let heroRes = await user.put(`/hall/heroes/${hero._id}`, { + contributor: {level: 6}, + }); + + // test response + expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys + '_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']); + + // test response values + expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level + expect(heroRes.contributor.level).to.equal(6); + expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5); + // test hero values + await hero.sync(); + expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level + expect(hero.contributor.level).to.equal(6); + expect(hero.flags.contributor).to.equal(true); + expect(hero.items.pets['Dragon-Hydra']).to.equal(5); + }); + + it('updates contributor data', async () => { + let hero = await generateUser({ + contributor: {level: 5}, + }); + let heroRes = await user.put(`/hall/heroes/${hero._id}`, { + contributor: {text: 'Astronaut'}, + }); + + // test response + expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys + '_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']); + + // test response values + expect(heroRes.contributor.level).to.equal(5); // doesn't modify previous values + expect(heroRes.contributor.text).to.equal('Astronaut'); + // test hero values + await hero.sync(); + expect(hero.contributor.level).to.equal(5); // doesn't modify previous values + expect(hero.contributor.text).to.equal('Astronaut'); + }); + + it('updates items', async () => { + let hero = await generateUser(); + let heroRes = await user.put(`/hall/heroes/${hero._id}`, { + itemPath: 'items.special.snowball', + itemVal: 5, + }); + + // test response + expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys + '_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']); + + // test response values + expect(heroRes.items.special.snowball).to.equal(5); + // test hero values + await hero.sync(); + expect(hero.items.special.snowball).to.equal(5); + }); +}); diff --git a/test/api/v3/integration/members/GET-members_id.test.js b/test/api/v3/integration/members/GET-members_id.test.js new file mode 100644 index 0000000000..d8ac3c4119 --- /dev/null +++ b/test/api/v3/integration/members/GET-members_id.test.js @@ -0,0 +1,49 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /members/:memberId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('validates req.params.memberId', async () => { + await expect(user.get('/members/invalidUUID')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns a member public data only', async () => { + let member = await generateUser({ // make sure user has all the fields that can be returned by the getMember call + contributor: {level: 1}, + backer: {tier: 3}, + preferences: { + costume: false, + background: 'volcano', + }, + }); + let memberRes = await user.get(`/members/${member._id}`); + expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys + '_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party', + 'backer', 'contributor', 'auth', 'items', + ]); + expect(Object.keys(memberRes.auth)).to.eql(['timestamps']); + expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt', + 'costume', 'sleep', 'background'].sort()); + }); + + it('handles non-existing members', async () => { + let dummyId = generateUUID(); + await expect(user.get(`/members/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: dummyId}), + }); + }); +}); diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js new file mode 100644 index 0000000000..3bd3380437 --- /dev/null +++ b/test/api/v3/integration/members/POST-send_private_message.test.js @@ -0,0 +1,107 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /members/send-private-message', () => { + let userToSendMessage; + let messageToSend = 'Test Private Message'; + + beforeEach(async () => { + userToSendMessage = await generateUser(); + }); + + it('returns error when message is not provided', async () => { + await expect(userToSendMessage.post('/members/send-private-message')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when toUserId is not provided', async () => { + await expect(userToSendMessage.post('/members/send-private-message', { + message: messageToSend, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when to user is not found', async () => { + await expect(userToSendMessage.post('/members/send-private-message', { + message: messageToSend, + toUserId: generateUUID(), + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userNotFound'), + }); + }); + + it('returns error when to user has blocked the sender', async () => { + let receiver = await generateUser({'inbox.blocks': [userToSendMessage._id]}); + + await expect(userToSendMessage.post('/members/send-private-message', { + message: messageToSend, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notAuthorizedToSendMessageToThisUser'), + }); + }); + + it('returns error when sender has blocked to user', async () => { + let receiver = await generateUser(); + let sender = await generateUser({'inbox.blocks': [receiver._id]}); + + await expect(sender.post('/members/send-private-message', { + message: messageToSend, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notAuthorizedToSendMessageToThisUser'), + }); + }); + + it('returns error when to user has opted out of messaging', async () => { + let receiver = await generateUser({'inbox.optOut': true}); + + await expect(userToSendMessage.post('/members/send-private-message', { + message: messageToSend, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notAuthorizedToSendMessageToThisUser'), + }); + }); + + it('sends a private message to a user', async () => { + let receiver = await generateUser(); + + await userToSendMessage.post('/members/send-private-message', { + message: messageToSend, + toUserId: receiver._id, + }); + + let updatedReceiver = await receiver.get('/user'); + let updatedSender = await userToSendMessage.get('/user'); + + let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => { + return message.uuid === userToSendMessage._id && message.text === messageToSend; + }); + + let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => { + return message.uuid === receiver._id && message.text === messageToSend; + }); + + expect(sendersMessageInReceiversInbox).to.exist; + expect(sendersMessageInSendersInbox).to.exist; + }); +}); diff --git a/test/api/v3/integration/members/POST-transfer_gems.test.js b/test/api/v3/integration/members/POST-transfer_gems.test.js new file mode 100644 index 0000000000..96644a3e88 --- /dev/null +++ b/test/api/v3/integration/members/POST-transfer_gems.test.js @@ -0,0 +1,174 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /members/transfer-gems', () => { + let userToSendMessage; + let receiver; + let message = 'Test Private Message'; + let gemAmount = 20; + + beforeEach(async () => { + userToSendMessage = await generateUser({balance: 5}); + receiver = await generateUser(); + }); + + it('returns error when no parameters are provided', async () => { + await expect(userToSendMessage.post('/members/transfer-gems')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when toUserId is not provided', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when to user is not found', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount, + toUserId: generateUUID(), + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userNotFound'), + }); + }); + + it('returns error when to user attempts to send gems to themselves', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount, + toUserId: userToSendMessage._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cannotSendGemsToYourself'), + }); + }); + + it('returns error when there is no gemAmount', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when gemAmount is not an integer', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount: 1.5, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when gemAmount is negative', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount: -5, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('badAmountOfGemsToSend'), + }); + }); + + it('returns error when gemAmount is more than the sender\'s balance', async () => { + await expect(userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount: gemAmount + 4, + toUserId: receiver._id, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('badAmountOfGemsToSend'), + }); + }); + + it('sends a private message about gems to a user', async () => { + await userToSendMessage.post('/members/transfer-gems', { + message, + gemAmount, + toUserId: receiver._id, + }); + + let updatedReceiver = await receiver.get('/user'); + let updatedSender = await userToSendMessage.get('/user'); + + let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { + return inboxMessage.uuid === userToSendMessage._id; + }); + + let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { + return inboxMessage.uuid === receiver._id; + }); + + let messageSentContent = t('privateMessageGiftIntro', { + receiverName: receiver.profile.name, + senderName: userToSendMessage.profile.name, + }); + messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount}); + messageSentContent += message; + + expect(sendersMessageInReceiversInbox).to.exist; + expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent); + expect(updatedReceiver.balance).to.equal(gemAmount / 4); + + expect(sendersMessageInSendersInbox).to.exist; + expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent); + expect(updatedSender.balance).to.equal(0); + }); + + it('does not requrie a message', async () => { + await userToSendMessage.post('/members/transfer-gems', { + gemAmount, + toUserId: receiver._id, + }); + + let updatedReceiver = await receiver.get('/user'); + let updatedSender = await userToSendMessage.get('/user'); + + let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { + return inboxMessage.uuid === userToSendMessage._id; + }); + + let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { + return inboxMessage.uuid === receiver._id; + }); + + let messageSentContent = t('privateMessageGiftIntro', { + receiverName: receiver.profile.name, + senderName: userToSendMessage.profile.name, + }); + messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount}); + + expect(sendersMessageInReceiversInbox).to.exist; + expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent); + expect(updatedReceiver.balance).to.equal(gemAmount / 4); + + expect(sendersMessageInSendersInbox).to.exist; + expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent); + expect(updatedSender.balance).to.equal(0); + }); +}); diff --git a/test/api/v3/integration/models/GET-model_paths.test.js b/test/api/v3/integration/models/GET-model_paths.test.js new file mode 100644 index 0000000000..0a9a94451a --- /dev/null +++ b/test/api/v3/integration/models/GET-model_paths.test.js @@ -0,0 +1,32 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /models/:model/paths', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns an error when model is not accessible or doesn\'t exists', async () => { + await expect(user.get('/models/1234/paths')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + let models = ['habit', 'daily', 'todo', 'reward', 'user', 'tag', 'challenge', 'group']; + models.forEach(model => { + it(`returns the model paths for ${model}`, async () => { + let res = await user.get(`/models/${model}/paths`); + + if (model !== 'tag') expect(res._id).to.equal('String'); + if (model === 'tag') expect(res.id).to.equal('String'); + + expect(res).to.not.have.keys('__v'); + }); + }); +}); diff --git a/test/api/v3/integration/notFound.test.js b/test/api/v3/integration/notFound.test.js new file mode 100644 index 0000000000..747b370af9 --- /dev/null +++ b/test/api/v3/integration/notFound.test.js @@ -0,0 +1,13 @@ +import { requester } from '../../../helpers/api-integration/v3'; + +describe('notFound Middleware', () => { + it('returns a 404 error when the resource is not found', async () => { + let request = requester().get('/api/v3/dummy-url'); + + await expect(request).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js new file mode 100644 index 0000000000..37588d1f18 --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('payments : amazon #subscribeCancel', () => { + let endpoint = '/amazon/subscribe/cancel'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies subscription', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js new file mode 100644 index 0000000000..7c692f31d1 --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +xdescribe('payments : paypal #checkout', () => { + let endpoint = '/paypal/checkout'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies subscription', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js new file mode 100644 index 0000000000..6de04c8848 --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +xdescribe('payments : paypal #checkoutSuccess', () => { + let endpoint = '/paypal/checkout/success'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies subscription', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js new file mode 100644 index 0000000000..54c540ee39 --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +xdescribe('payments : paypal #subscribe', () => { + let endpoint = '/paypal/subscribe'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js new file mode 100644 index 0000000000..1ba8b7af16 --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('payments : paypal #subscribeCancel', () => { + let endpoint = '/paypal/subscribe/cancel'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js new file mode 100644 index 0000000000..1a38342e9c --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +xdescribe('payments : paypal #subscribeSuccess', () => { + let endpoint = '/paypal/subscribe/success'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js new file mode 100644 index 0000000000..6d7ac87d0f --- /dev/null +++ b/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - stripe - #subscribeCancel', () => { + let endpoint = '/stripe/subscribe/cancel'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js b/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js new file mode 100644 index 0000000000..8745a74e85 --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js @@ -0,0 +1,20 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - amazon - #checkout', () => { + let endpoint = '/amazon/checkout'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Missing req.body.orderReferenceId', + }); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js b/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js new file mode 100644 index 0000000000..17a50520eb --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js @@ -0,0 +1,22 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - amazon - #createOrderReferenceId', () => { + let endpoint = '/amazon/createOrderReferenceId'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies billingAgreementId', async (done) => { + try { + await user.post(endpoint); + } catch (e) { + // Parameter AWSAccessKeyId cannot be empty. + expect(e.error).to.eql('BadRequest'); + done(); + } + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js b/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js new file mode 100644 index 0000000000..5c3b98ad87 --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - amazon - #subscribe', () => { + let endpoint = '/amazon/subscribe'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies subscription code', async () => { + await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('missingSubscriptionCode'), + }); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js b/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js new file mode 100644 index 0000000000..51ccf8c41c --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js @@ -0,0 +1,20 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('payments : amazon', () => { + let endpoint = '/amazon/verifyAccessToken'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies access token', async () => { + await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Missing req.body.access_token', + }); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js b/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js new file mode 100644 index 0000000000..219e9ce35b --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js @@ -0,0 +1,17 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - paypal - #ipn', () => { + let endpoint = '/paypal/ipn'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + let result = await user.post(endpoint); + expect(result).to.eql('OK'); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js b/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js new file mode 100644 index 0000000000..1443a3af74 --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js @@ -0,0 +1,20 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - stripe - #checkout', () => { + let endpoint = '/stripe/checkout'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'Error', + message: 'Invalid API Key provided: ****************************1111', + }); + }); +}); diff --git a/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js b/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js new file mode 100644 index 0000000000..d6d568ace4 --- /dev/null +++ b/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js @@ -0,0 +1,21 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('payments - stripe - #subscribeEdit', () => { + let endpoint = '/stripe/subscribe/edit'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('verifies credentials', async () => { + await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('missingSubscription'), + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js new file mode 100644 index 0000000000..665a185279 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js @@ -0,0 +1,119 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /groups/:groupId/quests/accept', () => { + const PET_QUEST = 'whale'; + + let questingGroup; + let leader; + let partyMembers; + let user; + + beforeEach(async () => { + user = await generateUser(); + + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + }); + + context('failure conditions', () => { + it('does not accept quest without an invite', async () => { + await expect(leader.post(`/groups/${questingGroup._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questInviteNotFound'), + }); + }); + + it('does not accept quest for a group in which user is not a member', async () => { + await expect(user.post(`/groups/${questingGroup._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not accept quest for a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('does not accept invite twice', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('questAlreadyAccepted'), + }); + }); + + it('does not accept invite for a quest already underway', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + // quest will start after everyone has accepted + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questAlreadyUnderway'), + }); + }); + }); + + context('successfully accepting a quest invitation', () => { + it('joins a quest from an invitation', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + + await Promise.all([partyMembers[0].sync(), questingGroup.sync()]); + expect(leader.party.quest.RSVPNeeded).to.equal(false); + expect(questingGroup.quest.members[partyMembers[0]._id]); + }); + + it('does not begin the quest if pending invitations remain', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + + await questingGroup.sync(); + expect(questingGroup.quest.active).to.equal(false); + }); + + it('begins the quest if accepting the last pending invite', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + // quest will start after everyone has accepted + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await questingGroup.sync(); + expect(questingGroup.quest.active).to.equal(true); + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js new file mode 100644 index 0000000000..b6d43f826b --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js @@ -0,0 +1,126 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('POST /groups/:groupId/quests/force-start', () => { + const PET_QUEST = 'whale'; + + let questingGroup; + let leader; + let partyMembers; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + }); + + context('failure conditions', () => { + it('does not force start a quest for a group in which user is not a member', async () => { + let nonMember = await generateUser(); + + await expect(nonMember.post(`/groups/${questingGroup._id}/quests/force-start`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not force start quest for a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('does not force start for a party without a pending quest', async () => { + await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questNotPending'), + }); + }); + + it('does not force start for a quest already underway', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + // quest will start after everyone has accepted + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questAlreadyUnderway'), + }); + }); + + it('does not allow non-quest leader or non-group leader to force start a quest', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/force-start`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questOrGroupLeaderOnlyStartQuest'), + }); + }); + }); + + context('successfully force starting a quest', () => { + it('allows quest leader to force start quest', async () => { + let questLeader = partyMembers[0]; + await questLeader.update({[`items.quests.${PET_QUEST}`]: 1}); + await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await questLeader.post(`/groups/${questingGroup._id}/quests/force-start`); + + await questingGroup.sync(); + + expect(questingGroup.quest.active).to.eql(true); + }); + + it('allows group leader to force start quest', async () => { + let questLeader = partyMembers[0]; + await questLeader.update({[`items.quests.${PET_QUEST}`]: 1}); + await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await leader.post(`/groups/${questingGroup._id}/quests/force-start`); + + await questingGroup.sync(); + + expect(questingGroup.quest.active).to.eql(true); + }); + + it('sends back the quest object', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + let quest = await leader.post(`/groups/${questingGroup._id}/quests/force-start`); + + expect(quest.active).to.eql(true); + expect(quest.key).to.eql(PET_QUEST); + expect(quest.members).to.eql({ + [`${leader._id}`]: true, + }); + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js new file mode 100644 index 0000000000..973a51a1a5 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js @@ -0,0 +1,192 @@ +import { + createAndPopulateGroup, + translate as t, + sleep, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; +import { quests as questScrolls } from '../../../../../common/script/content'; + +describe('POST /groups/:groupId/quests/invite/:questKey', () => { + let questingGroup; + let leader; + let member; + const PET_QUEST = 'whale'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 1, + }); + + questingGroup = group; + leader = groupLeader; + member = members[0]; + }); + + context('failure conditions', () => { + it('does not issue invites with an invalid group ID', async () => { + await expect(leader.post(`/groups/${generateUUID()}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not issue invites for a group in which user is not a member', async () => { + let { group } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 1, + }); + + let alternateGroup = group; + + await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not issue invites for Guilds', async () => { + let { group } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'public' }, + members: 1, + }); + + let alternateGroup = group; + + await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('does not issue invites with an invalid quest key', async () => { + const FAKE_QUEST = 'herkimer'; + + await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${FAKE_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questNotFound', {key: FAKE_QUEST}), + }); + }); + + it('does not issue invites for a quest the user does not own', async () => { + await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questNotOwned'), + }); + }); + + it('does not issue invites if the user is of insufficient Level', async () => { + const LEVELED_QUEST = 'atom1'; + const LEVELED_QUEST_REQ = questScrolls[LEVELED_QUEST].lvl; + const leaderUpdate = {}; + leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 1; + leaderUpdate['stats.lvl'] = LEVELED_QUEST_REQ - 1; + + await leader.update(leaderUpdate); + + await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${LEVELED_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questLevelTooHigh', {level: LEVELED_QUEST_REQ}), + }); + }); + + it('does not issue invites if a quest is already underway', async () => { + const QUEST_IN_PROGRESS = 'atom1'; + const leaderUpdate = {}; + leaderUpdate[`items.quests.${PET_QUEST}`] = 1; + + await leader.update(leaderUpdate); + await questingGroup.update({ 'quest.key': QUEST_IN_PROGRESS }); + + await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questAlreadyUnderway'), + }); + }); + }); + + context('successfully issuing a quest invitation', () => { + beforeEach(async () => { + const memberUpdate = {}; + memberUpdate[`items.quests.${PET_QUEST}`] = 1; + + await Promise.all([ + leader.update(memberUpdate), + member.update(memberUpdate), + ]); + }); + + it('adds quest details to group object', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await questingGroup.sync(); + + let quest = questingGroup.quest; + + expect(quest.key).to.eql(PET_QUEST); + expect(quest.active).to.eql(false); + expect(quest.leader).to.eql(leader._id); + expect(quest.members).to.have.property(leader._id, true); + expect(quest.members).to.have.property(member._id, null); + expect(quest).to.have.property('progress'); + }); + + it('adds quest details to user objects', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await sleep(0.1); // member updates happen in the background + + await Promise.all([ + leader.sync(), + member.sync(), + ]); + + expect(leader.party.quest.key).to.eql(PET_QUEST); + expect(member.party.quest.key).to.eql(PET_QUEST); + expect(leader.party.quest.RSVPNeeded).to.eql(false); + expect(member.party.quest.RSVPNeeded).to.eql(true); + }); + + it('sends back the quest object', async () => { + let inviteResponse = await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + expect(inviteResponse.key).to.eql(PET_QUEST); + expect(inviteResponse.active).to.eql(false); + expect(inviteResponse.leader).to.eql(leader._id); + expect(inviteResponse.members).to.have.property(leader._id, true); + expect(inviteResponse.members).to.have.property(member._id, null); + expect(inviteResponse).to.have.property('progress'); + }); + + it('allows non-party-leader party members to send invites', async () => { + let inviteResponse = await member.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await questingGroup.sync(); + + expect(inviteResponse.key).to.eql(PET_QUEST); + expect(questingGroup.quest.key).to.eql(PET_QUEST); + }); + + it('starts quest automatically if user is in a solo party', async () => { + let leaderDetails = { balance: 10 }; + leaderDetails[`items.quests.${PET_QUEST}`] = 1; + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + leaderDetails, + }); + + await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`); + + await group.sync(); + + expect(group.quest.active).to.eql(true); + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js new file mode 100644 index 0000000000..850cf53646 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js @@ -0,0 +1,126 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /groups/:groupId/quests/abort', () => { + let questingGroup; + let partyMembers; + let user; + let leader; + + const PET_QUEST = 'whale'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + user = await generateUser(); + }); + + context('failure conditions', () => { + it('returns an error when group is not found', async () => { + await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/abort`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error for a group in which user is not a member', async () => { + await expect(user.post(`/groups/${questingGroup._id}/quests/abort`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error when group is a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('returns an error when quest is not active', async () => { + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('noActiveQuestToAbort'), + }); + }); + + it('returns an error when non quest leader attempts to abort', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyLeaderAbortQuest'), + }); + }); + }); + + it('aborts a quest', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + let res = await leader.post(`/groups/${questingGroup._id}/quests/abort`); + await Promise.all([ + leader.sync(), + questingGroup.sync(), + partyMembers[0].sync(), + partyMembers[1].sync(), + ]); + + let cleanUserQuestObj = { + key: null, + progress: { + up: 0, + down: 0, + collect: {}, + }, + completed: null, + RSVPNeeded: false, + }; + + expect(leader.party.quest).to.eql(cleanUserQuestObj); + expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj); + expect(partyMembers[1].party.quest).to.eql(cleanUserQuestObj); + expect(leader.items.quests[PET_QUEST]).to.equal(1); + expect(questingGroup.quest).to.deep.equal(res); + expect(questingGroup.quest).to.eql({ + key: null, + active: false, + leader: null, + progress: { + collect: {}, + }, + members: {}, + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js new file mode 100644 index 0000000000..f3bd03a180 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js @@ -0,0 +1,138 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /groups/:groupId/quests/cancel', () => { + let questingGroup; + let partyMembers; + let user; + let leader; + + const PET_QUEST = 'whale'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + user = await generateUser(); + }); + + context('failure conditions', () => { + it('returns an error when group is not found', async () => { + await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not reject quest for a group in which user is not a member', async () => { + await expect(user.post(`/groups/${questingGroup._id}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error when group is a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('returns an error when group is not on a quest', async () => { + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questInvitationDoesNotExist'), + }); + }); + + it('only the leader can cancel the quest', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyLeaderCancelQuest'), + }); + }); + + it('does not cancel a quest already underway', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + // quest will start after everyone has accepted + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(leader.post(`/groups/${questingGroup._id}/quests/cancel`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cantCancelActiveQuest'), + }); + }); + }); + + it('cancels a quest', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + + let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`); + + await Promise.all([ + leader.sync(), + partyMembers[0].sync(), + partyMembers[1].sync(), + questingGroup.sync(), + ]); + + let clean = { + key: null, + progress: { + up: 0, + down: 0, + collect: {}, + }, + completed: null, + RSVPNeeded: false, + }; + + expect(leader.party.quest).to.eql(clean); + expect(partyMembers[1].party.quest).to.eql(clean); + expect(partyMembers[0].party.quest).to.eql(clean); + + expect(res).to.eql(questingGroup.quest); + expect(questingGroup.quest).to.eql({ + key: null, + active: false, + leader: null, + progress: { + collect: {}, + }, + members: {}, + }); + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js new file mode 100644 index 0000000000..65d781c163 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js @@ -0,0 +1,124 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /groups/:groupId/quests/leave', () => { + let questingGroup; + let partyMembers; + let user; + let leader; + + const PET_QUEST = 'whale'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + user = await generateUser(); + }); + + context('failure conditions', () => { + it('returns an error when group is not found', async () => { + await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error for a group in which user is not a member', async () => { + await expect(user.post(`/groups/${questingGroup._id}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error when group is a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('returns an error when quest is not active', async () => { + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('noActiveQuestToLeave'), + }); + }); + + it('returns an error when quest leader attempts to leave', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(leader.post(`/groups/${questingGroup._id}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questLeaderCannotLeaveQuest'), + }); + }); + + it('returns an error when non quest member attempts to leave', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`); + + await expect(partyMembers[1].post(`/groups/${questingGroup._id}/quests/leave`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notPartOfQuest'), + }); + }); + }); + + it('leaves a quest', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + let leaveResult = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`); + await Promise.all([ + partyMembers[0].sync(), + questingGroup.sync(), + ]); + + expect(partyMembers[0].party.quest).to.eql({ + key: null, + progress: { + up: 0, + down: 0, + collect: {}, + }, + completed: null, + RSVPNeeded: false, + }); + expect(questingGroup.quest).to.deep.equal(leaveResult); + expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false; + }); +}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js new file mode 100644 index 0000000000..2dcddfe727 --- /dev/null +++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js @@ -0,0 +1,146 @@ +import { + createAndPopulateGroup, + translate as t, + generateUser, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /groups/:groupId/quests/reject', () => { + let questingGroup; + let partyMembers; + let user; + let leader; + + const PET_QUEST = 'whale'; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 2, + }); + + questingGroup = group; + leader = groupLeader; + partyMembers = members; + + await leader.update({ + [`items.quests.${PET_QUEST}`]: 1, + }); + user = await generateUser(); + }); + + context('failure conditions', () => { + it('returns an error when group is not found', async () => { + await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('does not accept quest for a group in which user is not a member', async () => { + await expect(user.post(`/groups/${questingGroup._id}/quests/accept`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('groupNotFound'), + }); + }); + + it('returns an error when group is a guild', async () => { + let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'guild', privacy: 'private' }, + }); + + await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('guildQuestsNotSupported'), + }); + }); + + it('returns an error when group is not on a quest', async () => { + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questInvitationDoesNotExist'), + }); + }); + + it('return an error when a user rejects an invite twice', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('questAlreadyRejected'), + }); + }); + + it('return an error when a user rejects an invite already accepted', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('questAlreadyAccepted'), + }); + }); + + it('does not reject invite for a quest already underway', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + // quest will start after everyone has accepted + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); + + await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('questAlreadyUnderway'), + }); + }); + }); + + context('successfully quest rejection', () => { + let cleanUserQuestObj = { + key: null, + progress: { + up: 0, + down: 0, + collect: {}, + }, + completed: null, + RSVPNeeded: false, + }; + + it('rejects a quest invitation', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + let res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`); + await partyMembers[0].sync(); + await questingGroup.sync(); + + expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj); + expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false; + expect(questingGroup.quest.active).to.be.false; + expect(res).to.eql(questingGroup.quest); + }); + + it('starts the quest when the last user reject', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); + await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`); + await questingGroup.sync(); + + expect(questingGroup.quest.active).to.be.true; + }); + }); +}); diff --git a/test/api/v3/integration/status/GET-status.test.js b/test/api/v3/integration/status/GET-status.test.js new file mode 100644 index 0000000000..1d4d33a7d7 --- /dev/null +++ b/test/api/v3/integration/status/GET-status.test.js @@ -0,0 +1,12 @@ +import { + requester, +} from '../../../../helpers/api-v3-integration.helper'; + +describe('GET /status', () => { + it('returns status: up', async () => { + let res = await requester().get('/status'); + expect(res).to.eql({ + status: 'up', + }); + }); +}); diff --git a/test/api/v3/integration/tags/DELETE-tags_id.test.js b/test/api/v3/integration/tags/DELETE-tags_id.test.js new file mode 100644 index 0000000000..c03e8bb9e0 --- /dev/null +++ b/test/api/v3/integration/tags/DELETE-tags_id.test.js @@ -0,0 +1,27 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('DELETE /tags/:tagId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('deletes a tag given it\'s id', async () => { + let tagName = 'Tag 1'; + let tag = await user.post('/tags', {name: tagName}); + let numberOfTags = (await user.get('/tags')).length; + + await user.del(`/tags/${tag.id}`); + + let tags = await user.get('/tags'); + let tagNames = tags.map((t) => { + return t.name; + }); + + expect(tags.length).to.equal(numberOfTags - 1); + expect(tagNames).to.not.include(tagName); + }); +}); diff --git a/test/api/v3/integration/tags/GET-tags.test.js b/test/api/v3/integration/tags/GET-tags.test.js new file mode 100644 index 0000000000..7a24963474 --- /dev/null +++ b/test/api/v3/integration/tags/GET-tags.test.js @@ -0,0 +1,22 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /tags', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns all user\'s tags', async () => { + let tag1 = await user.post('/tags', {name: 'Tag 1'}); + let tag2 = await user.post('/tags', {name: 'Tag 2'}); + + let tags = await user.get('/tags'); + + expect(tags.length).to.equal(2 + 3); // + 3 because 1 is a default task + expect(tags[tags.length - 2].name).to.equal(tag1.name); + expect(tags[tags.length - 1].name).to.equal(tag2.name); + }); +}); diff --git a/test/api/v3/integration/tags/GET-tags_id.test.js b/test/api/v3/integration/tags/GET-tags_id.test.js new file mode 100644 index 0000000000..4ab818593d --- /dev/null +++ b/test/api/v3/integration/tags/GET-tags_id.test.js @@ -0,0 +1,20 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /tags/:tagId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns a tag given it\'s id', async () => { + let createdTag = await user.post('/tags', {name: 'Tag 1'}); + let tag = await user.get(`/tags/${createdTag.id}`); + + expect(tag).to.deep.equal(createdTag); + }); + + it('handles non-existing tags'); +}); diff --git a/test/api/v3/integration/tags/POST-tag-reorder.test.js b/test/api/v3/integration/tags/POST-tag-reorder.test.js new file mode 100644 index 0000000000..0710cecf3e --- /dev/null +++ b/test/api/v3/integration/tags/POST-tag-reorder.test.js @@ -0,0 +1,44 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /reorder-tags', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns error when no parameters are provided', async () => { + await expect(user.post('/reorder-tags')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('returns error when tag is not found', async () => { + await expect(user.post('/reorder-tags', {tagId: 'fake-id', to: 3})) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('tagNotFound'), + }); + }); + + it('updates tags', async () => { + let tag1Name = 'Tag 1'; + let tag2Name = 'Tag 2'; + await user.post('/tags', {name: tag1Name}); + await user.post('/tags', {name: tag2Name}); + await user.sync(); + + await user.post('/reorder-tags', {tagId: user.tags[4].id, to: 3}); + await user.sync(); + + expect(user.tags[3].name).to.equal(tag2Name); + expect(user.tags[4].name).to.equal(tag1Name); + }); +}); diff --git a/test/api/v3/integration/tags/POST-tags.test.js b/test/api/v3/integration/tags/POST-tags.test.js new file mode 100644 index 0000000000..93f2dfdb60 --- /dev/null +++ b/test/api/v3/integration/tags/POST-tags.test.js @@ -0,0 +1,25 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /tags', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('creates a tag correctly', async () => { + let tagName = 'Tag 1'; + let createdTag = await user.post('/tags', { + name: tagName, + ignored: false, + }); + + let tag = await user.get(`/tags/${createdTag.id}`); + + expect(tag.name).to.equal(tagName); + expect(tag.ignored).to.not.exist; + expect(tag).to.deep.equal(createdTag); + }); +}); diff --git a/test/api/v3/integration/tags/PUT-tags_id.test.js b/test/api/v3/integration/tags/PUT-tags_id.test.js new file mode 100644 index 0000000000..4c16453ac3 --- /dev/null +++ b/test/api/v3/integration/tags/PUT-tags_id.test.js @@ -0,0 +1,28 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('PUT /tags/:tagId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('updates a tag given it\'s id', async () => { + let updatedTagName = 'Tag updated'; + let createdTag = await user.post('/tags', {name: 'Tag 1'}); + let updatedTag = await user.put(`/tags/${createdTag.id}`, { + name: updatedTagName, + ignored: true, + }); + + createdTag = await user.get(`/tags/${updatedTag.id}`); + + expect(updatedTag.name).to.equal(updatedTagName); + expect(updatedTag.ignored).to.not.exist; + + expect(createdTag.name).to.equal(updatedTagName); + expect(createdTag.ignored).to.not.exist; + }); +}); diff --git a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js new file mode 100644 index 0000000000..bb92e4759f --- /dev/null +++ b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js @@ -0,0 +1,59 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('DELETE /tasks/:id', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + context('task can be deleted', () => { + let task; + + beforeEach(async () => { + task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + }); + + it('deletes a user\'s task', async () => { + await user.del(`/tasks/${task._id}`); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + }); + + context('task cannot be deleted', () => { + it('cannot delete a non-existant task', async () => { + await expect(user.del('/tasks/550e8400-e29b-41d4-a716-446655440000')).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('cannot delete a task owned by someone else', async () => { + let anotherUser = await generateUser(); + let anotherUsersTask = await anotherUser.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + + await expect(user.del(`/tasks/${anotherUsersTask._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('removes a task from user.tasksOrder'); // TODO + }); +}); diff --git a/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js new file mode 100644 index 0000000000..3fed4cfaed --- /dev/null +++ b/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js @@ -0,0 +1,75 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { each } from 'lodash'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /tasks/:taskId', () => { + let user; + let guild; + let challenge; + let task; + let tasksToTest = { + habit: { + text: 'test habit', + type: 'habit', + up: false, + down: true, + }, + todo: { + text: 'test todo', + type: 'todo', + }, + daily: { + text: 'test daily', + type: 'daily', + frequency: 'daily', + everyX: 5, + startDate: new Date(), + }, + reward: { + text: 'test reward', + type: 'reward', + }, + }; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('returns error when incorrect id is passed', async () => { + await expect(user.get(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + each(tasksToTest, (taskValue, taskType) => { + context(`${taskType}`, () => { + before(async () => { + task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue); + }); + + it('gets challenge task', async () => { + let getTask = await user.get(`/tasks/${task._id}`); + expect(getTask).to.eql(task); + }); + + it('returns error when user is not a member of the challenge', async () => { + let anotherUser = await generateUser(); + + await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/GET-tasks_id.test.js b/test/api/v3/integration/tasks/GET-tasks_id.test.js new file mode 100644 index 0000000000..99f2d769f4 --- /dev/null +++ b/test/api/v3/integration/tasks/GET-tasks_id.test.js @@ -0,0 +1,58 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /tasks/:id', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + context('task can be accessed', async () => { + let task; + + beforeEach(async () => { + task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + }); + + it('gets specified task', async () => { + let getTask = await user.get(`/tasks/${task._id}`); + expect(getTask).to.eql(task); + }); + + // TODO after challenges are implemented + it('can get active challenge task that user does not own'); // Yes? + }); + + context('task cannot be accessed', () => { + it('cannot get a non-existant task', async () => { + let dummyId = generateUUID(); + + await expect(user.get(`/tasks/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('cannot get a task owned by someone else', async () => { + let anotherUser = await generateUser(); + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + + await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/GET-tasks_user.test.js b/test/api/v3/integration/tasks/GET-tasks_user.test.js new file mode 100644 index 0000000000..406c0d43fb --- /dev/null +++ b/test/api/v3/integration/tasks/GET-tasks_user.test.js @@ -0,0 +1,84 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /tasks/user', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns all user\'s tasks', async () => { + let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]); + let tasks = await user.get('/tasks/user'); + expect(tasks.length).to.equal(createdTasks.length + 1); // + 1 because 1 is a default task + }); + + it('returns only a type of user\'s tasks if req.query.type is specified', async () => { + let createdTasks = await user.post('/tasks/user', [ + {text: 'test habit', type: 'habit'}, + {text: 'test daily', type: 'daily'}, + {text: 'test reward', type: 'reward'}, + {text: 'test todo', type: 'todo'}, + ]); + let habits = await user.get('/tasks/user?type=habits'); + let dailys = await user.get('/tasks/user?type=dailys'); + let rewards = await user.get('/tasks/user?type=rewards'); + + expect(habits.length).to.be.at.least(1); + expect(habits[0]._id).to.equal(createdTasks[0]._id); + expect(dailys.length).to.be.at.least(1); + expect(dailys[0]._id).to.equal(createdTasks[1]._id); + expect(rewards.length).to.be.at.least(1); + expect(rewards[0]._id).to.equal(createdTasks[2]._id); + }); + + it('returns uncompleted todos if req.query.type is "todos"', async () => { + let existingTodos = await user.get('/tasks/user?type=todos'); + + // populate user with other task types + await user.post('/tasks/user', [ + {text: 'daily', type: 'daily'}, + {text: 'reward', type: 'reward'}, + {text: 'habit', type: 'habit'}, + ]); + + let newUncompletedTodos = await user.post('/tasks/user', [ + {text: 'test todo 1', type: 'todo'}, + {text: 'test todo 2', type: 'todo'}, + ]); + let todoToBeCompleted = await user.post('/tasks/user', { + text: 'wll be completed todo', type: 'todo', + }); + + await user.post(`/tasks/${todoToBeCompleted._id}/score/up`); + + let uncompletedTodos = [...existingTodos, ...newUncompletedTodos]; + + let todos = await user.get('/tasks/user?type=todos'); + + expect(todos.length).to.be.gte(2); + expect(todos.length).to.eql(uncompletedTodos.length); + expect(todos.every(task => task.type === 'todo')); + expect(todos.every(task => task.completed === false)); + }); + + it('returns completed todos sorted by reverse completion date if req.query.type is "completeTodos"', async () => { + let todo1 = await user.post('/tasks/user', {text: 'todo to complete 1', type: 'todo'}); + let todo2 = await user.post('/tasks/user', {text: 'todo to complete 2', type: 'todo'}); + + await user.sync(); + let initialTodoCount = user.tasksOrder.todos.length; + + await user.post(`/tasks/${todo2._id}/score/up`); + await user.post(`/tasks/${todo1._id}/score/up`); + await user.sync(); + + expect(user.tasksOrder.todos.length).to.equal(initialTodoCount - 2); + + let completedTodos = await user.get('/tasks/user?type=completedTodos'); + expect(completedTodos.length).to.equal(2); + expect(completedTodos[completedTodos.length - 1].text).to.equal('todo to complete 2'); // last is the todo that was completed most recently + }); +}); diff --git a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js new file mode 100644 index 0000000000..d6cdf21749 --- /dev/null +++ b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js @@ -0,0 +1,43 @@ +import { + generateUser, + generateGroup, + generateChallenge, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /tasks/clearCompletedTodos', () => { + it('deletes all completed todos except the ones from a challenge', async () => { + let user = await generateUser({balance: 1}); + let guild = await generateGroup(user); + let challenge = await generateChallenge(user, guild); + + let initialTodoCount = user.tasksOrder.todos.length; + await user.post('/tasks/user', [ + {text: 'todo 1', type: 'todo'}, + {text: 'todo 2', type: 'todo'}, + {text: 'todo 3', type: 'todo'}, + {text: 'todo 4', type: 'todo'}, + {text: 'todo 5', type: 'todo'}, + ]); + + await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'todo 6', + type: 'todo', + }); + + let tasks = await user.get('/tasks/user?type=todos'); + expect(tasks.length).to.equal(initialTodoCount + 6); + + for (let task of tasks) { + if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) { + await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop + } + } + + await user.post('/tasks/clearCompletedTodos'); + let completedTodos = await user.get('/tasks/user?type=completedTodos'); + let todos = await user.get('/tasks/user?type=todos'); + let allTodos = todos.concat(completedTodos); + expect(allTodos.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge) + expect(allTodos[allTodos.length - 1].text).to.equal('todo 6'); + }); +}); diff --git a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js new file mode 100644 index 0000000000..86236fd110 --- /dev/null +++ b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js @@ -0,0 +1,298 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:id/score/:direction', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.gp': 100, + }); + }); + + context('all', () => { + it('requires a task id', async () => { + await expect(user.post('/tasks/123/score/up')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires a task direction', async () => { + await expect(user.post(`/tasks/${generateUUID()}/score/tt`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + }); + + context('todos', () => { + let todo; + + beforeEach(async () => { + todo = await user.post('/tasks/user', { + text: 'test todo', + type: 'todo', + }); + }); + + it('completes todo when direction is up', async () => { + await user.post(`/tasks/${todo._id}/score/up`); + let task = await user.get(`/tasks/${todo._id}`); + + expect(task.completed).to.equal(true); + expect(task.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type + }); + + it('moves completed todos out of user.tasksOrder.todos', async () => { + let getUser = await user.get('/user'); + expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1); + + await user.post(`/tasks/${todo._id}/score/up`); + let updatedTask = await user.get(`/tasks/${todo._id}`); + expect(updatedTask.completed).to.equal(true); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(-1); + }); + + it('moves un-completed todos back into user.tasksOrder.todos', async () => { + let getUser = await user.get('/user'); + expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1); + + await user.post(`/tasks/${todo._id}/score/up`); + await user.post(`/tasks/${todo._id}/score/down`); + + let updatedTask = await user.get(`/tasks/${todo._id}`); + expect(updatedTask.completed).to.equal(false); + + let updatedUser = await user.get('/user'); + let l = updatedUser.tasksOrder.todos.length; + expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).not.to.equal(-1); + expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(l - 1); // Check that it was pushed at the bottom + }); + + it('uncompletes todo when direction is down', async () => { + await user.post(`/tasks/${todo._id}/score/down`); + let updatedTask = await user.get(`/tasks/${todo._id}`); + + expect(updatedTask.completed).to.equal(false); + expect(updatedTask.dateCompleted).to.be.a('undefined'); + }); + + it('scores up todo even if it is already completed'); // Yes? + + it('scores down todo even if it is already uncompleted'); // Yes? + + context('user stats when direction is up', () => { + let updatedUser; + + beforeEach(async () => { + await user.post(`/tasks/${todo._id}/score/up`); + updatedUser = await user.get('/user'); + }); + + it('increases user\'s mp', () => { + expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); + }); + + it('increases user\'s exp', () => { + expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); + }); + + it('increases user\'s gold', () => { + expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); + }); + }); + + context('user stats when direction is down', () => { + let updatedUser; + + beforeEach(async () => { + await user.post(`/tasks/${todo._id}/score/down`); + updatedUser = await user.get('/user'); + }); + + it('decreases user\'s mp', () => { + expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); + }); + + it('decreases user\'s exp', () => { + expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); + }); + + it('decreases user\'s gold', () => { + expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); + }); + }); + }); + + context('dailys', () => { + let daily; + + beforeEach(async () => { + daily = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + }); + }); + + it('completes daily when direction is up', async () => { + await user.post(`/tasks/${daily._id}/score/up`); + let task = await user.get(`/tasks/${daily._id}`); + + expect(task.completed).to.equal(true); + }); + + it('uncompletes daily when direction is down', async () => { + await user.post(`/tasks/${daily._id}/score/down`); + let task = await user.get(`/tasks/${daily._id}`); + + expect(task.completed).to.equal(false); + }); + + it('scores up daily even if it is already completed'); // Yes? + + it('scores down daily even if it is already uncompleted'); // Yes? + + context('user stats when direction is up', () => { + let updatedUser; + + beforeEach(async () => { + await user.post(`/tasks/${daily._id}/score/up`); + updatedUser = await user.get('/user'); + }); + + it('increases user\'s mp', () => { + expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); + }); + + it('increases user\'s exp', () => { + expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); + }); + + it('increases user\'s gold', () => { + expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); + }); + }); + + context('user stats when direction is down', () => { + let updatedUser; + + beforeEach(async () => { + await user.post(`/tasks/${daily._id}/score/down`); + updatedUser = await user.get('/user'); + }); + + it('decreases user\'s mp', () => { + expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); + }); + + it('decreases user\'s exp', () => { + expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); + }); + + it('decreases user\'s gold', () => { + expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); + }); + }); + }); + + context('habits', () => { + let habit, minusHabit, plusHabit, neitherHabit; // eslint-disable-line no-unused-vars + + beforeEach(async () => { + habit = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + + minusHabit = await user.post('/tasks/user', { + text: 'test min habit', + type: 'habit', + up: false, + }); + + plusHabit = await user.post('/tasks/user', { + text: 'test plus habit', + type: 'habit', + down: false, + }); + + neitherHabit = await user.post('/tasks/user', { + text: 'test neither habit', + type: 'habit', + up: false, + down: false, + }); + }); + + it('prevents plus only habit from scoring down'); // Yes? + + it('prevents minus only habit from scoring up'); // Yes? + + it('increases user\'s mp when direction is up', async () => { + await user.post(`/tasks/${habit._id}/score/up`); + let updatedUser = await user.get('/user'); + + expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); + }); + + it('decreases user\'s mp when direction is down', async () => { + await user.post(`/tasks/${habit._id}/score/down`); + let updatedUser = await user.get('/user'); + + expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); + }); + + it('increases user\'s exp when direction is up', async () => { + await user.post(`/tasks/${habit._id}/score/up`); + let updatedUser = await user.get('/user'); + + expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); + }); + + it('increases user\'s gold when direction is up', async () => { + await user.post(`/tasks/${habit._id}/score/up`); + let updatedUser = await user.get('/user'); + + expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); + }); + }); + + context('reward', () => { + let reward, updatedUser; + + beforeEach(async () => { + reward = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + value: 5, + }); + + await user.post(`/tasks/${reward._id}/score/up`); + updatedUser = await user.get('/user'); + }); + + it('purchases reward', () => { + expect(user.stats.gp).to.equal(updatedUser.stats.gp + 5); + }); + + it('does not change user\'s mp', () => { + expect(user.stats.mp).to.equal(updatedUser.stats.mp); + }); + + it('does not change user\'s exp', () => { + expect(user.stats.exp).to.equal(updatedUser.stats.exp); + }); + + it('does not allow a down direction', () => { + expect(user.stats.mp).to.equal(updatedUser.stats.mp); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js b/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js new file mode 100644 index 0000000000..211c7ea975 --- /dev/null +++ b/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js @@ -0,0 +1,84 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:taskId/move/to/:position', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('requires a valid taskId', async () => { + await expect(user.post('/tasks/123/move/to/1')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires a numeric position parameter', async () => { + await expect(user.post(`/tasks/${generateUUID()}/move/to/notANumber`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('taskId must match a valid task', async () => { + await expect(user.post(`/tasks/${generateUUID()}/move/to/1`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('can move task to new position', async () => { + let tasks = await user.post('/tasks/user', [ + {type: 'habit', text: 'habit 1'}, + {type: 'habit', text: 'habit 2'}, + {type: 'daily', text: 'daily 1'}, + {type: 'habit', text: 'habit 3'}, + {type: 'habit', text: 'habit 4'}, + {type: 'todo', text: 'todo 1'}, + {type: 'habit', text: 'habit 5'}, + ]); + + let taskToMove = tasks[1]; + expect(taskToMove.text).to.equal('habit 2'); + let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`); + expect(newOrder[3]).to.equal(taskToMove._id); + expect(newOrder.length).to.equal(5); + }); + + it('can\'t move completed todo', async () => { + let task = await user.post('/tasks/user', {type: 'todo', text: 'todo 1'}); + await user.post(`/tasks/${task._id}/score/up`); + + await expect(user.post(`/tasks/${task._id}/move/to/1`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('cantMoveCompletedTodo'), + }); + }); + + it('can push to bottom', async () => { + let tasks = await user.post('/tasks/user', [ + {type: 'habit', text: 'habit 1'}, + {type: 'habit', text: 'habit 2'}, + {type: 'daily', text: 'daily 1'}, + {type: 'habit', text: 'habit 3'}, + {type: 'habit', text: 'habit 4'}, + {type: 'todo', text: 'todo 1'}, + {type: 'habit', text: 'habit 5'}, + ]); + + let taskToMove = tasks[1]; + expect(taskToMove.text).to.equal('habit 2'); + let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/-1`); + expect(newOrder[4]).to.equal(taskToMove._id); + expect(newOrder.length).to.equal(5); + }); +}); diff --git a/test/api/v3/integration/tasks/POST-tasks_user.test.js b/test/api/v3/integration/tasks/POST-tasks_user.test.js new file mode 100644 index 0000000000..623d9bb4a4 --- /dev/null +++ b/test/api/v3/integration/tasks/POST-tasks_user.test.js @@ -0,0 +1,593 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/user', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + context('validates params', async () => { + it('returns an error if req.body.type is absent', async () => { + await expect(user.post('/tasks/user', { + notType: 'habit', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidTaskType'), + }); + }); + + it('returns an error if req.body.type is not valid', async () => { + await expect(user.post('/tasks/user', { + type: 'habitF', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidTaskType'), + }); + }); + + it('returns an error if one object inside an array is invalid', async () => { + await expect(user.post('/tasks/user', [ + {type: 'habitF'}, + {type: 'habit'}, + ])).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidTaskType'), + }); + }); + + it('returns an error if req.body.text is absent', async () => { + await expect(user.post('/tasks/user', { + type: 'habit', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'habit validation failed', + }); + }); + + it('does not update user.tasksOrder.{taskType} when the task is not saved because invalid', async () => { + let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits; + await expect(user.post('/tasks/user', { + type: 'habit', + })).to.eventually.be.rejected.and.eql({ // this block is necessary + code: 400, + error: 'BadRequest', + message: 'habit validation failed', + }); + + let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits; + expect(updatedHabitsOrder).to.eql(originalHabitsOrder); + }); + + it('does not update user.tasksOrder.{taskType} when a task inside an array is not saved because invalid', async () => { + let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits; + await expect(user.post('/tasks/user', [ + {type: 'habit'}, // Missing text + {type: 'habit', text: 'valid'}, // Valid + ])).to.eventually.be.rejected.and.eql({ // this block is necessary + code: 400, + error: 'BadRequest', + message: 'habit validation failed', + }); + + let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits; + expect(updatedHabitsOrder).to.eql(originalHabitsOrder); + }); + + it('does not save any task sent in an array when 1 is invalid', async () => { + let originalTasks = await user.get('/tasks/user'); + await expect(user.post('/tasks/user', [ + {type: 'habit'}, // Missing text + {type: 'habit', text: 'valid'}, // Valid + ])).to.eventually.be.rejected.and.eql({ // this block is necessary + code: 400, + error: 'BadRequest', + message: 'habit validation failed', + }).then(async () => { + let updatedTasks = await user.get('/tasks/user'); + + expect(updatedTasks).to.eql(originalTasks); + }); + }); + + it('automatically sets "task.userId" to user\'s uuid', async () => { + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + + expect(task.userId).to.equal(user._id); + }); + + it(`ignores setting userId, history, createdAt, + updatedAt, challenge, completed, + dateCompleted fields`, async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + userId: 123, + history: [123], + createdAt: 'yesterday', + updatedAt: 'tomorrow', + challenge: 'no', + completed: true, + dateCompleted: 'never', + value: 324, // ignored because not a reward + }); + + expect(task.userId).to.equal(user._id); + expect(task.history).to.eql([]); + expect(task.createdAt).not.to.equal('yesterday'); + expect(task.updatedAt).not.to.equal('tomorrow'); + expect(task.challenge).not.to.equal('no'); + expect(task.completed).to.equal(false); + expect(task.streak).not.to.equal('never'); + expect(task.value).not.to.equal(324); + }); + + it('ignores invalid fields', async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + notValid: true, + }); + + expect(task).not.to.have.property('notValid'); + }); + }); + + context('all types', () => { + it('can create reminders', async () => { + let id1 = generateUUID(); + + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + reminders: [ + {id: id1, startDate: new Date(), time: new Date()}, + ], + }); + + expect(task.reminders).to.be.an('array'); + expect(task.reminders.length).to.eql(1); + expect(task.reminders[0]).to.be.an('object'); + expect(task.reminders[0].id).to.eql(id1); + expect(task.reminders[0].startDate).to.be.a('string'); // json doesn't have dates + expect(task.reminders[0].time).to.be.a('string'); + }); + }); + + context('habits', () => { + it('creates a habit', async () => { + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + }); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test habit'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('habit'); + expect(task.up).to.eql(false); + expect(task.down).to.eql(true); + }); + + it('updates user.tasksOrder.habits when a new habit is created', async () => { + let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length; + let task = await user.post('/tasks/user', { + type: 'habit', + text: 'an habit', + }); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.habits[0]).to.eql(task._id); + expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 1); + }); + + it('updates user.tasksOrder.habits when multiple habits are created', async () => { + let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length; + let [task, task2] = await user.post('/tasks/user', [{ + type: 'habit', + text: 'an habit', + }, { + type: 'habit', + text: 'another habit', + }]); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.habits[0]).to.eql(task2._id); + expect(updatedUser.tasksOrder.habits[1]).to.eql(task._id); + expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 2); + }); + + it('creates multiple habits', async () => { + let [task, task2] = await user.post('/tasks/user', [{ + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + }, { + text: 'test habit 2', + type: 'habit', + up: true, + down: false, + notes: 1977, + }]); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test habit'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('habit'); + expect(task.up).to.eql(false); + expect(task.down).to.eql(true); + + expect(task2.userId).to.equal(user._id); + expect(task2.text).to.eql('test habit 2'); + expect(task2.notes).to.eql('1977'); + expect(task2.type).to.eql('habit'); + expect(task2.up).to.eql(true); + expect(task2.down).to.eql(false); + }); + + it('defaults to setting up and down to true', async () => { + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + notes: 1976, + }); + + expect(task.up).to.eql(true); + expect(task.down).to.eql(true); + }); + + it('cannot create checklists', async () => { + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + checklist: [ + {_id: 123, completed: false, text: 'checklist'}, + ], + }); + + expect(task).not.to.have.property('checklist'); + }); + }); + + context('todos', () => { + it('creates a todo', async () => { + let task = await user.post('/tasks/user', { + text: 'test todo', + type: 'todo', + notes: 1976, + }); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test todo'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('todo'); + }); + + it('creates multiple todos', async () => { + let [task, task2] = await user.post('/tasks/user', [{ + text: 'test todo', + type: 'todo', + notes: 1976, + }, { + text: 'test todo 2', + type: 'todo', + notes: 1977, + }]); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test todo'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('todo'); + + expect(task2.userId).to.equal(user._id); + expect(task2.text).to.eql('test todo 2'); + expect(task2.notes).to.eql('1977'); + expect(task2.type).to.eql('todo'); + }); + + it('updates user.tasksOrder.todos when a new todo is created', async () => { + let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length; + let task = await user.post('/tasks/user', { + type: 'todo', + text: 'a todo', + }); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.todos[0]).to.eql(task._id); + expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 1); + }); + + it('updates user.tasksOrder.todos when multiple todos are created', async () => { + let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length; + let [task, task2] = await user.post('/tasks/user', [{ + type: 'todo', + text: 'a todo', + }, { + type: 'todo', + text: 'another todo', + }]); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.todos[0]).to.eql(task2._id); + expect(updatedUser.tasksOrder.todos[1]).to.eql(task._id); + expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 2); + }); + + it('can create checklists', async () => { + let task = await user.post('/tasks/user', { + text: 'test todo', + type: 'todo', + checklist: [ + {completed: false, text: 'checklist'}, + ], + }); + + expect(task.checklist).to.be.an('array'); + expect(task.checklist.length).to.eql(1); + expect(task.checklist[0]).to.be.an('object'); + expect(task.checklist[0].text).to.eql('checklist'); + expect(task.checklist[0].completed).to.eql(false); + expect(task.checklist[0].id).to.be.a('string'); + }); + }); + + context('dailys', () => { + it('creates a daily', async () => { + let now = new Date(); + + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + notes: 1976, + frequency: 'daily', + everyX: 5, + startDate: now, + }); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test daily'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('daily'); + expect(task.frequency).to.eql('daily'); + expect(task.everyX).to.eql(5); + expect(new Date(task.startDate)).to.eql(now); + }); + + it('creates multiple dailys', async () => { + let [task, task2] = await user.post('/tasks/user', [{ + text: 'test daily', + type: 'daily', + notes: 1976, + }, { + text: 'test daily 2', + type: 'daily', + notes: 1977, + }]); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test daily'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('daily'); + + expect(task2.userId).to.equal(user._id); + expect(task2.text).to.eql('test daily 2'); + expect(task2.notes).to.eql('1977'); + expect(task2.type).to.eql('daily'); + }); + + it('updates user.tasksOrder.dailys when a new daily is created', async () => { + let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length; + let task = await user.post('/tasks/user', { + type: 'daily', + text: 'a daily', + }); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.dailys[0]).to.eql(task._id); + expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 1); + }); + + it('updates user.tasksOrder.dailys when multiple dailys are created', async () => { + let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length; + let [task, task2] = await user.post('/tasks/user', [{ + type: 'daily', + text: 'a daily', + }, { + type: 'daily', + text: 'another daily', + }]); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.dailys[0]).to.eql(task2._id); + expect(updatedUser.tasksOrder.dailys[1]).to.eql(task._id); + expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 2); + }); + + it('defaults to a weekly frequency, with every day set', async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + }); + + expect(task.frequency).to.eql('weekly'); + expect(task.everyX).to.eql(1); + expect(task.repeat).to.eql({ + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + su: true, + }); + }); + + it('allows repeat field to be configured', async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + repeat: { + m: false, + w: false, + su: false, + }, + }); + + expect(task.repeat).to.eql({ + m: false, + t: true, + w: false, + th: true, + f: true, + s: true, + su: false, + }); + }); + + it('defaults startDate to today', async () => { + let today = (new Date()).getDay(); + + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + }); + + expect((new Date(task.startDate)).getDay()).to.eql(today); + }); + + it('can create checklists', async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + checklist: [ + {completed: false, text: 'checklist'}, + ], + }); + + expect(task.checklist).to.be.an('array'); + expect(task.checklist.length).to.eql(1); + expect(task.checklist[0]).to.be.an('object'); + expect(task.checklist[0].text).to.eql('checklist'); + expect(task.checklist[0].completed).to.eql(false); + expect(task.checklist[0].id).to.be.a('string'); + }); + }); + + context('rewards', () => { + it('creates a reward', async () => { + let task = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + notes: 1976, + value: 10, + }); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test reward'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('reward'); + expect(task.value).to.eql(10); + }); + + it('creates multiple rewards', async () => { + let [task, task2] = await user.post('/tasks/user', [{ + text: 'test reward', + type: 'reward', + notes: 1976, + value: 11, + }, { + text: 'test reward 2', + type: 'reward', + notes: 1977, + value: 12, + }]); + + expect(task.userId).to.equal(user._id); + expect(task.text).to.eql('test reward'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('reward'); + expect(task.value).to.eql(11); + + expect(task2.userId).to.equal(user._id); + expect(task2.text).to.eql('test reward 2'); + expect(task2.notes).to.eql('1977'); + expect(task2.type).to.eql('reward'); + expect(task2.value).to.eql(12); + }); + + it('updates user.tasksOrder.rewards when a new reward is created', async () => { + let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length; + let task = await user.post('/tasks/user', { + type: 'reward', + text: 'a reward', + }); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.rewards[0]).to.eql(task._id); + expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 1); + }); + + it('updates user.tasksOrder.dreward when multiple rewards are created', async () => { + let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length; + let [task, task2] = await user.post('/tasks/user', [{ + type: 'reward', + text: 'a reward', + }, { + type: 'reward', + text: 'another reward', + }]); + + let updatedUser = await user.get('/user'); + expect(updatedUser.tasksOrder.rewards[0]).to.eql(task2._id); + expect(updatedUser.tasksOrder.rewards[1]).to.eql(task._id); + expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 2); + }); + + it('defaults to a 0 value', async () => { + let task = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + }); + + expect(task.value).to.eql(0); + }); + + it('requires value to be coerced into a number', async () => { + let task = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + value: '10', + }); + + expect(task.value).to.eql(10); + }); + + it('cannot create checklists', async () => { + let task = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + checklist: [ + {_id: 123, completed: false, text: 'checklist'}, + ], + }); + + expect(task).not.to.have.property('checklist'); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js new file mode 100644 index 0000000000..88343cb47c --- /dev/null +++ b/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js @@ -0,0 +1,323 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('PUT /tasks/:id', () => { + let user; + let guild; + let challenge; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + context('errors', () => { + let task; + + beforeEach(async () => { + task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + }); + + it('returns error when incorrect id is passed', async () => { + await expect(user.put(`/tasks/${generateUUID()}`, { + text: 'some new text', + up: false, + down: false, + notes: 'some new notes', + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('returns error when user is not a member of the challenge', async () => { + let anotherUser = await generateUser(); + + await expect(anotherUser.put(`/tasks/${task._id}`, { + text: 'some new text', + up: false, + down: false, + notes: 'some new notes', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + }); + + context('validates params', () => { + let task; + + beforeEach(async () => { + task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + }); + + it(`ignores setting _id, type, userId, history, createdAt, + updatedAt, challenge, completed, streak, + dateCompleted fields`, async () => { + let savedTask = await user.put(`/tasks/${task._id}`, { + _id: 123, + type: 'daily', + userId: 123, + history: [123], + createdAt: 'yesterday', + updatedAt: 'tomorrow', + challenge: 'no', + completed: true, + streak: 25, + dateCompleted: 'never', + value: 324, // ignored because not a reward + }); + + expect(savedTask._id).to.equal(task._id); + expect(savedTask.type).to.equal(task.type); + expect(savedTask.userId).to.equal(task.userId); + expect(savedTask.history).to.eql(task.history); + expect(savedTask.createdAt).to.equal(task.createdAt); + expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt); + expect(savedTask.challenge._id).to.equal(task.challenge._id); + expect(savedTask.completed).to.equal(task.completed); + expect(savedTask.streak).to.equal(task.streak); + expect(savedTask.dateCompleted).to.equal(task.dateCompleted); + expect(savedTask.value).to.equal(task.value); + }); + + it('ignores invalid fields', async () => { + let savedTask = await user.put(`/tasks/${task._id}`, { + notValid: true, + }); + + expect(savedTask.notValid).to.be.undefined; + }); + }); + + context('habits', () => { + let habit; + + beforeEach(async () => { + habit = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + notes: 1976, + }); + }); + + it('updates a habit', async () => { + let savedHabit = await user.put(`/tasks/${habit._id}`, { + text: 'some new text', + up: false, + down: false, + notes: 'some new notes', + }); + + expect(savedHabit.text).to.eql('some new text'); + expect(savedHabit.notes).to.eql('some new notes'); + expect(savedHabit.up).to.eql(false); + expect(savedHabit.down).to.eql(false); + }); + }); + + context('todos', () => { + let todo; + + beforeEach(async () => { + todo = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test todo', + type: 'todo', + notes: 1976, + }); + }); + + it('updates a todo', async () => { + let savedTodo = await user.put(`/tasks/${todo._id}`, { + text: 'some new text', + notes: 'some new notes', + }); + + expect(savedTodo.text).to.eql('some new text'); + expect(savedTodo.notes).to.eql('some new notes'); + }); + + it('can update checklists (replace it)', async () => { + await user.put(`/tasks/${todo._id}`, { + checklist: [ + {text: 123, completed: false}, + {text: 456, completed: true}, + ], + }); + + let savedTodo = await user.put(`/tasks/${todo._id}`, { + checklist: [ + {text: 789, completed: false}, + ], + }); + + expect(savedTodo.checklist.length).to.equal(1); + expect(savedTodo.checklist[0].text).to.equal('789'); + expect(savedTodo.checklist[0].completed).to.equal(false); + }); + + it('can update tags (replace them)', async () => { + let finalUUID = generateUUID(); + await user.put(`/tasks/${todo._id}`, { + tags: [generateUUID(), generateUUID()], + }); + + let savedTodo = await user.put(`/tasks/${todo._id}`, { + tags: [finalUUID], + }); + + expect(savedTodo.tags.length).to.equal(1); + expect(savedTodo.tags[0]).to.equal(finalUUID); + }); + }); + + context('dailys', () => { + let daily; + + beforeEach(async () => { + daily = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test daily', + type: 'daily', + notes: 1976, + }); + }); + + it('updates a daily', async () => { + let savedDaily = await user.put(`/tasks/${daily._id}`, { + text: 'some new text', + notes: 'some new notes', + frequency: 'daily', + everyX: 5, + }); + + expect(savedDaily.text).to.eql('some new text'); + expect(savedDaily.notes).to.eql('some new notes'); + expect(savedDaily.frequency).to.eql('daily'); + expect(savedDaily.everyX).to.eql(5); + }); + + it('can update checklists (replace it)', async () => { + await user.put(`/tasks/${daily._id}`, { + checklist: [ + {text: 123, completed: false}, + {text: 456, completed: true}, + ], + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + checklist: [ + {text: 789, completed: false}, + ], + }); + + expect(savedDaily.checklist.length).to.equal(1); + expect(savedDaily.checklist[0].text).to.equal('789'); + expect(savedDaily.checklist[0].completed).to.equal(false); + }); + + it('can update tags (replace them)', async () => { + let finalUUID = generateUUID(); + await user.put(`/tasks/${daily._id}`, { + tags: [generateUUID(), generateUUID()], + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + tags: [finalUUID], + }); + + expect(savedDaily.tags.length).to.equal(1); + expect(savedDaily.tags[0]).to.equal(finalUUID); + }); + + it('updates repeat, even if frequency is set to daily', async () => { + await user.put(`/tasks/${daily._id}`, { + frequency: 'daily', + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + repeat: { + m: false, + su: false, + }, + }); + + expect(savedDaily.repeat).to.eql({ + m: false, + t: true, + w: true, + th: true, + f: true, + s: true, + su: false, + }); + }); + + it('updates everyX, even if frequency is set to weekly', async () => { + await user.put(`/tasks/${daily._id}`, { + frequency: 'weekly', + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + everyX: 5, + }); + + expect(savedDaily.everyX).to.eql(5); + }); + + it('defaults startDate to today if none date object is passed in', async () => { + let savedDaily = await user.put(`/tasks/${daily._id}`, { + frequency: 'weekly', + }); + + expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay()); + }); + }); + + context('rewards', () => { + let reward; + + beforeEach(async () => { + reward = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test reward', + type: 'reward', + notes: 1976, + value: 10, + }); + }); + + it('updates a reward', async () => { + let savedReward = await user.put(`/tasks/${reward._id}`, { + text: 'some new text', + notes: 'some new notes', + value: 11, + }); + + expect(savedReward.text).to.eql('some new text'); + expect(savedReward.notes).to.eql('some new notes'); + expect(savedReward.value).to.eql(11); + }); + + it('requires value to be coerced into a number', async () => { + let savedReward = await user.put(`/tasks/${reward._id}`, { + value: '100', + }); + + expect(savedReward.value).to.eql(100); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js new file mode 100644 index 0000000000..1325f8b392 --- /dev/null +++ b/test/api/v3/integration/tasks/PUT-tasks_id.test.js @@ -0,0 +1,396 @@ +import { + generateUser, + generateGroup, + sleep, + generateChallenge, +} from '../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('PUT /tasks/:id', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + context('validates params', () => { + let task; + + beforeEach(async () => { + task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + }); + + it(`ignores setting _id, type, userId, history, createdAt, + updatedAt, challenge, completed, streak, + dateCompleted fields`, async () => { + let savedTask = await user.put(`/tasks/${task._id}`, { + _id: 123, + type: 'daily', + userId: 123, + history: [123], + createdAt: 'yesterday', + updatedAt: 'tomorrow', + challenge: 'no', + completed: true, + streak: 25, + dateCompleted: 'never', + }); + + expect(savedTask._id).to.equal(task._id); + expect(savedTask.type).to.equal(task.type); + expect(savedTask.userId).to.equal(task.userId); + expect(savedTask.history).to.eql(task.history); + expect(savedTask.createdAt).to.equal(task.createdAt); + expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt); + expect(savedTask.challenge).to.equal(task.challenge); + expect(savedTask.completed).to.eql(task.completed); + expect(savedTask.streak).to.equal(savedTask.streak); // it's an habit, dailies can change it + expect(savedTask.dateCompleted).to.equal(task.dateCompleted); + }); + + it('ignores invalid fields', async () => { + let savedTask = await user.put(`/tasks/${task._id}`, { + notValid: true, + }); + + expect(savedTask.notValid).to.be.undefined; + }); + + it(`only allows setting streak, reminders, checklist, notes, attribute, tags + fields for challenge tasks owned by a user`, async () => { + let guild = await generateGroup(user); + let challenge = await generateChallenge(user, guild); + + let challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily in challenge', + reminders: [ + {time: new Date(), startDate: new Date()}, + ], + checklist: [ + {text: 123, completed: false}, + ], + }); + await sleep(2); + + await user.sync(); + + // Pick challenge task + let challengeUserTaskId = user.tasksOrder.dailys[user.tasksOrder.dailys.length - 1]; + + let challengeUserTask = await user.get(`/tasks/${challengeUserTaskId}`); + + let savedChallengeUserTask = await user.put(`/tasks/${challengeUserTaskId}`, { + _id: 123, + type: 'daily', + userId: 123, + history: [123], + createdAt: 'yesterday', + updatedAt: 'tomorrow', + challenge: 'no', + completed: true, + streak: 25, + priority: 1.5, + repeat: { + m: false, + }, + everyX: 15, + frequency: 'weekly', + text: 'new text', + dateCompleted: 'never', + reminders: [ + {time: new Date(), startDate: new Date()}, + {time: new Date(), startDate: new Date()}, + ], + checklist: [ + {text: 123, completed: false}, + {text: 456, completed: true}, + ], + notes: 'new notes', + attribute: 'per', + tags: [challengeUserTaskId], + }); + + // original task is not touched + let updatedChallengeTask = await user.get(`/tasks/${challengeTask._id}`); + expect(updatedChallengeTask).to.eql(challengeTask); + + // ignored + expect(savedChallengeUserTask._id).to.equal(challengeUserTask._id); + expect(savedChallengeUserTask.type).to.equal(challengeUserTask.type); + expect(savedChallengeUserTask.repeat.m).to.equal(true); + expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority); + expect(savedChallengeUserTask.frequency).to.equal(challengeUserTask.frequency); + expect(savedChallengeUserTask.userId).to.equal(challengeUserTask.userId); + expect(savedChallengeUserTask.text).to.equal(challengeUserTask.text); + expect(savedChallengeUserTask.history).to.eql(challengeUserTask.history); + expect(savedChallengeUserTask.createdAt).to.equal(challengeUserTask.createdAt); + expect(savedChallengeUserTask.updatedAt).to.be.greaterThan(challengeUserTask.updatedAt); + expect(savedChallengeUserTask.challenge).to.eql(challengeUserTask.challenge); + expect(savedChallengeUserTask.completed).to.equal(challengeUserTask.completed); + expect(savedChallengeUserTask.dateCompleted).to.equal(challengeUserTask.dateCompleted); + expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority); + + // changed + expect(savedChallengeUserTask.notes).to.equal('new notes'); + expect(savedChallengeUserTask.attribute).to.equal('per'); + expect(savedChallengeUserTask.tags).to.eql([challengeUserTaskId]); + expect(savedChallengeUserTask.streak).to.equal(25); + expect(savedChallengeUserTask.reminders.length).to.equal(2); + expect(savedChallengeUserTask.checklist.length).to.equal(2); + }); + }); + + context('all types', () => { + let daily; + + beforeEach(async () => { + daily = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + notes: 1976, + }); + }); + + it('can update reminders (replace them)', async () => { + await user.put(`/tasks/${daily._id}`, { + reminders: [ + {time: new Date(), startDate: new Date()}, + ], + }); + + let id1 = generateUUID(); + let id2 = generateUUID(); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + reminders: [ + {id: id1, time: new Date(), startDate: new Date()}, + {id: id2, time: new Date(), startDate: new Date()}, + ], + }); + + expect(savedDaily.reminders.length).to.equal(2); + expect(savedDaily.reminders[0].id).to.equal(id1); + expect(savedDaily.reminders[1].id).to.equal(id2); + }); + }); + + context('habits', () => { + let habit; + + beforeEach(async () => { + habit = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + notes: 1976, + }); + }); + + it('updates a habit', async () => { + let savedHabit = await user.put(`/tasks/${habit._id}`, { + text: 'some new text', + up: false, + down: false, + notes: 'some new notes', + }); + + expect(savedHabit.text).to.eql('some new text'); + expect(savedHabit.notes).to.eql('some new notes'); + expect(savedHabit.up).to.eql(false); + expect(savedHabit.down).to.eql(false); + }); + }); + + context('todos', () => { + let todo; + + beforeEach(async () => { + todo = await user.post('/tasks/user', { + text: 'test todo', + type: 'todo', + notes: 1976, + }); + }); + + it('updates a todo', async () => { + let savedTodo = await user.put(`/tasks/${todo._id}`, { + text: 'some new text', + notes: 'some new notes', + }); + + expect(savedTodo.text).to.eql('some new text'); + expect(savedTodo.notes).to.eql('some new notes'); + }); + + it('can update checklists (replace it)', async () => { + await user.put(`/tasks/${todo._id}`, { + checklist: [ + {text: 123, completed: false}, + {text: 456, completed: true}, + ], + }); + + let savedTodo = await user.put(`/tasks/${todo._id}`, { + checklist: [ + {text: 789, completed: false}, + ], + }); + + expect(savedTodo.checklist.length).to.equal(1); + expect(savedTodo.checklist[0].text).to.equal('789'); + expect(savedTodo.checklist[0].completed).to.equal(false); + }); + + it('can update tags (replace them)', async () => { + let finalUUID = generateUUID(); + await user.put(`/tasks/${todo._id}`, { + tags: [generateUUID(), generateUUID()], + }); + + let savedTodo = await user.put(`/tasks/${todo._id}`, { + tags: [finalUUID], + }); + + expect(savedTodo.tags.length).to.equal(1); + expect(savedTodo.tags[0]).to.equal(finalUUID); + }); + }); + + context('dailys', () => { + let daily; + + beforeEach(async () => { + daily = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + notes: 1976, + }); + }); + + it('updates a daily', async () => { + let savedDaily = await user.put(`/tasks/${daily._id}`, { + text: 'some new text', + notes: 'some new notes', + frequency: 'daily', + everyX: 5, + }); + + expect(savedDaily.text).to.eql('some new text'); + expect(savedDaily.notes).to.eql('some new notes'); + expect(savedDaily.frequency).to.eql('daily'); + expect(savedDaily.everyX).to.eql(5); + }); + + it('can update checklists (replace it)', async () => { + await user.put(`/tasks/${daily._id}`, { + checklist: [ + {text: 123, completed: false}, + {text: 456, completed: true}, + ], + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + checklist: [ + {text: 789, completed: false}, + ], + }); + + expect(savedDaily.checklist.length).to.equal(1); + expect(savedDaily.checklist[0].text).to.equal('789'); + expect(savedDaily.checklist[0].completed).to.equal(false); + }); + + it('can update tags (replace them)', async () => { + let finalUUID = generateUUID(); + await user.put(`/tasks/${daily._id}`, { + tags: [generateUUID(), generateUUID()], + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + tags: [finalUUID], + }); + + expect(savedDaily.tags.length).to.equal(1); + expect(savedDaily.tags[0]).to.equal(finalUUID); + }); + + it('updates repeat, even if frequency is set to daily', async () => { + await user.put(`/tasks/${daily._id}`, { + frequency: 'daily', + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + repeat: { + m: false, + su: false, + }, + }); + + expect(savedDaily.repeat).to.eql({ + m: false, + t: true, + w: true, + th: true, + f: true, + s: true, + su: false, + }); + }); + + it('updates everyX, even if frequency is set to weekly', async () => { + await user.put(`/tasks/${daily._id}`, { + frequency: 'weekly', + }); + + let savedDaily = await user.put(`/tasks/${daily._id}`, { + everyX: 5, + }); + + expect(savedDaily.everyX).to.eql(5); + }); + + it('defaults startDate to today if none date object is passed in', async () => { + let savedDaily = await user.put(`/tasks/${daily._id}`, { + frequency: 'weekly', + }); + + expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay()); + }); + }); + + context('rewards', () => { + let reward; + + beforeEach(async () => { + reward = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + notes: 1976, + value: 10, + }); + }); + + it('updates a reward', async () => { + let savedReward = await user.put(`/tasks/${reward._id}`, { + text: 'some new text', + notes: 'some new notes', + value: 10, + }); + + expect(savedReward.text).to.eql('some new text'); + expect(savedReward.notes).to.eql('some new notes'); + expect(savedReward.value).to.eql(10); + }); + + it('requires value to be coerced into a number', async () => { + let savedReward = await user.put(`/tasks/${reward._id}`, { + value: '100', + }); + + expect(savedReward.value).to.eql(100); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js new file mode 100644 index 0000000000..eac6d455ea --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js @@ -0,0 +1,115 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /tasks/:taskId/checklist/:itemId', () => { + let user; + let guild; + let challenge; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('fails on task not found', async () => { + await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('fails on checklist item not found', async () => { + let createdTask = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'daily with checklist', + }); + + await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); + + it('returns error when user is not a member of the challenge', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + let anotherUser = await generateUser(); + + await expect(anotherUser.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + + it('deletes a checklist item from a daily', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); + + await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); + savedTask = await user.get(`/tasks/${task._id}`); + + expect(savedTask.checklist.length).to.equal(0); + }); + + it('deletes a checklist item from a todo', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'todo', + text: 'Todo with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); + + await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); + savedTask = await user.get(`/tasks/${task._id}`); + + expect(savedTask.checklist.length).to.equal(0); + }); + + it('does not work with habits', async () => { + let habit = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('does not work with rewards', async () => { + let reward = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js new file mode 100644 index 0000000000..34c93c2f05 --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js @@ -0,0 +1,115 @@ +import { + generateUser, + generateGroup, + generateChallenge, + sleep, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /tasks/:id', () => { + let user; + let guild; + let challenge; + let task; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + beforeEach(async () => { + task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + }); + + it('cannot delete a non-existant task', async () => { + await expect(user.del(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('returns error when user is not leader of the challenge', async () => { + let anotherUser = await generateUser(); + + await expect(anotherUser.del(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + + it('deletes a user\'s task', async () => { + await user.del(`/tasks/${task._id}`); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + context('challenge member', () => { + let anotherUser; + let anotherUsersNewChallengeTaskID; + let newChallengeTask; + + beforeEach(async () => { + anotherUser = await generateUser(); + await user.post(`/groups/${guild._id}/invite`, { uuids: [anotherUser._id] }); + await anotherUser.post(`/groups/${guild._id}/join`); + await anotherUser.post(`/challenges/${challenge._id}/join`); + + newChallengeTask = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + + let anotherUserWithNewChallengeTask = await anotherUser.get('/user'); + anotherUsersNewChallengeTaskID = anotherUserWithNewChallengeTask.tasksOrder.habits[0]; + }); + + it('returns error when user attempts to delete an active challenge task', async () => { + await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`)) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cantDeleteChallengeTasks'), + }); + }); + + it('allows user to delete challenge task after user leaves challenge', async () => { + await anotherUser.post(`/challenges/${challenge._id}/leave`); + + await anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`); + + await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + // TODO for some reason this test fails on TravisCI, review after mongodb indexes have been added + xit('allows user to delete challenge task after challenge task is broken', async () => { + await expect(user.del(`/tasks/${newChallengeTask._id}`)); + + await sleep(2); + + await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`)); + + await sleep(2); + + await expect(anotherUser.get(`/tasks/${anotherUsersNewChallengeTaskID}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js b/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js new file mode 100644 index 0000000000..2399db04b6 --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js @@ -0,0 +1,86 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; +import { each } from 'lodash'; + +describe('GET /tasks/challenge/:challengeId', () => { + let user; + let guild; + let challenge; + let task; + let tasks = []; + let challengeWithTask; + let tasksToTest = { + habit: { + text: 'test habit', + type: 'habit', + up: false, + down: true, + }, + todo: { + text: 'test todo', + type: 'todo', + }, + daily: { + text: 'test daily', + type: 'daily', + frequency: 'daily', + everyX: 5, + startDate: new Date(), + }, + reward: { + text: 'test reward', + type: 'reward', + }, + }; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('returns error when challenge is not found', async () => { + let dummyId = generateUUID(); + + await expect(user.get(`/tasks/challenge/${dummyId}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + each(tasksToTest, (taskValue, taskType) => { + context(`${taskType}`, () => { + before(async () => { + task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue); + tasks.push(task); + challengeWithTask = await user.get(`/challenges/${challenge._id}`); + }); + + it('gets challenge tasks', async () => { + let getTask = await user.get(`/tasks/challenge/${challengeWithTask._id}`); + expect(getTask).to.eql(tasks); + }); + + it('gets challenge tasks filtered by type', async () => { + let challengeTasks = await user.get(`/tasks/challenge/${challengeWithTask._id}?type=${task.type}s`); + expect(challengeTasks).to.eql([task]); + }); + + it('cannot get a task owned by someone else', async () => { + let anotherUser = await generateUser(); + + await expect(anotherUser.get(`/tasks/challenge/${challengeWithTask._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js new file mode 100644 index 0000000000..06bc7b68b2 --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js @@ -0,0 +1,119 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:taskId/checklist/', () => { + let user; + let guild; + let challenge; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('fails on task not found', async () => { + await expect(user.post(`/tasks/${generateUUID()}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('returns error when user is not a member of the challenge', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily with checklist', + }); + + let anotherUser = await generateUser(); + + await expect(anotherUser.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + ignored: false, + _id: 123, + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + + it('adds a checklist item to a daily', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + ignored: false, + _id: 123, + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); + expect(savedTask.checklist[0].completed).to.equal(false); + expect(savedTask.checklist[0].id).to.be.a('string'); + expect(savedTask.checklist[0].id).to.not.equal('123'); + expect(savedTask.checklist[0].ignored).to.be.an('undefined'); + }); + + it('adds a checklist item to a todo', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'todo', + text: 'Todo with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + ignored: false, + _id: 123, + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); + expect(savedTask.checklist[0].completed).to.equal(false); + expect(savedTask.checklist[0].id).to.be.a('string'); + expect(savedTask.checklist[0].id).to.not.equal('123'); + expect(savedTask.checklist[0].ignored).to.be.an('undefined'); + }); + + it('does not add a checklist to habits', async () => { + let habit = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.post(`/tasks/${habit._id}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('does not add a checklist to rewards', async () => { + let reward = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.post(`/tasks/${reward._id}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js new file mode 100644 index 0000000000..62899ffb59 --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js @@ -0,0 +1,125 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../../helpers/api-v3-integration.helper'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/challenge/:challengeId', () => { + let user; + let guild; + let challenge; + + beforeEach(async () => { + user = await generateUser({balance: 1}); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('returns error when challenge is not found', async () => { + let fakeChallengeId = generateUUID(); + + await expect(user.post(`/tasks/challenge/${fakeChallengeId}`, { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('returns error when user does not have the challenge', async () => { + let userWithoutChallenge = await generateUser(); + + await expect(userWithoutChallenge.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('challengeNotFound'), + }); + }); + + it('returns error when non leader tries to edit challenge', async () => { + let userThatIsNotLeaderOfChallenge = await generateUser({ + challenges: [challenge._id], + }); + + await expect(userThatIsNotLeaderOfChallenge.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + + it('creates a habit', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + up: false, + down: true, + notes: 1976, + }); + let challengeWithTask = await user.get(`/challenges/${challenge._id}`); + + expect(challengeWithTask.tasksOrder.habits.indexOf(task._id)).to.be.above(-1); + expect(task.challenge.id).to.equal(challenge._id); + expect(task.text).to.eql('test habit'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('habit'); + expect(task.up).to.eql(false); + expect(task.down).to.eql(true); + }); + + it('creates a todo', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test todo', + type: 'todo', + notes: 1976, + }); + let challengeWithTask = await user.get(`/challenges/${challenge._id}`); + + expect(challengeWithTask.tasksOrder.todos.indexOf(task._id)).to.be.above(-1); + expect(task.challenge.id).to.equal(challenge._id); + expect(task.text).to.eql('test todo'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('todo'); + }); + + it('creates a daily', async () => { + let now = new Date(); + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test daily', + type: 'daily', + notes: 1976, + frequency: 'daily', + everyX: 5, + startDate: now, + }); + let challengeWithTask = await user.get(`/challenges/${challenge._id}`); + + expect(challengeWithTask.tasksOrder.dailys.indexOf(task._id)).to.be.above(-1); + expect(task.challenge.id).to.equal(challenge._id); + expect(task.text).to.eql('test daily'); + expect(task.notes).to.eql('1976'); + expect(task.type).to.eql('daily'); + expect(task.frequency).to.eql('daily'); + expect(task.everyX).to.eql(5); + expect(new Date(task.startDate)).to.eql(now); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js new file mode 100644 index 0000000000..a264826884 --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js @@ -0,0 +1,140 @@ +import { + generateUser, + generateGroup, + generateChallenge, +} from '../../../../../helpers/api-integration/v3'; +import { find } from 'lodash'; + +describe('POST /tasks/:id/score/:direction', () => { + let user; + let guild; + let challenge; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + context('habits', () => { + let habit; + let usersChallengeTaskId; + let previousTaskHistory; + + before(async () => { + habit = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test habit', + type: 'habit', + }); + let updatedUser = await user.get('/user'); + usersChallengeTaskId = updatedUser.tasksOrder.habits[0]; + }); + + it('scores and adds history', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: habit._id}); + previousTaskHistory = task.history[0]; + + expect(task.value).to.equal(1); + expect(task.history).to.have.lengthOf(1); + }); + + it('should update the history', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: habit._id}); + + expect(task.history).to.have.lengthOf(1); + expect(task.history[0].date).to.not.equal(previousTaskHistory.date); + expect(task.history[0].value).to.not.equal(previousTaskHistory.value); + }); + }); + + context('dailies', () => { + let daily; + let usersChallengeTaskId; + let previousTaskHistory; + + before(async () => { + daily = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test daily', + type: 'daily', + }); + let updatedUser = await user.get('/user'); + usersChallengeTaskId = updatedUser.tasksOrder.dailys[0]; + }); + + it('it scores and adds history', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: daily._id}); + previousTaskHistory = task.history[0]; + + expect(task.history).to.have.lengthOf(1); + expect(task.value).to.equal(1); + }); + + it('should update the history', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: daily._id}); + + expect(task.history).to.have.lengthOf(1); + expect(task.history[0].date).to.not.equal(previousTaskHistory.date); + expect(task.history[0].value).to.not.equal(previousTaskHistory.value); + }); + }); + + context('todos', () => { + let todo; + let usersChallengeTaskId; + + before(async () => { + todo = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test todo', + type: 'todo', + }); + let updatedUser = await user.get('/user'); + usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; + }); + + it('scores but does not add history', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: todo._id}); + + expect(task.history).to.not.exist; + expect(task.value).to.equal(1); + }); + }); + + context('rewards', () => { + let reward; + let usersChallengeTaskId; + + before(async () => { + reward = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test reward', + type: 'reward', + }); + let updatedUser = await user.get('/user'); + usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; + }); + + it('does not score', async () => { + await user.post(`/tasks/${usersChallengeTaskId}/score/up`); + + let tasks = await user.get(`/tasks/challenge/${challenge._id}`); + let task = find(tasks, {_id: reward._id}); + + expect(task.history).to.not.exist; + expect(task.value).to.equal(0); + }); + }); +}); diff --git a/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js new file mode 100644 index 0000000000..4dc2ceaa3e --- /dev/null +++ b/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js @@ -0,0 +1,155 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('PUT /tasks/:taskId/checklist/:itemId', () => { + let user; + let guild; + let challenge; + + before(async () => { + user = await generateUser(); + guild = await generateGroup(user); + challenge = await generateChallenge(user, guild); + }); + + it('fails on task not found', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'todo', + text: 'Todo with checklist', + }); + + await expect(user.put(`/tasks/${task._id}/checklist/${generateUUID()}`, { + text: 'updated', + completed: true, + _id: 123, // ignored + })) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); + + it('returns error when user is not a member of the challenge', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'todo', + text: 'Todo with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + let anotherUser = await generateUser(); + + await expect(anotherUser.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { + text: 'updated', + completed: true, + _id: 123, // ignored + })) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyChalLeaderEditTasks'), + }); + }); + + it('updates a checklist item on dailies', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { + text: 'updated', + completed: true, + _id: 123, // ignored + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('updated'); + expect(savedTask.checklist[0].completed).to.equal(true); + expect(savedTask.checklist[0].id).to.not.equal('123'); + }); + + it('updates a checklist item on todos', async () => { + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + type: 'todo', + text: 'Todo with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { + text: 'updated', + completed: true, + _id: 123, // ignored + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('updated'); + expect(savedTask.checklist[0].completed).to.equal(true); + expect(savedTask.checklist[0].id).to.not.equal('123'); + }); + + it('fails on habits', async () => { + let habit = await user.post('/tasks/user', { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on rewards', async () => { + let reward = await user.post('/tasks/user', { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on task not found', async () => { + await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('fails on checklist item not found', async () => { + let createdTask = await user.post('/tasks/user', { + type: 'daily', + text: 'daily with checklist', + }); + + await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js new file mode 100644 index 0000000000..2cf08bbece --- /dev/null +++ b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js @@ -0,0 +1,74 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /tasks/:taskId/checklist/:itemId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('deletes a checklist item', async () => { + let task = await user.post('/tasks/user', { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); + + await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); + savedTask = await user.get(`/tasks/${task._id}`); + + expect(savedTask.checklist.length).to.equal(0); + }); + + it('does not work with habits', async () => { + let habit = await user.post('/tasks/user', { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('does not work with rewards', async () => { + let reward = await user.post('/tasks/user', { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on task not found', async () => { + await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('fails on checklist item not found', async () => { + let createdTask = await user.post('/tasks/user', { + type: 'daily', + text: 'daily with checklist', + }); + + await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js new file mode 100644 index 0000000000..7166db02da --- /dev/null +++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js @@ -0,0 +1,73 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:taskId/checklist/', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('adds a checklist item to a task', async () => { + let task = await user.post('/tasks/user', { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + ignored: false, + _id: 123, + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); + expect(savedTask.checklist[0].completed).to.equal(false); + expect(savedTask.checklist[0].id).to.be.a('string'); + expect(savedTask.checklist[0].id).to.not.equal('123'); + expect(savedTask.checklist[0].ignored).to.be.an('undefined'); + }); + + it('does not add a checklist to habits', async () => { + let habit = await user.post('/tasks/user', { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.post(`/tasks/${habit._id}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('does not add a checklist to rewards', async () => { + let reward = await user.post('/tasks/user', { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.post(`/tasks/${reward._id}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on task not found', async () => { + await expect(user.post(`/tasks/${generateUUID()}/checklist`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js new file mode 100644 index 0000000000..edb65dfb65 --- /dev/null +++ b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js @@ -0,0 +1,79 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:taskId/checklist/:itemId/score', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('scores a checklist item', async () => { + let task = await user.post('/tasks/user', { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + savedTask = await user.post(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}/score`); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].completed).to.equal(true); + }); + + it('fails on habits', async () => { + let habit = await user.post('/tasks/user', { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.post(`/tasks/${habit._id}/checklist/${generateUUID()}/score`, { + text: 'Checklist Item 1', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on rewards', async () => { + let reward = await user.post('/tasks/user', { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.post(`/tasks/${reward._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on task not found', async () => { + await expect(user.post(`/tasks/${generateUUID()}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('fails on checklist item not found', async () => { + let createdTask = await user.post('/tasks/user', { + type: 'daily', + text: 'daily with checklist', + }); + + await expect(user.post(`/tasks/${createdTask._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js new file mode 100644 index 0000000000..003bcb2650 --- /dev/null +++ b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js @@ -0,0 +1,83 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('PUT /tasks/:taskId/checklist/:itemId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('updates a checklist item', async () => { + let task = await user.post('/tasks/user', { + type: 'daily', + text: 'Daily with checklist', + }); + + let savedTask = await user.post(`/tasks/${task._id}/checklist`, { + text: 'Checklist Item 1', + completed: false, + }); + + savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { + text: 'updated', + completed: true, + _id: 123, // ignored + }); + + expect(savedTask.checklist.length).to.equal(1); + expect(savedTask.checklist[0].text).to.equal('updated'); + expect(savedTask.checklist[0].completed).to.equal(true); + expect(savedTask.checklist[0].id).to.not.equal('123'); + }); + + it('fails on habits', async () => { + let habit = await user.post('/tasks/user', { + type: 'habit', + text: 'habit with checklist', + }); + + await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on rewards', async () => { + let reward = await user.post('/tasks/user', { + type: 'reward', + text: 'reward with checklist', + }); + + await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('checklistOnlyDailyTodo'), + }); + }); + + it('fails on task not found', async () => { + await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('fails on checklist item not found', async () => { + let createdTask = await user.post('/tasks/user', { + type: 'daily', + text: 'daily with checklist', + }); + + await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('checklistItemNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js new file mode 100644 index 0000000000..ebb1a3c9e8 --- /dev/null +++ b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('DELETE /tasks/:taskId/tags/:tagId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('removes a tag from a task', async () => { + let task = await user.post('/tasks/user', { + type: 'habit', + text: 'Task with tag', + }); + + let tag = await user.post('/tags', {name: 'Tag 1'}); + + await user.post(`/tasks/${task._id}/tags/${tag.id}`); + await user.del(`/tasks/${task._id}/tags/${tag.id}`); + + let updatedTask = await user.get(`/tasks/${task._id}`); + + expect(updatedTask.tags.length).to.equal(0); + }); + + it('only deletes existing tags', async () => { + let createdTask = await user.post('/tasks/user', { + type: 'habit', + text: 'Task with tag', + }); + + await expect(user.del(`/tasks/${createdTask._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('tagNotFound'), + }); + }); +}); diff --git a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js new file mode 100644 index 0000000000..d6cea02036 --- /dev/null +++ b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js @@ -0,0 +1,55 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateUUID } from 'uuid'; + +describe('POST /tasks/:taskId/tags/:tagId', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('adds a tag to a task', async () => { + let task = await user.post('/tasks/user', { + type: 'habit', + text: 'Task with tag', + }); + + let tag = await user.post('/tags', {name: 'Tag 1'}); + let savedTask = await user.post(`/tasks/${task._id}/tags/${tag.id}`); + + expect(savedTask.tags[0]).to.equal(tag.id); + }); + + it('does not add a tag to a task twice', async () => { + let task = await user.post('/tasks/user', { + type: 'habit', + text: 'Task with tag', + }); + + let tag = await user.post('/tags', {name: 'Tag 1'}); + + await user.post(`/tasks/${task._id}/tags/${tag.id}`); + + await expect(user.post(`/tasks/${task._id}/tags/${tag.id}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('alreadyTagged'), + }); + }); + + it('does not add a non existing tag to a task', async () => { + let task = await user.post('/tasks/user', { + type: 'habit', + text: 'Task with tag', + }); + + await expect(user.post(`/tasks/${task._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); +}); diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js new file mode 100644 index 0000000000..2a284add53 --- /dev/null +++ b/test/api/v3/integration/user/DELETE-user.test.js @@ -0,0 +1,176 @@ +import { + checkExistence, + createAndPopulateGroup, + generateGroup, + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import { + find, + each, + map, +} from 'lodash'; +import Bluebird from 'bluebird'; + +describe('DELETE /user', () => { + let user; + let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js + + beforeEach(async () => { + user = await generateUser({balance: 10}); + }); + + it('returns an errors if password is wrong', async () => { + await expect(user.del('/user', { + password: 'wrong-password', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('wrongPassword'), + }); + }); + + it('returns an error if user has active subscription', async () => { + let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'}); + + await expect(userWithSubscription.del('/user', { + password, + })).to.be.rejected.and.to.eventually.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cannotDeleteActiveAccount'), + }); + }); + + it('deletes the user\'s tasks', async () => { + // gets the user's tasks ids + let ids = []; + each(user.tasksOrder, (idsForOrder) => { + ids.push(...idsForOrder); + }); + + expect(ids.length).to.be.above(0); // make sure the user has some task to delete + + await user.del('/user', { + password, + }); + + await Bluebird.all(map(ids, id => { + return expect(checkExistence('tasks', id)).to.eventually.eql(false); + })); + }); + + it('deletes the user', async () => { + await user.del('/user', { + password, + }); + await expect(checkExistence('users', user._id)).to.eventually.eql(false); + }); + + context('last member of a party', () => { + let party; + + beforeEach(async () => { + party = await generateGroup(user, { + type: 'party', + privacy: 'private', + }); + }); + + it('deletes party when user is the only member', async () => { + await user.del('/user', { + password, + }); + await expect(checkExistence('party', party._id)).to.eventually.eql(false); + }); + }); + + context('last member of a private guild', () => { + let privateGuild; + + beforeEach(async () => { + privateGuild = await generateGroup(user, { + type: 'guild', + privacy: 'private', + }); + }); + + it('deletes guild when user is the only member', async () => { + await user.del('/user', { + password, + }); + await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false); + }); + }); + + context('groups user is leader of', () => { + let guild, oldLeader, newLeader; + + beforeEach(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + guild = group; + newLeader = members[0]; + oldLeader = groupLeader; + }); + + it('chooses new group leader for any group user was the leader of', async () => { + await oldLeader.del('/user', { + password, + }); + + let updatedGuild = await newLeader.get(`/groups/${guild._id}`); + + expect(updatedGuild.leader).to.exist; + expect(updatedGuild.leader._id).to.not.eql(oldLeader._id); + }); + }); + + context('groups user is a part of', () => { + let group1, group2, userToDelete, otherUser; + + beforeEach(async () => { + userToDelete = await generateUser({balance: 10}); + + group1 = await generateGroup(userToDelete, { + type: 'guild', + privacy: 'public', + }); + + let {group, members} = await createAndPopulateGroup({ + groupDetails: { + type: 'guild', + privacy: 'public', + }, + members: 3, + }); + + group2 = group; + otherUser = members[0]; + + await userToDelete.post(`/groups/${group2._id}/join`); + }); + + it('removes user from all groups user was a part of', async () => { + await userToDelete.del('/user', { + password, + }); + + let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`); + let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`); + let userInGroup = find(updatedGroup2Members, (member) => { + return member._id === userToDelete._id; + }); + + expect(updatedGroup1Members).to.be.empty; + expect(updatedGroup2Members).to.not.be.empty; + expect(userInGroup).to.not.exist; + }); + }); +}); diff --git a/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js b/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js new file mode 100644 index 0000000000..46844dd855 --- /dev/null +++ b/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js @@ -0,0 +1,23 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +let user; +let endpoint = '/user/webhook'; + +describe('DELETE /user/webhook', () => { + beforeEach(async () => { + user = await generateUser(); + }); + + it('succeeds', async () => { + let id = 'some-id'; + user.preferences.webhooks[id] = { url: 'http://some-url.com', enabled: true }; + await user.sync(); + expect(user.preferences.webhooks).to.eql({}); + let response = await user.del(`${endpoint}/${id}`); + expect(response).to.eql({}); + await user.sync(); + expect(user.preferences.webhooks).to.eql({}); + }); +}); diff --git a/test/api/v3/integration/user/DELETE-user_messages.test.js b/test/api/v3/integration/user/DELETE-user_messages.test.js new file mode 100644 index 0000000000..98df8e0209 --- /dev/null +++ b/test/api/v3/integration/user/DELETE-user_messages.test.js @@ -0,0 +1,27 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('DELETE user message', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ inbox: { messages: { first: 'message', second: 'message' } } }); + expect(user.inbox.messages.first).to.eql('message'); + expect(user.inbox.messages.second).to.eql('message'); + }); + + it('one message', async () => { + let result = await user.del('/user/messages/first'); + await user.sync(); + expect(result).to.eql({ second: 'message' }); + expect(user.inbox.messages).to.eql({ second: 'message' }); + }); + + it('clear all', async () => { + let result = await user.del('/user/messages'); + await user.sync(); + expect(user.inbox.messages).to.eql({}); + expect(result).to.eql({}); + }); +}); diff --git a/test/api/v3/integration/user/GET-user.test.js b/test/api/v3/integration/user/GET-user.test.js new file mode 100644 index 0000000000..f4ed75f03f --- /dev/null +++ b/test/api/v3/integration/user/GET-user.test.js @@ -0,0 +1,24 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /user', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns the authenticated user', async () => { + let returnedUser = await user.get('/user'); + expect(returnedUser._id).to.equal(user._id); + }); + + it('does not return private paths (and apiToken)', async () => { + let returnedUser = await user.get('/user'); + + expect(returnedUser.auth.local.hashed_password).to.not.exist; + expect(returnedUser.auth.local.salt).to.not.exist; + expect(returnedUser.apiToken).to.not.exist; + }); +}); diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js new file mode 100644 index 0000000000..bbcaaf6249 --- /dev/null +++ b/test/api/v3/integration/user/GET-user_anonymized.test.js @@ -0,0 +1,90 @@ +import { + generateUser, + generateHabit, + generateDaily, + generateReward, +} from '../../../../helpers/api-integration/v3'; +import common from '../../../../../common'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /user/anonymized', () => { + let user; + let endpoint = '/user/anonymized'; + + before(async () => { + user = await generateUser(); + await user.update({ newMessages: ['some', 'new', 'messages'], profile: 'profile', 'purchased.plan': 'purchased plan', + contributor: 'contributor', invitations: 'invitations', 'items.special.nyeReceived': 'some', 'items.special.valentineReceived': 'some', + webhooks: 'some', 'achievements.challenges': 'some', + 'inbox.messages': [{ text: 'some text' }], + tags: [{ name: 'some name', challenge: 'some challenge' }], + }); + + await generateHabit({ userId: user._id }); + await generateHabit({ userId: user._id, text: generateUUID() }); + let daily = await generateDaily({ userId: user._id, checklist: [{ completed: false, text: 'this-text' }] }); + expect(daily.checklist[0].text.substr(0, 5)).to.not.eql('item '); + await generateReward({ userId: user._id, text: 'some text 4' }); + + expect(user.newMessages).to.exist; + expect(user.profile).to.exist; + expect(user.purchased.plan).to.exist; + expect(user.contributor).to.exist; + expect(user.invitations).to.exist; + expect(user.items.special.nyeReceived).to.exist; + expect(user.items.special.valentineReceived).to.exist; + expect(user.webhooks).to.exist; + expect(user.achievements.challenges).to.exist; + expect(user.inbox.messages[0].text).to.exist; + expect(user.inbox.messages[0].text).to.not.eql('inbox message text'); + expect(user.tags[0].name).to.exist; + expect(user.tags[0].name).to.not.eql('tag'); + expect(user.tags[0].challenge).to.not.eql('challenge'); + }); + + it('returns the authenticated user', async () => { + let returnedUser = await user.get(endpoint); + returnedUser = returnedUser.user; + expect(returnedUser._id).to.equal(user._id); + }); + + it('does not return private paths (and apiToken)', async () => { + let returnedUser = await user.get(endpoint); + let tasks2 = returnedUser.tasks; + returnedUser = returnedUser.user; + expect(returnedUser.auth.local).to.not.exist; + expect(returnedUser.apiToken).to.not.exist; + expect(returnedUser.stats.maxHealth).to.eql(common.maxHealth); + expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl)); + expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30? + expect(returnedUser.newMessages).to.not.exist; + expect(returnedUser.profile).to.not.exist; + expect(returnedUser.purchased.plan).to.not.exist; + expect(returnedUser.contributor).to.not.exist; + expect(returnedUser.invitations).to.not.exist; + expect(returnedUser.items.special.nyeReceived).to.not.exist; + expect(returnedUser.items.special.valentineReceived).to.not.exist; + expect(returnedUser.webhooks).to.not.exist; + expect(returnedUser.achievements.challenges).to.not.exist; + _.forEach(returnedUser.inbox.messages, (msg) => { + expect(msg.text).to.eql('inbox message text'); + }); + _.forEach(returnedUser.tags, (tag) => { + expect(tag.name).to.eql('tag'); + expect(tag.challenge).to.eql('challenge'); + }); + // tasks + expect(tasks2).to.exist; + expect(tasks2.length).to.eql(5); // +1 because generateUser() assigns one todo + expect(tasks2[0].checklist).to.exist; + _.forEach(tasks2, (task) => { + expect(task.text).to.eql('task text'); + expect(task.notes).to.eql('task notes'); + if (task.checklist) { + _.forEach(task.checklist, (c) => { + expect(c.text.substr(0, 5)).to.eql('item '); + }); + } + }); + }); +}); diff --git a/test/api/v3/integration/user/GET-user_inventory_buy.test.js b/test/api/v3/integration/user/GET-user_inventory_buy.test.js new file mode 100644 index 0000000000..fd2a25b4ee --- /dev/null +++ b/test/api/v3/integration/user/GET-user_inventory_buy.test.js @@ -0,0 +1,26 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /user/inventory/buy', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns the gear items available for purchase', async () => { + let buyList = await user.get('/user/inventory/buy'); + + expect(_.find(buyList, item => { + return item.text === t('armorWarrior1Text'); + })).to.exist; + + expect(_.find(buyList, item => { + return item.text === t('armorWarrior2Text'); + })).to.not.exist; + }); +}); diff --git a/test/api/v3/integration/user/POST-user_addPushDevice.test.js b/test/api/v3/integration/user/POST-user_addPushDevice.test.js new file mode 100644 index 0000000000..1a3a5d4f03 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_addPushDevice.test.js @@ -0,0 +1,35 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/addPushDevice', () => { + let user; + let regId = '10'; + let type = 'someRandomType'; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error if user already has the push device', async () => { + await user.post('/user/addPushDevice', {type, regId}); + await expect(user.post('/user/addPushDevice', {type, regId})) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('pushDeviceAlreadyAdded'), + }); + }); + + // More tests in common code unit tests + + it('adds a push device to the user', async () => { + let response = await user.post('/user/addPushDevice', {type, regId}); + await user.sync(); + + expect(response.message).to.equal(t('pushDeviceAdded')); + expect(user.pushDevices[0].type).to.equal(type); + expect(user.pushDevices[0].regId).to.equal(regId); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_add_webhook.test.js b/test/api/v3/integration/user/POST-user_add_webhook.test.js new file mode 100644 index 0000000000..d13f15baa4 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_add_webhook.test.js @@ -0,0 +1,29 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +let user; +let endpoint = '/user/webhook'; + +describe('POST /user/webhook', () => { + beforeEach(async () => { + user = await generateUser(); + }); + + it('validates', async () => { + await expect(user.post(endpoint, { enabled: true })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidUrl'), + }); + }); + + it('successfully adds the webhook', async () => { + expect(user.preferences.webhooks).to.eql({}); + let response = await user.post(endpoint, { enabled: true, url: 'http://some-url.com'}); + expect(response.id).to.exist; + await user.sync(); + expect(user.preferences.webhooks).to.not.eql({}); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_allocate.test.js b/test/api/v3/integration/user/POST-user_allocate.test.js new file mode 100644 index 0000000000..02d4990092 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_allocate.test.js @@ -0,0 +1,41 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/allocate', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error if an invalid attribute is supplied', async () => { + await expect(user.post('/user/allocate?stat=invalid')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidAttribute', {attr: 'invalid'}), + }); + }); + + it('returns an error if the user doesn\'t have attribute points', async () => { + await expect(user.post('/user/allocate')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughAttrPoints'), + }); + }); + + it('allocates attribute points', async () => { + await user.update({'stats.points': 1}); + let res = await user.post('/user/allocate?stat=con'); + await user.sync(); + expect(user.stats.con).to.equal(1); + expect(user.stats.points).to.equal(0); + expect(res.con).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_allocate_now.test.js b/test/api/v3/integration/user/POST-user_allocate_now.test.js new file mode 100644 index 0000000000..b45f2156be --- /dev/null +++ b/test/api/v3/integration/user/POST-user_allocate_now.test.js @@ -0,0 +1,28 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/allocate-now', () => { + // More tests in common code unit tests + + it('auto allocates all points', async () => { + let user = await generateUser({ + 'stats.points': 5, + 'stats.int': 3, + 'stats.con': 9, + 'stats.per': 9, + 'stats.str': 9, + 'preferences.allocationMode': 'flat', + }); + + let res = await user.post('/user/allocate-now'); + await user.sync(); + + expect(res).to.eql(user.stats); + expect(user.stats.points).to.equal(0); + expect(user.stats.con).to.equal(9); + expect(user.stats.int).to.equal(8); + expect(user.stats.per).to.equal(9); + expect(user.stats.str).to.equal(9); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_block.test.js b/test/api/v3/integration/user/POST-user_block.test.js new file mode 100644 index 0000000000..51766eb51e --- /dev/null +++ b/test/api/v3/integration/user/POST-user_block.test.js @@ -0,0 +1,34 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('block user', () => { + let user; + let blockedUser; + let blockedUser2; + + beforeEach(async () => { + blockedUser = await generateUser(); + blockedUser2 = await generateUser(); + user = await generateUser({ inbox: { blocks: [blockedUser._id] } }); + expect(user.inbox.blocks.length).to.eql(1); + expect(user.inbox.blocks).to.eql([blockedUser._id]); + }); + + it('validates uuid', async () => { + await expect(user.post('/user/block/1')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidUUID'), + }); + }); + + it('successfully', async () => { + let response = await user.post(`/user/block/${blockedUser2._id}`); + await user.sync(); + expect(response).to.eql([blockedUser._id, blockedUser2._id]); + expect(user.inbox.blocks.length).to.eql(2); + expect(user.inbox.blocks).to.include(blockedUser2._id); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy.test.js b/test/api/v3/integration/user/POST-user_buy.test.js new file mode 100644 index 0000000000..ffad12f2a0 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy.test.js @@ -0,0 +1,62 @@ +/* eslint-disable camelcase */ + +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.gp': 400, + }); + }); + + // More tests in common code unit tests + + it('returns an error if the item is not found', async () => { + await expect(user.post('/user/buy/notExisting')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('itemNotFound', {key: 'notExisting'}), + }); + }); + + it('buys a potion', async () => { + await user.update({ + 'stats.gp': 400, + }); + + let potion = content.potion; + let res = await user.post('/user/buy/potion'); + await user.sync(); + + expect(user.stats.hp).to.equal(50); + expect(res.data).to.eql(user.stats); + expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); + }); + + it('buys a piece of gear', async () => { + let key = 'armor_warrior_1'; + + await user.post(`/user/buy/${key}`); + await user.sync(); + + expect(user.items.gear.owned).to.eql({ + armor_warrior_1: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_armoire.test.js b/test/api/v3/integration/user/POST-user_buy_armoire.test.js new file mode 100644 index 0000000000..32b8134647 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_armoire.test.js @@ -0,0 +1,41 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/buy-armoire', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.gp': 400, + }); + }); + + // More tests in common code unit tests + + it('returns an error if user does not have enough gold', async () => { + await user.update({ + 'stats.gp': 5, + }); + + await expect(user.post('/user/buy-armoire')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageNotEnoughGold'), + }); + }); + + it('reduces gold when buying from the armoire', async () => { + await user.post('/user/buy-armoire'); + + await user.sync(); + + expect(user.stats.gp).to.equal(300); + }); + + xit('buys a piece of armoire', async () => { + // Skipped because can't stub predictableRandom correctly + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_gear.test.js b/test/api/v3/integration/user/POST-user_buy_gear.test.js new file mode 100644 index 0000000000..f577263d4a --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_gear.test.js @@ -0,0 +1,45 @@ +/* eslint-disable camelcase */ + +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/buy-gear/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.gp': 400, + }); + }); + + // More tests in common code unit tests + + it('returns an error if the item is not found', async () => { + await expect(user.post('/user/buy-gear/notExisting')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('itemNotFound', {key: 'notExisting'}), + }); + }); + + it('buys a piece of gear', async () => { + let key = 'armor_warrior_1'; + + await user.post(`/user/buy-gear/${key}`); + await user.sync(); + + expect(user.items.gear.owned).to.eql({ + armor_warrior_1: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_health_potion.test.js b/test/api/v3/integration/user/POST-user_buy_health_potion.test.js new file mode 100644 index 0000000000..835e893bd7 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_health_potion.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy-health-potion', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.hp': 40, + }); + }); + + // More tests in common code unit tests + + it('returns an error if user does not have enough gold', async () => { + await expect(user.post('/user/buy-health-potion')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageNotEnoughGold'), + }); + }); + + it('buys a potion', async () => { + await user.update({ + 'stats.gp': 400, + }); + + let potion = content.potion; + let res = await user.post('/user/buy-health-potion'); + await user.sync(); + + expect(user.stats.hp).to.equal(50); + expect(res.data).to.eql(user.stats); + expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js new file mode 100644 index 0000000000..da7116d732 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js @@ -0,0 +1,38 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/buy-mystery-set/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'purchased.plan.consecutive.trinkets': 1, + }); + }); + + // More tests in common code unit tests + + it('returns an error if the mystery set is not found', async () => { + await expect(user.post('/user/buy-mystery-set/notExisting')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('mysterySetNotFound'), + }); + }); + + it('buys a mystery set', async () => { + let key = 301404; + + let res = await user.post(`/user/buy-mystery-set/${key}`); + await user.sync(); + + expect(res.data).to.eql({ + items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared + purchasedPlanConsecutive: user.purchased.plan.consecutive, + }); + expect(res.message).to.equal(t('hourglassPurchaseSet')); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_quest.test.js b/test/api/v3/integration/user/POST-user_buy_quest.test.js new file mode 100644 index 0000000000..3330988537 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_quest.test.js @@ -0,0 +1,40 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy-quest/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error if the quest is not found', async () => { + await expect(user.post('/user/buy-quest/notExisting')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questNotFound', {key: 'notExisting'}), + }); + }); + + it('buys a quest', async () => { + let key = 'dilatoryDistress1'; + let item = content.quests[key]; + + await user.update({'stats.gp': 250}); + let res = await user.post(`/user/buy-quest/${key}`); + await user.sync(); + + expect(res.data).to.eql(user.items.quests); + expect(res.message).to.equal(t('messageBought', { + itemText: item.text(), + })); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_special_spell.test.js b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js new file mode 100644 index 0000000000..2ae16d1baf --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js @@ -0,0 +1,43 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy-special-spell/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error if the special spell is not found', async () => { + await expect(user.post('/user/buy-special-spell/notExisting')) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('spellNotFound', {spellId: 'notExisting'}), + }); + }); + + it('buys a special spell', async () => { + let key = 'thankyou'; + let item = content.special[key]; + + await user.update({'stats.gp': 250}); + let res = await user.post(`/user/buy-special-spell/${key}`); + await user.sync(); + + expect(res.data).to.eql({ + items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared + stats: user.stats, + }); + expect(res.message).to.equal(t('messageBought', { + itemText: item.text(), + })); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_change-class.test.js b/test/api/v3/integration/user/POST-user_change-class.test.js new file mode 100644 index 0000000000..d4b4192f23 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_change-class.test.js @@ -0,0 +1,30 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/change-class', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'flags.classSelected': false, + 'stats.lvl': 10, + }); + }); + + // More tests in common code unit tests + + it('changes class', async () => { + let res = await user.post('/user/change-class?class=rogue'); + await user.sync(); + + expect(res).to.eql(JSON.parse( + JSON.stringify({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + items: user.items, + }) + )); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js new file mode 100644 index 0000000000..8ada2ede7d --- /dev/null +++ b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js @@ -0,0 +1,172 @@ +import { + generateUser, + translate as t, + createAndPopulateGroup, + generateChallenge, + sleep, +} from '../../../../helpers/api-integration/v3'; + +import { v4 as generateUUID } from 'uuid'; + +describe('POST /user/class/cast/:spellId', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error if spell does not exist', async () => { + await user.update({'stats.class': 'rogue'}); + let spellId = 'invalidSpell'; + await expect(user.post(`/user/class/cast/${spellId}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('spellNotFound', {spellId}), + }); + }); + + it('returns an error if spell does not exist in user\'s class', async () => { + let spellId = 'pickPocket'; + await expect(user.post(`/user/class/cast/${spellId}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('spellNotFound', {spellId}), + }); + }); + + it('returns an error if spell.mana > user.mana', async () => { + await user.update({'stats.class': 'rogue'}); + await expect(user.post('/user/class/cast/backStab')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughMana'), + }); + }); + + it('returns an error if spell.value > user.gold', async () => { + await expect(user.post('/user/class/cast/birthday')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('messageNotEnoughGold'), + }); + }); + + it('returns an error if spell.lvl > user.level', async () => { + await user.update({'stats.mp': 200, 'stats.class': 'wizard'}); + await expect(user.post('/user/class/cast/earth')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('spellLevelTooHigh', {level: 13}), + }); + }); + + it('returns an error if user doesn\'t own the spell', async () => { + await expect(user.post('/user/class/cast/snowball')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('spellNotOwned'), + }); + }); + + it('returns an error if targetId is not an UUID', async () => { + await expect(user.post('/user/class/cast/spellId?targetId=notAnUUID')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns an error if targetId is required but missing', async () => { + await user.update({'stats.class': 'rogue', 'stats.lvl': 11}); + await expect(user.post('/user/class/cast/pickPocket')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('targetIdUUID'), + }); + }); + + it('returns an error if targeted task doesn\'t exist', async () => { + await user.update({'stats.class': 'rogue', 'stats.lvl': 11}); + await expect(user.post(`/user/class/cast/pickPocket?targetId=${generateUUID()}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + }); + + it('returns an error if a challenge task was targeted', async () => { + let {group, groupLeader} = await createAndPopulateGroup(); + let challenge = await generateChallenge(groupLeader, group); + await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ + {type: 'habit', text: 'task text'}, + ]); + await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11}); + await sleep(0.5); + await groupLeader.sync(); + await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${groupLeader.tasksOrder.habits[0]}`)) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('challengeTasksNoCast'), + }); + }); + + it('returns an error if targeted party member doesn\'t exist', async () => { + let {groupLeader} = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 1, + }); + await groupLeader.update({'items.special.snowball': 3}); + + let target = generateUUID(); + await expect(groupLeader.post(`/user/class/cast/snowball?targetId=${target}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userWithIDNotFound', {userId: target}), + }); + }); + + it('returns an error if party does not exists', async () => { + await user.update({'items.special.snowball': 3}); + + await expect(user.post(`/user/class/cast/snowball?targetId=${generateUUID()}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('partyNotFound'), + }); + }); + + it('send message in party chat if party && !spell.silent', async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + members: 1, + }); + await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13}); + await groupLeader.post('/user/class/cast/earth'); + await sleep(1); + await group.sync(); + expect(group.chat[0]).to.exists; + expect(group.chat[0].uuid).to.equal('system'); + }); + + // TODO find a way to have sinon working in integration tests + // it doesn't work when tests are running separately from server + it('passes correct target to spell when targetType === \'task\''); + it('passes correct target to spell when targetType === \'tasks\''); + it('passes correct target to spell when targetType === \'self\''); + it('passes correct target to spell when targetType === \'party\''); + it('passes correct target to spell when targetType === \'user\''); + it('passes correct target to spell when targetType === \'party\' and user is not in a party'); + it('passes correct target to spell when targetType === \'user\' and user is not in a party'); +}); diff --git a/test/api/v3/integration/user/POST-user_custom-day-start.test.js b/test/api/v3/integration/user/POST-user_custom-day-start.test.js new file mode 100644 index 0000000000..868b9ae91d --- /dev/null +++ b/test/api/v3/integration/user/POST-user_custom-day-start.test.js @@ -0,0 +1,47 @@ +import moment from 'moment'; +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +let user; +let endpoint = '/user/custom-day-start'; + +describe('POST /user/custom-day-start', () => { + beforeEach(async () => { + user = await generateUser(); + }); + + it('updates user.preferences.dayStart', async () => { + expect(user.preferences.dayStart).to.eql(0); + + await user.post(endpoint, { dayStart: 1 }); + await user.sync(); + + expect(user.preferences.dayStart).to.eql(1); + }); + + it('sets lastCron to the current time to prevent an unexpected cron', async () => { + let oldCron = moment().subtract(7, 'hours'); + + await user.update({lastCron: oldCron}); + await user.post(endpoint, { dayStart: 1 }); + await user.sync(); + + expect(user.lastCron.valueOf()).to.be.gt(oldCron.valueOf()); + }); + + it('returns a confirmation message', async () => { + let {message} = await user.post(endpoint, { dayStart: 1 }); + + expect(message).to.eql(t('customDayStartHasChanged')); + }); + + it('errors if invalid value is passed', async () => { + await expect(user.post(endpoint, { dayStart: 'foo' })) + .to.eventually.be.rejected; + + await expect(user.post(endpoint, { dayStart: 24})) + .to.eventually.be.rejected; + }); +}); diff --git a/test/api/v3/integration/user/POST-user_disable-classes.test.js b/test/api/v3/integration/user/POST-user_disable-classes.test.js new file mode 100644 index 0000000000..0632a8adc7 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_disable-classes.test.js @@ -0,0 +1,26 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/disable-classes', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('disable classes', async () => { + let res = await user.post('/user/disable-classes'); + await user.sync(); + + expect(res).to.eql(JSON.parse( + JSON.stringify({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + }) + )); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_equip_type_key.test.js b/test/api/v3/integration/user/POST-user_equip_type_key.test.js new file mode 100644 index 0000000000..c5cde777df --- /dev/null +++ b/test/api/v3/integration/user/POST-user_equip_type_key.test.js @@ -0,0 +1,40 @@ +/* eslint-disable camelcase */ + +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/equip/:type/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('equip an item', async () => { + await user.update({ + 'items.gear.owned': { + weapon_warrior_0: true, + weapon_warrior_1: true, + weapon_warrior_2: true, + weapon_wizard_1: true, + weapon_wizard_2: true, + shield_base_0: true, + shield_warrior_1: true, + }, + 'items.gear.equipped': { + weapon: 'weapon_warrior_0', + shield: 'shield_base_0', + }, + 'stats.gp': 200, + }); + + await user.post('/user/equip/equipped/weapon_warrior_1'); + let res = await user.post('/user/equip/equipped/weapon_warrior_2'); + await user.sync(); + + expect(res).to.eql(JSON.parse(JSON.stringify(user.items))); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_feed_pet_food.test.js b/test/api/v3/integration/user/POST-user_feed_pet_food.test.js new file mode 100644 index 0000000000..7581c266ee --- /dev/null +++ b/test/api/v3/integration/user/POST-user_feed_pet_food.test.js @@ -0,0 +1,45 @@ +/* eslint-disable camelcase */ + +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import content from '../../../../../common/script/content'; + +describe('POST /user/feed/:pet/:food', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('does not enjoy the food', async () => { + await user.update({ + 'items.pets.Wolf-Base': 5, + 'items.food.Milk': 2, + }); + + let food = content.food.Milk; + let [egg, potion] = 'Wolf-Base'.split('-'); + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let res = await user.post('/user/feed/Wolf-Base/Milk'); + await user.sync(); + expect(res).to.eql({ + data: user.items.pets['Wolf-Base'], + message: t('messageDontEnjoyFood', { + egg: t('petName', { + potion: potionText, + egg: eggText, + }), + foodText: food.text(), + }), + }); + + expect(user.items.food.Milk).to.equal(1); + expect(user.items.pets['Wolf-Base']).to.equal(7); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js b/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js new file mode 100644 index 0000000000..9621377beb --- /dev/null +++ b/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js @@ -0,0 +1,31 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/hatch/:egg/:hatchingPotion', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('hatch a new pet', async () => { + await user.update({ + 'items.eggs.Wolf': 1, + 'items.hatchingPotions.Base': 1, + }); + let res = await user.post('/user/hatch/Wolf/Base'); + await user.sync(); + expect(user.items.pets['Wolf-Base']).to.equal(5); + expect(user.items.eggs.Wolf).to.equal(0); + expect(user.items.hatchingPotions.Base).to.equal(0); + + expect(res).to.eql({ + message: t('messageHatched'), + data: JSON.parse(JSON.stringify(user.items)), + }); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_mark_pms_read.test.js b/test/api/v3/integration/user/POST-user_mark_pms_read.test.js new file mode 100644 index 0000000000..50552359ef --- /dev/null +++ b/test/api/v3/integration/user/POST-user_mark_pms_read.test.js @@ -0,0 +1,22 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/mark-pms-read', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('marks user\'s private messages as read', async () => { + await user.update({ + 'inbox.newMessages': 1, + }); + await user.post('/user/mark-pms-read'); + await user.sync(); + expect(user.inbox.newMessages).to.equal(0); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js new file mode 100644 index 0000000000..d9e9fe7326 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js @@ -0,0 +1,26 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/open-mystery-item', () => { + let user; + let mysteryItemKey = 'eyewear_special_summerRogue'; + + beforeEach(async () => { + user = await generateUser({ + 'purchased.plan.mysteryItems': [mysteryItemKey], + }); + }); + + // More tests in common code unit tests + + it('opens a mystery item', async () => { + let response = await user.post('/user/open-mystery-item'); + await user.sync(); + + expect(user.items.gear.owned[mysteryItemKey]).to.be.true; + expect(response.message).to.equal(t('mysteryItemOpened')); + expect(response.data).to.deep.equal(user.items.gear.owned); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_purchase.test.js b/test/api/v3/integration/user/POST-user_purchase.test.js new file mode 100644 index 0000000000..dff6d59c48 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_purchase.test.js @@ -0,0 +1,34 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/purchase/:type/:key', () => { + let user; + let type = 'hatchingPotions'; + let key = 'Base'; + + beforeEach(async () => { + user = await generateUser({ + balance: 40, + }); + }); + + // More tests in common code unit tests + + it('returns an error when key is not provided', async () => { + await expect(user.post('/user/purchase/gems/gem')) + .to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('mustSubscribeToPurchaseGems'), + }); + }); + + it('purchases a gem item', async () => { + await user.post(`/user/purchase/${type}/${key}`); + await user.sync(); + + expect(user.items[type][key]).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js b/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js new file mode 100644 index 0000000000..cd43334d00 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js @@ -0,0 +1,25 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/purchase-hourglass/:type/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'purchased.plan.consecutive.trinkets': 2, + }); + }); + + // More tests in common code unit tests + + it('buys a hourglass pet', async () => { + let response = await user.post('/user/purchase-hourglass/pets/MantisShrimp-Base'); + await user.sync(); + + expect(response.message).to.eql(t('hourglassPurchase')); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_read_card.test.js b/test/api/v3/integration/user/POST-user_read_card.test.js new file mode 100644 index 0000000000..3b3573b6cc --- /dev/null +++ b/test/api/v3/integration/user/POST-user_read_card.test.js @@ -0,0 +1,38 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/read-card/:cardType', () => { + let user; + let cardType = 'greeting'; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error when unknown cardType is provded', async () => { + await expect(user.post('/user/read-card/randomCardType')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cardTypeNotAllowed'), + }); + }); + + // More tests in common code unit tests + + it('reads a card', async () => { + await user.update({ + 'items.special.greetingReceived': [true], + 'flags.cardReceived': true, + }); + + let response = await user.post(`/user/read-card/${cardType}`); + await user.sync(); + + expect(response.message).to.equal(t('readCard', {cardType})); + expect(user.items.special[`${cardType}Received`]).to.be.empty; + expect(user.flags.cardReceived).to.be.false; + }); +}); diff --git a/test/api/v3/integration/user/POST-user_rebirth.test.js b/test/api/v3/integration/user/POST-user_rebirth.test.js new file mode 100644 index 0000000000..21fbed0b8d --- /dev/null +++ b/test/api/v3/integration/user/POST-user_rebirth.test.js @@ -0,0 +1,57 @@ +import { + generateUser, + generateDaily, + generateReward, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/rebirth', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error when user balance is too low', async () => { + await expect(user.post('/user/rebirth')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('resets user\'s tasks', async () => { + await user.update({ + balance: 2, + }); + + let daily = await generateDaily({ + text: 'test habit', + type: 'daily', + value: 1, + streak: 1, + userId: user._id, + }); + + let reward = await generateReward({ + text: 'test reward', + type: 'reward', + value: 1, + userId: user._id, + }); + + let response = await user.post('/user/rebirth'); + await user.sync(); + + let updatedDaily = await user.get(`/tasks/${daily._id}`); + let updatedReward = await user.get(`/tasks/${reward._id}`); + + expect(response.message).to.equal(t('rebirthComplete')); + expect(updatedDaily.streak).to.equal(0); + expect(updatedDaily.value).to.equal(0); + expect(updatedReward.value).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_release_both.test.js b/test/api/v3/integration/user/POST-user_release_both.test.js new file mode 100644 index 0000000000..8c47d95dfe --- /dev/null +++ b/test/api/v3/integration/user/POST-user_release_both.test.js @@ -0,0 +1,48 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/release-both', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(async () => { + user = await generateUser({ + 'items.currentMount': animal, + 'items.currentPet': animal, + 'items.pets': {animal: 5}, + 'items.mounts': {animal: true}, + }); + }); + + it('returns an error when user balance is too low and user does not have triadBingo', async () => { + await expect(user.post('/user/release-both')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('grants triad bingo with gems', async () => { + await user.update({ + balance: 1.5, + }); + + let response = await user.post('/user/release-both'); + await user.sync(); + + expect(response.message).to.equal(t('mountsAndPetsReleased')); + expect(user.balance).to.equal(0); + expect(user.items.currentMount).to.be.empty; + expect(user.items.currentPet).to.be.empty; + expect(user.items.pets[animal]).to.be.empty; + expect(user.items.mounts[animal]).to.equal(null); + expect(user.achievements.beastMasterCount).to.equal(1); + expect(user.achievements.mountMasterCount).to.equal(1); + expect(user.achievements.triadBingoCount).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_release_mounts.test.js b/test/api/v3/integration/user/POST-user_release_mounts.test.js new file mode 100644 index 0000000000..86391599f0 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_release_mounts.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/release-mounts', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(async () => { + user = await generateUser({ + 'items.currentMount': animal, + 'items.mounts': {animal: true}, + }); + }); + + it('returns an error when user balance is too low', async () => { + await expect(user.post('/user/release-mounts')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('releases mounts', async () => { + await user.update({ + balance: 1, + }); + + let response = await user.post('/user/release-mounts'); + await user.sync(); + + expect(response.message).to.equal(t('mountsReleased')); + expect(user.balance).to.equal(0); + expect(user.items.currentMount).to.be.empty; + expect(user.items.mounts[animal]).to.equal(null); + expect(user.achievements.mountMasterCount).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_release_pets.test.js b/test/api/v3/integration/user/POST-user_release_pets.test.js new file mode 100644 index 0000000000..a7f7b9b66f --- /dev/null +++ b/test/api/v3/integration/user/POST-user_release_pets.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/release-pets', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(async () => { + user = await generateUser({ + 'items.currentPet': animal, + 'items.pets': {animal: 5}, + }); + }); + + it('returns an error when user balance is too low', async () => { + await expect(user.post('/user/release-pets')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('releases pets', async () => { + await user.update({ + balance: 1, + }); + + let response = await user.post('/user/release-pets'); + await user.sync(); + + expect(response.message).to.equal(t('petsReleased')); + expect(user.balance).to.equal(0); + expect(user.items.currentPet).to.be.empty; + expect(user.items.pets[animal]).to.equal(0); + expect(user.achievements.beastMasterCount).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_reroll.test.js b/test/api/v3/integration/user/POST-user_reroll.test.js new file mode 100644 index 0000000000..29774d1239 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_reroll.test.js @@ -0,0 +1,54 @@ +import { + generateUser, + generateDaily, + generateReward, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/reroll', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error when user balance is too low', async () => { + await expect(user.post('/user/reroll')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('resets user\'s tasks', async () => { + await user.update({ + balance: 2, + }); + + let daily = await generateDaily({ + text: 'test habit', + type: 'daily', + userId: user._id, + }); + + let reward = await generateReward({ + text: 'test reward', + type: 'reward', + value: 1, + userId: user._id, + }); + + let response = await user.post('/user/reroll'); + await user.sync(); + + let updatedDaily = await user.get(`/tasks/${daily._id}`); + let updatedReward = await user.get(`/tasks/${reward._id}`); + + expect(response.message).to.equal(t('fortifyComplete')); + expect(updatedDaily.value).to.equal(0); + expect(updatedReward.value).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_reset.test.js b/test/api/v3/integration/user/POST-user_reset.test.js new file mode 100644 index 0000000000..2baf7bd083 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_reset.test.js @@ -0,0 +1,104 @@ +import { + generateUser, + generateGroup, + generateChallenge, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/reset', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('resets user\'s habits', async () => { + let task = await user.post('/tasks/user', { + text: 'test habit', + type: 'habit', + }); + + await user.post('/user/reset'); + await user.sync(); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + + expect(user.tasksOrder.habits).to.be.empty; + }); + + it('resets user\'s dailys', async () => { + let task = await user.post('/tasks/user', { + text: 'test daily', + type: 'daily', + }); + + await user.post('/user/reset'); + await user.sync(); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + + expect(user.tasksOrder.dailys).to.be.empty; + }); + + it('resets user\'s todos', async () => { + let task = await user.post('/tasks/user', { + text: 'test todo', + type: 'todo', + }); + + await user.post('/user/reset'); + await user.sync(); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + + expect(user.tasksOrder.todos).to.be.empty; + }); + + it('resets user\'s rewards', async () => { + let task = await user.post('/tasks/user', { + text: 'test reward', + type: 'reward', + }); + + await user.post('/user/reset'); + await user.sync(); + + await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('taskNotFound'), + }); + + expect(user.tasksOrder.rewards).to.be.empty; + }); + + it('does not delete challenge tasks', async () => { + let guild = await generateGroup(user); + let challenge = await generateChallenge(user, guild); + let task = await user.post(`/tasks/challenge/${challenge._id}`, { + text: 'test challenge habit', + type: 'habit', + }); + + await user.post('/user/reset'); + await user.sync(); + + let userChallengeTask = await user.get(`/tasks/${task._id}`); + + expect(userChallengeTask).to.eql(task); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_revive.test.js b/test/api/v3/integration/user/POST-user_revive.test.js new file mode 100644 index 0000000000..6ba85ac87f --- /dev/null +++ b/test/api/v3/integration/user/POST-user_revive.test.js @@ -0,0 +1,37 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/revive', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'user.items.gear.owned': {weaponKey: true}, + }); + }); + + it('returns an error when user is not dead', async () => { + await expect(user.post('/user/revive')) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cannotRevive'), + }); + }); + + // More tests in common code unit tests + + it('decreases a stat', async () => { + await user.update({ + 'stats.str': 2, + 'stats.hp': 0, + }); + + await user.post('/user/revive'); + await user.sync(); + + expect(user.stats.str).to.equal(1); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_sell.test.js b/test/api/v3/integration/user/POST-user_sell.test.js new file mode 100644 index 0000000000..1914336175 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_sell.test.js @@ -0,0 +1,41 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import content from '../../../../../common/script/content'; + +describe('POST /user/sell/:type/:key', () => { + let user; + let type = 'eggs'; + let key = 'Wolf'; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error when user does not have item', async () => { + await expect(user.post(`/user/sell/${type}/${key}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userItemsKeyNotFound', {type}), + }); + }); + + it('sells an item', async () => { + await user.update({ + items: { + eggs: { + Wolf: 1, + }, + }, + }); + + await user.post(`/user/sell/${type}/${key}`); + await user.sync(); + + expect(user.stats.gp).to.equal(content[type][key].value); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_sleep.test.js b/test/api/v3/integration/user/POST-user_sleep.test.js new file mode 100644 index 0000000000..0e9773150e --- /dev/null +++ b/test/api/v3/integration/user/POST-user_sleep.test.js @@ -0,0 +1,25 @@ +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/sleep', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('toggles sleep status', async () => { + let res = await user.post('/user/sleep'); + expect(res).to.eql(true); + await user.sync(); + expect(user.preferences.sleep).to.be.true; + + let res2 = await user.post('/user/sleep'); + expect(res2).to.eql(false); + await user.sync(); + expect(user.preferences.sleep).to.be.false; + }); +}); diff --git a/test/api/v3/integration/user/POST-user_unlock.js b/test/api/v3/integration/user/POST-user_unlock.js new file mode 100644 index 0000000000..6dbdb3c1b1 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_unlock.js @@ -0,0 +1,37 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/unlock', () => { + let user; + let unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; + let unlockCost = 1.25; + let usersStartingGems = 5; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error when user balance is too low', async () => { + await expect(user.post(`/user/unlock?path=${unlockPath}`)) + .to.eventually.be.rejected.and.to.eql({ + code: 401, + error: 'NotAuthorized', + message: t('notEnoughGems'), + }); + }); + + // More tests in common code unit tests + + it('reduces a user\'s balance', async () => { + await user.update({ + balance: usersStartingGems, + }); + let response = await user.post(`/user/unlock?path=${unlockPath}`); + await user.sync(); + + expect(response.message).to.equal(t('unlocked')); + expect(user.balance).to.equal(usersStartingGems - unlockCost); + }); +}); diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js new file mode 100644 index 0000000000..f606c95c2c --- /dev/null +++ b/test/api/v3/integration/user/PUT-user.test.js @@ -0,0 +1,201 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +import { each, get } from 'lodash'; + +describe('PUT /user', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + context('Allowed Operations', () => { + it('updates the user', async () => { + await user.put('/user', { + 'profile.name': 'Frodo', + 'preferences.costume': true, + 'stats.hp': 14, + }); + + await user.sync(); + + expect(user.profile.name).to.eql('Frodo'); + expect(user.preferences.costume).to.eql(true); + expect(user.stats.hp).to.eql(14); + }); + }); + + context('Top Level Protected Operations', () => { + let protectedOperations = { + 'gem balance': {balance: 100}, + auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()}, + contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'}, + backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'}, + subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000}, + 'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true}, + }; + + each(protectedOperations, (data, testName) => { + it(`does not allow updating ${testName}`, async () => { + let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] }); + + await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: errorText, + }); + }); + }); + }); + + context('Sub-Level Protected Operations', () => { + let protectedOperations = { + 'class stat': {'stats.class': 'wizard'}, + 'flags unless whitelisted': {'flags.dropsEnabled': true}, + webhooks: {'preferences.webhooks': [1, 2, 3]}, + sleep: {'preferences.sleep': true}, + 'disable classes': {'preferences.disableClasses': true}, + }; + + each(protectedOperations, (data, testName) => { + it(`does not allow updating ${testName}`, async () => { + let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] }); + + await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: errorText, + }); + }); + }); + }); + + context('Default Appearance Preferences', () => { + let testCases = { + shirt: 'yellow', + skin: 'ddc994', + 'hair.color': 'blond', + 'hair.bangs': 2, + 'hair.base': 1, + 'hair.flower': 4, + size: 'broad', + }; + + each(testCases, (item, type) => { + const update = {}; + update[`preferences.${type}`] = item; + + it(`updates user with ${type} that is a default`, async () => { + let dbUpdate = {}; + dbUpdate[`purchased.${type}.${item}`] = true; + await user.update(dbUpdate); + + // Sanity checks to make sure user is not already equipped with item + expect(get(user.preferences, type)).to.not.eql(item); + + let updatedUser = await user.put('/user', update); + + expect(get(updatedUser.preferences, type)).to.eql(item); + }); + }); + + it('returns an error if user tries to update body size with invalid type', async () => { + await expect(user.put('/user', { + 'preferences.size': 'round', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('mustPurchaseToSet', { val: 'round', key: 'preferences.size' }), + }); + }); + + it('can set beard to default', async () => { + await user.update({ + 'purchased.hair.beard': 3, + 'preferences.hair.beard': 3, + }); + + let updatedUser = await user.put('/user', { + 'preferences.hair.beard': 0, + }); + + expect(updatedUser.preferences.hair.beard).to.eql(0); + }); + + it('can set mustache to default', async () => { + await user.update({ + 'purchased.hair.mustache': 2, + 'preferences.hair.mustache': 2, + }); + + let updatedUser = await user.put('/user', { + 'preferences.hair.mustache': 0, + }); + + expect(updatedUser.preferences.hair.mustache).to.eql(0); + }); + }); + + context('Purchasable Appearance Preferences', () => { + let testCases = { + background: 'volcano', + shirt: 'convict', + skin: 'cactus', + 'hair.base': 7, + 'hair.beard': 2, + 'hair.color': 'rainbow', + 'hair.mustache': 2, + }; + + each(testCases, (item, type) => { + const update = {}; + update[`preferences.${type}`] = item; + + it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => { + await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('mustPurchaseToSet', {val: item, key: `preferences.${type}`}), + }); + }); + + it(`updates user with ${type} user does own`, async () => { + let dbUpdate = {}; + dbUpdate[`purchased.${type}.${item}`] = true; + await user.update(dbUpdate); + + // Sanity check to make sure user is not already equipped with item + expect(get(user.preferences, type)).to.not.eql(item); + + let updatedUser = await user.put('/user', update); + + expect(get(updatedUser.preferences, type)).to.eql(item); + }); + }); + }); + + context('Improvement Categories', () => { + it('sets valid categories', async () => { + await user.put('/user', { + 'preferences.improvementCategories': ['work', 'school'], + }); + + await user.sync(); + + expect(user.preferences.improvementCategories).to.eql(['work', 'school']); + }); + + it('discards invalid categories', async () => { + await expect(user.put('/user', { + 'preferences.improvementCategories': ['work', 'procrastination', 'school'], + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'User validation failed', + }); + }); + }); +}); diff --git a/test/api/v3/integration/user/PUT-user_update_webhook.test.js b/test/api/v3/integration/user/PUT-user_update_webhook.test.js new file mode 100644 index 0000000000..13ca9ff00c --- /dev/null +++ b/test/api/v3/integration/user/PUT-user_update_webhook.test.js @@ -0,0 +1,32 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +let user; +let url = 'http://new-url.com'; +let enabled = true; + +describe('PUT /user/webhook/:id', () => { + beforeEach(async () => { + user = await generateUser(); + }); + + it('validation fails', async () => { + await expect(user.put('/user/webhook/some-id'), { enabled: true }).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidUrl'), + }); + }); + + it('succeeds', async () => { + let response = await user.post('/user/webhook', { enabled: true, url: 'http://some-url.com'}); + await user.sync(); + expect(user.preferences.webhooks[response.id].url).to.not.eql(url); + let response2 = await user.put(`/user/webhook/${response.id}`, {url, enabled}); + expect(response2.url).to.eql(url); + await user.sync(); + expect(user.preferences.webhooks[response.id].url).to.eql(url); + }); +}); diff --git a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js new file mode 100644 index 0000000000..cf1354b095 --- /dev/null +++ b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js @@ -0,0 +1,40 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; + +describe('DELETE social registration', () => { + let user; + let endpoint = '/user/auth/social/facebook'; + beforeEach(async () => { + user = await generateUser(); + await user.update({ 'auth.facebook.id': 'some-fb-id' }); + expect(user.auth.local.username).to.not.be.empty; + expect(user.auth.facebook).to.not.be.empty; + }); + context('of NOT-FACEBOOK', () => { + it('is not supported', async () => { + await expect(user.del('/user/auth/social/SOME-OTHER-NETWORK')).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlyFbSupported'), + }); + }); + }); + context('of facebook', () => { + it('fails if local registration does not exist for this user', async () => { + await user.update({ 'auth.local': { ok: true } }); + await expect(user.del(endpoint)).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cantDetachFb'), + }); + }); + it('succeeds', async () => { + let response = await user.del(endpoint); + expect(response).to.eql({}); + await user.sync(); + expect(user.auth.facebook).to.be.empty; + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/GET-logout.test.js b/test/api/v3/integration/user/auth/GET-logout.test.js new file mode 100644 index 0000000000..731c523bde --- /dev/null +++ b/test/api/v3/integration/user/auth/GET-logout.test.js @@ -0,0 +1,3 @@ +describe('GET /user/auth/logout', () => { + // TODO Test manually +}); diff --git a/test/api/v3/integration/user/auth/POST-firebase.test.js b/test/api/v3/integration/user/auth/POST-firebase.test.js new file mode 100644 index 0000000000..7ebd5a20cb --- /dev/null +++ b/test/api/v3/integration/user/auth/POST-firebase.test.js @@ -0,0 +1,18 @@ +import { + generateUser, +} from '../../../../../helpers/api-integration/v3'; +import moment from 'moment'; + +describe('POST /user/auth/firebase', () => { + let user; + + before(async () => { + user = await generateUser(); + }); + + it('returns a Firebase token', async () => { + let {token, expires} = await user.post('/user/auth/firebase'); + expect(moment(expires).isValid()).to.be.true; + expect(token).to.be.a('string'); + }); +}); diff --git a/test/api/v3/integration/user/auth/POST-login-local.test.js b/test/api/v3/integration/user/auth/POST-login-local.test.js new file mode 100644 index 0000000000..571b23c3ea --- /dev/null +++ b/test/api/v3/integration/user/auth/POST-login-local.test.js @@ -0,0 +1,69 @@ +import { + generateUser, + requester, + translate as t, +} from '../../../../../helpers/api-integration/v3'; + +describe('POST /user/auth/local/login', () => { + let api; + let user; + let endpoint = '/user/auth/local/login'; + let password = 'password'; + beforeEach(async () => { + api = requester(); + user = await generateUser(); + }); + it('success with username', async () => { + let response = await api.post(endpoint, { + username: user.auth.local.username, + password, + }); + expect(response.apiToken).to.eql(user.apiToken); + }); + it('success with email', async () => { + let response = await api.post(endpoint, { + username: user.auth.local.email, + password, + }); + expect(response.apiToken).to.eql(user.apiToken); + }); + it('user is blocked', async () => { + await user.update({ 'auth.blocked': 1 }); + await expect(api.post(endpoint, { + username: user.auth.local.username, + password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('accountSuspended', { userId: user._id }), + }); + }); + it('wrong password', async () => { + await expect(api.post(endpoint, { + username: user.auth.local.username, + password: 'wrong-password', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('invalidLoginCredentialsLong'), + }); + }); + it('missing username', async () => { + await expect(api.post(endpoint, { + password: 'wrong-password', + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + it('missing password', async () => { + await expect(api.post(endpoint, { + username: user.auth.local.username, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js new file mode 100644 index 0000000000..63d8f755a5 --- /dev/null +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -0,0 +1,349 @@ +import { + generateUser, + requester, + translate as t, + createAndPopulateGroup, +} from '../../../../../helpers/api-integration/v3'; +import { v4 as generateRandomUserName } from 'uuid'; +import { each } from 'lodash'; +import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption'; + +describe('POST /user/auth/local/register', () => { + context('username and email are free', () => { + let api; + + beforeEach(async () => { + api = requester(); + }); + + it('registers a new user', async () => { + let username = generateRandomUserName(); + let email = `${username}@example.com`; + let password = 'password'; + + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user._id).to.exist; + expect(user.apiToken).to.exist; + expect(user.auth.local.username).to.eql(username); + }); + + it('requires password and confirmPassword to match', async () => { + let username = generateRandomUserName(); + let email = `${username}@example.com`; + let password = 'password'; + let confirmPassword = 'not password'; + + await expect(api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires a username', async () => { + let email = `${generateRandomUserName()}@example.com`; + let password = 'password'; + let confirmPassword = 'password'; + + await expect(api.post('/user/auth/local/register', { + email, + password, + confirmPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires an email', async () => { + let username = generateRandomUserName(); + let password = 'password'; + + await expect(api.post('/user/auth/local/register', { + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires a valid email', async () => { + let username = generateRandomUserName(); + let email = 'notanemail@sdf'; + let password = 'password'; + + await expect(api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('requires a password', async () => { + let username = generateRandomUserName(); + let email = `${username}@example.com`; + let confirmPassword = 'password'; + + await expect(api.post('/user/auth/local/register', { + username, + email, + confirmPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + }); + + context('attach to facebook user', () => { + let user; + let email = 'some@email.net'; + let username = 'some-username'; + let password = 'some-password'; + beforeEach(async () => { + user = await generateUser(); + }); + it('checks onlySocialAttachLocal', async () => { + await expect(user.post('/user/auth/local/register', { + email, + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlySocialAttachLocal'), + }); + }); + it('succeeds', async () => { + await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } }); + await user.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + await user.sync(); + expect(user.auth.local.username).to.eql(username); + expect(user.auth.local.email).to.eql(email); + }); + }); + + context('login is already taken', () => { + let username, email, api; + + beforeEach(async () => { + api = requester(); + username = generateRandomUserName(); + email = `${username}@example.com`; + + return generateUser({ + 'auth.local.username': username, + 'auth.local.lowerCaseUsername': username, + 'auth.local.email': email, + }); + }); + + it('rejects if username is already taken', async () => { + let uniqueEmail = `${generateRandomUserName()}@exampe.com`; + let password = 'password'; + + await expect(api.post('/user/auth/local/register', { + username, + email: uniqueEmail, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('usernameTaken'), + }); + }); + + it('rejects if email is already taken', async () => { + let uniqueUsername = generateRandomUserName(); + let password = 'password'; + + await expect(api.post('/user/auth/local/register', { + username: uniqueUsername, + email, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('emailTaken'), + }); + }); + }); + + context('req.query.groupInvite', () => { + let api, username, email, password; + + beforeEach(() => { + api = requester(); + username = generateRandomUserName(); + email = `${username}@example.com`; + password = 'password'; + }); + + it('does not crash the signup process when it\'s invalid', async () => { + let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user._id).to.be.a('string'); + }); + + it('supports invite using req.query.groupInvite', async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + }); + + let invite = encrypt(JSON.stringify({ + id: group._id, + inviter: groupLeader._id, + sentAt: Date.now(), // so we can let it expire + })); + + let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.invitations.party).to.eql({ + id: group._id, + name: group.name, + inviter: groupLeader._id, + }); + }); + }); + + context('successful login via api', () => { + let api, username, email, password; + + beforeEach(() => { + api = requester(); + username = generateRandomUserName(); + email = `${username}@example.com`; + password = 'password'; + }); + + it('sets all site tour values to -2 (already seen)', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.flags.tour).to.not.be.empty; + + each(user.flags.tour, (value) => { + expect(value).to.eql(-2); + }); + }); + + it('populates user with default todos, not no other task types', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.tasksOrder.todos).to.not.be.empty; + expect(user.tasksOrder.dailys).to.be.empty; + expect(user.tasksOrder.habits).to.be.empty; + expect(user.tasksOrder.rewards).to.be.empty; + }); + + it('populates user with default tags', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.tags).to.not.be.empty; + }); + }); + + context('successful login with habitica-web header', () => { + let api, username, email, password; + + beforeEach(() => { + api = requester({}, {'x-client': 'habitica-web'}); + username = generateRandomUserName(); + email = `${username}@example.com`; + password = 'password'; + }); + + it('sets all common tutorial flags to true', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.flags.tour).to.not.be.empty; + + each(user.flags.tutorial.common, (value) => { + expect(value).to.eql(true); + }); + }); + + it('populates user with default todos, habits, and rewards', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.tasksOrder.todos).to.not.be.empty; + expect(user.tasksOrder.dailys).to.be.empty; + expect(user.tasksOrder.habits).to.not.be.empty; + expect(user.tasksOrder.rewards).to.not.be.empty; + }); + + it('populates user with default tags', async () => { + let user = await api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + expect(user.tags).to.not.be.empty; + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js new file mode 100644 index 0000000000..773d199db6 --- /dev/null +++ b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js @@ -0,0 +1,38 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-integration/v3'; + +describe('POST /user/reset-password', async () => { + let endpoint = '/user/reset-password'; + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('resets password', async () => { + let previousPassword = user.auth.local.hashed_password; + let response = await user.post(endpoint, { + email: user.auth.local.email, + }); + expect(response).to.eql({ data: {}, message: t('passwordReset') }); + await user.sync(); + expect(user.auth.local.hashed_password).to.not.eql(previousPassword); + }); + + it('same message on error as on success', async () => { + let response = await user.post(endpoint, { + email: 'nonExistent@email.com', + }); + expect(response).to.eql({ data: {}, message: t('passwordReset') }); + }); + + it('errors if email is not provided', async () => { + await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_email.test.js b/test/api/v3/integration/user/auth/PUT-user_update_email.test.js new file mode 100644 index 0000000000..47357d3c85 --- /dev/null +++ b/test/api/v3/integration/user/auth/PUT-user_update_email.test.js @@ -0,0 +1,79 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-v3-integration.helper'; + +const ENDPOINT = '/user/auth/update-email'; + +describe('PUT /user/auth/update-email', () => { + let newEmail = 'some-new-email_2@example.net'; + let oldPassword = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js + + context('Local Authenticaion User', async () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('does not change email if email is not provided', async () => { + await expect(user.put(ENDPOINT)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('does not change email if password is not provided', async () => { + await expect(user.put(ENDPOINT, { + newEmail, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('does not change email if wrong password is provided', async () => { + await expect(user.put(ENDPOINT, { + newEmail, + password: 'wrong password', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('wrongPassword'), + }); + }); + + it('changes email if new email and existing password are provided', async () => { + let response = await user.put(ENDPOINT, { + newEmail, + password: oldPassword, + }); + expect(response).to.eql({ email: 'some-new-email_2@example.net' }); + + await user.sync(); + expect(user.auth.local.email).to.eql(newEmail); + }); + }); + + context('Social Login User', async () => { + let socialUser; + + beforeEach(async () => { + socialUser = await generateUser(); + await socialUser.update({ 'auth.local': { ok: true } }); + }); + + it('does not change email if user.auth.local.email does not exist for this user', async () => { + await expect(socialUser.put(ENDPOINT, { + newEmail, + password: oldPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('userHasNoLocalRegistration'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js new file mode 100644 index 0000000000..bcc1ac25d3 --- /dev/null +++ b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js @@ -0,0 +1,53 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-v3-integration.helper'; + +const ENDPOINT = '/user/auth/update-password'; + +describe('PUT /user/auth/update-password', async () => { + let user; + let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js + let wrongPassword = 'wrong-password'; + let newPassword = 'new-password'; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('successfully changes the password', async () => { + let previousHashedPassword = user.auth.local.hashed_password; + let response = await user.put(ENDPOINT, { + password, + newPassword, + confirmPassword: newPassword, + }); + expect(response).to.eql({}); + await user.sync(); + expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword); + }); + + it('returns an error when confirmPassword does not match newPassword', async () => { + await expect(user.put(ENDPOINT, { + password, + newPassword, + confirmPassword: `${newPassword}-wrong-confirmation`, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('passwordConfirmationMatch'), + }); + }); + + it('returns an error when existing password is wrong', async () => { + await expect(user.put(ENDPOINT, { + password: wrongPassword, + newPassword, + confirmPassword: newPassword, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('wrongPassword'), + }); + }); +}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_username.test.js b/test/api/v3/integration/user/auth/PUT-user_update_username.test.js new file mode 100644 index 0000000000..372248db84 --- /dev/null +++ b/test/api/v3/integration/user/auth/PUT-user_update_username.test.js @@ -0,0 +1,76 @@ +import { + generateUser, + translate as t, +} from '../../../../../helpers/api-v3-integration.helper'; + +const ENDPOINT = '/user/auth/update-username'; + +describe('PUT /user/auth/update-username', async () => { + let user; + let newUsername = 'new-username'; + let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js + + beforeEach(async () => { + user = await generateUser(); + }); + + it('successfully changes username', async () => { + let response = await user.put(ENDPOINT, { + username: newUsername, + password, + }); + expect(response).to.eql({ username: newUsername }); + await user.sync(); + expect(user.auth.local.username).to.eql(newUsername); + }); + + context('errors', async () => { + it('prevents username update if new username is already taken', async () => { + let existingUsername = 'existing-username'; + await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername }); + + await expect(user.put(ENDPOINT, { + username: existingUsername, + password, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('usernameTaken'), + }); + }); + + it('errors if password is wrong', async () => { + await expect(user.put(ENDPOINT, { + username: newUsername, + password: 'wrong-password', + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('wrongPassword'), + }); + }); + + it('prevents social-only user from changing username', async () => { + let socialUser = await generateUser({ 'auth.local': { ok: true } }); + + await expect(socialUser.put(ENDPOINT, { + username: newUsername, + password, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('userHasNoLocalRegistration'), + }); + }); + + it('errors if new username is not provided', async () => { + await expect(user.put(ENDPOINT, { + password, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + }); +}); diff --git a/test/api/v3/unit/libs/analyticsService.test.js b/test/api/v3/unit/libs/analyticsService.test.js new file mode 100644 index 0000000000..771678cc3d --- /dev/null +++ b/test/api/v3/unit/libs/analyticsService.test.js @@ -0,0 +1,309 @@ +import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; + +import nock from 'nock'; + +describe('analyticsService', () => { + let amplitudeNock, gaNock; + + beforeEach(() => { + amplitudeNock = nock('https://api.amplitude.com') + .filteringPath(/httpapi.*/g, '') + .post('/') + .reply(200, {status: 'OK'}); + + gaNock = nock('http://www.google-analytics.com'); + }); + + describe('#track', () => { + let eventType, data; + + beforeEach(() => { + eventType = 'Cron'; + data = { + category: 'behavior', + uuid: 'unique-user-id', + resting: true, + cronCount: 5, + }; + }); + + context('Amplitude', () => { + it('calls out to amplitude', () => { + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('uses a dummy user id if none is provided', () => { + delete data.uuid; + + amplitudeNock + .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sets platform as server', () => { + amplitudeNock + .filteringPath(/httpapi.*platform.*server.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends details about event', () => { + amplitudeNock + .filteringPath(/httpapi.*event_properties%22%3A%7B%22category%22%3A%22behavior%22%2C%22resting%22%3Atrue%2C%22cronCount%22%3A5%7D%2C%22.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for gear if itemKey is provided', () => { + data.itemKey = 'headAccessory_special_foxEars'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Fox%20Ears.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for egg if itemKey is provided', () => { + data.itemKey = 'Wolf'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Wolf%20Egg.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for food if itemKey is provided', () => { + data.itemKey = 'Cake_Skeleton'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Bare%20Bones%20Cake.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for hatching potion if itemKey is provided', () => { + data.itemKey = 'Golden'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Golden%20Hatching%20Potion.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for quest if itemKey is provided', () => { + data.itemKey = 'atom1'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Attack%20of%20the%20Mundane%2C%20Part%201%3A%20Dish%20Disaster!.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends english item name for purchased spell if itemKey is provided', () => { + data.itemKey = 'seafoam'; + + amplitudeNock + .filteringPath(/httpapi.*itemName.*Seafoam.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends user data if provided', () => { + let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; + let user = { + stats, + contributor: { level: 1 }, + purchased: { plan: { planId: 'foo-plan' } }, + flags: {tour: {intro: -2}}, + habits: [{_id: 'habit'}], + dailys: [{_id: 'daily'}], + todos: [{_id: 'todo'}], + rewards: [{_id: 'reward'}], + }; + + data.user = user; + + amplitudeNock + .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, ''); + + return analyticsService.track(eventType, data) + .then(() => { + amplitudeNock.done(); + }); + }); + }); + + context('GA', () => { + it('calls out to GA', () => { + gaNock + .post('/collect') + .reply(200, {status: 'OK'}); + + return analyticsService.track(eventType, data) + .then(() => { + gaNock.done(); + }); + }); + + it('sends details about event', () => { + gaNock + .post('/collect', /ec=behavior&ea=Cron&v=1&tid=GA_ID&cid=.*&t=event/) + .reply(200, {status: 'OK'}); + + return analyticsService.track(eventType, data) + .then(() => { + gaNock.done(); + }); + }); + }); + }); + + describe('#trackPurchase', () => { + let data; + + beforeEach(() => { + data = { + uuid: 'user-id', + sku: 'paypal-checkout', + paymentMethod: 'PayPal', + itemPurchased: 'Gems', + purchaseValue: 8, + purchaseType: 'checkout', + gift: false, + quantity: 1, + }; + }); + + context('Amplitude', () => { + it('calls out to amplitude', () => { + return analyticsService.trackPurchase(data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('uses a dummy user id if none is provided', () => { + delete data.uuid; + + amplitudeNock + .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, ''); + + return analyticsService.trackPurchase(data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sets platform as server', () => { + amplitudeNock + .filteringPath(/httpapi.*platform.*server.*/g, ''); + + return analyticsService.trackPurchase(data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends details about purchase', () => { + amplitudeNock + .filteringPath(/httpapi.*aypal-checkout%22%2C%22paymentMethod%22%3A%22PayPal%22%2C%22itemPurchased%22%3A%22Gems%22%2C%22purchaseType%22%3A%22checkout%22%2C%22gift%22%3Afalse%2C%22quantity%22%3A1%7D%2C%22event_type%22%3A%22purchase%22%2C%22revenue.*/g, ''); + + return analyticsService.trackPurchase(data) + .then(() => { + amplitudeNock.done(); + }); + }); + + it('sends user data if provided', () => { + let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; + let user = { + stats, + contributor: { level: 1 }, + purchased: { plan: { planId: 'foo-plan' } }, + flags: {tour: {intro: -2}}, + habits: [{_id: 'habit'}], + dailys: [{_id: 'daily'}], + todos: [{_id: 'todo'}], + rewards: [{_id: 'reward'}], + }; + + data.user = user; + + amplitudeNock + .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, ''); + + return analyticsService.trackPurchase(data) + .then(() => { + amplitudeNock.done(); + }); + }); + }); + + context('GA', () => { + it('calls out to GA', () => { + gaNock + .post('/collect') + .reply(200, {status: 'OK'}); + + return analyticsService.trackPurchase(data) + .then(() => { + gaNock.done(); + }); + }); + + it('sends details about purchase', () => { + gaNock + .post('/collect', /ti=user-id&tr=8&v=1&tid=GA_ID&cid=.*&t=transaction/) + .reply(200, {status: 'OK'}) + .post('/collect', /ec=commerce&ea=checkout&el=PayPal&ev=8&v=1&tid=GA_ID&cid=.*&t=event/) + .reply(200, {status: 'OK'}); + + return analyticsService.trackPurchase(data) + .then(() => { + gaNock.done(); + }); + }); + }); + }); + + describe('mockAnalyticsService', () => { + it('has stubbed track method', () => { + expect(analyticsService.mockAnalyticsService).to.respondTo('track'); + }); + + it('has stubbed trackPurchase method', () => { + expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase'); + }); + }); +}); diff --git a/test/api/v3/unit/libs/baseModel.test.js b/test/api/v3/unit/libs/baseModel.test.js new file mode 100644 index 0000000000..39bf7df047 --- /dev/null +++ b/test/api/v3/unit/libs/baseModel.test.js @@ -0,0 +1,97 @@ +import baseModel from '../../../../../website/server/libs/api-v3/baseModel'; +import mongoose from 'mongoose'; + +describe('Base model plugin', () => { + let schema; + + beforeEach(() => { + schema = new mongoose.Schema(); + sandbox.stub(schema, 'add'); + }); + + it('adds a _id field to the schema', () => { + schema.plugin(baseModel); + + expect(schema.add).to.be.calledWith(sinon.match({ + _id: sinon.match.object, + })); + }); + + it('can add timestamps fields', () => { + schema.plugin(baseModel, {timestamps: true}); + + expect(schema.add).to.be.calledTwice; + }); + + it('can sanitize input objects', () => { + schema.plugin(baseModel, { + noSet: ['noUpdateForMe'], + }); + + expect(schema.statics.sanitize).to.exist; + let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true}); + + expect(sanitized).to.have.property('ok'); + expect(sanitized).not.to.have.property('noUpdateForMe'); + expect(sanitized.noUpdateForMe).to.equal(undefined); + }); + + it('accepts an array of additional fields to sanitize at runtime', () => { + schema.plugin(baseModel, { + noSet: ['noUpdateForMe'], + }); + + expect(schema.statics.sanitize).to.exist; + let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true, usuallySettable: true}, ['usuallySettable']); + + expect(sanitized).to.have.property('ok'); + expect(sanitized).not.to.have.property('noUpdateForMe'); + expect(sanitized).not.to.have.property('usuallySettable'); + }); + + + it('can make fields private', () => { + schema.plugin(baseModel, { + private: ['amPrivate'], + }); + + expect(schema.options.toJSON.transform).to.exist; + let objToTransform = {ok: true, amPrivate: true}; + let privatized = schema.options.toJSON.transform({}, objToTransform); + + expect(privatized).to.have.property('ok'); + expect(privatized).not.to.have.property('amPrivate'); + }); + + it('accepts a further transform function for toJSON', () => { + let options = { + private: ['amPrivate'], + toJSONTransform: sandbox.stub().returns(true), + }; + + schema.plugin(baseModel, options); + + let objToTransform = {ok: true, amPrivate: true}; + let doc = {doc: true}; + let privatized = schema.options.toJSON.transform(doc, objToTransform); + + expect(privatized).to.equals(true); + expect(options.toJSONTransform).to.be.calledWith(objToTransform, doc); + }); + + it('accepts a transform function for sanitize', () => { + let options = { + private: ['amPrivate'], + sanitizeTransform: sandbox.stub().returns(true), + }; + + schema.plugin(baseModel, options); + + expect(schema.options.toJSON.transform).to.exist; + let objToSanitize = {ok: true, noUpdateForMe: true}; + let sanitized = schema.statics.sanitize(objToSanitize); + + expect(sanitized).to.equals(true); + expect(options.sanitizeTransform).to.be.calledWith(objToSanitize); + }); +}); diff --git a/test/api/v3/unit/libs/buildManifest.test.js b/test/api/v3/unit/libs/buildManifest.test.js new file mode 100644 index 0000000000..1444738f10 --- /dev/null +++ b/test/api/v3/unit/libs/buildManifest.test.js @@ -0,0 +1,19 @@ +import { + getManifestFiles, +} from '../../../../../website/server/libs/api-v3/buildManifest'; + +describe('Build Manifest', () => { + describe('getManifestFiles', () => { + it('returns an html string', () => { + let htmlCode = getManifestFiles('app'); + + expect(htmlCode.startsWith(' { + expect(() => { + getManifestFiles('strange name here'); + }).to.throw(Error); + }); + }); +}); diff --git a/test/api/v3/unit/libs/collectionManipulators.test.js b/test/api/v3/unit/libs/collectionManipulators.test.js new file mode 100644 index 0000000000..da44fd5319 --- /dev/null +++ b/test/api/v3/unit/libs/collectionManipulators.test.js @@ -0,0 +1,88 @@ +import mongoose from 'mongoose'; +import { + removeFromArray, +} from '../../../../../website/server/libs/api-v3/collectionManipulators'; + +describe('Collection Manipulators', () => { + describe('removeFromArray', () => { + it('removes element from array', () => { + let array = ['a', 'b', 'c', 'd']; + + removeFromArray(array, 'c'); + + expect(array).to.not.include('c'); + }); + + it('removes object from array', () => { + let array = [ + { id: 'a', foo: 'bar' }, + { id: 'b', foo: 'bar' }, + { id: 'c', foo: 'bar' }, + { id: 'd', foo: 'bar' }, + { id: 'e', foo: 'bar' }, + ]; + + removeFromArray(array, { id: 'c' }); + + expect(array).to.not.include({ id: 'c', foo: 'bar' }); + }); + + it('does not change array if value is not found', () => { + let array = ['a', 'b', 'c', 'd']; + + removeFromArray(array, 'z'); + + expect(array).to.have.a.lengthOf(4); + expect(array[0]).to.eql('a'); + expect(array[1]).to.eql('b'); + expect(array[2]).to.eql('c'); + expect(array[3]).to.eql('d'); + }); + + it('returns the removed element', () => { + let array = ['a', 'b', 'c']; + + let result = removeFromArray(array, 'b'); + + expect(result).to.eql('b'); + }); + + it('returns the removed object element', () => { + let array = [ + { id: 'a', foo: 'bar' }, + { id: 'b', foo: 'bar' }, + { id: 'c', foo: 'bar' }, + { id: 'd', foo: 'bar' }, + { id: 'e', foo: 'bar' }, + ]; + + let result = removeFromArray(array, { id: 'c' }); + + expect(result).to.eql({ id: 'c', foo: 'bar' }); + }); + + it('returns false if item is not found', () => { + let array = ['a', 'b', 'c']; + + let result = removeFromArray(array, 'z'); + + expect(result).to.eql(false); + }); + + it('persists removal of element when mongoose document is saved', async () => { + let schema = new mongoose.Schema({ + array: Array, + }); + let Model = mongoose.model('ModelToTestRemoveFromArray', schema); + let model = await new Model({ + array: ['a', 'b', 'c'], + }).save(); // Initial creation + + removeFromArray(model.array, 'b'); + + let savedModel = await model.save(); + + expect(savedModel.array).to.not.include('b'); + }); + }); +}); diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js new file mode 100644 index 0000000000..a7e732442c --- /dev/null +++ b/test/api/v3/unit/libs/cron.test.js @@ -0,0 +1,573 @@ +/* eslint-disable global-require */ +import moment from 'moment'; +import { cron } from '../../../../../website/server/libs/api-v3/cron'; +import { model as User } from '../../../../../website/server/models/user'; +import * as Tasks from '../../../../../website/server/models/task'; +import { clone } from 'lodash'; +import common from '../../../../../common'; + +// const scoreTask = common.ops.scoreTask; + +describe('cron', () => { + let user; + let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; + let daysMissed = 0; + let analytics = { + track: sinon.spy(), + }; + + beforeEach(() => { + user = new User({ + auth: { + local: { + username: 'username', + lowerCaseUsername: 'username', + email: 'email@email.email', + salt: 'salt', + hashed_password: 'hashed_password', // eslint-disable-line camelcase + }, + }, + }); + + user._statsComputed = { + mp: 10, + }; + }); + + it('updates user.auth.timestamps.loggedin and lastCron', () => { + let now = new Date(); + + cron({user, tasksByType, daysMissed, analytics, now}); + + expect(user.auth.timestamps.loggedin).to.equal(now); + expect(user.lastCron).to.equal(now); + }); + + it('updates user.preferences.timezoneOffsetAtLastCron', () => { + let timezoneOffsetFromUserPrefs = 1; + + cron({user, tasksByType, daysMissed, analytics, timezoneOffsetFromUserPrefs}); + + expect(user.preferences.timezoneOffsetAtLastCron).to.equal(timezoneOffsetFromUserPrefs); + }); + + it('resets user.items.lastDrop.count', () => { + user.items.lastDrop.count = 4; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.items.lastDrop.count).to.equal(0); + }); + + it('increments user cron count', () => { + let cronCountBefore = user.flags.cronCount; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore); + }); + + describe('end of the month perks', () => { + beforeEach(() => { + user.purchased.plan.customerId = 'subscribedId'; + user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); + }); + + it('resets plan.gemsBought on a new month', () => { + user.purchased.plan.gemsBought = 10; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.gemsBought).to.equal(0); + }); + + it('resets plan.dateUpdated on a new month', () => { + let currentMonth = moment().format('MMYYYY'); + cron({user, tasksByType, daysMissed, analytics}); + expect(moment(user.purchased.plan.dateUpdated).format('MMYYYY')).to.equal(currentMonth); + }); + + it('increments plan.consecutive.count', () => { + user.purchased.plan.consecutive.count = 0; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.count).to.equal(1); + }); + + it('decrements plan.consecutive.offset when offset is greater than 0', () => { + user.purchased.plan.consecutive.offset = 1; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.offset).to.equal(0); + }); + + it('increments plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => { + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.trinkets).to.equal(1); + }); + + it('increments plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => { + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(5); + }); + + it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => { + user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + }); + + it('does not reset plan stats if we are before the last day of the cancelled month', () => { + user.purchased.plan.dateTerminated = moment(new Date()).add({days: 1}); + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.customerId).to.exist; + }); + + it('does reset plan stats until we are after the last day of the cancelled month', () => { + user.purchased.plan.dateTerminated = moment(new Date()).subtract({days: 1}); + user.purchased.plan.consecutive.gemCapExtra = 20; + user.purchased.plan.consecutive.count = 5; + user.purchased.plan.consecutive.offset = 1; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.purchased.plan.customerId).to.not.exist; + expect(user.purchased.plan.consecutive.gemCapExtra).to.be.empty; + expect(user.purchased.plan.consecutive.count).to.be.empty; + expect(user.purchased.plan.consecutive.offset).to.be.empty; + }); + }); + + describe('end of the month perks when user is not subscribed', () => { + it('does not reset plan.gemsBought on a new month', () => { + user.purchased.plan.gemsBought = 10; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.gemsBought).to.equal(10); + }); + + it('does not reset plan.dateUpdated on a new month', () => { + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.dateUpdated).to.be.empty; + }); + + it('does not increment plan.consecutive.count', () => { + user.purchased.plan.consecutive.count = 0; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.count).to.equal(0); + }); + + it('does not decrement plan.consecutive.offset when offset is greater than 0', () => { + user.purchased.plan.consecutive.offset = 1; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.offset).to.equal(1); + }); + + it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => { + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.trinkets).to.equal(0); + }); + + it('doest not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => { + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0); + }); + + it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => { + user.purchased.plan.consecutive.gemCapExtra = 25; + user.purchased.plan.consecutive.count = 5; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); + }); + + it('does nothing to plan stats if we are before the last day of the cancelled month', () => { + user.purchased.plan.dateTerminated = moment(new Date()).add({days: 1}); + cron({user, tasksByType, daysMissed, analytics}); + expect(user.purchased.plan.customerId).to.not.exist; + }); + + xit('does nothing to plan stats when we are after the last day of the cancelled month', () => { + user.purchased.plan.dateTerminated = moment(new Date()).subtract({days: 1}); + user.purchased.plan.consecutive.gemCapExtra = 20; + user.purchased.plan.consecutive.count = 5; + user.purchased.plan.consecutive.offset = 1; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.purchased.plan.customerId).to.exist; + expect(user.purchased.plan.consecutive.gemCapExtra).to.exist; + expect(user.purchased.plan.consecutive.count).to.exist; + expect(user.purchased.plan.consecutive.offset).to.exist; + }); + }); + + describe('user is sleeping', () => { + beforeEach(() => { + user.preferences.sleep = true; + }); + + it('clears user buffs', () => { + user.stats.buffs = { + str: 1, + int: 1, + per: 1, + con: 1, + stealth: 1, + streaks: true, + }; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.stats.buffs.str).to.equal(0); + expect(user.stats.buffs.int).to.equal(0); + expect(user.stats.buffs.per).to.equal(0); + expect(user.stats.buffs.con).to.equal(0); + expect(user.stats.buffs.stealth).to.equal(0); + expect(user.stats.buffs.streaks).to.be.false; + }); + + it('resets all dailies without damaging user', () => { + let daily = { + text: 'test daily', + type: 'daily', + frequency: 'daily', + everyX: 5, + startDate: new Date(), + }; + + let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap + tasksByType.dailys.push(task); + tasksByType.dailys[0].completed = true; + + let healthBefore = user.stats.hp; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(tasksByType.dailys[0].completed).to.be.false; + expect(user.stats.hp).to.equal(healthBefore); + }); + }); + + describe('todos', () => { + beforeEach(() => { + let todo = { + text: 'test todo', + type: 'todo', + value: 0, + }; + + let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap + tasksByType.todos.push(task); + }); + + it('should make uncompleted todos redder', () => { + let valueBefore = tasksByType.todos[0].value; + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore); + }); + + it('should add history of completed todos to user history', () => { + tasksByType.todos[0].completed = true; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.history.todos).to.be.lengthOf(1); + }); + }); + + describe('dailys', () => { + beforeEach(() => { + let daily = { + text: 'test daily', + type: 'daily', + }; + + let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap + tasksByType.dailys = []; + tasksByType.dailys.push(task); + + user._statsComputed = { + con: 1, + }; + }); + + it('should add history', () => { + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.dailys[0].history).to.be.lengthOf(1); + }); + + it('should set tasks completed to false', () => { + tasksByType.dailys[0].completed = true; + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.dailys[0].completed).to.be.false; + }); + + it('should reset task checklist for completed dailys', () => { + tasksByType.dailys[0].checklist.push({title: 'test', completed: false}); + tasksByType.dailys[0].completed = true; + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; + }); + + it('should reset task checklist for dailys with scheduled misses', () => { + daysMissed = 10; + tasksByType.dailys[0].checklist.push({title: 'test', completed: false}); + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + cron({user, tasksByType, daysMissed, analytics}); + expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; + }); + + it('should do damage for missing a daily', () => { + daysMissed = 1; + let hpBefore = user.stats.hp; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.stats.hp).to.be.lessThan(hpBefore); + }); + + it('should not do damage for missing a daily if user stealth buff is greater than or equal to days missed', () => { + daysMissed = 1; + let hpBefore = user.stats.hp; + user.stats.buffs.stealth = 2; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.stats.hp).to.equal(hpBefore); + }); + + it('should do less damage for missing a daily with partial completion', () => { + daysMissed = 1; + let hpBefore = user.stats.hp; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + cron({user, tasksByType, daysMissed, analytics}); + let hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp; + + hpBefore = user.stats.hp; + tasksByType.dailys[0].checklist.push({title: 'test', completed: true}); + tasksByType.dailys[0].checklist.push({title: 'test2', completed: false}); + cron({user, tasksByType, daysMissed, analytics}); + let hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp; + + expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily); + }); + + it('should decrement quest progress down for missing a daily', () => { + daysMissed = 1; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + + let progress = cron({user, tasksByType, daysMissed, analytics}); + + expect(progress.down).to.equal(-1); + }); + }); + + describe('habits', () => { + beforeEach(() => { + let habit = { + text: 'test habit', + type: 'habit', + }; + + let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line babel/new-cap + tasksByType.habits = []; + tasksByType.habits.push(task); + }); + + it('should decrement only up value', () => { + tasksByType.habits[0].value = 1; + tasksByType.habits[0].down = false; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(tasksByType.habits[0].value).to.be.lessThan(1); + }); + + it('should decrement only down value', () => { + tasksByType.habits[0].value = 1; + tasksByType.habits[0].up = false; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(tasksByType.habits[0].value).to.be.lessThan(1); + }); + + it('should do nothing to habits with both up and down', () => { + tasksByType.habits[0].value = 1; + tasksByType.habits[0].up = true; + tasksByType.habits[0].down = true; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(tasksByType.habits[0].value).to.equal(1); + }); + }); + + describe('perfect day', () => { + beforeEach(() => { + let daily = { + text: 'test daily', + type: 'daily', + }; + + let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap + tasksByType.dailys = []; + tasksByType.dailys.push(task); + + user._statsComputed = { + con: 1, + }; + }); + + it('stores a new entry in user.history.exp', () => { + user.stats.lvl = 2; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.history.exp).to.have.lengthOf(1); + expect(user.history.exp[0].value).to.equal(150); + }); + + it('increments perfect day achievement', () => { + tasksByType.dailys[0].completed = true; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.achievements.perfect).to.equal(1); + }); + + it('increments user buffs if they have a perfect day', () => { + tasksByType.dailys[0].completed = true; + + let previousBuffs = clone(user.stats.buffs); + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); + expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int); + expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per); + expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con); + }); + + it('clears buffs if user does not have a perfect day', () => { + daysMissed = 1; + tasksByType.dailys[0].completed = false; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + + user.stats.buffs = { + str: 1, + int: 1, + per: 1, + con: 1, + stealth: 0, + streaks: true, + }; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.stats.buffs.str).to.equal(0); + expect(user.stats.buffs.int).to.equal(0); + expect(user.stats.buffs.per).to.equal(0); + expect(user.stats.buffs.con).to.equal(0); + expect(user.stats.buffs.stealth).to.equal(0); + expect(user.stats.buffs.streaks).to.be.false; + }); + }); + + describe('adding mp', () => { + it('should add mp to user', () => { + let mpBefore = user.stats.mp; + tasksByType.dailys[0].completed = true; + user._statsComputed.maxMP = 100; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.stats.mp).to.be.greaterThan(mpBefore); + }); + + it('set user\'s mp to user._statsComputed.maxMP when user.stats.mp is greater', () => { + user.stats.mp = 120; + user._statsComputed.maxMP = 100; + cron({user, tasksByType, daysMissed, analytics}); + expect(user.stats.mp).to.equal(user._statsComputed.maxMP); + }); + }); + + describe('quest progress', () => { + beforeEach(() => { + let daily = { + text: 'test daily', + type: 'daily', + }; + + let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap + tasksByType.dailys = []; + tasksByType.dailys.push(task); + + user._statsComputed = { + con: 1, + }; + + daysMissed = 1; + tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); + }); + + it('resets user progress', () => { + cron({user, tasksByType, daysMissed, analytics}); + expect(user.party.quest.progress.up).to.equal(0); + expect(user.party.quest.progress.down).to.equal(0); + expect(user.party.quest.progress.collect).to.be.empty; + }); + + it('applies the user progress', () => { + let progress = cron({user, tasksByType, daysMissed, analytics}); + expect(progress.down).to.equal(-1); + }); + }); + + describe('private messages', () => { + let lastMessageId; + + beforeEach(() => { + let maxPMs = 200; + for (let index = 0; index < maxPMs - 1; index += 1) { + let messageId = common.uuid(); + user.inbox.messages[messageId] = { + id: messageId, + text: `test ${index}`, + timestamp: Number(new Date()), + likes: {}, + flags: {}, + flagCount: 0, + }; + } + + lastMessageId = common.uuid(); + user.inbox.messages[lastMessageId] = { + id: lastMessageId, + text: `test ${lastMessageId}`, + timestamp: Number(new Date()), + likes: {}, + flags: {}, + flagCount: 0, + }; + }); + + xit('does not clear pms under 200', () => { + cron({user, tasksByType, daysMissed, analytics}); + expect(user.inbox.messages[lastMessageId]).to.exist; + }); + + xit('clears pms over 200', () => { + let messageId = common.uuid(); + user.inbox.messages[messageId] = { + id: messageId, + text: `test ${messageId}`, + timestamp: Number(new Date()), + likes: {}, + flags: {}, + flagCount: 0, + }; + + cron({user, tasksByType, daysMissed, analytics}); + + expect(user.inbox.messages[messageId]).to.not.exist; + }); + }); +}); diff --git a/test/api/v3/unit/libs/email.test.js b/test/api/v3/unit/libs/email.test.js new file mode 100644 index 0000000000..bb76e05cfb --- /dev/null +++ b/test/api/v3/unit/libs/email.test.js @@ -0,0 +1,232 @@ +/* eslint-disable global-require */ +import request from 'request'; +import nconf from 'nconf'; +import nodemailer from 'nodemailer'; +import Bluebird from 'bluebird'; +import requireAgain from 'require-again'; +import logger from '../../../../../website/server/libs/api-v3/logger'; + +function defer () { + let resolve; + let reject; + + let promise = new Bluebird((resolveParam, rejectParam) => { + resolve = resolveParam; + reject = rejectParam; + }); + + return { + resolve, + reject, + promise, + }; +} + +function getUser () { + return { + _id: 'random _id', + auth: { + local: { + username: 'username', + email: 'email@email', + }, + facebook: { + emails: [{ + value: 'email@facebook', + }], + displayName: 'fb display name', + }, + }, + profile: { + name: 'profile name', + }, + preferences: { + emailNotifications: { + unsubscribeFromAll: false, + }, + }, + }; +} + +describe('emails', () => { + let pathToEmailLib = '../../../../../website/server/libs/api-v3/email'; + + describe('sendEmail', () => { + it('can send an email using the default transport', () => { + let sendMailSpy = sandbox.stub().returns(defer().promise); + + sandbox.stub(nodemailer, 'createTransport').returns({ + sendMail: sendMailSpy, + }); + + let attachEmail = requireAgain(pathToEmailLib); + attachEmail.send(); + expect(sendMailSpy).to.be.calledOnce; + }); + + it('logs errors', (done) => { + let deferred = defer(); + let sendMailSpy = sandbox.stub().returns(deferred.promise); + + sandbox.stub(nodemailer, 'createTransport').returns({ + sendMail: sendMailSpy, + }); + sandbox.stub(logger, 'error'); + + let attachEmail = requireAgain(pathToEmailLib); + attachEmail.send(); + expect(sendMailSpy).to.be.calledOnce; + deferred.reject(); + + // wait for unhandledRejection event to fire + setTimeout(() => { + expect(logger.error).to.be.calledOnce; + done(); + }, 20); + }); + }); + + describe('getUserInfo', () => { + it('returns an empty object if no field request', () => { + let attachEmail = requireAgain(pathToEmailLib); + let getUserInfo = attachEmail.getUserInfo; + expect(getUserInfo({}, [])).to.be.empty; + }); + + it('returns correct user data', () => { + let attachEmail = requireAgain(pathToEmailLib); + let getUserInfo = attachEmail.getUserInfo; + let user = getUser(); + let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); + + expect(data).to.have.property('name', user.profile.name); + expect(data).to.have.property('email', user.auth.local.email); + expect(data).to.have.property('_id', user._id); + expect(data).to.have.property('canSend', true); + }); + + it('returns correct user data [facebook users]', () => { + let attachEmail = requireAgain(pathToEmailLib); + let getUserInfo = attachEmail.getUserInfo; + let user = getUser(); + delete user.profile.name; + delete user.auth.local; + + let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); + + expect(data).to.have.property('name', user.auth.facebook.displayName); + expect(data).to.have.property('email', user.auth.facebook.emails[0].value); + expect(data).to.have.property('_id', user._id); + expect(data).to.have.property('canSend', true); + }); + + it('has fallbacks for missing data', () => { + let attachEmail = requireAgain(pathToEmailLib); + let getUserInfo = attachEmail.getUserInfo; + let user = getUser(); + delete user.profile.name; + delete user.auth.local.email; + delete user.auth.facebook; + + let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); + + expect(data).to.have.property('name', user.auth.local.username); + expect(data).not.to.have.property('email'); + expect(data).to.have.property('_id', user._id); + expect(data).to.have.property('canSend', true); + }); + }); + + describe('sendTxnEmail', () => { + beforeEach(() => { + sandbox.stub(request, 'post'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('can send a txn email to one recipient', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + let attachEmail = requireAgain(pathToEmailLib); + let sendTxnEmail = attachEmail.sendTxn; + let emailType = 'an email type'; + let mailingInfo = { + name: 'my name', + email: 'my@email', + }; + + sendTxnEmail(mailingInfo, emailType); + expect(request.post).to.be.calledWith(sinon.match({ + json: { + data: { + emailType: sinon.match.same(emailType), + to: sinon.match((value) => { + return Array.isArray(value) && value[0].name === mailingInfo.name; + }, 'matches mailing info array'), + }, + }, + })); + }); + + it('does not send email if address is missing', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + let attachEmail = requireAgain(pathToEmailLib); + let sendTxnEmail = attachEmail.sendTxn; + let emailType = 'an email type'; + let mailingInfo = { + name: 'my name', + // email: 'my@email', + }; + + sendTxnEmail(mailingInfo, emailType); + expect(request.post).not.to.be.called; + }); + + it('uses getUserInfo in case of user data', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + let attachEmail = requireAgain(pathToEmailLib); + let sendTxnEmail = attachEmail.sendTxn; + let emailType = 'an email type'; + let mailingInfo = getUser(); + + sendTxnEmail(mailingInfo, emailType); + expect(request.post).to.be.calledWith(sinon.match({ + json: { + data: { + emailType: sinon.match.same(emailType), + to: sinon.match(val => val[0]._id === mailingInfo._id), + }, + }, + })); + }); + + it('sends email with some default variables', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + let attachEmail = requireAgain(pathToEmailLib); + let sendTxnEmail = attachEmail.sendTxn; + let emailType = 'an email type'; + let mailingInfo = { + name: 'my name', + email: 'my@email', + }; + let variables = [1, 2, 3]; + + sendTxnEmail(mailingInfo, emailType, variables); + expect(request.post).to.be.calledWith(sinon.match({ + json: { + data: { + variables: sinon.match((value) => { + return value[0].name === 'BASE_URL'; + }, 'matches variables'), + personalVariables: sinon.match((value) => { + return value[0].rcpt === mailingInfo.email && + value[0].vars[0].name === 'RECIPIENT_NAME' && + value[0].vars[1].name === 'RECIPIENT_UNSUB_URL'; + }, 'matches personal variables'), + }, + }, + })); + }); + }); +}); diff --git a/test/api/v3/unit/libs/encryption.test.js b/test/api/v3/unit/libs/encryption.test.js new file mode 100644 index 0000000000..a63a527e74 --- /dev/null +++ b/test/api/v3/unit/libs/encryption.test.js @@ -0,0 +1,15 @@ +import { + encrypt, + decrypt, +} from '../../../../../website/server/libs/api-v3/encryption'; + +describe('encryption', () => { + it('can encrypt and decrypt', () => { + let data = 'some secret text'; + let encrypted = encrypt(data); + let decrypted = decrypt(encrypted); + + expect(encrypted).not.to.equal(data); + expect(data).to.equal(decrypted); + }); +}); diff --git a/test/api/v3/unit/libs/errors.test.js b/test/api/v3/unit/libs/errors.test.js new file mode 100644 index 0000000000..efa694d5ab --- /dev/null +++ b/test/api/v3/unit/libs/errors.test.js @@ -0,0 +1,122 @@ +// TODO move to shared tests +import { + CustomError, + NotAuthorized, + BadRequest, + InternalServerError, + NotFound, +} from '../../../../../website/server/libs/api-v3/errors'; + +describe('Custom Errors', () => { + describe('CustomError', () => { + it('is an instance of Error', () => { + let customError = new CustomError(); + + expect(customError).to.be.an.instanceOf(Error); + }); + }); + + describe('NotAuthorized', () => { + it('is an instance of CustomError', () => { + let notAuthorizedError = new NotAuthorized(); + + expect(notAuthorizedError).to.be.an.instanceOf(CustomError); + }); + + it('it returns an http code of 401', () => { + let notAuthorizedError = new NotAuthorized(); + + expect(notAuthorizedError.httpCode).to.eql(401); + }); + + it('returns a default message', () => { + let notAuthorizedError = new NotAuthorized(); + + expect(notAuthorizedError.message).to.eql('Not authorized.'); + }); + + it('allows a custom message', () => { + let notAuthorizedError = new NotAuthorized('Custom Error Message'); + + expect(notAuthorizedError.message).to.eql('Custom Error Message'); + }); + }); + + describe('NotFound', () => { + it('is an instance of CustomError', () => { + let notAuthorizedError = new NotFound(); + + expect(notAuthorizedError).to.be.an.instanceOf(CustomError); + }); + + it('it returns an http code of 404', () => { + let notAuthorizedError = new NotFound(); + + expect(notAuthorizedError.httpCode).to.eql(404); + }); + + it('returns a default message', () => { + let notAuthorizedError = new NotFound(); + + expect(notAuthorizedError.message).to.eql('Not found.'); + }); + + it('allows a custom message', () => { + let notAuthorizedError = new NotFound('Custom Error Message'); + + expect(notAuthorizedError.message).to.eql('Custom Error Message'); + }); + }); + + describe('BadRequest', () => { + it('is an instance of CustomError', () => { + let badRequestError = new BadRequest(); + + expect(badRequestError).to.be.an.instanceOf(CustomError); + }); + + it('it returns an http code of 401', () => { + let badRequestError = new BadRequest(); + + expect(badRequestError.httpCode).to.eql(400); + }); + + it('returns a default message', () => { + let badRequestError = new BadRequest(); + + expect(badRequestError.message).to.eql('Bad request.'); + }); + + it('allows a custom message', () => { + let badRequestError = new BadRequest('Custom Error Message'); + + expect(badRequestError.message).to.eql('Custom Error Message'); + }); + }); + + describe('InternalServerError', () => { + it('is an instance of CustomError', () => { + let internalServerError = new InternalServerError(); + + expect(internalServerError).to.be.an.instanceOf(CustomError); + }); + + it('it returns an http code of 500', () => { + let internalServerError = new InternalServerError(); + + expect(internalServerError.httpCode).to.eql(500); + }); + + it('returns a default message', () => { + let internalServerError = new InternalServerError(); + + expect(internalServerError.message).to.eql('An unexpected error occurred.'); + }); + + it('allows a custom message', () => { + let internalServerError = new InternalServerError('Custom Error Message'); + + expect(internalServerError.message).to.eql('Custom Error Message'); + }); + }); +}); diff --git a/test/api/v3/unit/libs/i18n.test.js b/test/api/v3/unit/libs/i18n.test.js new file mode 100644 index 0000000000..06ebcbc0b6 --- /dev/null +++ b/test/api/v3/unit/libs/i18n.test.js @@ -0,0 +1,39 @@ +import { + translations, + localePath, + langCodes, +} from '../../../../../website/server/libs/api-v3/i18n'; +import fs from 'fs'; +import path from 'path'; + +describe('i18n', () => { + let listOfLocales = []; + + before((done) => { + fs.readdir(localePath, (err, files) => { + if (err) return done(err); + + files.forEach((file) => { + if (fs.statSync(path.join(localePath, file)).isDirectory() === false) return; + listOfLocales.push(file); + }); + + listOfLocales = listOfLocales.sort(); + done(); + }); + }); + + describe('translations', () => { + it('includes a translation object for each locale', () => { + listOfLocales.forEach((locale) => { + expect(translations[locale]).to.be.an('object'); + }); + }); + }); + + describe('langCodes', () => { + it('is a list of all the language codes', () => { + expect(langCodes.sort()).to.eql(listOfLocales); + }); + }); +}); diff --git a/test/api/v3/unit/libs/logger.js b/test/api/v3/unit/libs/logger.js new file mode 100644 index 0000000000..b7e1d490fc --- /dev/null +++ b/test/api/v3/unit/libs/logger.js @@ -0,0 +1,57 @@ +import winston from 'winston'; +import requireAgain from 'require-again'; + +/* eslint-disable global-require */ +describe('logger', () => { + let pathToLoggerLib = '../../../../../website/server/libs/api-v3/logger'; + let infoSpy; + let errorSpy; + + beforeEach(() => { + infoSpy = sandbox.stub(); + errorSpy = sandbox.stub(); + sandbox.stub(winston, 'Logger').returns({ + info: infoSpy, + error: errorSpy, + }); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('info', () => { + let attachLogger = requireAgain(pathToLoggerLib); + attachLogger.info(1, 2, 3); + expect(infoSpy).to.be.calledOnce; + expect(infoSpy).to.be.calledWith(1, 2, 3); + }); + + describe('error', () => { + it('with custom arguments', () => { + let attachLogger = requireAgain(pathToLoggerLib); + attachLogger.error(1, 2, 3, 4); + expect(errorSpy).to.be.calledOnce; + expect(errorSpy).to.be.calledWith(1, 2, 3, 4); + }); + + it('with error', () => { + let attachLogger = requireAgain(pathToLoggerLib); + let errInstance = new Error('An error.'); + attachLogger.error(errInstance, { + data: 1, + }, 2, 3); + expect(errorSpy).to.be.calledOnce; + // using calledWith doesn't work + let lastCallArgs = errorSpy.lastCall.args; + + expect(lastCallArgs[3]).to.equal(3); + expect(lastCallArgs[2]).to.equal(2); + expect(lastCallArgs[1]).to.eql({ + data: 1, + fullError: errInstance, + }); + expect(lastCallArgs[0]).to.eql(errInstance.stack); + }); + }); +}); diff --git a/test/api/v3/unit/libs/password.test.js b/test/api/v3/unit/libs/password.test.js new file mode 100644 index 0000000000..68290aebc6 --- /dev/null +++ b/test/api/v3/unit/libs/password.test.js @@ -0,0 +1,41 @@ +import { + encrypt as encryptPassword, + makeSalt, +} from '../../../../../website/server/libs/api-v3/password'; + +describe('Password Utilities', () => { + describe('Encrypt', () => { + it('always encrypt the same password to the same value when using the same salt', () => { + let textPassword = 'mySecretPassword'; + let salt = makeSalt(); + let encryptedPassword = encryptPassword(textPassword, salt); + + expect(encryptPassword(textPassword, salt)).to.eql(encryptedPassword); + }); + + it('never encrypt the same password to the same value when using a different salt', () => { + let textPassword = 'mySecretPassword'; + let aSalt = makeSalt(); + let anotherSalt = makeSalt(); + let anEncryptedPassword = encryptPassword(textPassword, aSalt); + let anotherEncryptedPassword = encryptPassword(textPassword, anotherSalt); + + expect(anEncryptedPassword).not.to.eql(anotherEncryptedPassword); + }); + }); + + describe('Make Salt', () => { + it('creates a salt with length 10 by default', () => { + let salt = makeSalt(); + + expect(salt.length).to.eql(10); + }); + + it('can create a salt of any length', () => { + let length = 24; + let salt = makeSalt(length); + + expect(salt.length).to.eql(length); + }); + }); +}); diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js new file mode 100644 index 0000000000..30fe78b643 --- /dev/null +++ b/test/api/v3/unit/libs/payments.test.js @@ -0,0 +1,72 @@ +import * as sender from '../../../../../website/server/libs/api-v3/email'; +import * as api from '../../../../../website/server/libs/api-v3/payments'; +import { model as User } from '../../../../../website/server/models/user'; +import moment from 'moment'; + +describe('payments/index', () => { + let fakeSend; + let data; + let user; + + describe('#createSubscription', () => { + beforeEach(async () => { + user = new User(); + }); + + it('succeeds', async () => { + data = { user, sub: { key: 'basic_3mo' } }; + expect(user.purchased.plan.planId).to.not.exist; + await api.createSubscription(data); + expect(user.purchased.plan.planId).to.exist; + }); + }); + + describe('#cancelSubscription', () => { + beforeEach(() => { + fakeSend = sinon.spy(sender, 'sendTxn'); + data = { user: new User() }; + }); + + afterEach(() => { + fakeSend.restore(); + }); + + it('plan.extraMonths is defined', () => { + api.cancelSubscription(data); + let terminated = data.user.purchased.plan.dateTerminated; + data.user.purchased.plan.extraMonths = 2; + api.cancelSubscription(data); + let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); + expect(difference - 60).to.be.lessThan(3); // the difference is approximately two months, +/- 2 days + }); + + it('plan.extraMonth is a fraction', () => { + api.cancelSubscription(data); + let terminated = data.user.purchased.plan.dateTerminated; + data.user.purchased.plan.extraMonths = 0.3; + api.cancelSubscription(data); + let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); + expect(difference - 10).to.be.lessThan(3); // the difference should be 10 days. + }); + + it('nextBill is defined', () => { + api.cancelSubscription(data); + let terminated = data.user.purchased.plan.dateTerminated; + data.nextBill = moment().add({ days: 25 }); + api.cancelSubscription(data); + let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); + expect(difference - 5).to.be.lessThan(2); // the difference should be 5 days, +/- 1 day + }); + + it('saves the canceled subscription for the user', () => { + expect(data.user.purchased.plan.dateTerminated).to.not.exist; + api.cancelSubscription(data); + expect(data.user.purchased.plan.dateTerminated).to.exist; + }); + + it('sends a text', async () => { + await api.cancelSubscription(data); + sinon.assert.called(fakeSend); + }); + }); +}); diff --git a/test/api/v3/unit/libs/preening.test.js b/test/api/v3/unit/libs/preening.test.js new file mode 100644 index 0000000000..af503ca480 --- /dev/null +++ b/test/api/v3/unit/libs/preening.test.js @@ -0,0 +1,56 @@ +import { preenHistory } from '../../../../../website/server/libs/api-v3/preening'; +import moment from 'moment'; +import sinon from 'sinon'; // eslint-disable-line no-shadow +import { generateHistory } from '../../../../helpers/api-unit.helper.js'; + +describe('preenHistory', () => { + let clock; + + beforeEach(() => { + // Replace system clocks so we can get predictable results + clock = sinon.useFakeTimers(Number(moment('2013-10-20').zone(0).startOf('day').toDate()), 'Date'); + }); + afterEach(() => { + return clock.restore(); + }); + + it('does not modify history if all entries are more recent than cutoff (free users)', () => { + let h = generateHistory(60); + expect(preenHistory(_.cloneDeep(h), false, 0)).to.eql(h); + }); + + it('does not modify history if all entries are more recent than cutoff (subscribers)', () => { + let h = generateHistory(365); + expect(preenHistory(_.cloneDeep(h), true, 0)).to.eql(h); + }); + + it('does aggregate data in monthly entries before cutoff (free users)', () => { + let h = generateHistory(81); // Jumps to July + let preened = preenHistory(_.cloneDeep(h), false, 0); + expect(preened.length).to.eql(62); // Keeps 60 days + 2 entries per august and july + }); + + it('does aggregate data in monthly entries before cutoff (subscribers)', () => { + let h = generateHistory(396); // Jumps to September 2012 + let preened = preenHistory(_.cloneDeep(h), true, 0); + expect(preened.length).to.eql(367); // Keeps 365 days + 2 entries per october and september + }); + + it('does aggregate data in monthly and yearly entries before cutoff (free users)', () => { + let h = generateHistory(731); // Jumps to October 21 2012 + let preened = preenHistory(_.cloneDeep(h), false, 0); + expect(preened.length).to.eql(73); // Keeps 60 days + 11 montly entries and 2 yearly entry for 2011 and 2012 + }); + + it('does aggregate data in monthly and yearly entries before cutoff (subscribers)', () => { + let h = generateHistory(1031); // Jumps to October 21 2012 + let preened = preenHistory(_.cloneDeep(h), true, 0); + expect(preened.length).to.eql(380); // Keeps 365 days + 13 montly entries and 2 yearly entries for 2011 and 2010 + }); + + it('correctly aggregates values', () => { + let h = generateHistory(63); // Compress last 3 days + let preened = preenHistory(_.cloneDeep(h), false, 0); + expect(preened[0].value).to.eql((61 + 62 + 63) / 3); + }); +}); diff --git a/test/api/v3/unit/libs/setupNconf.test.js b/test/api/v3/unit/libs/setupNconf.test.js new file mode 100644 index 0000000000..3e848b845f --- /dev/null +++ b/test/api/v3/unit/libs/setupNconf.test.js @@ -0,0 +1,44 @@ +import setupNconf from '../../../../../website/server/libs/api-v3/setupNconf'; + +import path from 'path'; +import nconf from 'nconf'; + +describe('setupNconf', () => { + beforeEach(() => { + sandbox.stub(nconf, 'argv').returnsThis(); + sandbox.stub(nconf, 'env').returnsThis(); + sandbox.stub(nconf, 'file').returnsThis(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('sets up nconf', () => { + setupNconf(); + + expect(nconf.argv).to.be.calledOnce; + expect(nconf.env).to.be.calledOnce; + expect(nconf.file).to.be.calledOnce; + + let regexString = `\\${path.sep}config.json$`; + expect(nconf.file).to.be.calledWithMatch('user', new RegExp(regexString)); + }); + + it('sets IS_PROD variable', () => { + setupNconf(); + expect(nconf.get('IS_PROD')).to.exist; + }); + + it('sets IS_DEV variable', () => { + setupNconf(); + expect(nconf.get('IS_DEV')).to.exist; + }); + + it('allows a custom config.json file to be passed in', () => { + setupNconf('customfile.json'); + + expect(nconf.file).to.be.calledOnce; + expect(nconf.file).to.be.calledWithMatch('user', 'customfile.json'); + }); +}); diff --git a/test/api/v3/unit/libs/webhooks.test.js b/test/api/v3/unit/libs/webhooks.test.js new file mode 100644 index 0000000000..502bfe3839 --- /dev/null +++ b/test/api/v3/unit/libs/webhooks.test.js @@ -0,0 +1,135 @@ +import request from 'request'; +import { sendTaskWebhook } from '../../../../../website/server/libs/api-v3/webhook'; + +describe('webhooks', () => { + beforeEach(() => { + sandbox.stub(request, 'post'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('sendTaskWebhook', () => { + let task = { + details: { _id: 'task-id' }, + delta: 1.4, + direction: 'up', + }; + + let data = { + task, + user: { _id: 'user-id' }, + }; + + it('does not send if no webhook endpoints exist', () => { + let webhooks = { }; + + sendTaskWebhook(webhooks, data); + + expect(request.post).to.not.be.called; + }); + + it('does not send if no webhooks are enabled', () => { + let webhooks = { + 'some-id': { + sort: 0, + id: 'some-id', + enabled: false, + url: 'http://example.org/endpoint', + }, + }; + + sendTaskWebhook(webhooks, data); + + expect(request.post).to.not.be.called; + }); + + it('does not send if webhook url is not valid', () => { + let webhooks = { + 'some-id': { + sort: 0, + id: 'some-id', + enabled: true, + url: 'http://malformedurl/endpoint', + }, + }; + + sendTaskWebhook(webhooks, data); + + expect(request.post).to.not.be.called; + }); + + it('sends task direction, task, task delta, and abridged user data', () => { + let webhooks = { + 'some-id': { + sort: 0, + id: 'some-id', + enabled: true, + url: 'http://example.org/endpoint', + }, + }; + + sendTaskWebhook(webhooks, data); + + expect(request.post).to.be.calledOnce; + expect(request.post).to.be.calledWith({ + url: 'http://example.org/endpoint', + body: { + direction: 'up', + task: { _id: 'task-id' }, + delta: 1.4, + user: { + _id: 'user-id', + }, + }, + json: true, + }); + }); + + it('sends a post request for each webhook endpoint', () => { + let webhooks = { + 'some-id': { + sort: 0, + id: 'some-id', + enabled: true, + url: 'http://example.org/endpoint', + }, + 'second-webhook': { + sort: 1, + id: 'second-webhook', + enabled: true, + url: 'http://example.com/2/endpoint', + }, + }; + + sendTaskWebhook(webhooks, data); + + expect(request.post).to.be.calledTwice; + expect(request.post).to.be.calledWith({ + url: 'http://example.org/endpoint', + body: { + direction: 'up', + task: { _id: 'task-id' }, + delta: 1.4, + user: { + _id: 'user-id', + }, + }, + json: true, + }); + expect(request.post).to.be.calledWith({ + url: 'http://example.com/2/endpoint', + body: { + direction: 'up', + task: { _id: 'task-id' }, + delta: 1.4, + user: { + _id: 'user-id', + }, + }, + json: true, + }); + }); + }); +}); diff --git a/test/api/v3/unit/middlewares/analytics.test.js b/test/api/v3/unit/middlewares/analytics.test.js new file mode 100644 index 0000000000..2a25380713 --- /dev/null +++ b/test/api/v3/unit/middlewares/analytics.test.js @@ -0,0 +1,49 @@ +/* eslint-disable global-require */ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; +import nconf from 'nconf'; +import requireAgain from 'require-again'; + +describe('analytics middleware', () => { + let res, req, next; + let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/api-v3/analytics'; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + it('attaches analytics object res.locals', () => { + let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); + + attachAnalytics(req, res, next); + + expect(res.analytics).to.exist; + }); + + it('attaches stubbed methods for non-prod environments', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); + let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); + + attachAnalytics(req, res, next); + + expect(res.analytics.track).to.eql(analyticsService.mockAnalyticsService.track); + expect(res.analytics.trackPurchase).to.eql(analyticsService.mockAnalyticsService.trackPurchase); + }); + + it('attaches real methods for prod environments', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + + let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); + + attachAnalytics(req, res, next); + + expect(res.analytics.track).to.eql(analyticsService.track); + expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase); + }); +}); diff --git a/test/api/v3/unit/middlewares/cors.test.js b/test/api/v3/unit/middlewares/cors.test.js new file mode 100644 index 0000000000..78d11651f8 --- /dev/null +++ b/test/api/v3/unit/middlewares/cors.test.js @@ -0,0 +1,40 @@ +/* eslint-disable global-require */ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import cors from '../../../../../website/server/middlewares/api-v3/cors'; + +describe('cors middleware', () => { + let res, req, next; + + beforeEach(() => { + req = generateReq(); + res = generateRes(); + next = generateNext(); + }); + + it('sets the correct headers', () => { + cors(req, res, next); + expect(res.set).to.have.been.calledWith({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', + 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', + }); + expect(res.sendStatus).to.not.have.been.called; + expect(next).to.have.been.called.once; + }); + + it('responds immediately if method is OPTIONS', () => { + req.method = 'OPTIONS'; + cors(req, res, next); + expect(res.set).to.have.been.calledWith({ + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', + 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', + }); + expect(res.sendStatus).to.have.been.calledWith(200); + expect(next).to.not.have.been.called; + }); +}); diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js new file mode 100644 index 0000000000..f4e040a11c --- /dev/null +++ b/test/api/v3/unit/middlewares/cronMiddleware.js @@ -0,0 +1,177 @@ +import { + generateRes, + generateReq, + generateNext, + generateTodo, + generateDaily, +} from '../../../../helpers/api-unit.helper'; +import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron'; +import moment from 'moment'; +import { model as User } from '../../../../../website/server/models/user'; +import { model as Group } from '../../../../../website/server/models/group'; +import * as Tasks from '../../../../../website/server/models/task'; +import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; +import { v4 as generateUUID } from 'uuid'; + +describe('cron middleware', () => { + let res, req, next; + let user; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + user = new User({ + auth: { + local: { + username: 'username', + lowerCaseUsername: 'username', + email: 'email@email.email', + salt: 'salt', + hashed_password: 'hashed_password', // eslint-disable-line camelcase + }, + }, + }); + + user._statsComputed = { + mp: 10, + maxMP: 100, + }; + + res.locals.user = user; + res.analytics = analyticsService; + }); + + it('calls next when user is not attached', () => { + res.locals.user = null; + cronMiddleware(req, res, next); + expect(next).to.be.calledOnce; + }); + + it('calls next when days have not been missed', () => { + cronMiddleware(req, res, next); + expect(next).to.be.calledOnce; + }); + + it('should clear todos older than 30 days for free users', async (done) => { + user.lastCron = moment(new Date()).subtract({days: 2}); + let task = generateTodo(user); + task.dateCompleted = moment(new Date()).subtract({days: 31}); + task.completed = true; + await task.save(); + + cronMiddleware(req, res, () => { + Tasks.Task.findOne({_id: task}, function (err, taskFound) { + expect(err).to.not.exist; + expect(taskFound).to.not.exist; + done(); + }); + }); + }); + + it('should not clear todos older than 30 days for subscribed users', (done) => { + user.purchased.plan.customerId = 'subscribedId'; + user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); + user.lastCron = moment(new Date()).subtract({days: 2}); + let task = generateTodo(user); + task.dateCompleted = moment(new Date()).subtract({days: 31}); + task.completed = true; + task.save(); + + cronMiddleware(req, res, () => { + Tasks.Task.findOne({_id: task}, function (err, taskFound) { + expect(err).to.not.exist; + expect(taskFound).to.exist; + done(); + }); + }); + }); + + it('should clear todos older than 90 days for subscribed users', (done) => { + user.purchased.plan.customerId = 'subscribedId'; + user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); + user.lastCron = moment(new Date()).subtract({days: 2}); + + let task = generateTodo(user); + task.dateCompleted = moment(new Date()).subtract({days: 91}); + task.completed = true; + task.save(); + + cronMiddleware(req, res, () => { + Tasks.Task.findOne({_id: task}, function (err, taskFound) { + expect(err).to.not.exist; + expect(taskFound).to.not.exist; + done(); + }); + }); + }); + + it('should call next is user was not modified after cron', (done) => { + let hpBefore = user.stats.hp; + user.lastCron = moment(new Date()).subtract({days: 2}); + + user.save().then(function () { + cronMiddleware(req, res, function () { + expect(hpBefore).to.equal(user.stats.hp); + done(); + }); + }); + }); + + it('does damage for missing dailies', (done) => { + let hpBefore = user.stats.hp; + user.lastCron = moment(new Date()).subtract({days: 2}); + let daily = generateDaily(user); + daily.startDate = moment(new Date()).subtract({days: 2}); + daily.save(); + + cronMiddleware(req, res, () => { + expect(user.stats.hp).to.be.lessThan(hpBefore); + done(); + }); + }); + + it('updates tasks', (done) => { + user.lastCron = moment(new Date()).subtract({days: 2}); + let todo = generateTodo(user); + let todoValueBefore = todo.value; + + cronMiddleware(req, res, () => { + Tasks.Task.findOne({_id: todo._id}, function (err, todoFound) { + expect(err).to.not.exist; + expect(todoFound.value).to.be.lessThan(todoValueBefore); + done(); + }); + }); + }); + + it('applies quest progress', async (done) => { + let hpBefore = user.stats.hp; + user.lastCron = moment(new Date()).subtract({days: 2}); + let daily = generateDaily(user); + daily.startDate = moment(new Date()).subtract({days: 2}); + daily.save(); + + let questKey = 'dilatory'; + user.party.quest.key = questKey; + + let party = new Group({ + type: 'party', + name: generateUUID(), + leader: user._id, + }); + party.quest.members[user._id] = true; + party.quest.key = questKey; + await party.save(); + + user.party._id = party._id; + await user.save(); + + party.startQuest(user); + + cronMiddleware(req, res, () => { + expect(user.stats.hp).to.be.lessThan(hpBefore); + done(); + }); + }); +}); diff --git a/test/api/v3/unit/middlewares/ensureAccessRight.test.js b/test/api/v3/unit/middlewares/ensureAccessRight.test.js new file mode 100644 index 0000000000..cc25e4f16b --- /dev/null +++ b/test/api/v3/unit/middlewares/ensureAccessRight.test.js @@ -0,0 +1,57 @@ +/* eslint-disable global-require */ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import i18n from '../../../../../common/script/i18n'; +import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/api-v3/ensureAccessRight'; +import { NotAuthorized } from '../../../../../website/server/libs/api-v3/errors'; + +describe('ensure access middlewares', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + context('ensure admin', () => { + it('returns not authorized when user is not an admin', () => { + res.locals = {user: {contributor: {admin: false}}}; + + ensureAdmin(req, res, next); + + expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noAdminAccess'))); + }); + + it('passes when user is an admin', () => { + res.locals = {user: {contributor: {admin: true}}}; + + ensureAdmin(req, res, next); + + expect(next).to.be.calledOnce; + expect(next.args[0]).to.be.empty; + }); + }); + + context('ensure sudo', () => { + it('returns not authorized when user is not a sudo user', () => { + res.locals = {user: {contributor: {sudo: false}}}; + + ensureSudo(req, res, next); + + expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess'))); + }); + + it('passes when user is a sudo user', () => { + res.locals = {user: {contributor: {sudo: true}}}; + + ensureSudo(req, res, next); + + expect(next).to.be.calledOnce; + expect(next.args[0]).to.be.empty; + }); + }); +}); diff --git a/test/api/v3/unit/middlewares/ensureDevelpmentMode.js b/test/api/v3/unit/middlewares/ensureDevelpmentMode.js new file mode 100644 index 0000000000..d7915b365f --- /dev/null +++ b/test/api/v3/unit/middlewares/ensureDevelpmentMode.js @@ -0,0 +1,36 @@ +/* eslint-disable global-require */ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import ensureDevelpmentMode from '../../../../../website/server/middlewares/api-v3/ensureDevelpmentMode'; +import { NotFound } from '../../../../../website/server/libs/api-v3/errors'; +import nconf from 'nconf'; + +describe('developmentMode middleware', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + it('returns not found when in production mode', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); + + ensureDevelpmentMode(req, res, next); + + expect(next).to.be.calledWith(new NotFound()); + }); + + it('passes when not in production', () => { + sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); + + ensureDevelpmentMode(req, res, next); + + expect(next).to.be.calledOnce; + expect(next.args[0]).to.be.empty; + }); +}); diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js new file mode 100644 index 0000000000..72cad12a32 --- /dev/null +++ b/test/api/v3/unit/middlewares/errorHandler.test.js @@ -0,0 +1,173 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; + +import errorHandler from '../../../../../website/server/middlewares/api-v3/errorHandler'; +import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; +import { + getUserLanguage, + attachTranslateFunction, +} from '../../../../../website/server/middlewares/api-v3/language'; + +import { BadRequest } from '../../../../../website/server/libs/api-v3/errors'; +import logger from '../../../../../website/server/libs/api-v3/logger'; + +describe('errorHandler', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + responseMiddleware(req, res, next); + getUserLanguage(req, res, next); + attachTranslateFunction(req, res, next); + + sandbox.stub(logger, 'error'); + }); + + it('sends internal server error if error is not a CustomError and is not identified', () => { + let error = new Error(); + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(500); + expect(res.json).to.be.calledWith({ + success: false, + error: 'InternalServerError', + message: 'An unexpected error occurred.', + }); + }); + + it('identifies errors with statusCode property and format them correctly', () => { + let error = new Error('Error message'); + error.statusCode = 400; + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(400); + expect(res.json).to.be.calledWith({ + success: false, + error: 'Error', + message: 'Error message', + }); + }); + + it('doesn\'t leak info about 500 errors', () => { + let error = new Error('Some secret error message'); + error.statusCode = 500; + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(500); + expect(res.json).to.be.calledWith({ + success: false, + error: 'InternalServerError', + message: 'An unexpected error occurred.', + }); + }); + + it('sends CustomError', () => { + let error = new BadRequest(); + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(400); + expect(res.json).to.be.calledWith({ + success: false, + error: 'BadRequest', + message: 'Bad request.', + }); + }); + + it('handle http-errors errors', () => { + let error = new Error('custom message'); + error.statusCode = 422; + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(error.statusCode); + expect(res.json).to.be.calledWith({ + success: false, + error: error.name, + message: error.message, + }); + }); + + it('handle express-validator errors', () => { + let error = [{param: 'param', msg: 'invalid param', value: 123}]; + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(400); + expect(res.json).to.be.calledWith({ + success: false, + error: 'BadRequest', + message: 'Invalid request parameters.', + errors: [ + { param: error[0].param, value: error[0].value, message: error[0].msg }, + ], + }); + }); + + it('handle Mongoose Validation errors', () => { + let error = new Error('User validation failed.'); + error.name = 'ValidationError'; + + error.errors = { + 'auth.local.email': { + path: 'auth.local.email', + message: 'Invalid email.', + value: 'not an email', + }, + }; + + errorHandler(error, req, res, next); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(400); + expect(res.json).to.be.calledWith({ + success: false, + error: 'BadRequest', + message: 'User validation failed.', + errors: [ + { path: 'auth.local.email', message: 'Invalid email.', value: 'not an email' }, + ], + }); + }); + + it('logs error', () => { + let error = new BadRequest(); + + errorHandler(error, req, res, next); + + expect(logger.error).to.be.calledOnce; + expect(logger.error).to.be.calledWithExactly(error, { + originalUrl: req.originalUrl, + headers: req.headers, + body: req.body, + }); + }); +}); diff --git a/test/api/v3/unit/middlewares/language.test.js b/test/api/v3/unit/middlewares/language.test.js new file mode 100644 index 0000000000..23ef6deddd --- /dev/null +++ b/test/api/v3/unit/middlewares/language.test.js @@ -0,0 +1,307 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import { + getUserLanguage, + attachTranslateFunction, +} from '../../../../../website/server/middlewares/api-v3/language'; +import common from '../../../../../common'; +import Bluebird from 'bluebird'; +import { model as User } from '../../../../../website/server/models/user'; + +const i18n = common.i18n; + +describe('language middleware', () => { + describe('res.t', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + + sinon.stub(i18n, 't'); + }); + + afterEach(() => { + i18n.t.restore(); + }); + + it('attaches t method to res', () => { + attachTranslateFunction(req, res, next); + + expect(res.t).to.exist; + }); + + it('uses the language specified in req.language', () => { + req.language = 'de'; + + attachTranslateFunction(req, res, next); + res.t(1, 2); + + expect(i18n.t).to.be.calledOnce; + expect(i18n.t).to.be.calledWith(1, 2); + }); + }); + + describe('getUserLanguage', () => { + let res, req, next; + + let checkResT = (resToCheck) => { + expect(resToCheck.t).to.be.a('function'); + expect(resToCheck.t('help')).to.equal(i18n.t('help', req.language)); + }; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + attachTranslateFunction(req, res, next); + }); + + context('query parameter', () => { + it('uses the language in the query parameter if avalaible', () => { + req.query = { + lang: 'es', + }; + + getUserLanguage(req, res, next); + expect(req.language).to.equal('es'); + checkResT(res); + }); + + it('falls back to english if the query parameter language does not exists', () => { + req.query = { + lang: 'bla', + }; + + getUserLanguage(req, res, next); + expect(req.language).to.equal('en'); + checkResT(res); + }); + + it('uses query even if the request includes a user and session', () => { + req.query = { + lang: 'es', + }; + + req.locals = { + user: { + preferences: { + language: 'it', + }, + }, + }; + + req.session = { + userId: 123, + }; + + getUserLanguage(req, res, next); + expect(req.language).to.equal('es'); + checkResT(res); + }); + }); + + context('authorized request', () => { + it('uses the user preferred language if avalaible', () => { + req.locals = { + user: { + preferences: { + language: 'it', + }, + }, + }; + + getUserLanguage(req, res, next); + expect(req.language).to.equal('it'); + checkResT(res); + }); + + it('falls back to english if the user preferred language is not avalaible', (done) => { + req.locals = { + user: { + preferences: { + language: 'bla', + }, + }, + }; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('en'); + checkResT(res); + done(); + }); + }); + + it('uses the user preferred language even if a session is included in request', () => { + req.locals = { + user: { + preferences: { + language: 'it', + }, + }, + }; + + req.session = { + userId: 123, + }; + + getUserLanguage(req, res, next); + expect(req.language).to.equal('it'); + checkResT(res); + }); + }); + + context('request with session', () => { + it('uses the user preferred language if avalaible', (done) => { + sandbox.stub(User, 'findOne').returns({ + lean () { + return this; + }, + exec () { + return Bluebird.resolve({ + preferences: { + language: 'it', + }, + }); + }, + }); + + req.session = { + userId: 123, + }; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('it'); + checkResT(res); + done(); + }); + }); + }); + + context('browser fallback', () => { + it('uses browser specificed language', (done) => { + req.headers['accept-language'] = 'pt'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('pt'); + checkResT(res); + done(); + }); + }); + + it('uses first language in series if browser specifies multiple', (done) => { + req.headers['accept-language'] = 'he, pt, it'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('he'); + checkResT(res); + done(); + }); + }); + + it('skips invalid lanaguages and uses first language in series if browser specifies multiple', (done) => { + req.headers['accept-language'] = 'blah, he, pt, it'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('he'); + checkResT(res); + done(); + }); + }); + + it('uses normal version of language if specialized locale is passed in', (done) => { + req.headers['accept-language'] = 'fr-CA'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('fr'); + checkResT(res); + done(); + }); + }); + + it('uses normal version of language if specialized locale is passed in', (done) => { + req.headers['accept-language'] = 'fr-CA'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('fr'); + checkResT(res); + done(); + }); + }); + + it('uses es if es is passed in', (done) => { + req.headers['accept-language'] = 'es'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('es'); + checkResT(res); + done(); + }); + }); + + it('uses es_419 if applicable es-languages are passed in', (done) => { + req.headers['accept-language'] = 'es-mx'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('es_419'); + checkResT(res); + done(); + }); + }); + + it('uses es_419 if multiple es languages are passed in', (done) => { + req.headers['accept-language'] = 'es-GT, es-MX, es-CR'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('es_419'); + checkResT(res); + done(); + }); + }); + + it('zh', (done) => { + req.headers['accept-language'] = 'zh-TW'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('zh_TW'); + checkResT(res); + done(); + }); + }); + + it('uses english if browser specified language is not compatible', (done) => { + req.headers['accept-language'] = 'blah'; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('en'); + checkResT(res); + done(); + }); + }); + + it('uses english if browser does not specify', (done) => { + req.headers['accept-language'] = ''; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('en'); + checkResT(res); + done(); + }); + }); + + it('uses english if browser does not supply an accept-language header', (done) => { + delete req.headers['accept-language']; + + getUserLanguage(req, res, () => { + expect(req.language).to.equal('en'); + checkResT(res); + done(); + }); + }); + }); + }); +}); diff --git a/test/api/v3/unit/middlewares/maintenanceMode.test.js b/test/api/v3/unit/middlewares/maintenanceMode.test.js new file mode 100644 index 0000000000..21cabe963d --- /dev/null +++ b/test/api/v3/unit/middlewares/maintenanceMode.test.js @@ -0,0 +1,58 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import nconf from 'nconf'; +import requireAgain from 'require-again'; + +describe('maintenance mode middleware', () => { + let res, req, next; + let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/api-v3/maintenanceMode'; + + beforeEach(() => { + res = generateRes(); + next = generateNext(); + }); + + it('does not return 503 error when maintenance mode is off', () => { + req = generateReq(); + sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('false'); + let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); + + attachMaintenanceMode(req, res, next); + + expect(next).to.have.been.called.once; + expect(res.status).to.not.have.been.called; + }); + + it('returns 503 error when maintenance mode is on', () => { + req = generateReq(); + sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); + let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); + + attachMaintenanceMode(req, res, next); + + expect(next).to.not.have.been.called; + expect(res.status).to.have.been.calledOnce; + expect(res.status).to.have.been.calledWith(503); + }); + + it('renders maintenance page when request type is HTML', () => { + req = generateReq({headers: {accept: 'text/html'}}); + sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); + let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); + + attachMaintenanceMode(req, res, next); + expect(res.render).to.have.been.calledOnce; + }); + + it('sends error message when request type is JSON', () => { + req = generateReq({headers: {accept: 'application/json'}}); + sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); + let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); + + attachMaintenanceMode(req, res, next); + expect(res.send).to.have.been.calledOnce; + }); +}); diff --git a/test/api/v3/unit/middlewares/response.js b/test/api/v3/unit/middlewares/response.js new file mode 100644 index 0000000000..a24bd881ce --- /dev/null +++ b/test/api/v3/unit/middlewares/response.js @@ -0,0 +1,66 @@ +import { + generateRes, + generateReq, + generateNext, +} from '../../../../helpers/api-unit.helper'; +import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; + +describe('response middleware', () => { + let res, req, next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + + it('attaches respond method to res', () => { + responseMiddleware(req, res, next); + + expect(res.respond).to.exist; + }); + + it('can be used to respond to requests', () => { + responseMiddleware(req, res, next); + res.respond(200, {field: 1}); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(200); + expect(res.json).to.be.calledWith({ + success: true, + data: {field: 1}, + }); + }); + + it('can be passed a third parameter to be used as optional message', () => { + responseMiddleware(req, res, next); + res.respond(200, {field: 1}, 'hello'); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(200); + expect(res.json).to.be.calledWith({ + success: true, + data: {field: 1}, + message: 'hello', + }); + }); + + it('treats status >= 400 as failures', () => { + responseMiddleware(req, res, next); + res.respond(403, {field: 1}); + + expect(res.status).to.be.calledOnce; + expect(res.json).to.be.calledOnce; + + expect(res.status).to.be.calledWith(403); + expect(res.json).to.be.calledWith({ + success: false, + data: {field: 1}, + }); + }); +}); diff --git a/test/api/v3/unit/models/challenge.test.js b/test/api/v3/unit/models/challenge.test.js new file mode 100644 index 0000000000..b2f0e7ff98 --- /dev/null +++ b/test/api/v3/unit/models/challenge.test.js @@ -0,0 +1,161 @@ +import { model as Challenge } from '../../../../../website/server/models/challenge'; +import { model as Group } from '../../../../../website/server/models/group'; +import { model as User } from '../../../../../website/server/models/user'; +import * as Tasks from '../../../../../website/server/models/task'; +import common from '../../../../../common/'; +import { each, find } from 'lodash'; + +describe('Challenge Model', () => { + let guild, leader, challenge, task; + let tasksToTest = { + habit: { + text: 'test habit', + type: 'habit', + up: false, + down: true, + }, + todo: { + text: 'test todo', + type: 'todo', + }, + daily: { + text: 'test daily', + type: 'daily', + frequency: 'daily', + everyX: 5, + startDate: new Date(), + }, + reward: { + text: 'test reward', + type: 'reward', + }, + }; + + beforeEach(async () => { + guild = new Group({ + name: 'test party', + type: 'guild', + }); + + leader = new User({ + guilds: [guild._id], + }); + + guild.leader = leader._id; + + challenge = new Challenge({ + name: 'Test Challenge', + shortName: 'Test', + leader: leader._id, + group: guild._id, + }); + + leader.challenges = [challenge._id]; + + await Promise.all([ + guild.save(), + leader.save(), + challenge.save(), + ]); + }); + + each(tasksToTest, (taskValue, taskType) => { + context(`${taskType}`, () => { + beforeEach(async() => { + task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue)); + task.challenge.id = challenge._id; + await task.save(); + }); + + it('adds tasks to challenge and challenge members', async () => { + await challenge.addTasks([task]); + + let updatedLeader = await User.findOne({_id: leader._id}); + let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); + let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { + return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; + }); + + expect(syncedTask).to.exist; + }); + + it('syncs a challenge to a user', async () => { + await challenge.addTasks([task]); + + let newMember = new User({ + guilds: [guild._id], + }); + await newMember.save(); + + await challenge.syncToUser(newMember); + + let updatedNewMember = await User.findById(newMember._id); + let updatedNewMemberTasks = await Tasks.Task.find({_id: { $in: updatedNewMember.tasksOrder[`${taskType}s`]}}); + let syncedTask = find(updatedNewMemberTasks, function findNewTask (updatedNewMemberTask) { + return updatedNewMemberTask.type === taskValue.type && updatedNewMemberTask.text === taskValue.text; + }); + + expect(updatedNewMember.challenges).to.contain(challenge._id); + expect(updatedNewMember.tags[3].id).to.equal(challenge._id); + expect(updatedNewMember.tags[3].name).to.equal(challenge.shortName); + expect(syncedTask).to.exist; + }); + + it('updates tasks to challenge and challenge members', async () => { + let updatedTaskName = 'Updated Test Habit'; + await challenge.addTasks([task]); + + let req = { + body: { text: updatedTaskName }, + }; + + Tasks.Task.sanitize(req.body); + _.assign(task, common.ops.updateTask(task.toObject(), req)[0]); + + await challenge.updateTask(task); + + let updatedLeader = await User.findOne({_id: leader._id}); + let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder[`${taskType}s`][0]); + + expect(updatedUserTask.text).to.equal(updatedTaskName); + }); + + it('removes a tasks to challenge and challenge members', async () => { + await challenge.addTasks([task]); + await challenge.removeTask(task); + + let updatedLeader = await User.findOne({_id: leader._id}); + let updatedUserTask = await Tasks.Task.findOne({_id: updatedLeader.tasksOrder[`${taskType}s`][0]}).exec(); + + expect(updatedUserTask.challenge.broken).to.equal('TASK_DELETED'); + }); + + it('unlinks and deletes challenge tasks for a user when remove-all is specified', async () => { + await challenge.addTasks([task]); + await challenge.unlinkTasks(leader, 'remove-all'); + + let updatedLeader = await User.findOne({_id: leader._id}); + let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); + let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { + return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; + }); + + expect(syncedTask).to.not.exist; + }); + + it('unlinks and keeps challenge tasks for a user when keep-all is specified', async () => { + await challenge.addTasks([task]); + await challenge.unlinkTasks(leader, 'keep-all'); + + let updatedLeader = await User.findOne({_id: leader._id}); + let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); + let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { + return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; + }); + + expect(syncedTask).to.exist; + expect(syncedTask.challenge._id).to.be.empty; + }); + }); + }); +}); diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js new file mode 100644 index 0000000000..32007e068d --- /dev/null +++ b/test/api/v3/unit/models/group.test.js @@ -0,0 +1,300 @@ +import { sleep } from '../../../../helpers/api-unit.helper'; +import { model as Group } from '../../../../../website/server/models/group'; +import { model as User } from '../../../../../website/server/models/user'; +import { quests as questScrolls } from '../../../../../common/script/content'; +import * as email from '../../../../../website/server/libs/api-v3/email'; + +describe('Group Model', () => { + context('Instance Methods', () => { + describe('#startQuest', () => { + let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember; + + beforeEach(async () => { + sandbox.stub(email, 'sendTxn'); + + party = new Group({ + name: 'test party', + type: 'party', + privacy: 'private', + }); + + questLeader = new User({ + party: { _id: party._id }, + items: { + quests: { + whale: 1, + }, + }, + }); + + party.leader = questLeader._id; + + participatingMember = new User({ + party: { _id: party._id }, + }); + nonParticipatingMember = new User({ + party: { _id: party._id }, + }); + undecidedMember = new User({ + party: { _id: party._id }, + }); + + await Promise.all([ + party.save(), + questLeader.save(), + participatingMember.save(), + nonParticipatingMember.save(), + undecidedMember.save(), + ]); + }); + + context('Failure Conditions', () => { + it('throws an error if group is not a party', async () => { + let guild = new Group({ + type: 'guild', + }); + + await expect(guild.startQuest(participatingMember)).to.eventually.be.rejected; + }); + + it('throws an error if party is not on a quest', async () => { + await expect(party.startQuest(participatingMember)).to.eventually.be.rejected; + }); + + it('throws an error if quest is already active', async () => { + party.quest.key = 'whale'; + party.quest.active = true; + + await expect(party.startQuest(participatingMember)).to.eventually.be.rejected; + }); + }); + + context('Successes', () => { + beforeEach(() => { + party.quest.key = 'whale'; + party.quest.active = false; + party.quest.leader = questLeader._id; + party.quest.members = { }; + party.quest.members[questLeader._id] = true; + party.quest.members[participatingMember._id] = true; + party.quest.members[nonParticipatingMember._id] = false; + party.quest.members[undecidedMember._id] = null; + }); + + it('activates quest', () => { + party.startQuest(participatingMember); + + expect(party.quest.active).to.eql(true); + }); + + it('sets up boss quest', () => { + let bossQuest = questScrolls.whale; + party.quest.key = bossQuest.key; + + party.startQuest(participatingMember); + + expect(party.quest.progress.hp).to.eql(bossQuest.boss.hp); + }); + + it('sets up rage meter for rage boss quest', () => { + let rageBossQuest = questScrolls.trex_undead; + party.quest.key = rageBossQuest.key; + + party.startQuest(participatingMember); + + expect(party.quest.progress.rage).to.eql(0); + }); + + it('sets up collection quest', () => { + let collectionQuest = questScrolls.vice2; + party.quest.key = collectionQuest.key; + party.startQuest(participatingMember); + + expect(party.quest.progress.collect).to.eql({ + lightCrystal: 0, + }); + }); + + it('sets up collection quest with multiple items', () => { + let collectionQuest = questScrolls.evilsanta2; + party.quest.key = collectionQuest.key; + party.startQuest(participatingMember); + + expect(party.quest.progress.collect).to.eql({ + tracks: 0, + branches: 0, + }); + }); + + it('prunes non-participating members from quest members object', () => { + party.startQuest(participatingMember); + + let expectedQuestMembers = {}; + expectedQuestMembers[questLeader._id] = true; + expectedQuestMembers[participatingMember._id] = true; + + expect(party.quest.members).to.eql(expectedQuestMembers); + }); + + it('applies updates to user object directly if user is participating', async () => { + await party.startQuest(participatingMember); + + expect(participatingMember.party.quest.key).to.eql('whale'); + expect(participatingMember.party.quest.progress.down).to.eql(0); + expect(participatingMember.party.quest.progress.collect).to.eql({}); + expect(participatingMember.party.quest.completed).to.eql(null); + }); + + it('applies updates to other participating members', async () => { + await party.startQuest(nonParticipatingMember); + + questLeader = await User.findById(questLeader._id); + participatingMember = await User.findById(participatingMember._id); + + expect(participatingMember.party.quest.key).to.eql('whale'); + expect(participatingMember.party.quest.progress.down).to.eql(0); + expect(participatingMember.party.quest.progress.collect).to.eql({}); + expect(participatingMember.party.quest.completed).to.eql(null); + + expect(questLeader.party.quest.key).to.eql('whale'); + expect(questLeader.party.quest.progress.down).to.eql(0); + expect(questLeader.party.quest.progress.collect).to.eql({}); + expect(questLeader.party.quest.completed).to.eql(null); + }); + + it('does not apply updates to nonparticipating members', async () => { + await party.startQuest(participatingMember); + + nonParticipatingMember = await User.findById(nonParticipatingMember ._id); + undecidedMember = await User.findById(undecidedMember._id); + + expect(nonParticipatingMember.party.quest.key).to.not.eql('whale'); + expect(undecidedMember.party.quest.key).to.not.eql('whale'); + }); + + it('sends email to participating members that quest has started', async () => { + participatingMember.preferences.emailNotifications.questStarted = true; + questLeader.preferences.emailNotifications.questStarted = true; + await Promise.all([ + participatingMember.save(), + questLeader.save(), + ]); + + await party.startQuest(nonParticipatingMember); + + await sleep(0.5); + + expect(email.sendTxn).to.be.calledOnce; + + let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); + let typeOfEmail = email.sendTxn.args[0][1]; + + expect(memberIds).to.have.a.lengthOf(2); + expect(memberIds).to.include(participatingMember._id); + expect(memberIds).to.include(questLeader._id); + expect(typeOfEmail).to.eql('quest-started'); + }); + + it('sends email only to members who have not opted out', async () => { + participatingMember.preferences.emailNotifications.questStarted = false; + questLeader.preferences.emailNotifications.questStarted = true; + await Promise.all([ + participatingMember.save(), + questLeader.save(), + ]); + + await party.startQuest(nonParticipatingMember); + + await sleep(0.5); + + expect(email.sendTxn).to.be.calledOnce; + + let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); + + expect(memberIds).to.have.a.lengthOf(1); + expect(memberIds).to.not.include(participatingMember._id); + expect(memberIds).to.include(questLeader._id); + }); + + it('does not send email to initiating member', async () => { + participatingMember.preferences.emailNotifications.questStarted = true; + questLeader.preferences.emailNotifications.questStarted = true; + await Promise.all([ + participatingMember.save(), + questLeader.save(), + ]); + + await party.startQuest(participatingMember); + + await sleep(0.5); + + expect(email.sendTxn).to.be.calledOnce; + + let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); + + expect(memberIds).to.have.a.lengthOf(1); + expect(memberIds).to.not.include(participatingMember._id); + expect(memberIds).to.include(questLeader._id); + }); + + it('updates participting members (not including user)', async () => { + sandbox.spy(User, 'update'); + + await party.startQuest(nonParticipatingMember); + + let members = [questLeader._id, participatingMember._id]; + + expect(User.update).to.be.calledWith( + { _id: { $in: members } }, + { + $set: { + 'party.quest.key': 'whale', + 'party.quest.progress.down': 0, + 'party.quest.progress.collect': {}, + 'party.quest.completed': null, + }, + } + ); + }); + + it('updates non-user quest leader and decrements quest scroll', async () => { + sandbox.spy(User, 'update'); + + await party.startQuest(participatingMember); + + expect(User.update).to.be.calledWith( + { _id: questLeader._id }, + { + $inc: { + 'items.quests.whale': -1, + }, + } + ); + }); + + it('modifies the participating initiating user directly', async () => { + await party.startQuest(participatingMember); + + let userQuest = participatingMember.party.quest; + + expect(userQuest.key).to.eql('whale'); + expect(userQuest.progress.down).to.eql(0); + expect(userQuest.progress.collect).to.eql({}); + expect(userQuest.completed).to.eql(null); + }); + + it('does not modify user if not participating', async () => { + await party.startQuest(nonParticipatingMember); + + expect(nonParticipatingMember.party.quest.key).to.not.eql('whale'); + }); + + it('removes the quest directly if initiating user is the quest leader', async () => { + await party.startQuest(questLeader); + + expect(questLeader.items.quests.whale).to.eql(0); + }); + }); + }); + }); +}); diff --git a/test/api/v3/unit/models/task.test.js b/test/api/v3/unit/models/task.test.js new file mode 100644 index 0000000000..1c3082d863 --- /dev/null +++ b/test/api/v3/unit/models/task.test.js @@ -0,0 +1,74 @@ +import { model as Challenge } from '../../../../../website/server/models/challenge'; +import { model as Group } from '../../../../../website/server/models/group'; +import { model as User } from '../../../../../website/server/models/user'; +import * as Tasks from '../../../../../website/server/models/task'; +import { each } from 'lodash'; +import { generateHistory } from '../../../../helpers/api-unit.helper.js'; + +describe('Task Model', () => { + let guild, leader, challenge, task; + let tasksToTest = { + habit: { + text: 'test habit', + type: 'habit', + up: false, + down: true, + }, + daily: { + text: 'test daily', + type: 'daily', + frequency: 'daily', + everyX: 5, + startDate: new Date(), + }, + }; + + beforeEach(async () => { + guild = new Group({ + name: 'test guild', + type: 'guild', + }); + + leader = new User({ + guilds: [guild._id], + }); + + guild.leader = leader._id; + + challenge = new Challenge({ + name: 'Test Challenge', + shortName: 'Test', + leader: leader._id, + group: guild._id, + }); + + leader.challenges = [challenge._id]; + + await Promise.all([ + guild.save(), + leader.save(), + challenge.save(), + ]); + }); + + each(tasksToTest, (taskValue, taskType) => { + context(`${taskType}`, () => { + beforeEach(async() => { + task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue)); + task.challenge.id = challenge._id; + task.history = generateHistory(396); + await task.save(); + }); + + it('preens challenge tasks history when scored', async () => { + let historyLengthBeforePreen = task.history.length; + + await task.scoreChallengeTask(1.2); + + let updatedTask = await Tasks.Task.findOne({_id: task._id}); + + expect(historyLengthBeforePreen).to.be.greaterThan(updatedTask.history.length); + }); + }); + }); +}); diff --git a/test/api/v3/unit/models/user.test.js b/test/api/v3/unit/models/user.test.js new file mode 100644 index 0000000000..d7f509712c --- /dev/null +++ b/test/api/v3/unit/models/user.test.js @@ -0,0 +1,33 @@ +import { model as User } from '../../../../../website/server/models/user'; + +describe('User Model', () => { + it('keeps user._tmp when calling .toJSON', () => { + let user = new User({ + auth: { + local: { + username: 'username', + lowerCaseUsername: 'username', + email: 'email@email.email', + salt: 'salt', + hashed_password: 'hashed_password', // eslint-disable-line camelcase + }, + }, + }); + + user._tmp = {ok: true}; + user._nonTmp = {ok: true}; + + expect(user._tmp).to.eql({ok: true}); + expect(user._nonTmp).to.eql({ok: true}); + + let toObject = user.toObject(); + let toJSON = user.toJSON(); + + expect(toObject).to.not.have.keys('_tmp'); + expect(toObject).to.not.have.keys('_nonTmp'); + + expect(toJSON).to.have.any.key('_tmp'); + expect(toJSON._tmp).to.eql({ok: true}); + expect(toJSON).to.not.have.keys('_nonTmp'); + }); +}); diff --git a/test/common/algos.mocha.js b/test/common/algos.mocha.js deleted file mode 100644 index 12581410bf..0000000000 --- a/test/common/algos.mocha.js +++ /dev/null @@ -1,1491 +0,0 @@ -/* eslint-disable camelcase, func-names, no-shadow */ - -import { - generateUser, - generateDaily, - generateHabit, - generateTodo, -} from '../helpers/common.helper'; - -import { - DAY_MAPPING, - startOfWeek, - startOfDay, - daysSince, -} from '../../common/script/cron'; - -let expect = require('expect.js'); -let sinon = require('sinon'); -let moment = require('moment'); -let test_helper = require('./test_helper'); -let shared = require('../../common/script/index.js'); -let $w = (s) => { - return s.split(' '); -}; - -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; -test_helper.addCustomMatchers(); - -/* Helper Functions */ -let rewrapUser = (user) => { - user._wrapped = false; - shared.wrap(user); - return user; -}; - -let beforeAfter = (options = {}) => { - let lastCron; - let user = generateUser(); - let daily = generateDaily(); - let habit = generateHabit(); - let todo = generateTodo(); - - user.dailys.push(daily); - user.habits.push(habit); - user.todos.push(todo); - - let ref = [user, _.cloneDeep(user)]; - let before = ref[0]; - let after = ref[1]; - - rewrapUser(after); - if (options.dayStart) { - before.preferences.dayStart = after.preferences.dayStart = options.dayStart; - } - before.preferences.timezoneOffset = after.preferences.timezoneOffset = options.timezoneOffset || moment().zone(); - before.preferences.timezoneOffsetAtLastCron = after.preferences.timezoneOffsetAtLastCron = before.preferences.timezoneOffset; - if (options.limitOne) { - before[`${options.limitOne}s`] = [before[`${options.limitOne}s`][0]]; - after[`${options.limitOne}s`] = [after[`${options.limitOne}s`][0]]; - } - if (options.daysAgo) { - lastCron = moment(options.now || Number(new Date())).subtract({ - days: options.daysAgo, - }); - } - if (options.daysAgo && options.cronAfterStart) { - lastCron.add({ - hours: options.dayStart, - minutes: 1, - }); - } - if (options.daysAgo) { - lastCron = Number(lastCron); - } - _.each([before, after], (obj) => { - if (options.daysAgo) { - obj.lastCron = lastCron; - } - }); - return { - before, - after, - }; -}; - -let expectLostPoints = (before, after, taskType) => { - if (taskType === 'daily' || taskType === 'habit') { - expect(after.stats.hp).to.be.lessThan(before.stats.hp); - expect(after[`${taskType}s`][0].history).to.have.length(1); - } else { - expect(after.history.todos).to.have.length(1); - } - expect(after).toHaveExp(0); - expect(after).toHaveGP(0); - expect(after[`${taskType}s`][0].value).to.be.lessThan(before[`${taskType}s`][0].value); -}; - -let expectGainedPoints = (before, after, taskType) => { - expect(after.stats.hp).to.be(50); - expect(after.stats.exp).to.be.greaterThan(before.stats.exp); - expect(after.stats.gp).to.be.greaterThan(before.stats.gp); - expect(after[`${taskType}s`][0].value).to.be.greaterThan(before[`${taskType}s`][0].value); - if (taskType === 'habit') { - expect(after[`${taskType}s`][0].history).to.have.length(1); - } -}; - -let expectNoChange = (before, after) => { - _.each($w('stats items gear dailys todos rewards preferences'), (attr) => { - expect(after[attr]).to.eql(before[attr]); - }); -}; - -let expectClosePoints = (before, after, taskType) => { - expect(Math.abs(after.stats.exp - before.stats.exp)).to.be.lessThan(0.0001); - expect(Math.abs(after.stats.gp - before.stats.gp)).to.be.lessThan(0.0001); - expect(Math.abs(after[taskType + 's'][0].value - before[taskType + 's'][0].value)).to.be.lessThan(0.0001); // eslint-disable-line prefer-template -}; - -let expectDayResetNoDamage = (b, a) => { - let ref = [_.cloneDeep(b), _.cloneDeep(a)]; - let before = ref[0]; - let after = ref[1]; - - _.each(after.dailys, (task, i) => { - expect(task.completed).to.be(false); - expect(before.dailys[i].value).to.be(task.value); - expect(before.dailys[i].streak).to.be(task.streak); - expect(task.history).to.have.length(1); - }); - _.each(after.todos, (task, i) => { - expect(task.completed).to.be(false); - expect(before.todos[i].value).to.be.greaterThan(task.value); - }); - expect(after.history.todos).to.have.length(1); - _.each([before, after], (obj) => { - delete obj.stats.buffs; - _.each($w('dailys todos history lastCron'), (path) => { - return delete obj[path]; - }); - }); - delete after._tmp; - expectNoChange(before, after); -}; - -let repeatWithoutLastWeekday = () => { - let repeat = { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - }; - - if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { - repeat.su = false; - } else { - repeat.s = false; - } - return { - repeat, - }; -}; - -describe('User', () => { - it('calculates max MP', () => { - let user = generateUser(); - - expect(user).toHaveMaxMP(30); - user.stats.int = 10; - expect(user).toHaveMaxMP(50); - user.stats.lvl = 5; - expect(user).toHaveMaxMP(54); - user.stats.class = 'wizard'; - user.items.gear.equipped.weapon = 'weapon_wizard_1'; - expect(user).toHaveMaxMP(63); - }); - - it('handles perfect days', () => { - let user = generateUser(); - - user.dailys = []; - _.times(3, () => { - return user.dailys.push(shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - })); - }); - let cron = () => { - user.lastCron = moment().subtract(1, 'days'); - return user.fns.cron(); - }; - - cron(); - expect(user.stats.buffs.str).to.be(0); - expect(user.achievements.perfect).to.not.be.ok(); - user.dailys[0].completed = true; - cron(); - expect(user.stats.buffs.str).to.be(0); - expect(user.achievements.perfect).to.not.be.ok(); - _.each(user.dailys, (d) => { - d.completed = true; - }); - cron(); - expect(user.stats.buffs.str).to.be(1); - expect(user.achievements.perfect).to.be(1); - - let yesterday = moment().subtract(1, 'days'); - - user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; - _.each(user.dailys.slice(1), (d) => { - d.completed = true; - }); - cron(); - expect(user.stats.buffs.str).to.be(1); - expect(user.achievements.perfect).to.be(2); - }); - - describe('Resting in the Inn', () => { - let user = null; - let cron = null; - - beforeEach(() => { - user = generateUser(); - user.preferences.sleep = true; - cron = () => { - user.lastCron = moment().subtract(1, 'days'); - return user.fns.cron(); - }; - user.dailys = []; - _.times(2, () => { - return user.dailys.push(shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - })); - }); - }); - - it('remains in the inn on cron', () => { - cron(); - expect(user.preferences.sleep).to.be(true); - }); - - it('resets dailies', () => { - user.dailys[0].completed = true; - cron(); - expect(user.dailys[0].completed).to.be(false); - }); - - it('resets checklist on incomplete dailies', () => { - user.dailys[0].checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - cron(); - _.each(user.dailys[0].checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('resets checklist on complete dailies', () => { - user.dailys[0].checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - user.dailys[0].completed = true; - cron(); - _.each(user.dailys[0].checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('does not reset checklist on grey incomplete dailies', () => { - let yesterday = moment().subtract(1, 'days'); - - user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; - user.dailys[0].checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: true, - }, - ]; - cron(); - _.each(user.dailys[0].checklist, (box) => { - expect(box.completed).to.be(true); - }); - }); - - it('resets checklist on complete grey complete dailies', () => { - let yesterday = moment().subtract(1, 'days'); - - user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; - user.dailys[0].checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: true, - }, - ]; - user.dailys[0].completed = true; - cron(); - _.each(user.dailys[0].checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('does not damage user for incomplete dailies', () => { - expect(user).toHaveHP(50); - user.dailys[0].completed = true; - user.dailys[1].completed = false; - cron(); - expect(user).toHaveHP(50); - }); - - it('gives credit for complete dailies', () => { - user.dailys[0].completed = true; - expect(user.dailys[0].history).to.be.empty; - cron(); - expect(user.dailys[0].history).to.not.be.empty; - }); - - it('damages user for incomplete dailies after checkout', () => { - expect(user).toHaveHP(50); - user.dailys[0].completed = true; - user.dailys[1].completed = false; - user.preferences.sleep = false; - cron(); - expect(user.stats.hp).to.be.lessThan(50); - }); - }); - - describe('Death', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('revives correctly', () => { - user.stats = { - gp: 10, - exp: 100, - lvl: 2, - hp: 0, - class: 'warrior', - }; - user.items.gear.owned.weapon_warrior_0 = true; - user.ops.revive(); - - expect(user).toHaveGP(0); - expect(user).toHaveExp(0); - expect(user).toHaveLevel(1); - expect(user).toHaveHP(50); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: false, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_yellowTopFrame: true, - eyewear_special_whiteTopFrame: true, - }); - }); - - it('doesn\'t break unbreakables', () => { - let ce = shared.countExists; - - user.items.gear.owned = { - weapon_warrior_0: true, - shield_warrior_1: true, - shield_rogue_1: true, - head_special_nye: true, - }; - - expect(ce(user.items.gear.owned)).to.be(4); - - user.stats.hp = 0; - user.ops.revive(); - - expect(ce(user.items.gear.owned)).to.be(3); - - user.stats.hp = 0; - user.ops.revive(); - - expect(ce(user.items.gear.owned)).to.be(2); - - user.stats.hp = 0; - user.ops.revive(); - - expect(ce(user.items.gear.owned)).to.be(2); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: false, - shield_warrior_1: false, - shield_rogue_1: true, - head_special_nye: true, - }); - }); - - it('handles event items', () => { - user.items.gear.owned.head_special_nye = true; - - shared.content.gear.flat.head_special_nye.event.start = '2012-01-01'; - shared.content.gear.flat.head_special_nye.event.end = '2012-02-01'; - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); - delete user.items.gear.owned.head_special_nye; - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(false); - shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5, 'days'); - shared.content.gear.flat.head_special_nye.event.end = moment().add(5, 'days'); - expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); - }); - }); - - describe('Rebirth', () => { - it('removes correct gear', () => { - let user = generateUser(); - - user.stats.lvl = 100; - user.items.gear.owned = { - weapon_warrior_0: true, - weapon_warrior_1: true, - armor_warrior_1: false, - armor_mystery_201402: true, - back_mystery_201402: false, - head_mystery_201402: true, - weapon_armoire_basicCrossbow: true, - }; - user.ops.rebirth(); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - weapon_warrior_1: false, - armor_warrior_1: false, - armor_mystery_201402: true, - back_mystery_201402: false, - head_mystery_201402: true, - weapon_armoire_basicCrossbow: false, - }); - }); - }); - - describe('store', () => { - it('buys a Quest scroll', () => { - let user = generateUser(); - - user.stats.gp = 205; - user.ops.buyQuest({ - params: { - key: 'dilatoryDistress1', - }, - }); - expect(user.items.quests).to.eql({ - dilatoryDistress1: 1, - }); - expect(user).toHaveGP(5); - }); - - it('does not buy Quests without enough Gold', () => { - let user = generateUser(); - - user.stats.gp = 1; - user.ops.buyQuest({ - params: { - key: 'dilatoryDistress1', - }, - }); - expect(user.items.quests).to.eql({}); - expect(user).toHaveGP(1); - }); - - it('does not buy nonexistent Quests', () => { - let user = generateUser(); - - user.stats.gp = 9999; - user.ops.buyQuest({ - params: { - key: 'snarfblatter', - }, - }); - expect(user.items.quests).to.eql({}); - expect(user).toHaveGP(9999); - }); - - it('does not buy Gem-premium Quests', () => { - let user = generateUser(); - - user.stats.gp = 9999; - user.ops.buyQuest({ - params: { - key: 'kraken', - }, - }); - expect(user.items.quests).to.eql({}); - expect(user).toHaveGP(9999); - }); - }); - - describe('Gem purchases', () => { - it('does not purchase items without enough Gems', () => { - let user = generateUser(); - - user.items.eggs = {}; - user.items.gear.owned = {}; - - user.ops.purchase({ - params: { - type: 'eggs', - key: 'Cactus', - }, - }); - user.ops.purchase({ - params: { - type: 'gear', - key: 'headAccessory_special_foxEars', - }, - }); - user.ops.unlock({ - query: { - path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', - }, - }); - expect(user.items.eggs).to.eql({}); - expect(user.items.gear.owned).to.eql({}); - }); - - it('purchases an egg', () => { - let user = generateUser(); - - user.balance = 1; - user.ops.purchase({ - params: { - type: 'eggs', - key: 'Cactus', - }, - }); - expect(user.items.eggs).to.eql({ - Cactus: 1, - }); - expect(user.balance).to.eql(0.25); - }); - - it('purchases fox ears', () => { - let user = generateUser(); - - user.balance = 1; - user.ops.purchase({ - params: { - type: 'gear', - key: 'headAccessory_special_foxEars', - }, - }); - - expect(user.items.gear.owned.headAccessory_special_foxEars).to.eql(true); - expect(user.balance).to.eql(0.5); - }); - - it('unlocks all the animal ears at once', () => { - let user = generateUser(); - - user.balance = 2; - user.ops.unlock({ - query: { - path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', - }, - }); - - expect(user.items.gear.owned.headAccessory_special_bearEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_cactusEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_foxEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_lionEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_pandaEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_pigEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_tigerEars).to.eql(true); - expect(user.items.gear.owned.headAccessory_special_wolfEars).to.eql(true); - expect(user.balance).to.eql(0.75); - }); - }); - - describe('spells', () => { - _.each(shared.content.spells, (spellClass) => { - _.each(spellClass, (spell) => { - it(`${spell.text} has valid values`, () => { - expect(spell.target).to.match(/^(task|self|party|user)$/); - expect(spell.mana).to.be.an('number'); - if (spell.lvl) { - expect(spell.lvl).to.be.an('number'); - expect(spell.lvl).to.be.above(0); - } - expect(spell.cast).to.be.a('function'); - }); - }); - }); - }); - - describe('drop system', () => { - let user = null; - const MIN_RANGE_FOR_POTION = 0; - const MAX_RANGE_FOR_POTION = 0.3; - const MIN_RANGE_FOR_EGG = 0.4; - const MAX_RANGE_FOR_EGG = 0.6; - const MIN_RANGE_FOR_FOOD = 0.7; - const MAX_RANGE_FOR_FOOD = 1; - - beforeEach(function () { - user = generateUser(); - user.flags.dropsEnabled = true; - this.task_id = shared.uuid(); - return user.ops.addTask({ - body: { - type: 'daily', - id: this.task_id, - }, - }); - }); - - it('drops a hatching potion', function () { - let results = []; - - for (let random = MIN_RANGE_FOR_POTION; random <= MAX_RANGE_FOR_POTION; random += 0.1) { - sinon.stub(user.fns, 'predictableRandom').returns(random); - user.ops.score({ - params: { - id: this.task_id, - direction: 'up', - }, - }); - expect(user.items.eggs).to.be.empty; - expect(user.items.hatchingPotions).to.not.be.empty; - expect(user.items.food).to.be.empty; - results.push(user.fns.predictableRandom.restore()); - } - return results; - }); - - it('drops a pet egg', function () { - let results = []; - - for (let random = MIN_RANGE_FOR_EGG; random <= MAX_RANGE_FOR_EGG; random += 0.1) { - sinon.stub(user.fns, 'predictableRandom').returns(random); - user.ops.score({ - params: { - id: this.task_id, - direction: 'up', - }, - }); - expect(user.items.eggs).to.not.be.empty; - expect(user.items.hatchingPotions).to.be.empty; - expect(user.items.food).to.be.empty; - results.push(user.fns.predictableRandom.restore()); - } - return results; - }); - - it('drops food', function () { - let results = []; - - for (let random = MIN_RANGE_FOR_FOOD; random <= MAX_RANGE_FOR_FOOD; random += 0.1) { - sinon.stub(user.fns, 'predictableRandom').returns(random); - user.ops.score({ - params: { - id: this.task_id, - direction: 'up', - }, - }); - expect(user.items.eggs).to.be.empty; - expect(user.items.hatchingPotions).to.be.empty; - expect(user.items.food).to.not.be.empty; - results.push(user.fns.predictableRandom.restore()); - } - return results; - }); - - it('does not get a drop', function () { - sinon.stub(user.fns, 'predictableRandom').returns(0.5); - user.ops.score({ - params: { - id: this.task_id, - direction: 'up', - }, - }); - expect(user.items.eggs).to.eql({}); - expect(user.items.hatchingPotions).to.eql({}); - expect(user.items.food).to.eql({}); - - user.fns.predictableRandom.restore(); - }); - }); - - describe('Quests', () => { - _.each(shared.content.quests, (quest) => { - it(`${ quest.text() } has valid values`, () => { - expect(quest.notes()).to.be.an('string'); - if (quest.completion) { - expect(quest.completion()).to.be.an('string'); - } - if (quest.previous) { - expect(quest.previous).to.be.an('string'); - } - if (quest.canBuy()) { - expect(quest.value).to.be.greaterThan(0); - } - expect(quest.drop.gp).to.not.be.lessThan(0); - expect(quest.drop.exp).to.not.be.lessThan(0); - expect(quest.category).to.match(/pet|unlockable|gold|world/); - if (quest.drop.items) { - expect(quest.drop.items).to.be.an(Array); - } - if (quest.boss) { - expect(quest.boss.name()).to.be.an('string'); - expect(quest.boss.hp).to.be.greaterThan(0); - expect(quest.boss.str).to.be.greaterThan(0); - } else if (quest.collect) { - _.each(quest.collect, (collect) => { - expect(collect.text()).to.be.an('string'); - expect(collect.count).to.be.greaterThan(0); - }); - } - }); - }); - }); - - describe('Achievements', () => { - _.each(shared.content.classes, (klass) => { - let user = generateUser(); - - user.achievements.ultimateGearSets = {}; - - user.stats.gp = 10000; - _.each(shared.content.gearTypes, (type) => { - _.each([1, 2, 3, 4, 5], (i) => { - return user.ops.buy({ - params: `${type}_${klass}_${i}`, - }); - }); - }); - - it(`does not get ultimateGear ${klass}`, () => { - expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok(); - }); - _.each(shared.content.gearTypes, (type) => { - return user.ops.buy({ - params: `${type}_${klass}_6`, - }); - }); - - xit(`gets ultimateGear ${klass}`, () => { - expect(user.achievements.ultimateGearSets[klass]).to.be.ok(); - }); - }); - - it('does not remove existing Ultimate Gear achievements', () => { - let user = generateUser(); - - user.achievements.ultimateGearSets = { - healer: true, - wizard: true, - rogue: true, - warrior: true, - }; - user.items.gear.owned.shield_warrior_5 = false; - user.items.gear.owned.weapon_rogue_6 = false; - user.ops.buy({ - params: 'shield_warrior_5', - }); - expect(user.achievements.ultimateGearSets).to.eql({ - healer: true, - wizard: true, - rogue: true, - warrior: true, - }); - }); - }); - - describe('unlocking features', () => { - it('unlocks drops at level 3', () => { - let user = generateUser(); - - user.stats.lvl = 3; - user.fns.updateStats(user.stats); - expect(user.flags.dropsEnabled).to.be.ok(); - }); - - it('unlocks Rebirth at level 50', () => { - let user = generateUser(); - - user.stats.lvl = 50; - user.fns.updateStats(user.stats); - expect(user.flags.rebirthEnabled).to.be.ok(); - }); - - describe('level-awarded Quests', () => { - it('gets Attack of the Mundane at level 15', () => { - let user = generateUser(); - - user.stats.lvl = 15; - user.fns.updateStats(user.stats); - expect(user.flags.levelDrops.atom1).to.be.ok(); - expect(user.items.quests.atom1).to.eql(1); - }); - - it('gets Vice at level 30', () => { - let user = generateUser(); - - user.stats.lvl = 30; - user.fns.updateStats(user.stats); - expect(user.flags.levelDrops.vice1).to.be.ok(); - expect(user.items.quests.vice1).to.eql(1); - }); - - it('gets Golden Knight at level 40', () => { - let user = generateUser(); - - user.stats.lvl = 40; - user.fns.updateStats(user.stats); - expect(user.flags.levelDrops.goldenknight1).to.be.ok(); - expect(user.items.quests.goldenknight1).to.eql(1); - }); - - it('gets Moonstone Chain at level 60', () => { - let user = generateUser(); - - user.stats.lvl = 60; - user.fns.updateStats(user.stats); - expect(user.flags.levelDrops.moonstone1).to.be.ok(); - expect(user.items.quests.moonstone1).to.eql(1); - }); - }); - }); -}); - -describe('Simple Scoring', () => { - beforeEach(function () { - let ref = beforeAfter(); - - this.before = ref.before; - this.after = ref.after; - }); - - it('Habits : Up', function () { - this.after.ops.score({ - params: { - id: this.after.habits[0].id, - direction: 'down', - }, - query: { - times: 5, - }, - }); - expectLostPoints(this.before, this.after, 'habit'); - }); - - it('Habits : Down', function () { - this.after.ops.score({ - params: { - id: this.after.habits[0].id, - direction: 'up', - }, - query: { - times: 5, - }, - }); - expectGainedPoints(this.before, this.after, 'habit'); - }); - - it('Dailys : Up', function () { - this.after.ops.score({ - params: { - id: this.after.dailys[0].id, - direction: 'up', - }, - }); - expectGainedPoints(this.before, this.after, 'daily'); - }); - - it('Dailys : Up, Down', function () { - this.after.ops.score({ - params: { - id: this.after.dailys[0].id, - direction: 'up', - }, - }); - this.after.ops.score({ - params: { - id: this.after.dailys[0].id, - direction: 'down', - }, - }); - expectClosePoints(this.before, this.after, 'daily'); - }); - - it('Todos : Up', function () { - this.after.ops.score({ - params: { - id: this.after.todos[0].id, - direction: 'up', - }, - }); - expectGainedPoints(this.before, this.after, 'todo'); - }); - - it('Todos : Up, Down', function () { - this.after.ops.score({ - params: { - id: this.after.todos[0].id, - direction: 'up', - }, - }); - this.after.ops.score({ - params: { - id: this.after.todos[0].id, - direction: 'down', - }, - }); - expectClosePoints(this.before, this.after, 'todo'); - }); -}); - -describe('Cron', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('computes shouldCron', () => { - let paths = {}; - - user.fns.cron({ - paths, - }); - expect(user.lastCron).to.not.be.ok; - user.lastCron = Number(moment().subtract(1, 'days')); - paths = {}; - user.fns.cron({ - paths, - }); - expect(user.lastCron).to.be.greaterThan(0); - }); - - it('only dailies & todos are affected', () => { - let ref = beforeAfter({ - daysAgo: 1, - }); - let before = ref.before; - let after = ref.after; - - before.dailys = before.todos = after.dailys = after.todos = []; - after.fns.cron(); - before.stats.mp = after.stats.mp; - expect(after.lastCron).to.not.be(before.lastCron); - delete after.stats.buffs; - delete before.stats.buffs; - expect(before.stats).to.eql(after.stats); - - let beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards); - let afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards); - - expect(beforeTasks).to.eql(afterTasks); - }); - - describe('preening', () => { - beforeEach(function () { - this.clock = sinon.useFakeTimers(Date.parse('2013-11-20'), 'Date'); - }); - afterEach(function () { - return this.clock.restore(); - }); - - it('should preen user history', function () { - let ref = beforeAfter({ - daysAgo: 1, - }); - let after = ref.after; - - let history = [ - { - date: '09/01/2012', - value: 0, - }, { - date: '10/01/2012', - value: 0, - }, { - date: '11/01/2012', - value: 2, - }, { - date: '12/01/2012', - value: 2, - }, { - date: '01/01/2013', - value: 1, - }, { - date: '01/15/2013', - value: 3, - }, { - date: '02/01/2013', - value: 2, - }, { - date: '02/15/2013', - value: 4, - }, { - date: '03/01/2013', - value: 3, - }, { - date: '03/15/2013', - value: 5, - }, { - date: '04/01/2013', - value: 4, - }, { - date: '04/15/2013', - value: 6, - }, { - date: '05/01/2013', - value: 5, - }, { - date: '05/15/2013', - value: 7, - }, { - date: '06/01/2013', - value: 6, - }, { - date: '06/15/2013', - value: 8, - }, { - date: '07/01/2013', - value: 7, - }, { - date: '07/15/2013', - value: 9, - }, { - date: '08/01/2013', - value: 8, - }, { - date: '08/15/2013', - value: 10, - }, { - date: '09/01/2013', - value: 9, - }, { - date: '09/15/2013', - value: 11, - }, { - date: '010/01/2013', - value: 10, - }, { - date: '010/15/2013', - value: 12, - }, { - date: '011/01/2013', - value: 12, - }, { - date: '011/02/2013', - value: 13, - }, { - date: '011/03/2013', - value: 14, - }, { - date: '011/04/2013', - value: 15, - }, - ]; - - after.history = { - exp: _.cloneDeep(history), - todos: _.cloneDeep(history), - }; - after.habits[0].history = _.cloneDeep(history); - after.fns.cron(); - after.history.exp.pop(); - after.history.todos.pop(); - _.each([after.history.exp, after.history.todos, after.habits[0].history], function (arr) { - expect(_.map(arr, (x) => { - return x.value; - })).to.eql([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); - }); - }); - }); - - describe('Todos', () => { - it('1 day missed', () => { - let ref = beforeAfter({ - daysAgo: 1, - }); - let before = ref.before; - let after = ref.after; - - before.dailys = after.dailys = []; - after.fns.cron(); - expect(after).toHaveHP(50); - expect(after).toHaveExp(0); - expect(after).toHaveGP(0); - expect(before.todos[0].value).to.be(0); - expect(after.todos[0].value).to.be(-1); - expect(after.history.todos).to.have.length(1); - }); - - it('2 days missed', () => { - let ref = beforeAfter({ - daysAgo: 2, - }); - let before = ref.before; - let after = ref.after; - - before.dailys = after.dailys = []; - after.fns.cron(); - expect(before.todos[0].value).to.be(0); - expect(after.todos[0].value).to.be(-1); - }); - }); - - describe('cron day calculations', () => { - let dayStart = 4; - let fstr = 'YYYY-MM-DD HH: mm: ss'; - - it('startOfDay before dayStart', () => { - let start = startOfDay({ - now: moment('2014-10-09 02: 30: 00'), - dayStart, - }); - - expect(start.format(fstr)).to.eql('2014-10-08 04: 00: 00'); - }); - - it('startOfDay after dayStart', () => { - let start = startOfDay({ - now: moment('2014-10-09 05: 30: 00'), - dayStart, - }); - - expect(start.format(fstr)).to.eql('2014-10-09 04: 00: 00'); - }); - - it('daysSince cron before, now after', () => { - let lastCron = moment('2014-10-09 02: 30: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-09 11: 30: 00'), - dayStart, - }); - - expect(days).to.eql(1); - }); - - it('daysSince cron before, now before', () => { - let lastCron = moment('2014-10-09 02: 30: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-09 03: 30: 00'), - dayStart, - }); - - expect(days).to.eql(0); - }); - - it('daysSince cron after, now after', () => { - let lastCron = moment('2014-10-09 05: 30: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-09 06: 30: 00'), - dayStart, - }); - - expect(days).to.eql(0); - }); - - it('daysSince cron after, now tomorrow before', () => { - let lastCron = moment('2014-10-09 12: 30: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-10 01: 30: 00'), - dayStart, - }); - - expect(days).to.eql(0); - }); - - it('daysSince cron after, now tomorrow after', () => { - let lastCron = moment('2014-10-09 12: 30: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-10 10: 30: 00'), - dayStart, - }); - - expect(days).to.eql(1); - }); - xit('daysSince, last cron before new dayStart', () => { - let lastCron = moment('2014-10-09 01: 00: 00'); - let days = daysSince(lastCron, { - now: moment('2014-10-09 05: 00: 00'), - dayStart, - }); - - expect(days).to.eql(0); - }); - }); - - describe('dailies', () => { - describe('new day', () => { - /* - This section runs through a 'cron matrix' of all permutations (that I can easily account for). It sets - task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron, - and so on - testing each possible outcome along the way - */ - - function runCron (options) { - _.each([480, 240, 0, -120], function (timezoneOffset) { - let now = startOfWeek({ - timezoneOffset, - }).add(options.currentHour || 0, 'hours'); - - let ref = beforeAfter({ - now, - timezoneOffset, - daysAgo: 1, - cronAfterStart: options.cronAfterStart || true, - dayStart: options.dayStart || 0, - limitOne: 'daily', - }); - - let before = ref.before; - let after = ref.after; - - if (options.repeat) { - before.dailys[0].repeat = after.dailys[0].repeat = options.repeat; - } - before.dailys[0].streak = after.dailys[0].streak = 10; - if (options.checked) { - before.dailys[0].completed = after.dailys[0].completed = true; - } - before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days'); - if (options.shouldDo) { - expect(shared.shouldDo(now.toDate(), after.dailys[0], { - timezoneOffset, - dayStart: options.dayStart, - now, - })).to.be.ok(); - } - after.fns.cron({ - now, - }); - before.stats.mp = after.stats.mp; - - if (options.expect === 'losePoints') { - expectLostPoints(before, after, 'daily'); - } else if (options.expect === 'noChange') { - expectNoChange(before, after); - } else if (options.expect === 'noDamage') { - expectDayResetNoDamage(before, after); - } - - return { - before, - after, - }; - }); - } - - let cronMatrix = { - steps: { - 'due yesterday': { - defaults: { - daysAgo: 1, - cronAfterStart: true, - limitOne: 'daily', - }, - steps: { - '(simple)': { - expect: 'losePoints', - }, - 'due today': { - defaults: { - repeat: { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - }, - }, - steps: { - 'pre-dayStart': { - defaults: { - currentHour: 3, - dayStart: 4, - shouldDo: true, - }, - steps: { - checked: { - checked: true, - expect: 'noChange', - }, - 'un-checked': { - checked: false, - expect: 'noChange', - }, - }, - }, - 'post-dayStart': { - defaults: { - currentHour: 5, - dayStart: 4, - shouldDo: true, - }, - steps: { - checked: { - checked: true, - expect: 'noDamage', - }, - unchecked: { - checked: false, - expect: 'losePoints', - }, - }, - }, - }, - }, - 'NOT due today': { - defaults: { - repeat: { - su: true, - m: false, - t: true, - w: true, - th: true, - f: true, - s: true, - }, - }, - steps: { - 'pre-dayStart': { - defaults: { - currentHour: 3, - dayStart: 4, - shouldDo: true, - }, - steps: { - checked: { - checked: true, - expect: 'noChange', - }, - 'un-checked': { - checked: false, - expect: 'noChange', - }, - }, - }, - 'post-dayStart': { - defaults: { - currentHour: 5, - dayStart: 4, - shouldDo: false, - }, - steps: { - checked: { - checked: true, - expect: 'noDamage', - }, - unchecked: { - checked: false, - expect: 'losePoints', - }, - }, - }, - }, - }, - }, - }, - 'not due yesterday': { - defaults: repeatWithoutLastWeekday(), - steps: { - '(simple)': { - expect: 'noDamage', - }, - 'post-dayStart': { - currentHour: 5, - dayStart: 4, - expect: 'noDamage', - }, - 'pre-dayStart': { - currentHour: 3, - dayStart: 4, - expect: 'noChange', - }, - }, - }, - }, - }; - - let recurseCronMatrix = (obj, options = {}) => { - if (obj.steps) { - _.each(obj.steps, (step, text) => { - let o = _.cloneDeep(options); - - if (!o.text) { - o.text = ''; - } - o.text += `${text}`; - return recurseCronMatrix(step, _.defaults(o, obj.defaults)); - }); - } else { - it(`${options.text}`, () => { - return runCron(_.defaults(obj, options)); - }); - } - }; - - return recurseCronMatrix(cronMatrix); - }); - }); -}); - -describe('Helper', () => { - it('calculates gold coins', () => { - expect(shared.gold(10)).to.eql(10); - expect(shared.gold(1.957)).to.eql(1); - expect(shared.gold()).to.eql(0); - }); - - it('calculates silver coins', () => { - expect(shared.silver(10)).to.eql(0); - expect(shared.silver(1.957)).to.eql(95); - expect(shared.silver(0.01)).to.eql('01'); - expect(shared.silver()).to.eql('00'); - }); - - it('calculates experience to next level', () => { - expect(shared.tnl(1)).to.eql(150); - expect(shared.tnl(2)).to.eql(160); - expect(shared.tnl(10)).to.eql(260); - expect(shared.tnl(99)).to.eql(3580); - }); - - it('calculates the start of the day', () => { - let fstr = 'YYYY-MM-DD HH: mm: ss'; - let today = '2013-01-01 00: 00: 00'; - let zone = moment(today).zone(); - - expect(startOfDay({ - now: new Date(2013, 0, 1, 0), - }, { - timezoneOffset: zone, - }).format(fstr)).to.eql(today); - expect(startOfDay({ - now: new Date(2013, 0, 1, 5), - }, { - timezoneOffset: zone, - }).format(fstr)).to.eql(today); - expect(startOfDay({ - now: new Date(2013, 0, 1, 23, 59, 59), - timezoneOffset: zone, - }).format(fstr)).to.eql(today); - }); -}); diff --git a/test/common/dailies.js b/test/common/dailies.js deleted file mode 100644 index d3aa84cae8..0000000000 --- a/test/common/dailies.js +++ /dev/null @@ -1,499 +0,0 @@ -/* eslint-disable camelcase */ -import { - startOfWeek, -} from '../../common/script/cron'; - -let expect = require('expect.js'); // eslint-disable-line no-shadow -let moment = require('moment'); -let shared = require('../../common/script/index.js'); - -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; - -let repeatWithoutLastWeekday = () => { // eslint-disable-line no-unused-vars - let repeat = { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - }; - - if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { - repeat.su = false; - } else { - repeat.s = false; - } - return { - repeat, - }; -}; - - -/* Helper Functions */ - -import { - generateUser, -} from '../helpers/common.helper'; - -let cron = (usr, missedDays = 1) => { - usr.lastCron = moment().subtract(missedDays, 'days'); - usr.fns.cron(); -}; - -describe('daily/weekly that repeats everyday (default)', () => { - let user = null; - let daily = null; - let weekly = null; - - describe('when startDate is in the future', () => { - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().add(7, 'days'), - frequency: 'daily', - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().add(7, 'days'), - frequency: 'weekly', - repeat: { - su: true, - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - }, - }), - ]; - daily = user.dailys[0]; - weekly = user.dailys[1]; - }); - - it('does not damage user for not completing it', () => { - cron(user); - expect(user.stats.hp).to.be(50); - }); - - it('does not change value on cron if daily is incomplete', () => { - cron(user); - expect(daily.value).to.be(0); - expect(weekly.value).to.be(0); - }); - - it('does not reset checklists if daily is not marked as complete', () => { - let checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - expect(daily.checklist[0].completed).to.be(true); - expect(daily.checklist[1].completed).to.be(true); - expect(daily.checklist[2].completed).to.be(false); - expect(weekly.checklist[0].completed).to.be(true); - expect(weekly.checklist[1].completed).to.be(true); - expect(weekly.checklist[2].completed).to.be(false); - }); - - it('resets checklists if daily is marked as complete', () => { - let checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - - daily.checklist = checklist; - weekly.checklist = checklist; - daily.completed = true; - weekly.completed = true; - cron(user); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - _.each(weekly.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('is due on startDate', () => { - let daily_due_today = shared.shouldDo(moment(), daily); - let daily_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), daily); - - expect(daily_due_today).to.be(false); - expect(daily_due_on_start_date).to.be(true); - - let weekly_due_today = shared.shouldDo(moment(), weekly); - let weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); - - expect(weekly_due_today).to.be(false); - expect(weekly_due_on_start_date).to.be(true); - }); - }); - - describe('when startDate is in the past', () => { - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - frequency: 'daily', - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(7, 'days'), - frequency: 'weekly', - }), - ]; - daily = user.dailys[0]; - weekly = user.dailys[1]; - }); - - it('does damage user for not completing it', () => { - cron(user); - expect(user.stats.hp).to.be.lessThan(50); - }); - - it('decreases value on cron if daily is incomplete', () => { - cron(user, 1); - expect(daily.value).to.be(-1); - expect(weekly.value).to.be(-1); - }); - - it('decreases value on cron once only if daily is incomplete and multiple days are missed', () => { - cron(user, 7); - expect(daily.value).to.be(-1); - expect(weekly.value).to.be(-1); - }); - - it('resets checklists if daily is not marked as complete', () => { - let checklist; - - checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - _.each(weekly.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('resets checklists if daily is marked as complete', () => { - let checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - - daily.checklist = checklist; - daily.completed = true; - weekly.checklist = checklist; - weekly.completed = true; - cron(user); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - _.each(weekly.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - }); - - describe('when startDate is today', () => { - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(1, 'days'), - frequency: 'daily', - }), shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(1, 'days'), - frequency: 'weekly', - }), - ]; - daily = user.dailys[0]; - weekly = user.dailys[1]; - }); - - it('does damage user for not completing it', () => { - cron(user); - expect(user.stats.hp).to.be.lessThan(50); - }); - - it('decreases value on cron if daily is incomplete', () => { - cron(user); - expect(daily.value).to.be.lessThan(0); - expect(weekly.value).to.be.lessThan(0); - }); - - it('resets checklists if daily is not marked as complete', () => { - let checklist; - - checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - daily.checklist = checklist; - weekly.checklist = checklist; - cron(user); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - _.each(weekly.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('resets checklists if daily is marked as complete', () => { - let checklist; - - checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, { - text: '2', - id: 'checklist-two', - completed: true, - }, { - text: '3', - id: 'checklist-three', - completed: false, - }, - ]; - daily.checklist = checklist; - daily.completed = true; - weekly.checklist = checklist; - weekly.completed = true; - cron(user); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - _.each(weekly.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - }); -}); - -describe('daily that repeats every x days', () => { - let user = null; - let daily = null; - - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment(), - frequency: 'daily', - }), - ]; - daily = user.dailys[0]; - }); - _.times(11, (due) => { - it(`where x equals ${due}`, () => { - daily.everyX = due; - _.times(30, (day) => { - let isDue; - - isDue = shared.shouldDo(moment().add(day, 'days'), daily); - if (day % due === 0) { - expect(isDue).to.be(true); - } - if (day % due !== 0) { - expect(isDue).to.be(false); - } - }); - }); - }); -}); - -describe('daily that repeats every X days when multiple days are missed', () => { - let everyX = 3; - let startDateDaysAgo = everyX * 3; - let user = null; - let daily = null; - - describe('including missing a due date', () => { - let missedDays = everyX * 2 + 1; - - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(startDateDaysAgo, 'days'), - frequency: 'daily', - everyX, - }), - ]; - daily = user.dailys[0]; - }); - - it('decreases value on cron once only if daily is incomplete', () => { - cron(user, missedDays); - expect(daily.value).to.be(-1); - }); - - it('resets checklists if daily is incomplete', () => { - let checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, - ]; - - daily.checklist = checklist; - cron(user, missedDays); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - - it('resets checklists if daily is marked as complete', () => { - let checklist; - - checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, - ]; - daily.checklist = checklist; - daily.completed = true; - cron(user, missedDays); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - }); - - describe('but not missing a due date', () => { - let missedDays; - - missedDays = everyX - 1; - beforeEach(() => { - user = generateUser(); - user.dailys = [ - shared.taskDefaults({ - type: 'daily', - startDate: moment().subtract(startDateDaysAgo, 'days'), - frequency: 'daily', - everyX, - }), - ]; - daily = user.dailys[0]; - }); - - it('does not decrease value on cron', () => { - cron(user, missedDays); - expect(daily.value).to.be(0); - }); - - it('does not reset checklists if daily is incomplete', () => { - let checklist; - - checklist = [ - { - text: '1', - id: 'checklist-one', - completed: true, - }, - ]; - daily.checklist = checklist; - cron(user, missedDays); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(true); - }); - }); - - it('resets checklists if daily is marked as complete', () => { - let checklist; - - checklist = [ - { - text: 1, - id: 'checklist-one', - completed: true, - }, - ]; - daily.checklist = checklist; - daily.completed = true; - cron(user, missedDays); - _.each(daily.checklist, (box) => { - expect(box.completed).to.be(false); - }); - }); - }); -}); diff --git a/test/common/fns/autoAllocate.test.js b/test/common/fns/autoAllocate.test.js new file mode 100644 index 0000000000..e00f8dd17d --- /dev/null +++ b/test/common/fns/autoAllocate.test.js @@ -0,0 +1,86 @@ +import autoAllocate from '../../../common/script/fns/autoAllocate'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.fns.autoAllocate', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('user.preferences.allocationMode === flat', () => { + user.stats.con = 5; + user.stats.int = 5; + user.stats.per = 3; + user.stats.str = 8; + + user.preferences.allocationMode = 'flat'; + + autoAllocate(user); + + expect(user.stats.con).to.equal(5); + expect(user.stats.int).to.equal(5); + expect(user.stats.per).to.equal(4); + expect(user.stats.str).to.equal(8); + }); + + it('user.preferences.allocationMode === taskbased', () => { + user.stats.con = 5; + user.stats.int = 5; + user.stats.per = 3; + user.stats.str = 8; + user.stats.training.con = 2; + user.stats.training.int = 5; + user.stats.training.per = 7; + user.stats.training.str = 4; + + user.preferences.allocationMode = 'taskbased'; + + autoAllocate(user); + + expect(user.stats.con).to.equal(5); + expect(user.stats.int).to.equal(5); + expect(user.stats.per).to.equal(4); + expect(user.stats.str).to.equal(8); + + expect(user.stats.training.con).to.equal(0); + expect(user.stats.training.int).to.equal(0); + expect(user.stats.training.per).to.equal(0); + expect(user.stats.training.str).to.equal(0); + }); + + it('user.preferences.allocationMode === classbased', () => { + user.stats.lvl = 35; + user.stats.class = 'healer'; + user.stats.con = 5; + user.stats.int = 5; + user.stats.per = 3; + user.stats.str = 8; + + user.preferences.allocationMode = 'classbased'; + + autoAllocate(user); + + expect(user.stats.con).to.equal(6); + expect(user.stats.int).to.equal(5); + expect(user.stats.per).to.equal(3); + expect(user.stats.str).to.equal(8); + }); + + it('user.preferences.allocationMode === anything', () => { + user.stats.con = 5; + user.stats.int = 5; + user.stats.per = 3; + user.stats.str = 8; + user.preferences.allocationMode = 'wrong'; + + autoAllocate(user); + + expect(user.stats.con).to.equal(5); + expect(user.stats.int).to.equal(5); + expect(user.stats.per).to.equal(3); + expect(user.stats.str).to.equal(9); + }); +}); diff --git a/test/common/fns/crit.test.js b/test/common/fns/crit.test.js new file mode 100644 index 0000000000..4f43c55fa1 --- /dev/null +++ b/test/common/fns/crit.test.js @@ -0,0 +1,17 @@ +import crit from '../../../common/script/fns/crit'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('crit', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('computes', () => { + let result = crit(user); + expect(result).to.eql(1); + }); +}); diff --git a/test/common/fns/handleTwoHanded.js b/test/common/fns/handleTwoHanded.js new file mode 100644 index 0000000000..8d191ff114 --- /dev/null +++ b/test/common/fns/handleTwoHanded.js @@ -0,0 +1,38 @@ +import handleTwoHanded from '../../../common/script/fns/handleTwoHanded'; +import content from '../../../common/script/content/index'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.fns.handleTwoHanded', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('uses "messageTwoHandedUnequip" message if item is a shield and current weapon is two handed (and sets the user\'s weapon to the base one)', () => { + let item = content.gear.tree.shield.warrior['2']; + let currentWeapon = content.gear.tree.weapon.armoire.rancherLasso; + user.items.gear.equipped.weapon = 'weapon_armoire_rancherLasso'; + + let message = handleTwoHanded(user, item); + expect(message).to.equal(i18n.t('messageTwoHandedUnequip', { + twoHandedText: currentWeapon.text(), offHandedText: item.text(), + })); + expect(user.items.gear.equipped.weapon).to.equal('weapon_base_0'); + }); + + it('uses "messageTwoHandedEquip" message if item is two handed and currentShield exists but is not "shield_base_0" (and sets the user\'s shield to the base one)', () => { + let item = content.gear.tree.weapon.armoire.rancherLasso; + let currentShield = content.gear.tree.shield.armoire.gladiatorShield; + user.items.gear.equipped.shield = 'shield_armoire_gladiatorShield'; + + let message = handleTwoHanded(user, item); + expect(message).to.equal(i18n.t('messageTwoHandedEquip', { + twoHandedText: item.text(), offHandedText: currentShield.text(), + })); + expect(user.items.gear.equipped.shield).to.equal('shield_base_0'); + }); +}); diff --git a/test/common/fns/predictableRandom.test.js b/test/common/fns/predictableRandom.test.js new file mode 100644 index 0000000000..1cd47fc426 --- /dev/null +++ b/test/common/fns/predictableRandom.test.js @@ -0,0 +1,51 @@ +import predictableRandom from '../../../common/script/fns/predictableRandom'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.fns.predictableRandom', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('returns a number', () => { + expect(predictableRandom(user)).to.be.a('number'); + }); + + it('returns the same value when user.stats is the same and no seed is passed', () => { + user.stats.hp = 43; + user.stats.gp = 34; + + let val1 = predictableRandom(user); + let val2 = predictableRandom(user); + + expect(val2).to.equal(val1); + }); + + it('returns a different value when user.stats is not the same and no seed is passed', () => { + user.stats.hp = 43; + user.stats.gp = 34; + let val1 = predictableRandom(user); + + user.stats.gp = 35; + let val2 = predictableRandom(user); + + expect(val2).to.not.equal(val1); + }); + + it('returns the same value when the same seed is passed', () => { + let val1 = predictableRandom(user, 4452673762); + let val2 = predictableRandom(user, 4452673762); + + expect(val2).to.equal(val1); + }); + + it('returns a different value when a different seed is passed', () => { + let val1 = predictableRandom(user, 4452673761); + let val2 = predictableRandom(user, 4452673762); + + expect(val2).to.not.equal(val1); + }); +}); diff --git a/test/common/fns/randomDrop.test.js b/test/common/fns/randomDrop.test.js new file mode 100644 index 0000000000..9569067ffc --- /dev/null +++ b/test/common/fns/randomDrop.test.js @@ -0,0 +1,165 @@ +// TODO disable until we can find a way to stub predictableRandom + +/* eslint-disable */ + +import randomDrop from '../../../common/script/fns/randomDrop'; +import { + generateUser, + generateTodo, + generateHabit, + generateDaily, + generateReward, +} from '../../helpers/common.helper'; +// import predictableRandom from '../../../common/script/fns/predictableRandom'; // eslint-disable +import content from '../../../common/script/content/index'; + +xdescribe('common.fns.randomDrop', () => { + let user; + let task; + let predictableRandom; + + beforeEach(() => { + user = generateUser(); + user._tmp = user._tmp ? user._tmp : {}; + task = generateTodo({ userId: user._id }); + predictableRandom = () => { + return 0.5; + }; + }); + + /** + * function signature as follows: + * randomDrop(user, modifiers) {} + * modifiers = { task, delta = null } + **/ + + it('drops an item for the user.party.quest.progress', () => { + expect(user.party.quest.progress.collect).to.eql({}); + user.party.quest.key = 'vice2'; + let collectWhat = Object.keys(content.quests[user.party.quest.key].collect)[0]; // lightCrystal + predictableRandom = () => { + return 0.0001; + }; + randomDrop(user, { task }); + expect(user.party.quest.progress.collect[collectWhat]).to.eql(1); + randomDrop(user, { task }); + expect(user.party.quest.progress.collect[collectWhat]).to.eql(2); + }); + + context('drops enabled', () => { + beforeEach(() => { + user.flags.dropsEnabled = true; + task.priority = 100000; + }); + + it('does nothing if user.items.lastDrop.count is exceeded', () => { + user.items.lastDrop.count = 100; + randomDrop(user, { task }); + expect(user._tmp).to.eql({}); + }); + + it('drops something when the task is a todo', () => { + expect(user._tmp).to.eql({}); + user.flags.dropsEnabled = true; + predictableRandom = () => { + return 0.1; + }; + randomDrop(user, { task }); + expect(user._tmp).to.not.eql({}); + }); + + it('drops something when the task is a habit', () => { + task = generateHabit({ userId: user._id }); + expect(user._tmp).to.eql({}); + user.flags.dropsEnabled = true; + predictableRandom = () => { + return 0.1; + }; + randomDrop(user, { task }); + expect(user._tmp).to.not.eql({}); + }); + + it('drops something when the task is a daily', () => { + task = generateDaily({ userId: user._id }); + expect(user._tmp).to.eql({}); + user.flags.dropsEnabled = true; + predictableRandom = () => { + return 0.1; + }; + randomDrop(user, { task }); + expect(user._tmp).to.not.eql({}); + }); + + it('drops something when the task is a reward', () => { + task = generateReward({ userId: user._id }); + expect(user._tmp).to.eql({}); + user.flags.dropsEnabled = true; + predictableRandom = () => { + return 0.1; + }; + randomDrop(user, { task }); + expect(user._tmp).to.not.eql({}); + }); + + it('drops food', () => { + predictableRandom = () => { + return 0.65; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('Food'); + }); + + it('drops eggs', () => { + predictableRandom = () => { + return 0.35; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('Egg'); + }); + + context('drops hatching potion', () => { + it('drops a very rare potion', () => { + predictableRandom = () => { + return 0.01; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('HatchingPotion'); + expect(user._tmp.drop.value).to.eql(5); + expect(user._tmp.drop.key).to.eql('Golden'); + }); + + it('drops a rare potion', () => { + predictableRandom = () => { + return 0.08; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('HatchingPotion'); + expect(user._tmp.drop.value).to.eql(4); + let acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']; + expect(acceptableDrops).to.contain(user._tmp.drop.key); // deterministically 'CottonCandyBlue' + }); + + it('drops an uncommon potion', () => { + predictableRandom = () => { + return 0.17; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('HatchingPotion'); + expect(user._tmp.drop.value).to.eql(3); + let acceptableDrops = ['Red', 'Shade', 'Skeleton']; + expect(acceptableDrops).to.contain(user._tmp.drop.key); // always skeleton + }); + + it('drops a common potion', () => { + predictableRandom = () => { + return 0.20; + }; + randomDrop(user, { task }); + expect(user._tmp.drop.type).to.eql('HatchingPotion'); + expect(user._tmp.drop.value).to.eql(2); + let acceptableDrops = ['Base', 'White', 'Desert']; + expect(acceptableDrops).to.contain(user._tmp.drop.key); // always Desert + }); + }); + }); +}); diff --git a/test/common/fns/randomVal.js b/test/common/fns/randomVal.js new file mode 100644 index 0000000000..b4b8e377d3 --- /dev/null +++ b/test/common/fns/randomVal.js @@ -0,0 +1,119 @@ +import randomVal from '../../../common/script/fns/randomVal'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.fns.randomVal', () => { + let user; + let obj = { + a: 1, + b: 2, + c: 3, + d: 4, + }; + + beforeEach(() => { + user = generateUser(); + }); + + describe('returns a random property value from an object', () => { + it('returns the same value when the seed is the same', () => { + let val1 = randomVal(user, obj, { + seed: 222, + }); + + let val2 = randomVal(user, obj, { + seed: 222, + }); + + expect(val2).to.equal(val1); + }); + + it('returns the same value when user.stats is the same', () => { + user.stats.gp = 34; + let val1 = randomVal(user, obj); + let val2 = randomVal(user, obj); + + expect(val2).to.equal(val1); + }); + + it('returns a different value when the seed is different', () => { + let val1 = randomVal(user, obj, { + seed: 222, + }); + + let val2 = randomVal(user, obj, { + seed: 333, + }); + + expect(val2).to.not.equal(val1); + }); + + it('returns a different value when user.stats is different', () => { + user.stats.gp = 34; + let val1 = randomVal(user, obj); + user.stats.gp = 343; + let val2 = randomVal(user, obj); + + expect(val2).to.not.equal(val1); + }); + }); + + describe('returns a random key from an object', () => { + it('returns the same key when the seed is the same', () => { + let key1 = randomVal(user, obj, { + key: true, + seed: 222, + }); + + let key2 = randomVal(user, obj, { + key: true, + seed: 222, + }); + + expect(key2).to.equal(key1); + }); + + it('returns the same key when user.stats is the same', () => { + user.stats.gp = 45; + let key1 = randomVal(user, obj, { + key: true, + }); + + let key2 = randomVal(user, obj, { + key: true, + }); + + expect(key2).to.equal(key1); + }); + + it('returns a different key when the seed is different', () => { + let key1 = randomVal(user, obj, { + key: true, + seed: 222, + }); + + let key2 = randomVal(user, obj, { + key: true, + seed: 333, + }); + + expect(key2).to.not.equal(key1); + }); + + it('returns a different key when user.stats is different', () => { + user.stats.gp = 45; + let key1 = randomVal(user, obj, { + key: true, + }); + + user.stats.gp = 43; + + let key2 = randomVal(user, obj, { + key: true, + }); + + expect(key2).to.not.equal(key1); + }); + }); +}); diff --git a/test/common/fns/statsComputed.test.js b/test/common/fns/statsComputed.test.js new file mode 100644 index 0000000000..07b009368d --- /dev/null +++ b/test/common/fns/statsComputed.test.js @@ -0,0 +1,28 @@ +import statsComputed from '../../../common/script/libs/statsComputed'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('common.fns.statsComputed', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('returns the same result if called directly, through user.fns.statsComputed, or user._statsComputed', () => { + let result = statsComputed(user); + let result2 = user._statsComputed; + let result3 = user.fns.statsComputed(); + expect(result).to.eql(result2); + expect(result).to.eql(result3); + }); + + it('returns default values', () => { + let result = statsComputed(user); + expect(result.per).to.eql(0); + expect(result.con).to.eql(0); + expect(result.str).to.eql(0); + expect(result.maxMP).to.eql(30); + }); +}); diff --git a/test/common/fns/ultimateGear.js b/test/common/fns/ultimateGear.js new file mode 100644 index 0000000000..8da991b565 --- /dev/null +++ b/test/common/fns/ultimateGear.js @@ -0,0 +1,33 @@ +import ultimateGear from '../../../common/script/fns/ultimateGear'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.fns.ultimateGear', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('sets armoirEnabled when partial achievement already achieved', () => { + let items = { + gear: { + owned: { + toObject: () => { + return { + armor_warrior_5: true, // eslint-disable-line camelcase + shield_warrior_5: true, // eslint-disable-line camelcase + head_warrior_5: true, // eslint-disable-line camelcase + weapon_warrior_6: true, // eslint-disable-line camelcase + }; + }, + }, + }, + }; + + user.items = items; + ultimateGear(user); + expect(user.flags.armoireEnabled).to.equal(true); + }); +}); diff --git a/test/common/fns/updateStats.test.js b/test/common/fns/updateStats.test.js new file mode 100644 index 0000000000..cea5f6e6ca --- /dev/null +++ b/test/common/fns/updateStats.test.js @@ -0,0 +1,170 @@ +import updateStats from '../../../common/script/fns/updateStats'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('common.fns.updateStats', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + context('No Hp', () => { + it('updates user\s hp', () => { + let stats = { hp: 0 }; + expect(user.stats.hp).to.not.eql(0); + updateStats(user, stats); + expect(user.stats.hp).to.eql(0); + updateStats(user, { hp: 2 }); + expect(user.stats.hp).to.eql(2); + }); + + it('does not lower hp below 0', () => { + let stats = { + hp: -5, + }; + updateStats(user, stats); + expect(user.stats.hp).to.eql(0); + }); + }); + + context('Stat Allocation', () => { + it('adds only attribute points up to user\'s level', () => { + let stats = { + exp: 261, + }; + expect(user.stats.points).to.eql(0); + + user.stats.lvl = 10; + + updateStats(user, stats); + + expect(user.stats.points).to.eql(11); + }); + + it('adds an attibute point when user\'s stat points are less than max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 24; + + updateStats(user, stats); + + expect(user.stats.points).to.eql(1); + }); + + it('does not add an attibute point when user\'s stat points are equal to max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 25; + + updateStats(user, stats); + + expect(user.stats.points).to.eql(0); + }); + + it('does not add an attibute point when user\'s stat points + unallocated points are equal to max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 15; + user.stats.points = 10; + + updateStats(user, stats); + + expect(user.stats.points).to.eql(10); + }); + + it('only awards stat points up to level 100 if user is missing unallocated stat points and is over level 100', () => { + let stats = { + exp: 5581, + }; + + user.stats.lvl = 104; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 15; + user.stats.points = 0; + + updateStats(user, stats); + + expect(user.stats.points).to.eql(10); + }); + + context('assigns flags.levelDrops', () => { + it('for atom1', () => { + user.stats.lvl = 16; + user.flags.levelDrops.atom1 = false; + expect(user.items.quests.atom1).to.eql(undefined); + updateStats(user, { atom1: true }); + expect(user.items.quests.atom1).to.eql(1); + expect(user.flags.levelDrops.atom1).to.eql(true); + updateStats(user, { atom1: true }); + expect(user.items.quests.atom1).to.eql(1); // no change + }); + it('for vice1', () => { + user.stats.lvl = 31; + user.flags.levelDrops.vice1 = false; + expect(user.items.quests.vice1).to.eql(undefined); + updateStats(user, { vice1: true }); + expect(user.items.quests.vice1).to.eql(1); + expect(user.flags.levelDrops.vice1).to.eql(true); + updateStats(user, { vice1: true }); + expect(user.items.quests.vice1).to.eql(1); + }); + it('moonstone', () => { + user.stats.lvl = 60; + user.flags.levelDrops.moonstone1 = false; + expect(user.items.quests.moonstone1).to.eql(undefined); + updateStats(user, { moonstone1: true }); + expect(user.flags.levelDrops.moonstone1).to.eql(true); + expect(user.items.quests.moonstone1).to.eql(1); + updateStats(user, { moonstone1: true }); + expect(user.items.quests.moonstone1).to.eql(1); + }); + it('for goldenknight1', () => { + user.stats.lvl = 40; + user.flags.levelDrops.goldenknight1 = false; + expect(user.items.quests.goldenknight1).to.eql(undefined); + updateStats(user, { goldenknight1: true }); + expect(user.items.quests.goldenknight1).to.eql(1); + expect(user.flags.levelDrops.goldenknight1).to.eql(true); + updateStats(user, { goldenknight1: true }); + expect(user.items.quests.goldenknight1).to.eql(1); + }); + }); + + // @TODO: Set up sinon sandbox + xit('auto allocates stats if automaticAllocation is turned on', () => { + sandbox.stub(user.fns, 'autoAllocate'); + + let stats = { + exp: 261, + }; + + user.stats.lvl = 10; + + user.fns.updateStats(stats); + + expect(user.fns.autoAllocate).to.be.calledOnce; + }); + }); +}); diff --git a/test/common/libs/appliedTags.test.js b/test/common/libs/appliedTags.test.js new file mode 100644 index 0000000000..66f3de4758 --- /dev/null +++ b/test/common/libs/appliedTags.test.js @@ -0,0 +1,10 @@ +import appliedTags from '../../../common/script/libs/appliedTags'; + +describe('appliedTags', () => { + it('returns the tasks', () => { + let userTags = [{ id: 'tag1', name: 'tag 1' }, { id: 'tag2', name: 'tag 2' }, { id: 'tag3', name: 'tag 3' }]; + let taskTags = ['tag2', 'tag3']; + let result = appliedTags(userTags, taskTags); + expect(result).to.eql('tag 2, tag 3'); + }); +}); diff --git a/test/common/libs/gold.test.js b/test/common/libs/gold.test.js new file mode 100644 index 0000000000..2cdfc3ef65 --- /dev/null +++ b/test/common/libs/gold.test.js @@ -0,0 +1,11 @@ +import gold from '../../../common/script/libs/gold'; + +describe('gold', () => { + it('is 0', () => { + expect(gold()).to.eql('0'); + }); + + it('is 5 in 5.2 of gold', () => { + expect(gold(5.2)).to.eql(5); + }); +}); diff --git a/test/common/libs/noTags.test.js b/test/common/libs/noTags.test.js new file mode 100644 index 0000000000..dcd2481854 --- /dev/null +++ b/test/common/libs/noTags.test.js @@ -0,0 +1,13 @@ +import noTags from '../../../common/script/libs/noTags'; + +describe('noTags', () => { + it('returns true for no tags', () => { + let result = noTags([]); + expect(result).to.eql(true); + }); + + it('returns false for some tags', () => { + let result = noTags(['a', 'b', 'c']); + expect(result).to.eql(false); + }); +}); diff --git a/test/common/libs/percent.test.js b/test/common/libs/percent.test.js new file mode 100644 index 0000000000..9d1ba31024 --- /dev/null +++ b/test/common/libs/percent.test.js @@ -0,0 +1,19 @@ +import percent from '../../../common/script/libs/percent'; + +describe('percent', () => { + it('with direction "up"', () => { + expect(percent(1, 10, 'up')).to.eql(10); + expect(percent(1, 20, 'up')).to.eql(5); + expect(percent(1.22, 10.99, 'up')).to.eql(12); + }); + + it('with direction "down"', () => { + expect(percent(1, 10, 'down')).to.eql(10); + expect(percent(1, 20, 'down')).to.eql(5); + expect(percent(1.22, 10.99, 'down')).to.eql(11); + }); + + it('with no direction', () => { + expect(percent(1.22, 10.99)).to.eql(11); + }); +}); diff --git a/test/common/libs/pickDeep.js b/test/common/libs/pickDeep.js new file mode 100644 index 0000000000..4a8741269d --- /dev/null +++ b/test/common/libs/pickDeep.js @@ -0,0 +1,34 @@ +import pickDeep from '../../../common/script/libs/pickDeep'; + +describe('pickDeep', () => { + it('throws an error if "properties" is not an array', () => { + expect(pickDeep).to.throw(Error); + }); + + it('returns an object of properties taken from the input object', () => { + let obj = { + a: true, + b: [1, 2, 3], + c: { + nested: { + two: { + times: true, + }, + }, + }, + d: false, + }; + + let res = pickDeep(obj, ['a', 'b[0]', 'c.nested.two.times']); + expect(res.a).to.be.true; + expect(res.b).to.eql([1]); + expect(res.c).to.eql({ + nested: { + two: { + times: true, + }, + }, + }); + expect(res).to.not.have.property('d'); + }); +}); diff --git a/test/common/libs/refPush.js b/test/common/libs/refPush.js new file mode 100644 index 0000000000..4183845c9a --- /dev/null +++ b/test/common/libs/refPush.js @@ -0,0 +1,53 @@ +import shared from '../../../common'; +import { v4 as generateUUID } from 'uuid'; + +describe('refPush', () => { + it('it hashes one object into another by its id', () => { + let referenceObject = {}; + let objectToHash = { + a: 1, + id: generateUUID(), + }; + + shared.refPush(referenceObject, objectToHash); + + expect(referenceObject[objectToHash.id].a).to.equal(objectToHash.a); + expect(referenceObject[objectToHash.id].id).to.equal(objectToHash.id); + expect(referenceObject[objectToHash.id].sort).to.equal(0); + }); + + it('it hashes one object into another by a uuid when object does not have an id', () => { + let referenceObject = {}; + let objectToHash = { + a: 1, + }; + + shared.refPush(referenceObject, objectToHash); + + let hashedObject = _.find(referenceObject, (hashedItem) => { + return objectToHash.a === hashedItem.a; + }); + + expect(hashedObject.a).to.equal(objectToHash.a); + expect(hashedObject.id).to.equal(objectToHash.id); + expect(hashedObject.sort).to.equal(0); + }); + + it('it hashes one object into another by a id and gives it the highest sort value', () => { + let referenceObject = {}; + referenceObject[generateUUID()] = { b: 2, sort: 1 }; + let objectToHash = { + a: 1, + }; + + shared.refPush(referenceObject, objectToHash); + + let hashedObject = _.find(referenceObject, (hashedItem) => { + return objectToHash.a === hashedItem.a; + }); + + expect(hashedObject.a).to.equal(objectToHash.a); + expect(hashedObject.id).to.equal(objectToHash.id); + expect(hashedObject.sort).to.equal(2); + }); +}); diff --git a/test/common/libs/silver.test.js b/test/common/libs/silver.test.js new file mode 100644 index 0000000000..5bd614fcd1 --- /dev/null +++ b/test/common/libs/silver.test.js @@ -0,0 +1,19 @@ +import silver from '../../../common/script/libs/silver'; + +describe('silver', () => { + it('is 0', () => { + expect(silver(0)).to.eql('00'); + }); + + it('20 coins in 5.2 of gold: two decimal places', () => { + expect(silver(5.2)).to.eql('20'); + }); + + it('4 coint in 5.04 of gold: one decimal place', () => { + expect(silver(5.04)).to.eql('04'); + }); + + it('is no value', () => { + expect(silver()).to.eql('00'); + }); +}); diff --git a/test/common/libs/splitWhitespace.test.js b/test/common/libs/splitWhitespace.test.js new file mode 100644 index 0000000000..0b445d5a38 --- /dev/null +++ b/test/common/libs/splitWhitespace.test.js @@ -0,0 +1,7 @@ +import splitWhitespace from '../../../common/script/libs/splitWhitespace'; + +describe('splitWhitespace', () => { + it('returns an array', () => { + expect(splitWhitespace('a b')).to.eql(['a', 'b']); + }); +}); diff --git a/test/common/libs/taskClasses.test.js b/test/common/libs/taskClasses.test.js new file mode 100644 index 0000000000..226d740bac --- /dev/null +++ b/test/common/libs/taskClasses.test.js @@ -0,0 +1,82 @@ +import taskClasses from '../../../common/script/libs/taskClasses'; + +describe('taskClasses', () => { + let task = {}; + let filters = {}; + let result; + + describe('a todo task', () => { + beforeEach(() => { + task = { type: 'todo', _editing: false, tags: [] }; + }); + + it('is hidden', () => { + filters = { a: true }; + result = taskClasses(task, filters, 0, Number(new Date()), false, true); + expect(result).to.eql('hidden'); + }); + it('is beingEdited', () => { + task._editing = true; + result = taskClasses(task, filters); + expect(result.split(' ').indexOf('beingEdited')).to.not.eql(-1); + }); + it('is completed', () => { + task.completed = true; + result = taskClasses(task, filters); + expect(result.split(' ').indexOf('completed')).to.not.eql(-1); + task.completed = false; + result = taskClasses(task, filters); + expect(result.split(' ').indexOf('completed')).to.eql(-1); + expect(result.split(' ').indexOf('uncompleted')).to.not.eql(-1); + }); + }); + + describe('a daily task', () => { + it('is completed', () => { + task = { type: 'daily' }; + result = taskClasses(task); + expect(result.split(' ').indexOf('completed')).to.not.eql(-1); + }); + + it('is uncompleted'); // this requires stubbing the internal dependency shouldDo in taskClasses + }); + + describe('a habit', () => { + it('that is wide', () => { + task = { type: 'habit', up: true, down: true }; + result = taskClasses(task); + expect(result.split(' ').indexOf('habit-wide')).to.not.eql(-1); + }); + it('that is narrow', () => { + task = { type: 'habit' }; + result = taskClasses(task); + expect(result.split(' ').indexOf('habit-narrow')).to.not.eql(-1); + }); + }); + + describe('varies based on priority', () => { + it('trivial', () => { + task.priority = 0.1; + result = taskClasses(task); + expect(result.split(' ').indexOf('difficulty-trivial')).to.not.eql(-1); + }); + it('hard', () => { + task.priority = 2; + result = taskClasses(task); + expect(result.split(' ').indexOf('difficulty-hard')).to.not.eql(-1); + }); + }); + + describe('varies based on value', () => { + it('color-worst', () => { + task.value = -30; + result = taskClasses(task); + expect(result.split(' ').indexOf('color-worst')).to.not.eql(-1); + }); + it('color-neutral', () => { + task.value = 0; + result = taskClasses(task); + expect(result.split(' ').indexOf('color-neutral')).to.not.eql(-1); + }); + }); +}); diff --git a/test/common/libs/taskDefaults.test.js b/test/common/libs/taskDefaults.test.js new file mode 100644 index 0000000000..c970634137 --- /dev/null +++ b/test/common/libs/taskDefaults.test.js @@ -0,0 +1,61 @@ +import taskDefaults from '../../../common/script/libs/taskDefaults'; + +describe('taskDefaults', () => { + it('applies defaults to undefined type or habit', () => { + let task = taskDefaults(); + expect(task.type).to.eql('habit'); + expect(task._id).to.exist; + expect(task.text).to.eql(task._id); + expect(task.tags).to.eql([]); + expect(task.value).to.eql(0); + expect(task.priority).to.eql(1); + expect(task.up).to.eql(true); + expect(task.down).to.eql(true); + expect(task.history).to.eql([]); + }); + + it('applies defaults to a daily', () => { + let task = taskDefaults({ type: 'daily' }); + expect(task.type).to.eql('daily'); + expect(task._id).to.exist; + expect(task.text).to.eql(task._id); + expect(task.tags).to.eql([]); + expect(task.value).to.eql(0); + expect(task.priority).to.eql(1); + expect(task.history).to.eql([]); + expect(task.completed).to.eql(false); + expect(task.streak).to.eql(0); + expect(task.repeat).to.eql({ + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + su: true, + }); + expect(task.frequency).to.eql('weekly'); + expect(task.startDate).to.exist; + }); + + it('applies defaults a reward', () => { + let task = taskDefaults({ type: 'reward' }); + expect(task.type).to.eql('reward'); + expect(task._id).to.exist; + expect(task.text).to.eql(task._id); + expect(task.tags).to.eql([]); + expect(task.value).to.eql(10); + expect(task.priority).to.eql(1); + }); + + it('applies defaults a todo', () => { + let task = taskDefaults({ type: 'todo' }); + expect(task.type).to.eql('todo'); + expect(task._id).to.exist; + expect(task.text).to.eql(task._id); + expect(task.tags).to.eql([]); + expect(task.value).to.eql(0); + expect(task.priority).to.eql(1); + expect(task.completed).to.eql(false); + }); +}); diff --git a/test/common/libs/updateStore.js b/test/common/libs/updateStore.js new file mode 100644 index 0000000000..97d00076af --- /dev/null +++ b/test/common/libs/updateStore.js @@ -0,0 +1,57 @@ +import shared from '../../../common'; +import { + generateUser, +} from '../../helpers/common.helper'; +import i18n from '../../../common/script/i18n'; + +describe('updateStore', () => { + context('returns a list of gear items available for purchase', () => { + let user = generateUser(); + user.items.gear.owned.armor_armoire_lunarArmor = false; // eslint-disable-line camelcase + user.contributor.level = 2; + user.purchased.plan.mysteryItems = ['armor_mystery_201402']; + user.items.gear.owned.armor_mystery_201402 = false; // eslint-disable-line camelcase + + let list = shared.updateStore(user); + + it('contains the first item not purchased for each gear type', () => { + expect(_.find(list, item => { + return item.text() === i18n.t('armorWarrior1Text'); + })).to.exist; + + expect(_.find(list, item => { + return item.text() === i18n.t('armorWarrior2Text'); + })).to.not.exist; + }); + + it('contains mystery items the user can own', () => { + expect(_.find(list, item => { + return item.text() === i18n.t('armorMystery201402Text'); + })).to.exist; + + expect(_.find(list, item => { + return item.text() === i18n.t('armorMystery201403Text'); + })).to.not.exist; + }); + + it('contains special items the user can own', () => { + expect(_.find(list, item => { + return item.text() === i18n.t('armorSpecial1Text'); + })).to.exist; + + expect(_.find(list, item => { + return item.text() === i18n.t('headSpecial1Text'); + })).to.not.exist; + }); + + it('contains armoire items the user can own', () => { + expect(_.find(list, item => { + return item.text() === i18n.t('armorArmoireLunarArmorText'); + })).to.exist; + + expect(_.find(list, item => { + return item.text() === i18n.t('armorArmoireGladiatorArmorText'); + })).to.not.exist; + }); + }); +}); diff --git a/test/common/ops/addPushDevice.js b/test/common/ops/addPushDevice.js new file mode 100644 index 0000000000..535d288384 --- /dev/null +++ b/test/common/ops/addPushDevice.js @@ -0,0 +1,59 @@ +import addPushDevice from '../../../common/script/ops/addPushDevice'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, + BadRequest, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.addPushDevice', () => { + let user; + let regId = '10'; + let type = 'someRandomType'; + + beforeEach(() => { + user = generateUser(); + user.stats.hp = 0; + }); + + it('returns an error when regId is not provided', (done) => { + try { + addPushDevice(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('regIdRequired')); + done(); + } + }); + + it('returns an error when type is not provided', (done) => { + try { + addPushDevice(user, {body: {regId}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('typeRequired')); + done(); + } + }); + + it('adds a push device', () => { + let [, message] = addPushDevice(user, {body: {regId, type}}); + + expect(message).to.equal(i18n.t('pushDeviceAdded')); + expect(user.pushDevices[0].type).to.equal(type); + expect(user.pushDevices[0].regId).to.equal(regId); + }); + + it('does not a push device twice', (done) => { + try { + addPushDevice(user, {body: {regId, type}}); + addPushDevice(user, {body: {regId, type}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('pushDeviceAlreadyAdded')); + done(); + } + }); +}); diff --git a/test/common/ops/addTask.js b/test/common/ops/addTask.js new file mode 100644 index 0000000000..cb207036db --- /dev/null +++ b/test/common/ops/addTask.js @@ -0,0 +1,139 @@ +import addTask from '../../../common/script/ops/addTask'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.addTask', () => { + let user; + + beforeEach(() => { + user = generateUser(); + user.habits = []; + user.todos = []; + user.dailys = []; + user.rewards = []; + }); + + it('adds an habit', () => { + let habit = addTask(user, { + body: { + type: 'habit', + text: 'habit', + down: false, + }, + }); + + expect(user.tasksOrder.habits).to.eql([ + habit._id, + ]); + expect(habit._id).to.be.a('string'); + expect(habit.text).to.equal('habit'); + expect(habit.type).to.equal('habit'); + expect(habit.up).to.equal(true); + expect(habit.down).to.equal(false); + expect(habit.history).to.eql([]); + expect(habit.checklist).to.not.exists; + }); + + it('adds an habtit when type is invalid', () => { + let habit = addTask(user, { + body: { + type: 'invalid', + text: 'habit', + down: false, + }, + }); + + expect(user.tasksOrder.habits).to.eql([ + habit._id, + ]); + expect(habit._id).to.be.a('string'); + expect(habit.text).to.equal('habit'); + expect(habit.type).to.equal('habit'); + expect(habit.up).to.equal(true); + expect(habit.down).to.equal(false); + expect(habit.history).to.eql([]); + expect(habit.checklist).to.not.exists; + }); + + it('adds a daily', () => { + let daily = addTask(user, { + body: { + type: 'daily', + text: 'daily', + }, + }); + + expect(user.tasksOrder.dailys).to.eql([ + daily._id, + ]); + expect(daily._id).to.be.a('string'); + expect(daily.type).to.equal('daily'); + expect(daily.text).to.equal('daily'); + expect(daily.history).to.eql([]); + expect(daily.checklist).to.eql([]); + expect(daily.completed).to.be.false; + expect(daily.up).to.not.exists; + }); + + it('adds a todo', () => { + let todo = addTask(user, { + body: { + type: 'todo', + text: 'todo', + }, + }); + + expect(user.tasksOrder.todos).to.eql([ + todo._id, + ]); + expect(todo._id).to.be.a('string'); + expect(todo.type).to.equal('todo'); + expect(todo.text).to.equal('todo'); + expect(todo.checklist).to.eql([]); + expect(todo.completed).to.be.false; + expect(todo.up).to.not.exists; + }); + + it('adds a reward', () => { + let reward = addTask(user, { + body: { + type: 'reward', + text: 'reward', + }, + }); + + expect(user.tasksOrder.rewards).to.eql([ + reward._id, + ]); + expect(reward._id).to.be.a('string'); + expect(reward.type).to.equal('reward'); + expect(reward.text).to.equal('reward'); + expect(reward.value).to.equal(10); + expect(reward.up).to.not.exists; + }); + + context('respects preferences', () => { + it('true', () => { + user.preferences.newTaskEdit = true; + user.preferences.tagsCollapsed = true; + user.preferences.advancedCollapsed = false; + let task = addTask(user); + + expect(task._editing).to.be.true; + expect(task._tags).to.be.true; + expect(task._advanced).to.be.true; + }); + + it('false', () => { + user.preferences.newTaskEdit = false; + user.preferences.tagsCollapsed = false; + user.preferences.advancedCollapsed = true; + let task = addTask(user); + + expect(task._editing).to.not.exists; + expect(task._tags).to.not.exists; + expect(task._advanced).to.not.exists; + }); + }); +}); diff --git a/test/common/ops/addWebhook.test.js b/test/common/ops/addWebhook.test.js new file mode 100644 index 0000000000..11d26e622b --- /dev/null +++ b/test/common/ops/addWebhook.test.js @@ -0,0 +1,57 @@ +import addWebhook from '../../../common/script/ops/addWebhook'; +import { + BadRequest, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.addWebhook', () => { + let user; + let req; + + beforeEach(() => { + user = generateUser(); + req = { body: { + enabled: true, + url: 'http://some-url.com', + } }; + }); + + context('adds webhook', () => { + it('validates req.body.url', (done) => { + delete req.body.url; + try { + addWebhook(user, req); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidUrl')); + done(); + } + }); + + it('validates req.body.enabled', (done) => { + delete req.body.enabled; + try { + addWebhook(user, req); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidEnabled')); + done(); + } + }); + + it('calls marksModified()', () => { + user.markModified = sinon.spy(); + addWebhook(user, req); + expect(user.markModified.called).to.eql(true); + }); + + it('succeeds', () => { + expect(user.preferences.webhooks).to.eql({}); + addWebhook(user, req); + expect(user.preferences.webhooks).to.not.eql({}); + }); + }); +}); diff --git a/test/common/ops/allocate.js b/test/common/ops/allocate.js new file mode 100644 index 0000000000..65f3c74fc9 --- /dev/null +++ b/test/common/ops/allocate.js @@ -0,0 +1,63 @@ +import allocate from '../../../common/script/ops/allocate'; +import { + BadRequest, + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.allocate', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('throws an error if an invalid attribute is supplied', (done) => { + try { + allocate(user, { + query: {stat: 'notValid'}, + }); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'notValid'})); + done(); + } + }); + + it('throws an error if the user doesn\'t have attribute points', (done) => { + try { + allocate(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughAttrPoints')); + done(); + } + }); + + it('defaults to the "str" attribute', () => { + expect(user.stats.str).to.equal(0); + user.stats.points = 1; + allocate(user); + expect(user.stats.str).to.equal(1); + }); + + it('allocates attribute points', () => { + expect(user.stats.con).to.equal(0); + user.stats.points = 1; + allocate(user, {query: {stat: 'con'}}); + expect(user.stats.con).to.equal(1); + expect(user.stats.points).to.equal(0); + }); + + it('increases mana when allocating to "int"', () => { + expect(user.stats.int).to.equal(0); + expect(user.stats.mp).to.equal(10); + user.stats.points = 1; + allocate(user, {query: {stat: 'int'}}); + expect(user.stats.int).to.equal(1); + expect(user.stats.mp).to.equal(11); + }); +}); diff --git a/test/common/ops/allocateNow.js b/test/common/ops/allocateNow.js new file mode 100644 index 0000000000..4fe473ec9e --- /dev/null +++ b/test/common/ops/allocateNow.js @@ -0,0 +1,30 @@ +import allocateNow from '../../../common/script/ops/allocateNow'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.allocateNow', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('auto allocates all points', () => { + user.stats.points = 5; + user.stats.int = 3; + user.stats.con = 9; + user.stats.per = 9; + user.stats.str = 9; + user.preferences.allocationMode = 'flat'; + + let [data] = allocateNow(user); + + expect(user.stats.points).to.equal(0); + expect(user.stats.con).to.equal(9); + expect(user.stats.int).to.equal(8); + expect(user.stats.per).to.equal(9); + expect(user.stats.str).to.equal(9); + expect(data).to.eql(user.stats); + }); +}); diff --git a/test/common/ops/blockUser.test.js b/test/common/ops/blockUser.test.js new file mode 100644 index 0000000000..950af25d0c --- /dev/null +++ b/test/common/ops/blockUser.test.js @@ -0,0 +1,44 @@ +import blockUser from '../../../common/script/ops/blockUser'; +import { + generateUser, +} from '../../helpers/common.helper'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.blockUser', () => { + let user; + let blockedUser; + let blockedUser2; + + beforeEach(() => { + blockedUser = generateUser(); + blockedUser2 = generateUser(); + user = generateUser(); + expect(user.inbox.blocks).to.eql([]); + }); + + it('validates uuid', (done) => { + try { + blockUser(user, { params: { uuid: 1 } }); + } catch (error) { + expect(error.message).to.eql(i18n.t('invalidUUID')); + done(); + } + }); + + it('blocks user', () => { + let [result] = blockUser(user, { params: { uuid: blockedUser._id } }); + expect(user.inbox.blocks).to.eql([blockedUser._id]); + expect(result).to.eql([blockedUser._id]); + [result] = blockUser(user, { params: { uuid: blockedUser2._id } }); + expect(user.inbox.blocks).to.eql([blockedUser._id, blockedUser2._id]); + expect(result).to.eql([blockedUser._id, blockedUser2._id]); + }); + + it('blocks, then unblocks user', () => { + blockUser(user, { params: { uuid: blockedUser._id } }); + expect(user.inbox.blocks).to.eql([blockedUser._id]); + let [result] = blockUser(user, { params: { uuid: blockedUser._id } }); + expect(user.inbox.blocks).to.eql([]); + expect(result).to.eql([]); + }); +}); diff --git a/test/common/ops/buy.js b/test/common/ops/buy.js new file mode 100644 index 0000000000..9e05cc5225 --- /dev/null +++ b/test/common/ops/buy.js @@ -0,0 +1,61 @@ +/* eslint-disable camelcase */ +import { + generateUser, +} from '../../helpers/common.helper'; +import buy from '../../../common/script/ops/buy'; +import { + BadRequest, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buy', () => { + let user; + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + equipped: { + weapon_warrior_0: true, + }, + }, + }, + stats: { gp: 200 }, + }); + }); + + it('returns error when key is not provided', (done) => { + try { + buy(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('missingKeyParam')); + done(); + } + }); + + it('recovers 15 hp', () => { + user.stats.hp = 30; + buy(user, {params: {key: 'potion'}}); + expect(user.stats.hp).to.eql(45); + }); + + it('adds equipment to inventory', () => { + user.stats.gp = 31; + buy(user, {params: {key: 'armor_warrior_1'}}); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + armor_warrior_1: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + }); +}); diff --git a/test/common/ops/buyArmoire.js b/test/common/ops/buyArmoire.js new file mode 100644 index 0000000000..cea6d7abde --- /dev/null +++ b/test/common/ops/buyArmoire.js @@ -0,0 +1,205 @@ +/* eslint-disable camelcase */ + +import sinon from 'sinon'; // eslint-disable-line no-shadow +import { + generateUser, +} from '../../helpers/common.helper'; +import count from '../../../common/script/count'; +import buyArmoire from '../../../common/script/ops/buyArmoire'; +import shared from '../../../common/script'; +import content from '../../../common/script/content/index'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buyArmoire', () => { + let user; + let YIELD_EQUIPMENT = 0.5; + let YIELD_FOOD = 0.7; + let YIELD_EXP = 0.9; + + let fullArmoire = {}; + + _(content.gearTypes).each((type) => { + _(content.gear.tree[type].armoire).each((gearObject) => { + let armoireKey = gearObject.key; + + fullArmoire[armoireKey] = true; + }).value(); + }).value(); + + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + equipped: { + weapon_warrior_0: true, + }, + }, + }, + stats: { gp: 200 }, + }); + + user.achievements.ultimateGearSets = { rogue: true }; + user.flags.armoireOpened = true; + user.stats.exp = 0; + user.items.food = {}; + + sinon.stub(shared.fns, 'randomVal'); + sinon.stub(shared.fns, 'predictableRandom'); + }); + + afterEach(() => { + shared.fns.randomVal.restore(); + shared.fns.predictableRandom.restore(); + }); + + context('failure conditions', () => { + it('does not open if user does not have enough gold', (done) => { + shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.stats.gp = 50; + + try { + buyArmoire(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + done(); + } + }); + + it('does not open without Ultimate Gear achievement', (done) => { + shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false}; + + try { + buyArmoire(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('cannotBuyItem')); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + done(); + } + }); + }); + + context('non-gear awards', () => { + // Skipped because can't stub predictableRandom correctly + xit('gives Experience', () => { + shared.fns.predictableRandom.returns(YIELD_EXP); + + buyArmoire(user); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(46); + expect(user.stats.gp).to.eql(100); + }); + + // Skipped because can't stub predictableRandom correctly + xit('gives food', () => { + let honey = content.food.Honey; + + shared.fns.randomVal.returns(honey); + shared.fns.predictableRandom.returns(YIELD_FOOD); + + buyArmoire(user); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.eql({Honey: 1}); + expect(user.stats.exp).to.eql(0); + expect(user.stats.gp).to.eql(100); + }); + + // Skipped because can't stub predictableRandom correctly + xit('does not give equipment if all equipment has been found', () => { + shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.items.gear.owned = fullArmoire; + user.stats.gp = 150; + + buyArmoire(user); + + expect(user.items.gear.owned).to.eql(fullArmoire); + let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(0); + + expect(user.stats.exp).to.eql(30); + expect(user.stats.gp).to.eql(50); + }); + }); + + context('gear awards', () => { + beforeEach(() => { + let shield = content.gear.tree.shield.armoire.gladiatorShield; + + shared.fns.randomVal.returns(shield); + }); + + // Skipped because can't stub predictableRandom correctly + xit('always drops equipment the first time', () => { + delete user.flags.armoireOpened; + shared.fns.predictableRandom.returns(YIELD_EXP); + + buyArmoire(user); + + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + shield_armoire_gladiatorShield: true, + }); + + let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 1); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + expect(user.stats.gp).to.eql(100); + }); + + // Skipped because can't stub predictableRandom correctly + xit('gives more equipment', () => { + shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.items.gear.owned = { + weapon_warrior_0: true, + head_armoire_hornedIronHelm: true, + }; + user.stats.gp = 200; + + buyArmoire(user); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true}); + let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 2); + expect(user.stats.gp).to.eql(100); + }); + }); +}); diff --git a/test/common/ops/buyGear.js b/test/common/ops/buyGear.js new file mode 100644 index 0000000000..4d1213e80d --- /dev/null +++ b/test/common/ops/buyGear.js @@ -0,0 +1,142 @@ +/* eslint-disable camelcase */ + +import sinon from 'sinon'; // eslint-disable-line no-shadow +import { + generateUser, +} from '../../helpers/common.helper'; +import buyGear from '../../../common/script/ops/buyGear'; +import shared from '../../../common/script'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buyGear', () => { + let user; + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + equipped: { + weapon_warrior_0: true, + }, + }, + }, + stats: { gp: 200 }, + }); + + sinon.stub(shared.fns, 'randomVal'); + sinon.stub(shared.fns, 'predictableRandom'); + }); + + afterEach(() => { + shared.fns.randomVal.restore(); + shared.fns.predictableRandom.restore(); + }); + + context('Gear', () => { + it('adds equipment to inventory', () => { + user.stats.gp = 31; + + buyGear(user, {params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + armor_warrior_1: true, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_whiteTopFrame: true, + eyewear_special_yellowTopFrame: true, + }); + }); + + it('deducts gold from user', () => { + user.stats.gp = 31; + + buyGear(user, {params: {key: 'armor_warrior_1'}}); + + expect(user.stats.gp).to.eql(1); + }); + + it('auto equips equipment if user has auto-equip preference turned on', () => { + user.stats.gp = 31; + user.preferences.autoEquip = true; + + buyGear(user, {params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1'); + }); + + it('buyGears equipment but does not auto-equip', () => { + user.stats.gp = 31; + user.preferences.autoEquip = false; + + buyGear(user, {params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.equipped.property).to.not.equal('armor_warrior_1'); + }); + + it('does not buyGear equipment twice', (done) => { + user.stats.gp = 62; + buyGear(user, {params: {key: 'armor_warrior_1'}}); + + try { + buyGear(user, {params: {key: 'armor_warrior_1'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('equipmentAlreadyOwned')); + done(); + } + }); + + // TODO after user.ops.equip is done + xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => { + user.stats.gp = 100; + user.preferences.autoEquip = true; + buyGear(user, {params: {key: 'shield_warrior_1'}}); + user.ops.equip({params: {key: 'shield_warrior_1'}}); + buyGear(user, {params: {key: 'weapon_warrior_1'}}); + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + + buyGear(user, {params: {key: 'weapon_wizard_1'}}); + + expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0'); + expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1'); + }); + + // TODO after user.ops.equip is done + xit('buyGears two-handed equipment but does not automatically remove sword or shield', () => { + user.stats.gp = 100; + user.preferences.autoEquip = false; + buyGear(user, {params: {key: 'shield_warrior_1'}}); + user.ops.equip({params: {key: 'shield_warrior_1'}}); + buyGear(user, {params: {key: 'weapon_warrior_1'}}); + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + + buyGear(user, {params: {key: 'weapon_wizard_1'}}); + + expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1'); + expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1'); + }); + + it('does not buyGear equipment without enough Gold', (done) => { + user.stats.gp = 20; + + try { + buyGear(user, {params: {key: 'armor_warrior_1'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + expect(user.items.gear.owned).to.not.have.property('armor_warrior_1'); + done(); + } + }); + }); +}); diff --git a/test/common/ops/buyHealthPotion.js b/test/common/ops/buyHealthPotion.js new file mode 100644 index 0000000000..6a70f71b62 --- /dev/null +++ b/test/common/ops/buyHealthPotion.js @@ -0,0 +1,65 @@ +/* eslint-disable camelcase */ +import { + generateUser, +} from '../../helpers/common.helper'; +import buyHealthPotion from '../../../common/script/ops/buyHealthPotion'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buyHealthPotion', () => { + let user; + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + equipped: { + weapon_warrior_0: true, + }, + }, + }, + stats: { gp: 200 }, + }); + }); + + context('Potion', () => { + it('recovers 15 hp', () => { + user.stats.hp = 30; + buyHealthPotion(user); + expect(user.stats.hp).to.eql(45); + }); + + it('does not increase hp above 50', () => { + user.stats.hp = 45; + buyHealthPotion(user); + expect(user.stats.hp).to.eql(50); + }); + + it('deducts 25 gp', () => { + user.stats.hp = 45; + buyHealthPotion(user); + + expect(user.stats.gp).to.eql(175); + }); + + it('does not purchase if not enough gp', (done) => { + user.stats.hp = 45; + user.stats.gp = 5; + try { + buyHealthPotion(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + expect(user.stats.hp).to.eql(45); + expect(user.stats.gp).to.eql(5); + + done(); + } + }); + }); +}); diff --git a/test/common/ops/buyMysterySet.js b/test/common/ops/buyMysterySet.js new file mode 100644 index 0000000000..8e523a5a0a --- /dev/null +++ b/test/common/ops/buyMysterySet.js @@ -0,0 +1,76 @@ +/* eslint-disable camelcase */ + +import { + generateUser, +} from '../../helpers/common.helper'; +import buyMysterySet from '../../../common/script/ops/buyMysterySet'; +import { + NotAuthorized, + NotFound, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buyMysterySet', () => { + let user; + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + }, + }, + }); + }); + + context('Mystery Sets', () => { + context('failure conditions', () => { + it('does not grant mystery sets without Mystic Hourglasses', (done) => { + try { + buyMysterySet(user, {params: {key: '201501'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); + expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true); + done(); + } + }); + + it('does not grant mystery set that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.gear.owned = { + weapon_warrior_0: true, + weapon_mystery_301404: true, + armor_mystery_301404: true, + head_mystery_301404: true, + eyewear_mystery_301404: true, + }; + + try { + buyMysterySet(user, {params: {key: '301404'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.eql(i18n.t('mysterySetNotFound')); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + } + }); + }); + + context('successful purchases', () => { + it('buys Steampunk Accessories Set', () => { + user.purchased.plan.consecutive.trinkets = 1; + buyMysterySet(user, {params: {key: '301404'}}); + + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true); + expect(user.items.gear.owned).to.have.property('weapon_mystery_301404', true); + expect(user.items.gear.owned).to.have.property('armor_mystery_301404', true); + expect(user.items.gear.owned).to.have.property('head_mystery_301404', true); + expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true); + }); + }); + }); +}); diff --git a/test/common/ops/buyQuest.js b/test/common/ops/buyQuest.js new file mode 100644 index 0000000000..aab691f64e --- /dev/null +++ b/test/common/ops/buyQuest.js @@ -0,0 +1,81 @@ +import { + generateUser, +} from '../../helpers/common.helper'; +import buyQuest from '../../../common/script/ops/buyQuest'; +import { + NotAuthorized, + NotFound, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.buyQuest', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('buys a Quest scroll', () => { + user.stats.gp = 205; + buyQuest(user, { + params: { + key: 'dilatoryDistress1', + }, + }); + expect(user.items.quests).to.eql({ + dilatoryDistress1: 1, + }); + expect(user.stats.gp).to.equal(5); + }); + + it('does not buy Quests without enough Gold', (done) => { + user.stats.gp = 1; + try { + buyQuest(user, { + params: { + key: 'dilatoryDistress1', + }, + }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + expect(user.items.quests).to.eql({}); + expect(user.stats.gp).to.equal(1); + done(); + } + }); + + it('does not buy nonexistent Quests', (done) => { + user.stats.gp = 9999; + try { + buyQuest(user, { + params: { + key: 'snarfblatter', + }, + }); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('questNotFound', {key: 'snarfblatter'})); + expect(user.items.quests).to.eql({}); + expect(user.stats.gp).to.equal(9999); + done(); + } + }); + + it('does not buy Gem-premium Quests', (done) => { + user.stats.gp = 9999; + try { + buyQuest(user, { + params: { + key: 'kraken', + }, + }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('questNotGoldPurchasable', {key: 'kraken'})); + expect(user.items.quests).to.eql({}); + expect(user.stats.gp).to.equal(9999); + done(); + } + }); +}); diff --git a/test/common/ops/buySpecialSpell.js b/test/common/ops/buySpecialSpell.js new file mode 100644 index 0000000000..249f4bc8a6 --- /dev/null +++ b/test/common/ops/buySpecialSpell.js @@ -0,0 +1,79 @@ +import buySpecialSpell from '../../../common/script/ops/buySpecialSpell'; +import { + BadRequest, + NotFound, + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import content from '../../../common/script/content/index'; + +describe('shared.ops.buySpecialSpell', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('throws an error if params.key is missing', (done) => { + try { + buySpecialSpell(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('missingKeyParam')); + done(); + } + }); + + it('throws an error if the spell doesn\'t exists', (done) => { + try { + buySpecialSpell(user, { + params: { + key: 'notExisting', + }, + }); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('spellNotFound', {spellId: 'notExisting'})); + done(); + } + }); + + it('throws an error if the user doesn\'t have enough gold', (done) => { + user.stats.gp = 1; + try { + buySpecialSpell(user, { + params: { + key: 'thankyou', + }, + }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + done(); + } + }); + + it('buys an item', () => { + user.stats.gp = 11; + let item = content.special.thankyou; + + let [data, message] = buySpecialSpell(user, { + params: { + key: 'thankyou', + }, + }); + + expect(user.stats.gp).to.equal(1); + expect(user.items.special.thankyou).to.equal(1); + expect(data).to.eql({ + items: user.items, + stats: user.stats, + }); + expect(message).to.equal(i18n.t('messageBought', { + itemText: item.text(), + })); + }); +}); diff --git a/test/common/ops/changeClass.js b/test/common/ops/changeClass.js new file mode 100644 index 0000000000..ae4a180178 --- /dev/null +++ b/test/common/ops/changeClass.js @@ -0,0 +1,139 @@ +import changeClass from '../../../common/script/ops/changeClass'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.changeClass', () => { + let user; + + beforeEach(() => { + user = generateUser(); + user.stats.lvl = 11; + user.stats.flagSelected = false; + }); + + it('user is not level 10', (done) => { + user.stats.lvl = 9; + try { + changeClass(user, {query: {class: 'rogue'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('lvl10ChangeClass')); + done(); + } + }); + + context('req.query.class is a valid class', () => { + it('errors if user.stats.flagSelected is true and user.balance < 0.75', (done) => { + user.flags.classSelected = true; + user.preferences.disableClasses = false; + user.balance = 0; + + try { + changeClass(user, {query: {class: 'rogue'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('changes class', () => { + user.stats.class = 'healer'; + user.items.gear.owned.armor_rogue_1 = true; // eslint-disable-line camelcase + + let [data] = changeClass(user, {query: {class: 'rogue'}}); + expect(data).to.eql({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + items: user.items, + }); + + expect(user.stats.class).to.equal('rogue'); + expect(user.flags.classSelected).to.be.true; + expect(user.items.gear.equipped.weapon).to.equal('weapon_rogue_0'); + expect(user.items.gear.owned.weapon_rogue_0).to.be.true; + expect(user.items.gear.equipped.armor).to.equal('armor_rogue_1'); + expect(user.items.gear.owned.armor_rogue_1).to.be.true; + expect(user.items.gear.equipped.shield).to.equal('shield_rogue_0'); + expect(user.items.gear.owned.shield_rogue_0).to.be.true; + expect(user.items.gear.equipped.head).to.equal('head_base_0'); + }); + }); + + context('req.query.class is missing or user.stats.flagSelected is true', () => { + it('has user.preferences.disableClasses === true', () => { + user.balance = 1; + user.preferences.disableClasses = true; + user.preferences.autoAllocate = true; + user.stats.points = 45; + user.stats.str = 1; + user.stats.con = 2; + user.stats.per = 3; + user.stats.int = 4; + user.flags.classSelected = true; + + let [data] = changeClass(user); + expect(data).to.eql({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + items: user.items, + }); + + expect(user.preferences.disableClasses).to.be.false; + expect(user.preferences.autoAllocate).to.be.false; + expect(user.balance).to.equal(1); + expect(user.stats.str).to.equal(0); + expect(user.stats.con).to.equal(0); + expect(user.stats.per).to.equal(0); + expect(user.stats.int).to.equal(0); + expect(user.stats.points).to.equal(11); + expect(user.flags.classSelected).to.equal(false); + }); + + context('has user.preferences.disableClasses !== true', () => { + it('and less than 3 gems', (done) => { + user.balance = 0.5; + try { + changeClass(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('and at least 3 gems', () => { + user.balance = 1; + user.stats.points = 45; + user.stats.str = 1; + user.stats.con = 2; + user.stats.per = 3; + user.stats.int = 4; + user.flags.classSelected = true; + + let [data] = changeClass(user); + expect(data).to.eql({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + items: user.items, + }); + + expect(user.balance).to.equal(0.25); + expect(user.stats.str).to.equal(0); + expect(user.stats.con).to.equal(0); + expect(user.stats.per).to.equal(0); + expect(user.stats.int).to.equal(0); + expect(user.stats.points).to.equal(11); + expect(user.flags.classSelected).to.equal(false); + }); + }); + }); +}); diff --git a/test/common/ops/clearCompleted.js b/test/common/ops/clearCompleted.js new file mode 100644 index 0000000000..4dceb3c1f0 --- /dev/null +++ b/test/common/ops/clearCompleted.js @@ -0,0 +1,37 @@ +import clearCompleted from '../../../common/script/ops/clearCompleted'; +import { + generateTodo, +} from '../../helpers/common.helper'; + +describe('shared.ops.clearCompleted', () => { + it('clear completed todos', () => { + let todos = [ + generateTodo({text: 'todo'}), + generateTodo({ + text: 'done', + completed: true, + }), + generateTodo({ + text: 'done chellenge broken', + completed: true, + challenge: { + id: 123, + broken: 'TASK_DELETED', + }, + }), + generateTodo({ + text: 'done chellenge not broken', + completed: true, + challenge: { + id: 123, + }, + }), + ]; + + clearCompleted(todos); + + expect(todos.length).to.equal(2); + expect(todos[0].text).to.equal('todo'); + expect(todos[1].text).to.equal('done chellenge not broken'); + }); +}); diff --git a/test/common/ops/clearPMs.test.js b/test/common/ops/clearPMs.test.js new file mode 100644 index 0000000000..cf1408e5a6 --- /dev/null +++ b/test/common/ops/clearPMs.test.js @@ -0,0 +1,20 @@ +import clearPMs from '../../../common/script/ops/clearPMs'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.clearPMs', () => { + let user; + + beforeEach(() => { + user = generateUser(); + user.inbox.messages = { first: 'message', second: 'message' }; + }); + + it('clears messages', () => { + expect(user.inbox.messages).to.not.eql({}); + let [result] = clearPMs(user); + expect(user.inbox.messages).to.eql({}); + expect(result).to.eql({}); + }); +}); diff --git a/test/common/ops/deletePM.test.js b/test/common/ops/deletePM.test.js new file mode 100644 index 0000000000..109595eca9 --- /dev/null +++ b/test/common/ops/deletePM.test.js @@ -0,0 +1,20 @@ +import deletePM from '../../../common/script/ops/deletePM'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.deletePM', () => { + let user; + + beforeEach(() => { + user = generateUser(); + user.inbox.messages = { first: 'message', second: 'message' }; + }); + + it('delete message', () => { + expect(user.inbox.messages).to.not.eql({ second: 'message' }); + let [response] = deletePM(user, { params: { id: 'first' } }); + expect(user.inbox.messages).to.eql({ second: 'message' }); + expect(response).to.eql({ second: 'message' }); + }); +}); diff --git a/test/common/ops/deleteWebhook.test.js b/test/common/ops/deleteWebhook.test.js new file mode 100644 index 0000000000..8e27a09e3e --- /dev/null +++ b/test/common/ops/deleteWebhook.test.js @@ -0,0 +1,21 @@ +import deleteWebhook from '../../../common/script/ops/deleteWebhook'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.deleteWebhook', () => { + let user; + let req; + + beforeEach(() => { + user = generateUser(); + req = { params: { id: 'some-id' } }; + }); + + it('succeeds', () => { + user.preferences.webhooks = { 'some-id': {}, 'another-id': {} }; + let [data] = deleteWebhook(user, req); + expect(user.preferences.webhooks).to.eql({'another-id': {}}); + expect(data).to.equal(user.preferences.webhooks); + }); +}); diff --git a/test/common/ops/disableClasses.js b/test/common/ops/disableClasses.js new file mode 100644 index 0000000000..81ac1a9792 --- /dev/null +++ b/test/common/ops/disableClasses.js @@ -0,0 +1,35 @@ +import disableClasses from '../../../common/script/ops/disableClasses'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.disableClasses', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('disable classes', () => { + user.stats.lvl = 34; + user.stats.str = 45; + user.stats.class = 'healer'; + user.preferences.disableClasses = false; + user.preferences.autoAllocate = false; + user.stats.points = 2; + + let [data] = disableClasses(user); + expect(data).to.eql({ + preferences: user.preferences, + stats: user.stats, + flags: user.flags, + }); + + expect(user.stats.class).to.equal('warrior'); + expect(user.flags.classSelected).to.equal(true); + expect(user.preferences.disableClasses).to.equal(true); + expect(user.preferences.autoAllocate).to.equal(true); + expect(user.stats.str).to.equal(34); + expect(user.stats.points).to.equal(0); + }); +}); diff --git a/test/common/ops/equip.js b/test/common/ops/equip.js new file mode 100644 index 0000000000..8641aa102e --- /dev/null +++ b/test/common/ops/equip.js @@ -0,0 +1,87 @@ +/* eslint-disable camelcase */ +import equip from '../../../common/script/ops/equip'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import content from '../../../common/script/content/index'; + +describe('shared.ops.equip', () => { + let user; + + beforeEach(() => { + user = generateUser({ + items: { + gear: { + owned: { + weapon_warrior_0: true, + weapon_warrior_1: true, + weapon_warrior_2: true, + weapon_wizard_1: true, + weapon_wizard_2: true, + shield_base_0: true, + shield_warrior_1: true, + }, + equipped: { + weapon: 'weapon_warrior_0', + shield: 'shield_base_0', + }, + }, + }, + stats: {gp: 200}, + }); + }); + + context('Gear', () => { + it('should not send a message if a weapon is equipped while only having zero or one weapons equipped', () => { + equip(user, {params: {key: 'weapon_warrior_1'}}); + + // one-handed to one-handed + let [, message] = equip(user, {params: {key: 'weapon_warrior_2'}}); + expect(message).to.not.exists; + + // one-handed to two-handed + [, message] = equip(user, {params: {key: 'weapon_wizard_1'}}); + expect(message).to.not.exists; + + // two-handed to two-handed + [, message] = equip(user, {params: {key: 'weapon_wizard_2'}}); + expect(message).to.not.exists; + + // two-handed to one-handed + [, message] = equip(user, {params: {key: 'weapon_warrior_2'}}); + expect(message).to.not.exists; + }); + + it('should send messages if equipping a two-hander causes the off-hander to be unequipped', () => { + equip(user, {params: {key: 'weapon_warrior_1'}}); + equip(user, {params: {key: 'shield_warrior_1'}}); + + // equipping two-hander + let [data, message] = equip(user, {params: {key: 'weapon_wizard_1'}}); + let weapon = content.gear.flat.weapon_wizard_1; + let item = content.gear.flat.shield_warrior_1; + + let res = {data, message}; + expect(res).to.eql({ + message: i18n.t('messageTwoHandedEquip', {twoHandedText: weapon.text(), offHandedText: item.text()}), + data: user.items, + }); + }); + + it('should send messages if equipping an off-hand item causes a two-handed weapon to be unequipped', () => { + // equipping two-hander + equip(user, {params: {key: 'weapon_wizard_1'}}); + let weapon = content.gear.flat.weapon_wizard_1; + let shield = content.gear.flat.shield_warrior_1; + + let [data, message] = equip(user, {params: {key: 'shield_warrior_1'}}); + + let res = {data, message}; + expect(res).to.eql({ + message: i18n.t('messageTwoHandedUnequip', {twoHandedText: weapon.text(), offHandedText: shield.text()}), + data: user.items, + }); + }); + }); +}); diff --git a/test/common/ops/feed.js b/test/common/ops/feed.js new file mode 100644 index 0000000000..0b726f5ec7 --- /dev/null +++ b/test/common/ops/feed.js @@ -0,0 +1,216 @@ +import feed from '../../../common/script/ops/feed'; +import content from '../../../common/script/content'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.feed', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + context('failure conditions', () => { + it('does not allow feeding without specifying pet and food', (done) => { + try { + feed(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('missingPetFoodFeed')); + done(); + } + }); + + it('does not allow feeding if pet name format is invalid', (done) => { + try { + feed(user, {params: {pet: 'invalid', food: 'food'}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidPetName')); + done(); + } + }); + + it('does not allow feeding if food does not exists', (done) => { + try { + feed(user, {params: {pet: 'valid-pet', food: 'invalid food name'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('messageFoodNotFound')); + done(); + } + }); + + it('does not allow feeding if pet is not owned', (done) => { + try { + feed(user, {params: {pet: 'not-owned', food: 'Meat'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('messagePetNotFound')); + done(); + } + }); + + it('does not allow feeding if food is not owned', (done) => { + user.items.pets['Wolf-Base'] = 5; + try { + feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('messageFoodNotFound')); + done(); + } + }); + + it('does not allow feeding of special pets', (done) => { + user.items.pets['Wolf-Veteran'] = 5; + user.items.food.Meat = 1; + try { + feed(user, {params: {pet: 'Wolf-Veteran', food: 'Meat'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageCannotFeedPet')); + done(); + } + }); + + it('does not allow feeding of mounts', (done) => { + user.items.pets['Wolf-Base'] = -1; + user.items.mounts['Wolf-Base'] = true; + user.items.food.Meat = 1; + try { + feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageAlreadyMount')); + done(); + } + }); + }); + + context('successful feeding', () => { + it('evolves the pet if the food is a Saddle', () => { + user.items.pets['Wolf-Base'] = 5; + user.items.food.Saddle = 2; + user.items.currentPet = 'Wolf-Base'; + let [egg, potion] = 'Wolf-Base'.split('-'); + + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Saddle'}}); + expect(data).to.eql(user.items.pets['Wolf-Base']); + expect(message).to.eql(i18n.t('messageEvolve', { + egg: i18n.t('petName', { + potion: potionText, + egg: eggText, + }), + })); + + expect(user.items.food.Saddle).to.equal(1); + expect(user.items.pets['Wolf-Base']).to.equal(-1); + expect(user.items.mounts['Wolf-Base']).to.equal(true); + expect(user.items.currentPet).to.equal(''); + }); + + it('enjoys the food', () => { + user.items.pets['Wolf-Base'] = 5; + user.items.food.Meat = 2; + + let food = content.food.Meat; + let [egg, potion] = 'Wolf-Base'.split('-'); + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); + expect(data).to.eql(user.items.pets['Wolf-Base']); + expect(message).to.eql(i18n.t('messageLikesFood', { + egg: i18n.t('petName', { + potion: potionText, + egg: eggText, + }), + foodText: food.text(), + })); + + expect(user.items.food.Meat).to.equal(1); + expect(user.items.pets['Wolf-Base']).to.equal(10); + }); + + it('enjoys the food (premium potion)', () => { + user.items.pets['Wolf-Spooky'] = 5; + user.items.food.Milk = 2; + + let food = content.food.Milk; + let [egg, potion] = 'Wolf-Spooky'.split('-'); + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let [data, message] = feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}}); + expect(data).to.eql(user.items.pets['Wolf-Spooky']); + expect(message).to.eql(i18n.t('messageLikesFood', { + egg: i18n.t('petName', { + potion: potionText, + egg: eggText, + }), + foodText: food.text(), + })); + + expect(user.items.food.Milk).to.equal(1); + expect(user.items.pets['Wolf-Spooky']).to.equal(10); + }); + + it('does not like the food', () => { + user.items.pets['Wolf-Base'] = 5; + user.items.food.Milk = 2; + + let food = content.food.Milk; + let [egg, potion] = 'Wolf-Base'.split('-'); + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Milk'}}); + expect(data).to.eql(user.items.pets['Wolf-Base']); + expect(message).to.eql(i18n.t('messageDontEnjoyFood', { + egg: i18n.t('petName', { + potion: potionText, + egg: eggText, + }), + foodText: food.text(), + })); + + expect(user.items.food.Milk).to.equal(1); + expect(user.items.pets['Wolf-Base']).to.equal(7); + }); + + it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50', () => { + user.items.pets['Wolf-Base'] = 49; + user.items.food.Milk = 2; + user.items.currentPet = 'Wolf-Base'; + + let [egg, potion] = 'Wolf-Base'.split('-'); + let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + + let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Milk'}}); + expect(data).to.eql(user.items.pets['Wolf-Base']); + expect(message).to.eql(i18n.t('messageEvolve', { + egg: i18n.t('petName', { + potion: potionText, + egg: eggText, + }), + })); + + expect(user.items.food.Milk).to.equal(1); + expect(user.items.pets['Wolf-Base']).to.equal(-1); + expect(user.items.mounts['Wolf-Base']).to.equal(true); + expect(user.items.currentPet).to.equal(''); + }); + }); +}); diff --git a/test/common/ops/hatch.js b/test/common/ops/hatch.js new file mode 100644 index 0000000000..87131db13b --- /dev/null +++ b/test/common/ops/hatch.js @@ -0,0 +1,147 @@ +import hatch from '../../../common/script/ops/hatch'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.hatch', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + context('Pet Hatching', () => { + context('failure conditions', () => { + it('does not allow hatching without specifying egg and potion', () => { + user.items.pets = {}; + try { + hatch(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('missingEggHatchingPotionHatch')); + expect(user.items.pets).to.be.empty; + } + }); + + it('does not allow hatching if user lacks specified egg', (done) => { + user.items.eggs.Wolf = 1; + user.items.hatchingPotions.Base = 1; + user.items.pets = {}; + try { + hatch(user, {params: {egg: 'Dragon', hatchingPotion: 'Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('messageMissingEggPotion')); + expect(user.items.pets).to.be.empty; + expect(user.items.eggs.Wolf).to.equal(1); + expect(user.items.hatchingPotions.Base).to.equal(1); + done(); + } + }); + + it('does not allow hatching if user lacks specified hatching potion', (done) => { + user.items.eggs.Wolf = 1; + user.items.hatchingPotions.Base = 1; + user.items.pets = {}; + try { + hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Golden'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('messageMissingEggPotion')); + expect(user.items.pets).to.be.empty; + expect(user.items.eggs.Wolf).to.equal(1); + expect(user.items.hatchingPotions.Base).to.equal(1); + done(); + } + }); + + it('does not allow hatching if user already owns target pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': 10}; + try { + hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageAlreadyPet')); + expect(user.items.pets).to.eql({'Wolf-Base': 10}); + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); + done(); + } + }); + + it('does not allow hatching quest pet egg using premium potion', (done) => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.items.pets = {}; + try { + hatch(user, {params: {egg: 'Cheetah', hatchingPotion: 'Spooky'}}); + } 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({Cheetah: 1}); + expect(user.items.hatchingPotions).to.eql({Spooky: 1}); + done(); + } + }); + }); + + context('successful hatching', () => { + it('hatches a basic pet', () => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {}; + let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); + expect(message).to.equal(i18n.t('messageHatched')); + expect(data).to.eql(user.items); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + }); + + it('hatches a quest pet', () => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {}; + let [data, message] = hatch(user, {params: {egg: 'Cheetah', hatchingPotion: 'Base'}}); + expect(message).to.equal(i18n.t('messageHatched')); + expect(data).to.eql(user.items); + expect(user.items.pets).to.eql({'Cheetah-Base': 5}); + expect(user.items.eggs).to.eql({Cheetah: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + }); + + it('hatches a premium pet', () => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.items.pets = {}; + let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}}); + expect(message).to.equal(i18n.t('messageHatched')); + expect(data).to.eql(user.items); + expect(user.items.pets).to.eql({'Wolf-Spooky': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Spooky: 0}); + }); + + it('hatches a pet previously raised to a mount', () => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': -1}; + let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); + expect(message).to.eql(i18n.t('messageHatched')); + expect(data).to.eql(user.items); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + }); + }); + }); +}); diff --git a/test/common/ops/hourglassPurchase.js b/test/common/ops/hourglassPurchase.js new file mode 100644 index 0000000000..98400f82b5 --- /dev/null +++ b/test/common/ops/hourglassPurchase.js @@ -0,0 +1,145 @@ +import hourglassPurchase from '../../../common/script/ops/hourglassPurchase'; +import { + BadRequest, + NotAuthorized, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import content from '../../../common/script/content/index'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('user.ops.hourglassPurchase', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + context('failure conditions', () => { + it('return error when key is not provided', (done) => { + try { + hourglassPurchase(user, {params: {}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.eql(i18n.t('missingKeyParam')); + done(); + } + }); + + it('returns error when type is not provided', (done) => { + try { + hourglassPurchase(user, {params: {key: 'Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.eql(i18n.t('missingTypeParam')); + done(); + } + }); + + it('returns error when inccorect type is provided', (done) => { + try { + hourglassPurchase(user, {params: {type: 'notAType', key: 'MantisShrimp-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()})); + done(); + } + }); + + it('does not grant to pets without Mystic Hourglasses', (done) => { + try { + hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); + done(); + } + }); + + it('does not grant to mounts without Mystic Hourglasses', (done) => { + try { + hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); + done(); + } + }); + + it('does not grant pet that is not part of the Time Travel Stable', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + + try { + hourglassPurchase(user, {params: {type: 'pets', key: 'Wolf-Veteran'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('notAllowedHourglass')); + done(); + } + }); + + it('does not grant mount that is not part of the Time Travel Stable', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + + try { + hourglassPurchase(user, {params: {type: 'mounts', key: 'Orca-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('notAllowedHourglass')); + done(); + } + }); + + it('does not grant pet that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.pets = { + 'MantisShrimp-Base': true, + }; + + try { + hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('petsAlreadyOwned')); + done(); + } + }); + + it('does not grant mount that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.mounts = { + 'MantisShrimp-Base': true, + }; + + try { + hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('mountsAlreadyOwned')); + done(); + } + }); + }); + + context('successful purchases', () => { + it('buys a pet', () => { + user.purchased.plan.consecutive.trinkets = 2; + + let [, message] = hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); + + expect(message).to.eql(i18n.t('hourglassPurchase')); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); + }); + + it('buys a mount', () => { + user.purchased.plan.consecutive.trinkets = 2; + + let [, message] = hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + expect(message).to.eql(i18n.t('hourglassPurchase')); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); + }); + }); +}); diff --git a/test/common/ops/openMysteryItem.js b/test/common/ops/openMysteryItem.js new file mode 100644 index 0000000000..712c032254 --- /dev/null +++ b/test/common/ops/openMysteryItem.js @@ -0,0 +1,38 @@ +import openMysteryItem from '../../../common/script/ops/openMysteryItem'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + BadRequest, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; + +describe('shared.ops.openMysteryItem', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('returns error when item key is empty', (done) => { + try { + openMysteryItem(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('mysteryItemIsEmpty')); + done(); + } + }); + + it('opens mystery item', () => { + let mysteryItemKey = 'eyewear_special_summerRogue'; + + user.purchased.plan.mysteryItems = [mysteryItemKey]; + + let [data, message] = openMysteryItem(user); + + expect(user.items.gear.owned[mysteryItemKey]).to.be.true; + expect(message).to.equal(i18n.t('mysteryItemOpened')); + expect(data).to.equal(user.items.gear.owned); + }); +}); diff --git a/test/common/ops/purchase.js b/test/common/ops/purchase.js new file mode 100644 index 0000000000..60227f21b2 --- /dev/null +++ b/test/common/ops/purchase.js @@ -0,0 +1,194 @@ +import purchase from '../../../common/script/ops/purchase'; +import planGemLimits from '../../../common/script/libs/planGemLimits'; +import { + BadRequest, + NotAuthorized, + NotFound, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.purchase', () => { + let user; + let goldPoints = 40; + let gemsBought = 40; + + before(() => { + user = generateUser({'stats.class': 'rogue'}); + }); + + context('failure conditions', () => { + it('returns an error when type is not provided', (done) => { + try { + purchase(user, {params: {}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('typeRequired')); + done(); + } + }); + + it('returns an error when key is not provided', (done) => { + try { + purchase(user, {params: {type: 'gems'}}); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('keyRequired')); + done(); + } + }); + + it('prevents unsubscribed user from buying gems', (done) => { + try { + purchase(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems')); + done(); + } + }); + + it('prevents user with not enough gold from buying gems', (done) => { + user.purchased.plan.customerId = 'customer-id'; + + try { + purchase(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); + done(); + } + }); + + it('prevents user that have reached the conversion cap from buying gems', (done) => { + user.stats.gp = goldPoints; + user.purchased.plan.gemsBought = gemsBought; + + try { + purchase(user, {params: {type: 'gems', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap})); + done(); + } + }); + + it('returns error when unknown type is provided', (done) => { + try { + purchase(user, {params: {type: 'randomType', key: 'gem'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('notAccteptedType')); + done(); + } + }); + + it('returns error when user attempts to purchase a piece of gear they own', (done) => { + user.items.gear.owned['shield_rogue_1'] = true; // eslint-disable-line dot-notation + + try { + purchase(user, {params: {type: 'gear', key: 'shield_rogue_1'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('alreadyHave')); + done(); + } + }); + + it('returns error when unknown item is requested', (done) => { + try { + purchase(user, {params: {type: 'gear', key: 'randomKey'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('contentKeyNotFound', {type: 'gear'})); + done(); + } + }); + + it('returns error when user does not have permission to buy an item', (done) => { + try { + purchase(user, {params: {type: 'gear', key: 'eyewear_mystery_301405'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('messageNotAvailable')); + done(); + } + }); + + it('returns error when user does not have enough gems to buy an item', (done) => { + try { + purchase(user, {params: {type: 'gear', key: 'headAccessory_special_wolfEars'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + }); + + context('successful purchase', () => { + let userGemAmount = 10; + + before(() => { + user.balance = userGemAmount; + user.stats.gp = goldPoints; + user.purchased.plan.gemsBought = 0; + }); + + it('purchases gems', () => { + let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}}); + + expect(message).to.equal(i18n.t('plusOneGem')); + expect(user.balance).to.equal(userGemAmount + 0.25); + expect(user.purchased.plan.gemsBought).to.equal(1); + expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); + }); + + it('purchases eggs', () => { + let type = 'eggs'; + let key = 'Wolf'; + + purchase(user, {params: {type, key}}); + + expect(user.items[type][key]).to.equal(1); + }); + + it('purchases hatchingPotions', () => { + let type = 'hatchingPotions'; + let key = 'Base'; + + purchase(user, {params: {type, key}}); + + expect(user.items[type][key]).to.equal(1); + }); + + it('purchases food', () => { + let type = 'food'; + let key = 'Meat'; + + purchase(user, {params: {type, key}}); + + expect(user.items[type][key]).to.equal(1); + }); + + it('purchases quests', () => { + let type = 'quests'; + let key = 'gryphon'; + + purchase(user, {params: {type, key}}); + + expect(user.items[type][key]).to.equal(1); + }); + + it('purchases gear', () => { + let type = 'gear'; + let key = 'headAccessory_special_tigerEars'; + + purchase(user, {params: {type, key}}); + + expect(user.items.gear.owned[key]).to.be.true; + }); + }); +}); diff --git a/test/common/ops/readCard.js b/test/common/ops/readCard.js new file mode 100644 index 0000000000..5d771ab0d6 --- /dev/null +++ b/test/common/ops/readCard.js @@ -0,0 +1,48 @@ +import readCard from '../../../common/script/ops/readCard'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + BadRequest, + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.readCard', () => { + let user; + let cardType = 'greeting'; + + beforeEach(() => { + user = generateUser(); + user.items.special[`${cardType}Received`] = [true]; + user.flags.cardReceived = true; + }); + + it('returns an error when cardType is not provided', (done) => { + try { + readCard(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('cardTypeRequired')); + done(); + } + }); + + it('returns an error when unknown cardType is provided', (done) => { + try { + readCard(user, {params: {cardType: 'randomCardType'}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('cardTypeNotAllowed')); + done(); + } + }); + + it('reads a card', () => { + let [, message] = readCard(user, {params: {cardType: 'greeting'}}); + + expect(message).to.equal(i18n.t('readCard', {cardType})); + expect(user.items.special[`${cardType}Received`]).to.be.empty; + expect(user.flags.cardReceived).to.be.false; + }); +}); diff --git a/test/common/ops/rebirth.js b/test/common/ops/rebirth.js new file mode 100644 index 0000000000..9916144669 --- /dev/null +++ b/test/common/ops/rebirth.js @@ -0,0 +1,236 @@ +import rebirth from '../../../common/script/ops/rebirth'; +import i18n from '../../../common/script/i18n'; +import { MAX_LEVEL } from '../../../common/script/constants'; +import { + generateUser, + generateHabit, + generateDaily, + generateTodo, + generateReward, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.rebirth', () => { + let user; + let animal = 'Wolf-Base'; + let userStats = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp']; + let tasks = []; + + beforeEach(() => { + user = generateUser(); + user.balance = 2; + tasks = [generateHabit(), generateDaily(), generateTodo(), generateReward()]; + }); + + it('returns an error when user balance is too low and user is less than max level', (done) => { + user.balance = 0; + + try { + rebirth(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('rebirths a user with enough gems', () => { + let [, message] = rebirth(user); + + expect(message).to.equal(i18n.t('rebirthComplete')); + }); + + it('rebirths a user with not enough gems but max level', () => { + user.balance = 0; + user.stats.lvl = MAX_LEVEL; + + let [, message] = rebirth(user); + + expect(message).to.equal(i18n.t('rebirthComplete')); + }); + + it('rebirths a user with not enough gems but more than max level', () => { + user.balance = 0; + user.stats.lvl = MAX_LEVEL + 1; + + let [, message] = rebirth(user); + + expect(message).to.equal(i18n.t('rebirthComplete')); + }); + + it('resets user\'s tasks values except for rewards to 0', () => { + tasks[0].value = 1; + tasks[1].value = 1; + tasks[2].value = 1; + tasks[3].value = 1; // Reward + + rebirth(user, tasks); + + expect(tasks[0].value).to.equal(0); + expect(tasks[1].value).to.equal(0); + expect(tasks[2].value).to.equal(0); + expect(tasks[3].value).to.equal(1); // Reward + }); + + it('resets user\'s daily streaks to 0', () => { + tasks[1].streak = 1; // Daily + + rebirth(user, tasks); + + expect(tasks[1].streak).to.equal(0); + }); + + it('resets a user\'s buffs', () => { + user.stats.buffs = {test: 'test'}; + + rebirth(user); + + expect(user.stats.buffs).to.be.empty; + }); + + it('resets a user\'s health points', () => { + user.stats.hp = 40; + + rebirth(user); + + expect(user.stats.hp).to.equal(50); + }); + + it('resets a user\'s class', () => { + user.stats.class = 'rouge'; + + rebirth(user); + + expect(user.stats.class).to.equal('warrior'); + }); + + it('resets a user\'s stats', () => { + user.stats.class = 'rouge'; + _.each(userStats, function setUsersStats (value) { + user.stats[value] = 10; + }); + + rebirth(user); + + _.each(userStats, function resetUserStats (value) { + user.stats[value] = 0; + }); + }); + + it('resets a user\'s gear', () => { + let gearReset = { + armor: 'armor_base_0', + weapon: 'weapon_warrior_0', + head: 'head_base_0', + shield: 'shield_base_0', + }; + + rebirth(user); + + expect(user.items.gear.equipped).to.deep.equal(gearReset); + expect(user.items.gear.costume).to.deep.equal(gearReset); + expect(user.preferences.costume).to.be.false; + }); + + it('resets a user\'s gear owned', () => { + user.items.gear.owned.weapon_warrior_1 = true; // eslint-disable-line camelcase + rebirth(user); + + expect(user.items.gear.owned.weapon_warrior_1).to.be.false; + expect(user.items.gear.owned.weapon_warrior_0).to.be.true; + }); + + it('resets a user\'s current pet', () => { + user.items.pets[animal] = true; + user.items.currentPet = animal; + rebirth(user); + + expect(user.items.currentPet).to.be.empty; + }); + + it('resets a user\'s current mount', () => { + user.items.mounts[animal] = true; + user.items.currentMount = animal; + rebirth(user); + + expect(user.items.currentMount).to.be.empty; + }); + + it('resets a user\'s flags', () => { + user.flags.itemsEnabled = true; + user.flags.dropsEnabled = true; + user.flags.classSelected = true; + user.flags.rebirthEnabled = true; + user.flags.levelDrops = {test: 'test'}; + + rebirth(user); + + expect(user.flags.itemsEnabled).to.be.false; + expect(user.flags.dropsEnabled).to.be.false; + expect(user.flags.classSelected).to.be.false; + expect(user.flags.rebirthEnabled).to.be.false; + expect(user.flags.levelDrops).to.be.empty; + }); + + it('does not reset rebirthEnabled if user has beastMaster', () => { + user.achievements.beastMaster = 1; + user.flags.rebirthEnabled = true; + + rebirth(user); + + expect(user.flags.rebirthEnabled).to.be.true; + }); + + it('sets rebirth achievement', () => { + rebirth(user); + + expect(user.achievements.rebirths).to.equal(1); + expect(user.achievements.rebirthLevel).to.equal(user.stats.lvl); + }); + + it('increments rebirth achievements', () => { + user.stats.lvl = 2; + user.achievements.rebirths = 1; + user.achievements.rebirthLevel = 1; + + rebirth(user); + + expect(user.achievements.rebirths).to.equal(2); + expect(user.achievements.rebirthLevel).to.equal(2); + }); + + it('does not increment rebirth achievements when level is lower than previous', () => { + user.stats.lvl = 2; + user.achievements.rebirths = 1; + user.achievements.rebirthLevel = 3; + + rebirth(user); + + expect(user.achievements.rebirths).to.equal(1); + expect(user.achievements.rebirthLevel).to.equal(3); + }); + + it('always increments rebirth achievements when level is MAX_LEVEL', () => { + user.stats.lvl = MAX_LEVEL; + user.achievements.rebirths = 1; + user.achievements.rebirthLevel = MAX_LEVEL + 1; // this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test + + rebirth(user); + + expect(user.achievements.rebirths).to.equal(2); + expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL); + }); + + it('always increments rebirth achievements when level is greater than MAX_LEVEL', () => { + user.stats.lvl = MAX_LEVEL + 1; + user.achievements.rebirths = 1; + user.achievements.rebirthLevel = MAX_LEVEL + 2; // this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test + + rebirth(user); + + expect(user.achievements.rebirths).to.equal(2); + expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL); + }); +}); diff --git a/test/common/ops/releaseBoth.js b/test/common/ops/releaseBoth.js new file mode 100644 index 0000000000..41e1bf6efb --- /dev/null +++ b/test/common/ops/releaseBoth.js @@ -0,0 +1,98 @@ +import releaseBoth from '../../../common/script/ops/releaseBoth'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.releaseBoth', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(() => { + user = generateUser(); + user.items.currentMount = animal; + user.items.currentPet = animal; + user.items.pets[animal] = 5; + user.items.mounts[animal] = true; + user.balance = 1.5; + }); + + it('returns an error when user balance is too low and user does not have triadBingo', (done) => { + user.balance = 0; + + try { + releaseBoth(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('grants triad bingo with gems', () => { + let [, message] = releaseBoth(user); + + expect(message).to.equal(i18n.t('mountsAndPetsReleased')); + expect(user.achievements.triadBingoCount).to.equal(1); + }); + + it('grants triad bingo without gems', () => { + user.balance = 0; + user.achievements.triadBingo = 1; + user.achievements.triadBingoCount = 1; + + let [, message] = releaseBoth(user); + + expect(message).to.equal(i18n.t('mountsAndPetsReleased')); + expect(user.achievements.triadBingoCount).to.equal(2); + }); + + it('releases pets', () => { + let [, message] = releaseBoth(user); + + expect(message).to.equal(i18n.t('mountsAndPetsReleased')); + expect(user.items.pets[animal]).to.be.empty; + expect(user.items.mounts[animal]).to.equal(null); + }); + + it('releases mounts', () => { + let [, message] = releaseBoth(user); + + expect(message).to.equal(i18n.t('mountsAndPetsReleased')); + expect(user.items.mounts[animal]).to.equal(null); + }); + + it('removes currentPet', () => { + releaseBoth(user); + + expect(user.items.currentMount).to.be.empty; + expect(user.items.currentPet).to.be.empty; + }); + + it('removes currentMount', () => { + releaseBoth(user); + + expect(user.items.currentMount).to.be.empty; + }); + + it('decreases user\'s balance', () => { + releaseBoth(user); + + expect(user.balance).to.equal(0); + }); + + it('incremenets beastMasterCount', () => { + releaseBoth(user); + + expect(user.achievements.beastMasterCount).to.equal(1); + }); + + it('incremenets mountMasterCount', () => { + releaseBoth(user); + + expect(user.achievements.mountMasterCount).to.equal(1); + }); +}); diff --git a/test/common/ops/releaseMounts.js b/test/common/ops/releaseMounts.js new file mode 100644 index 0000000000..29cb3cf6ac --- /dev/null +++ b/test/common/ops/releaseMounts.js @@ -0,0 +1,57 @@ +import releaseMounts from '../../../common/script/ops/releaseMounts'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.releaseMounts', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(() => { + user = generateUser(); + user.items.currentMount = animal; + user.items.mounts[animal] = true; + user.balance = 1; + }); + + it('returns an error when user balance is too low', (done) => { + user.balance = 0; + + try { + releaseMounts(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('releases mounts', () => { + let [, message] = releaseMounts(user); + + expect(message).to.equal(i18n.t('mountsReleased')); + expect(user.items.mounts[animal]).to.equal(null); + }); + + it('removes currentMount', () => { + releaseMounts(user); + + expect(user.items.currentMount).to.be.empty; + }); + + it('increases mountMasterCount achievement', () => { + releaseMounts(user); + + expect(user.achievements.mountMasterCount).to.equal(1); + }); + + it('subtracts gems from balance', () => { + releaseMounts(user); + + expect(user.balance).to.equal(0); + }); +}); diff --git a/test/common/ops/releasePets.js b/test/common/ops/releasePets.js new file mode 100644 index 0000000000..af175736cf --- /dev/null +++ b/test/common/ops/releasePets.js @@ -0,0 +1,57 @@ +import releasePets from '../../../common/script/ops/releasePets'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.releasePets', () => { + let user; + let animal = 'Wolf-Base'; + + beforeEach(() => { + user = generateUser(); + user.items.currentPet = animal; + user.items.pets[animal] = 5; + user.balance = 1; + }); + + it('returns an error when user balance is too low', (done) => { + user.balance = 0; + + try { + releasePets(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('releases pets', () => { + let [, message] = releasePets(user); + + expect(message).to.equal(i18n.t('petsReleased')); + expect(user.items.pets[animal]).to.equal(0); + }); + + it('removes currentPet', () => { + releasePets(user); + + expect(user.items.currentPet).to.be.empty; + }); + + it('decreases user\'s balance', () => { + releasePets(user); + + expect(user.balance).to.equal(0); + }); + + it('incremenets beastMasterCount', () => { + releasePets(user); + + expect(user.achievements.beastMasterCount).to.equal(1); + }); +}); diff --git a/test/common/ops/reroll.js b/test/common/ops/reroll.js new file mode 100644 index 0000000000..4dc5da70c1 --- /dev/null +++ b/test/common/ops/reroll.js @@ -0,0 +1,63 @@ +import reroll from '../../../common/script/ops/reroll'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, + generateDaily, + generateReward, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.reroll', () => { + let user; + let tasks = []; + + beforeEach(() => { + user = generateUser(); + user.balance = 1; + tasks = [generateDaily(), generateReward()]; + }); + + it('returns an error when user balance is too low', (done) => { + user.balance = 0; + + try { + reroll(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('rerolls a user with enough gems', () => { + let [, message] = reroll(user); + + expect(message).to.equal(i18n.t('fortifyComplete')); + }); + + it('reduces a user\'s balance', () => { + reroll(user); + + expect(user.balance).to.equal(0); + }); + + it('resets a user\'s health points', () => { + user.stats.hp = 40; + + reroll(user); + + expect(user.stats.hp).to.equal(50); + }); + + it('resets user\'s taks values except for rewards to 0', () => { + tasks[0].value = 1; + tasks[1].value = 1; + + reroll(user, tasks); + + expect(tasks[0].value).to.equal(0); + expect(tasks[1].value).to.equal(1); + }); +}); diff --git a/test/common/ops/reset.js b/test/common/ops/reset.js new file mode 100644 index 0000000000..50ebf90cb5 --- /dev/null +++ b/test/common/ops/reset.js @@ -0,0 +1,79 @@ +import reset from '../../../common/script/ops/reset'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, + generateDaily, + generateHabit, + generateReward, + generateTodo, +} from '../../helpers/common.helper'; + +describe('shared.ops.reset', () => { + let user; + let tasksToRemove; + + beforeEach(() => { + user = generateUser(); + user.balance = 2; + + let habit = generateHabit(); + let todo = generateTodo(); + let daily = generateDaily(); + let reward = generateReward(); + + user.tasksOrder.habits = [habit._id]; + user.tasksOrder.todos = [todo._id]; + user.tasksOrder.dailys = [daily._id]; + user.tasksOrder.rewards = [reward._id]; + + tasksToRemove = [habit, todo, daily, reward]; + }); + + + it('resets a user', () => { + let [, message] = reset(user); + + expect(message).to.equal(i18n.t('resetComplete')); + }); + + it('resets user\'s health', () => { + user.stats.hp = 40; + + reset(user); + + expect(user.stats.hp).to.equal(50); + }); + + it('resets user\'s level', () => { + user.stats.lvl = 2; + + reset(user); + + expect(user.stats.lvl).to.equal(1); + }); + + it('resets user\'s gold', () => { + user.stats.gp = 20; + + reset(user); + + expect(user.stats.gp).to.equal(0); + }); + + it('resets user\'s exp', () => { + user.stats.exp = 20; + + reset(user); + + expect(user.stats.exp).to.equal(0); + }); + + it('resets user\'s tasksOrder', () => { + reset(user, tasksToRemove); + + expect(user.tasksOrder.habits).to.be.empty; + expect(user.tasksOrder.todos).to.be.empty; + expect(user.tasksOrder.dailys).to.be.empty; + expect(user.tasksOrder.rewards).to.be.empty; + }); +}); diff --git a/test/common/ops/revive.js b/test/common/ops/revive.js new file mode 100644 index 0000000000..efd6968b42 --- /dev/null +++ b/test/common/ops/revive.js @@ -0,0 +1,91 @@ +import revive from '../../../common/script/ops/revive'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; +import content from '../../../common/script/content/index'; + +describe('shared.ops.revive', () => { + let user; + + beforeEach(() => { + user = generateUser(); + user.stats.hp = 0; + }); + + it('returns an error when user is not dead', (done) => { + user.stats.hp = 10; + + try { + revive(user); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('cannotRevive')); + done(); + } + }); + + it('resets user\'s hp, exp and gp', () => { + user.stats.exp = 100; + user.stats.gp = 100; + + revive(user); + + expect(user.stats.hp).to.equal(50); + expect(user.stats.exp).to.equal(0); + expect(user.stats.gp).to.equal(0); + }); + + it('decreases user\'s level', () => { + user.stats.lvl = 2; + revive(user); + + expect(user.stats.lvl).to.equal(1); + }); + + it('decreases a stat', () => { + user.stats.str = 2; + revive(user); + + expect(user.stats.str).to.equal(1); + }); + + it('removes a random item from user gear owned', () => { + let weaponKey = 'weapon_warrior_0'; + user.items.gear.owned[weaponKey] = true; + + let [, message] = revive(user); + + expect(message).to.equal(i18n.t('messageLostItem', { itemText: content.gear.flat[weaponKey].text()})); + expect(user.items.gear.owned[weaponKey]).to.be.false; + }); + + it('removes a random item from user gear equipped', () => { + let weaponKey = 'weapon_warrior_0'; + let itemToLose = content.gear.flat[weaponKey]; + + user.items.gear.owned[weaponKey] = true; + user.items.gear.equipped[itemToLose.type] = itemToLose.key; + + let [, message] = revive(user); + + expect(message).to.equal(i18n.t('messageLostItem', { itemText: itemToLose.text()})); + expect(user.items.gear.equipped[itemToLose.type]).to.equal(`${itemToLose.type}_base_0`); + }); + + it('removes a random item from user gear costume', () => { + let weaponKey = 'weapon_warrior_0'; + let itemToLose = content.gear.flat[weaponKey]; + + user.items.gear.owned[weaponKey] = true; + user.items.gear.costume[itemToLose.type] = itemToLose.key; + + let [, message] = revive(user); + + expect(message).to.equal(i18n.t('messageLostItem', { itemText: itemToLose.text()})); + expect(user.items.gear.costume[itemToLose.type]).to.equal(`${itemToLose.type}_base_0`); + }); +}); diff --git a/test/common/ops/scoreTask.test.js b/test/common/ops/scoreTask.test.js new file mode 100644 index 0000000000..727b316d62 --- /dev/null +++ b/test/common/ops/scoreTask.test.js @@ -0,0 +1,203 @@ +import scoreTask from '../../../common/script/ops/scoreTask'; +import { + generateUser, + generateDaily, + generateHabit, + generateTodo, + generateReward, +} from '../../helpers/common.helper'; +import common from '../../../common'; +import i18n from '../../../common/script/i18n'; +import { + NotAuthorized, +} from '../../../common/script/libs/errors'; + +let EPSILON = 0.0001; // negligible distance between datapoints + +/* Helper Functions */ +let rewrapUser = (user) => { + user._wrapped = false; + common.wrap(user); + return user; +}; + +let beforeAfter = () => { + let beforeUser = generateUser(); + let afterUser = _.cloneDeep(beforeUser); + rewrapUser(afterUser); + + return { + beforeUser, + afterUser, + }; +}; + +let expectGainedPoints = (beforeUser, afterUser, beforeTask, afterTask) => { + expect(afterUser.stats.hp).to.eql(50); + expect(afterUser.stats.exp).to.be.greaterThan(beforeUser.stats.exp); + expect(afterUser.stats.gp).to.be.greaterThan(beforeUser.stats.gp); + expect(afterTask.value).to.be.greaterThan(beforeTask.value); + if (afterTask.type === 'habit') { + expect(afterTask.history).to.have.length(1); + } +}; + +let expectClosePoints = (beforeUser, afterUser, beforeTask, task) => { + expect(Math.abs(afterUser.stats.exp - beforeUser.stats.exp)).to.be.lessThan(EPSILON); + expect(Math.abs(afterUser.stats.gp - beforeUser.stats.gp)).to.be.lessThan(EPSILON); + expect(Math.abs(task.value - beforeTask.value)).to.be.lessThan(EPSILON); +}; + +let _expectRoughlyEqualDates = (date1, date2) => { + expect(date1.toString()).to.eql(date2.toString()); +}; + +describe('shared.ops.scoreTask', () => { + let ref; + + beforeEach(() => { + ref = beforeAfter(); + }); + + it('throws an error when scoring a reward if user does not have enough gold', (done) => { + let reward = generateReward({ userId: ref.afterUser._id, text: 'some reward', value: 100 }); + try { + scoreTask({ user: ref.afterUser, task: reward }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.eql(i18n.t('messageNotEnoughGold')); + done(); + } + }); + + it('checks that the streak parameters affects the score', () => { + let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' }); + scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); + scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); + expect(task.streak).to.eql(2); + }); + + it('completes when the task direction is up', () => { + let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false }); + scoreTask({ user: ref.afterUser, task, direction: 'up' }); + expect(task.completed).to.eql(true); + _expectRoughlyEqualDates(task.dateCompleted, new Date()); + }); + + it('uncompletes when the task direction is down', () => { + let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false }); + scoreTask({ user: ref.afterUser, task, direction: 'down' }); + expect(task.completed).to.eql(false); + expect(task.dateCompleted).to.not.exist; + }); + + describe('verifies that times parameter in scoring works', () => { + let habit; + + beforeEach(() => { + ref = beforeAfter(); + habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); + }); + + it('works', () => { + let delta1, delta2, delta3; + + delta1 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }); + + ref = beforeAfter(); + habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); + + delta2 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 4, cron: false }); + + ref = beforeAfter(); + habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); + + delta3 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }); + + expect(Math.abs(delta1 - delta2)).to.be.greaterThan(EPSILON); + expect(Math.abs(delta1 - delta3)).to.be.lessThan(EPSILON); + }); + }); + + describe('scores', () => { + let options = {}; + let habit; + let freshDaily, daily; + let freshTodo, todo; + + beforeEach(() => { + ref = beforeAfter(options); + habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); + freshDaily = generateDaily({ userId: ref.afterUser._id, text: 'some daily' }); + daily = generateDaily({ userId: ref.afterUser._id, text: 'some daily' }); + freshTodo = generateTodo({ userId: ref.afterUser._id, text: 'some todo' }); + todo = generateTodo({ userId: ref.afterUser._id, text: 'some todo' }); + + expect(habit.history.length).to.eql(0); + + // before and after are the same user + expect(ref.beforeUser._id).to.exist; + expect(ref.beforeUser._id).to.eql(ref.afterUser._id); + }); + + context('habits', () => { + it('up', () => { + options = { user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }; + scoreTask(options); + + expect(habit.history.length).to.eql(1); + expect(habit.value).to.be.greaterThan(0); + + expect(ref.afterUser.stats.hp).to.eql(50); + expect(ref.afterUser.stats.exp).to.be.greaterThan(ref.beforeUser.stats.exp); + expect(ref.afterUser.stats.gp).to.be.greaterThan(ref.beforeUser.stats.gp); + }); + + it('down', () => { + scoreTask({user: ref.afterUser, task: habit, direction: 'down', times: 5, cron: false}, {}); + + expect(habit.history.length).to.eql(1); + expect(habit.value).to.be.lessThan(0); + + expect(ref.afterUser.stats.hp).to.be.lessThan(ref.beforeUser.stats.hp); + expect(ref.afterUser.stats.exp).to.eql(0); + expect(ref.afterUser.stats.gp).to.eql(0); + }); + }); + + context('dailys', () => { + it('up', () => { + expect(daily.completed).to.not.eql(true); + scoreTask({user: ref.afterUser, task: daily, direction: 'up'}); + expectGainedPoints(ref.beforeUser, ref.afterUser, freshDaily, daily); + expect(daily.completed).to.eql(true); + }); + + it('up, down', () => { + scoreTask({user: ref.afterUser, task: daily, direction: 'up'}); + scoreTask({user: ref.afterUser, task: daily, direction: 'down'}); + expectClosePoints(ref.beforeUser, ref.afterUser, freshDaily, daily); + }); + + it('sets completed = false on direction = down', () => { + daily.completed = true; + expect(daily.completed).to.not.eql(false); + scoreTask({user: ref.afterUser, task: daily, direction: 'down'}); + expect(daily.completed).to.eql(false); + }); + }); + + context('todos', () => { + it('up', () => { + scoreTask({user: ref.afterUser, task: todo, direction: 'up'}); + expectGainedPoints(ref.beforeUser, ref.afterUser, freshTodo, todo); + }); + + it('up, down', () => { + scoreTask({user: ref.afterUser, task: todo, direction: 'up'}); + scoreTask({user: ref.afterUser, task: todo, direction: 'down'}); + expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo); + }); + }); + }); +}); diff --git a/test/common/ops/sell.js b/test/common/ops/sell.js new file mode 100644 index 0000000000..727302ad9c --- /dev/null +++ b/test/common/ops/sell.js @@ -0,0 +1,79 @@ +import sell from '../../../common/script/ops/sell'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, + BadRequest, + NotFound, +} from '../../../common/script/libs/errors'; +import content from '../../../common/script/content/index'; + +describe('shared.ops.sell', () => { + let user; + let type = 'eggs'; + let key = 'Wolf'; + let acceptedTypes = ['eggs', 'hatchingPotions', 'food']; + + beforeEach(() => { + user = generateUser(); + user.items[type][key] = 1; + }); + + it('returns an error when type is not provided', (done) => { + try { + sell(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('typeRequired')); + done(); + } + }); + + it('returns an error when key is not provided', (done) => { + try { + sell(user, {params: { type } }); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('keyRequired')); + done(); + } + }); + + it('returns an error when non-sellable type is provided', (done) => { + let nonSellableType = 'nonSellableType'; + + try { + sell(user, {params: { type: nonSellableType, key } }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('typeNotSellable', {acceptedTypes: acceptedTypes.join(', ')})); + done(); + } + }); + + it('returns an error when key is not found with type provided', (done) => { + let fakeKey = 'fakeKey'; + + try { + sell(user, {params: { type, key: fakeKey } }); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('userItemsKeyNotFound', {type})); + done(); + } + }); + + it('reduces item count from user', () => { + sell(user, {params: { type, key } }); + + expect(user.items[type][key]).to.equal(0); + }); + + it('increases user\'s gold', () => { + sell(user, {params: { type, key } }); + + expect(user.stats.gp).to.equal(content[type][key].value); + }); +}); diff --git a/test/common/ops/sleep.js b/test/common/ops/sleep.js new file mode 100644 index 0000000000..f1e15625c9 --- /dev/null +++ b/test/common/ops/sleep.js @@ -0,0 +1,18 @@ +import sleep from '../../../common/script/ops/sleep'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.sleep', () => { + it('toggles user.preferences.sleep', () => { + let user = generateUser(); + + let [res] = sleep(user); + expect(res).to.eql(true); + expect(user.preferences.sleep).to.equal(true); + + let [res2] = sleep(user); + expect(res2).to.eql(false); + expect(user.preferences.sleep).to.equal(false); + }); +}); diff --git a/test/common/ops/unlock.js b/test/common/ops/unlock.js new file mode 100644 index 0000000000..ede5fe1d31 --- /dev/null +++ b/test/common/ops/unlock.js @@ -0,0 +1,121 @@ +import unlock from '../../../common/script/ops/unlock'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, + BadRequest, +} from '../../../common/script/libs/errors'; + +describe('shared.ops.unlock', () => { + let user; + let unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; + let unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'; + let backgroundUnlockPath = 'background.giant_florals'; + let unlockCost = 1.25; + let usersStartingGems = 5; + + beforeEach(() => { + user = generateUser(); + user.balance = usersStartingGems; + }); + + it('returns an error when path is not provided', (done) => { + try { + unlock(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('pathRequired')); + done(); + } + }); + + it('returns an error when user balance is too low', (done) => { + user.balance = 0; + + try { + unlock(user, {query: {path: unlockPath}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('notEnoughGems')); + done(); + } + }); + + it('returns an error when user already owns a full set', (done) => { + try { + unlock(user, {query: {path: unlockPath}}); + unlock(user, {query: {path: unlockPath}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('alreadyUnlocked')); + done(); + } + }); + + // disabled untill fully implemente + xit('returns an error when user already owns items in a full set', (done) => { + try { + unlock(user, {query: {path: unlockPath}}); + unlock(user, {query: {path: unlockPath}}); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('alreadyUnlocked')); + done(); + } + }); + + it('equips an item already owned', () => { + expect(user.purchased.background.giant_florals).to.not.exists; + + unlock(user, {query: {path: backgroundUnlockPath}}); + let afterBalance = user.balance; + let response = unlock(user, {query: {path: backgroundUnlockPath}}); + expect(user.balance).to.equal(afterBalance); // do not bill twice + + expect(response.message).to.not.exists; + expect(user.preferences.background).to.equal('giant_florals'); + }); + + it('un-equips an item already equipped', () => { + expect(user.purchased.background.giant_florals).to.not.exists; + + unlock(user, {query: {path: backgroundUnlockPath}}); // unlock + let afterBalance = user.balance; + unlock(user, {query: {path: backgroundUnlockPath}}); // equip + let response = unlock(user, {query: {path: backgroundUnlockPath}}); + expect(user.balance).to.equal(afterBalance); // do not bill twice + + expect(response.message).to.not.exists; + expect(user.preferences.background).to.equal(''); + }); + + it('unlocks a full set', () => { + let [, message] = unlock(user, {query: {path: unlockPath}}); + + expect(message).to.equal(i18n.t('unlocked')); + expect(user.purchased.shirt.convict).to.be.true; + }); + + it('unlocks a full set of gear', () => { + let [, message] = unlock(user, {query: {path: unlockGearSetPath}}); + + expect(message).to.equal(i18n.t('unlocked')); + expect(user.items.gear.owned.headAccessory_special_wolfEars).to.be.true; + }); + + it('unlocks a an item', () => { + let [, message] = unlock(user, {query: {path: backgroundUnlockPath}}); + + expect(message).to.equal(i18n.t('unlocked')); + expect(user.purchased.background.giant_florals).to.be.true; + }); + + it('reduces a user\'s balance', () => { + let [, message] = unlock(user, {query: {path: unlockPath}}); + + expect(message).to.equal(i18n.t('unlocked')); + expect(user.balance).to.equal(usersStartingGems - unlockCost); + }); +}); diff --git a/test/common/ops/updateTask.js b/test/common/ops/updateTask.js new file mode 100644 index 0000000000..99e8d80b22 --- /dev/null +++ b/test/common/ops/updateTask.js @@ -0,0 +1,53 @@ +import updateTask from '../../../common/script/ops/updateTask'; +import { + generateHabit, +} from '../../helpers/common.helper'; + +describe('shared.ops.updateTask', () => { + it('updates a task', () => { + let now = new Date(); + let habit = generateHabit({ + tags: [ + '123', + '456', + ], + + reminders: [{ + id: '123', + startDate: now, + time: now, + }], + }); + + let [res] = updateTask(habit, { + body: { + text: 'updated', + id: '123', + _id: '123', + type: 'todo', + tags: ['678'], + checklist: [{ + completed: false, + text: 'item', + id: '123', + }], + }, + }); + + expect(res.id).to.not.equal('123'); + expect(res._id).to.not.equal('123'); + expect(res.type).to.equal('habit'); + expect(res.text).to.equal('updated'); + expect(res.checklist).to.eql([{ + completed: false, + text: 'item', + id: '123', + }]); + expect(res.reminders).to.eql([{ + id: '123', + startDate: now, + time: now, + }]); + expect(res.tags).to.eql(['678']); + }); +}); diff --git a/test/common/ops/updateWebhook.test.js b/test/common/ops/updateWebhook.test.js new file mode 100644 index 0000000000..43c353626e --- /dev/null +++ b/test/common/ops/updateWebhook.test.js @@ -0,0 +1,42 @@ +import updateWebhook from '../../../common/script/ops/updateWebhook'; +import { + BadRequest, +} from '../../../common/script/libs/errors'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; + +describe('shared.ops.updateWebhook', () => { + let user; + let req; + let newUrl = 'http://new-url.com'; + + beforeEach(() => { + user = generateUser(); + req = { params: { + id: 'this-id', + }, body: { + url: newUrl, + enabled: true, + } }; + }); + + it('validates body', (done) => { + delete req.body.url; + try { + updateWebhook(user, req); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('invalidUrl')); + done(); + } + }); + + it('succeeds', () => { + let url = 'http://existing-url.com'; + user.preferences.webhooks = { 'this-id': { url } }; + updateWebhook(user, req); + expect(user.preferences.webhooks['this-id'].url).to.eql(newUrl); + }); +}); diff --git a/test/common/preenTodos.test.js b/test/common/preenTodos.test.js deleted file mode 100644 index c9f9a45028..0000000000 --- a/test/common/preenTodos.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import moment from 'moment'; -import { generateTodo } from '../helpers/common.helper'; -import { preenTodos } from '../../common/script/index.js'; - -describe('#preenTodos', () => { - let todos, uncompletedTodo, completedChallengeTodo, newlyCompletedTodo, completedTodoFromTwoDaysAgo, completedTodoFromThreeDaysAgo, completedTodoFromTenDaysAgo; - - beforeEach(() => { - uncompletedTodo = generateTodo({ completed: false }); - completedChallengeTodo = generateTodo({ - completed: true, - challenge: { id: 'some-challenge' }, - }); - newlyCompletedTodo = generateTodo({ - completed: true, - dateCompleted: moment(), - }); - completedTodoFromTwoDaysAgo = generateTodo({ - completed: true, - dateCompleted: moment().subtract({ days: 2 }), - }); - completedTodoFromThreeDaysAgo = generateTodo({ - completed: true, - dateCompleted: moment().subtract({ days: 3 }), - }); - completedTodoFromTenDaysAgo = generateTodo({ - completed: true, - dateCompleted: moment().subtract({ days: 10 }), - }); - - todos = [ - uncompletedTodo, - completedChallengeTodo, - newlyCompletedTodo, - completedTodoFromTwoDaysAgo, - completedTodoFromThreeDaysAgo, - completedTodoFromTenDaysAgo, - ]; - }); - - it('includes uncompleted todos', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.include(uncompletedTodo); - }); - - it('includes completed challenge todos', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.include(completedChallengeTodo); - }); - - it('includes recently completed todos', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.include(newlyCompletedTodo); - }); - - it('includes todos completed two days ago', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.include(completedTodoFromTwoDaysAgo); - }); - - it('does not include todos completed three days ago', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.not.include(completedTodoFromThreeDaysAgo); - }); - - it('does not include todos completed more than three days ago', () => { - let preenedTodos = preenTodos(todos); - - expect(preenedTodos).to.not.include(completedTodoFromTenDaysAgo); - }); -}); diff --git a/test/common/shared.spells.test.js b/test/common/shared.spells.test.js deleted file mode 100644 index 931f9b9991..0000000000 --- a/test/common/shared.spells.test.js +++ /dev/null @@ -1,103 +0,0 @@ -import shared from '../../common/script/index.js'; -import { - generateUser, - generateTodo, -} from '../helpers/common.helper'; - - -describe('Spells', () => { - let user; - - beforeEach(() => { - let todo = generateTodo(); - - user = generateUser({ - stats: { - int: 20, - str: 20, - con: 20, - per: 20, - lvl: 20, - }, - }); - user.todos.push(todo); - }); - - context('Rogue Spells', () => { - beforeEach(() => { - user.stats.class = 'rogue'; - }); - - describe('#backstab', () => { - it('adds exp to user', () => { - const PREVIOUS_EXP = user.stats.exp; - - shared.content.spells.rogue.backStab.cast(user, user.todos[0]); - - expect(user.stats.exp).to.be.greaterThan(PREVIOUS_EXP); - }); - - it('adds gp to user', () => { - const PREVIOUS_GP = user.stats.gp; - - shared.content.spells.rogue.backStab.cast(user, user.todos[0]); - - expect(user.stats.gp).to.be.greaterThan(PREVIOUS_GP); - }); - - it('levels up user if the gain in experience will level up the user', () => { - user.stats.exp = 399; - user.stats.lvl = 17; - - shared.content.spells.rogue.backStab.cast(user, user.todos[0]); - expect(user.stats.lvl).to.eql(18); - }); - - it('adds quest scroll to inventory when passing level milestone', () => { - user.stats.exp = 329; - user.stats.lvl = 14; - - expect(user.items.quests).to.not.have.property('atom1'); - - shared.content.spells.rogue.backStab.cast(user, user.todos[0]); - - expect(user.items.quests).to.have.property('atom1', 1); - }); - }); - }); - - context('Wizard Spells', () => { - beforeEach(() => { - user.stats.class = 'wizard'; - }); - - describe('#fireball (Burst of flames)', () => { - it('adds exp to user', () => { - const PREVIOUS_EXP = user.stats.exp; - - shared.content.spells.wizard.fireball.cast(user, user.todos[0]); - - expect(user.stats.exp).to.be.greaterThan(PREVIOUS_EXP); - }); - - it('levels up user if the gain in experience will level up the user', () => { - user.stats.exp = 399; - user.stats.lvl = 17; - - shared.content.spells.wizard.fireball.cast(user, user.todos[0]); - expect(user.stats.lvl).to.eql(18); - }); - - it('adds quest scroll to inventory when passing level milestone', () => { - user.stats.exp = 329; - user.stats.lvl = 14; - - expect(user.items.quests).to.not.have.property('atom1'); - - shared.content.spells.wizard.fireball.cast(user, user.todos[0]); - - expect(user.items.quests).to.have.property('atom1', 1); - }); - }); - }); -}); diff --git a/test/common/simulations/autoAllocate.js b/test/common/simulations/autoAllocate.js deleted file mode 100644 index 0b0348efee..0000000000 --- a/test/common/simulations/autoAllocate.js +++ /dev/null @@ -1,161 +0,0 @@ -var $w, _, id, modes, shared, user; - -shared = require('../../../common/script/index.js'); - -_ = require('lodash'); - -$w = function(s) { - return s.split(' '); -}; - -id = shared.uuid(); - -user = { - stats: { - "class": 'warrior', - lvl: 1, - hp: 50, - gp: 0, - exp: 10, - per: 0, - int: 0, - con: 0, - str: 0, - buffs: { - per: 0, - int: 0, - con: 0, - str: 0 - }, - training: { - int: 0, - con: 0, - per: 0, - str: 0 - } - }, - preferences: { - automaticAllocation: false - }, - party: { - quest: { - key: 'evilsanta', - progress: { - up: 0, - down: 0 - } - } - }, - achievements: {}, - items: { - eggs: {}, - hatchingPotions: {}, - food: {}, - gear: { - equipped: { - weapon: 'weapon_warrior_4', - armor: 'armor_warrior_4', - shield: 'shield_warrior_4', - head: 'head_warrior_4' - } - } - }, - habits: [ - { - id: 'a', - value: 1, - type: 'habit', - attribute: 'str' - } - ], - dailys: [ - { - id: 'b', - value: 1, - type: 'daily', - attribute: 'str' - } - ], - todos: [ - { - id: 'c', - value: 1, - type: 'todo', - attribute: 'con' - }, { - id: 'd', - value: 1, - type: 'todo', - attribute: 'per' - }, { - id: 'e', - value: 1, - type: 'todo', - attribute: 'int' - } - ], - rewards: [] -}; - -modes = { - flat: _.cloneDeep(user), - classbased_warrior: _.cloneDeep(user), - classbased_rogue: _.cloneDeep(user), - classbased_wizard: _.cloneDeep(user), - classbased_healer: _.cloneDeep(user), - taskbased: _.cloneDeep(user) -}; - -modes.classbased_warrior.stats["class"] = 'warrior'; - -modes.classbased_rogue.stats["class"] = 'rogue'; - -modes.classbased_wizard.stats["class"] = 'wizard'; - -modes.classbased_healer.stats["class"] = 'healer'; - -_.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { - _.merge(modes[mode].preferences, { - automaticAllocation: true, - allocationMode: mode.indexOf('classbased') === 0 ? 'classbased' : mode - }); - return shared.wrap(modes[mode]); -}); - -console.log("\n\n================================================"); - -console.log("New Simulation"); - -console.log("================================================\n\n"); - -_.times([20], function(lvl) { - console.log("[lvl " + lvl + "]\n--------------\n"); - return _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { - var str, u; - u = modes[mode]; - u.stats.exp = shared.tnl(lvl) + 1; - if (mode === 'taskbased') { - _.merge(u.stats, { - per: 0, - con: 0, - int: 0, - str: 0 - }); - } - u.habits[0].attribute = u.fns.randomVal({ - str: 'str', - int: 'int', - per: 'per', - con: 'con' - }); - u.ops.score({ - params: { - id: u.habits[0].id - }, - direction: 'up' - }); - u.fns.updateStats(u.stats); - str = mode + (mode === 'taskbased' ? " (" + u.habits[0].attribute + ")" : ""); - return console.log(str, _.pick(u.stats, $w('per int con str'))); - }); -}); diff --git a/test/common/simulations/passive_active_attrs.js b/test/common/simulations/passive_active_attrs.js deleted file mode 100644 index f69a2cafc2..0000000000 --- a/test/common/simulations/passive_active_attrs.js +++ /dev/null @@ -1,291 +0,0 @@ -var _, clearUser, id, party, s, shared, task, user; - -shared = require('../../../common/script/index.js'); - -_ = require('lodash'); - -id = shared.uuid(); - -user = { - stats: { - "class": 'warrior', - buffs: { - per: 0, - int: 0, - con: 0, - str: 0 - } - }, - party: { - quest: { - key: 'evilsanta', - progress: { - up: 0, - down: 0 - } - } - }, - preferences: { - automaticAllocation: false - }, - achievements: {}, - flags: { - levelDrops: {} - }, - items: { - eggs: {}, - hatchingPotions: {}, - food: {}, - quests: {}, - gear: { - equipped: { - weapon: 'weapon_warrior_4', - armor: 'armor_warrior_4', - shield: 'shield_warrior_4', - head: 'head_warrior_4' - } - } - }, - habits: [ - shared.taskDefaults({ - id: id, - value: 0 - }) - ], - dailys: [ - { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - }, { - "text": "1" - } - ], - todos: [], - rewards: [] -}; - -shared.wrap(user); - -s = user.stats; - -task = user.tasks[id]; - -party = [user]; - -console.log("\n\n================================================"); - -console.log("New Simulation"); - -console.log("================================================\n\n"); - -clearUser = function(lvl) { - if (lvl == null) { - lvl = 1; - } - _.merge(user.stats, { - exp: 0, - gp: 0, - hp: 50, - lvl: lvl, - str: lvl * 1.5, - con: lvl * 1.5, - per: lvl * 1.5, - int: lvl * 1.5, - mp: 100 - }); - _.merge(s.buffs, { - str: 0, - con: 0, - int: 0, - per: 0 - }); - _.merge(user.party.quest.progress, { - up: 0, - down: 0 - }); - return user.items.lastDrop = { - count: 0 - }; -}; - -_.each([1, 25, 50, 75, 100], function(lvl) { - console.log("[LEVEL " + lvl + "] (" + (lvl * 2) + " points total in every attr)\n\n"); - _.each({ - red: -25, - yellow: 0, - green: 35 - }, function(taskVal, color) { - var _party, b4, str; - console.log("[task.value = " + taskVal + " (" + color + ")]"); - console.log("direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"); - _.each(['up', 'down'], function(direction) { - var b4, delta; - clearUser(lvl); - b4 = { - hp: s.hp, - taskVal: taskVal - }; - task.value = taskVal; - if (direction === 'up') { - task.type = 'daily'; - } - delta = user.ops.score({ - params: { - id: id, - direction: direction - } - }); - return console.log((direction === 'up' ? '↑' : '↓') + "\t\t" + s.exp + "/" + (shared.tnl(s.lvl)) + "\t\t" + ((b4.hp - s.hp).toFixed(1)) + "\t" + (s.gp.toFixed(1)) + "\t" + (delta.toFixed(1)) + "\t\t" + ((task.value - b4.taskVal - delta).toFixed(1)) + "\t\t\t" + (user.party.quest.progress.up.toFixed(1))); - }); - str = '- [Wizard]'; - task.value = taskVal; - clearUser(lvl); - b4 = { - taskVal: taskVal - }; - shared.content.spells.wizard.fireball.cast(user, task); - str += "\tfireball(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " bossHit:" + (user.party.quest.progress.up.toFixed(2)) + ")"; - task.value = taskVal; - clearUser(lvl); - _party = [ - user, { - stats: { - mp: 0 - } - } - ]; - shared.content.spells.wizard.mpheal.cast(user, _party); - str += "\t| mpheal(mp:" + _party[1].stats.mp + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.wizard.earth.cast(user, party); - str += "\t\t\t\t| earth(buffs.int:" + s.buffs.int + ")"; - s.buffs.int = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.wizard.frost.cast(user, {}); - str += "\t\t\t| frost(N/A)"; - console.log(str); - str = '- [Warrior]'; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.smash.cast(user, task); - b4 = { - taskVal: taskVal - }; - str += "\tsmash(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.defensiveStance.cast(user, {}); - str += "\t\t| defensiveStance(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.valorousPresence.cast(user, party); - str += "\t\t\t| valorousPresence(buffs.str:" + s.buffs.str + ")"; - s.buffs.str = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.warrior.intimidate.cast(user, party); - str += "\t\t| intimidate(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - console.log(str); - str = '- [Rogue]'; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.pickPocket.cast(user, task); - str += "\tpickPocket(gp:" + (s.gp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.backStab.cast(user, task); - b4 = { - taskVal: taskVal - }; - str += "\t\t| backStab(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " gp:" + (s.gp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.toolsOfTrade.cast(user, party); - str += "\t| toolsOfTrade(buffs.per:" + s.buffs.per + ")"; - s.buffs.per = 0; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.rogue.stealth.cast(user, {}); - str += "\t\t| stealth(avoiding " + user.stats.buffs.stealth + " tasks)"; - user.stats.buffs.stealth = 0; - console.log(str); - str = '- [Healer]'; - task.value = taskVal; - clearUser(lvl); - s.hp = 0; - shared.content.spells.healer.heal.cast(user, {}); - str += "\theal(hp:" + (s.hp.toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.healer.brightness.cast(user, {}); - b4 = { - taskVal: taskVal - }; - str += "\t\t\t| brightness(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + ")"; - task.value = taskVal; - clearUser(lvl); - shared.content.spells.healer.protectAura.cast(user, party); - str += "\t\t\t| protectAura(buffs.con:" + s.buffs.con + ")"; - s.buffs.con = 0; - task.value = taskVal; - clearUser(lvl); - s.hp = 0; - shared.content.spells.healer.heallAll.cast(user, party); - str += "\t\t| heallAll(hp:" + (s.hp.toFixed(1)) + ")"; - console.log(str); - return console.log('\n'); - }); - return console.log('------------------------------------------------------------'); -}); - - -/* -_.each [1,25,50,75,100,125], (lvl) -> - console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" - _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> - console.log "[task.value = #{taskVal} (#{color})]" - console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" - _.each ['up','down'], (direction) -> - clearUser(lvl) - b4 = {hp:s.hp, taskVal} - task.value = taskVal - task.type = 'daily' if direction is 'up' - delta = user.ops.score params:{id, direction} - console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" - - task.value = taskVal;clearUser(lvl) - shared.content.spells.rogue.stealth.cast(user,{}) - console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" - user.stats.buffs.stealth = 0 - - console.log user.dailys.length - */ diff --git a/test/common/user.fns.buy.test.js b/test/common/user.fns.buy.test.js deleted file mode 100644 index 0cf39c1eb0..0000000000 --- a/test/common/user.fns.buy.test.js +++ /dev/null @@ -1,288 +0,0 @@ -/* eslint-disable camelcase */ - -import sinon from 'sinon'; // eslint-disable-line no-shadow - -let shared = require('../../common/script/index.js'); - -describe('user.fns.buy', () => { - let user; - - beforeEach(() => { - user = { - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - equipped: { - weapon_warrior_0: true, - }, - }, - }, - preferences: {}, - stats: { gp: 200 }, - achievements: { }, - flags: { }, - }; - - shared.wrap(user); - - sinon.stub(user.fns, 'randomVal'); - sinon.stub(user.fns, 'predictableRandom'); - }); - - afterEach(() => { - user.fns.randomVal.restore(); - user.fns.predictableRandom.restore(); - }); - - context('Potion', () => { - it('recovers 15 hp', () => { - user.stats.hp = 30; - user.ops.buy({params: {key: 'potion'}}); - expect(user.stats.hp).to.eql(45); - }); - - it('does not increase hp above 50', () => { - user.stats.hp = 45; - user.ops.buy({params: {key: 'potion'}}); - expect(user.stats.hp).to.eql(50); - }); - - it('deducts 25 gp', () => { - user.stats.hp = 45; - user.ops.buy({params: {key: 'potion'}}); - - expect(user.stats.gp).to.eql(175); - }); - - it('does not purchase if not enough gp', () => { - user.stats.hp = 45; - user.stats.gp = 5; - user.ops.buy({params: {key: 'potion'}}); - - expect(user.stats.hp).to.eql(45); - expect(user.stats.gp).to.eql(5); - }); - }); - - context('Gear', () => { - it('adds equipment to inventory', () => { - user.stats.gp = 31; - - user.ops.buy({params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true }); - }); - - it('deducts gold from user', () => { - user.stats.gp = 31; - - user.ops.buy({params: {key: 'armor_warrior_1'}}); - - expect(user.stats.gp).to.eql(1); - }); - - it('auto equips equipment if user has auto-equip preference turned on', () => { - user.stats.gp = 31; - user.preferences.autoEquip = true; - - user.ops.buy({params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1'); - }); - - it('buys equipment but does not auto-equip', () => { - user.stats.gp = 31; - user.preferences.autoEquip = false; - - user.ops.buy({params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.equipped).to.not.have.property('armor'); - }); - - it('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => { - user.stats.gp = 100; - user.preferences.autoEquip = true; - user.ops.buy({params: {key: 'shield_warrior_1'}}); - user.ops.equip({params: {key: 'shield_warrior_1'}}); - user.ops.buy({params: {key: 'weapon_warrior_1'}}); - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - - user.ops.buy({params: {key: 'weapon_wizard_1'}}); - - expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0'); - expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1'); - }); - - it('buys two-handed equipment but does not automatically remove sword or shield', () => { - user.stats.gp = 100; - user.preferences.autoEquip = false; - user.ops.buy({params: {key: 'shield_warrior_1'}}); - user.ops.equip({params: {key: 'shield_warrior_1'}}); - user.ops.buy({params: {key: 'weapon_warrior_1'}}); - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - - user.ops.buy({params: {key: 'weapon_wizard_1'}}); - - expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1'); - expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1'); - }); - - it('does not buy equipment without enough Gold', () => { - user.stats.gp = 20; - - user.ops.buy({params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.owned).to.not.have.property('armor_warrior_1'); - }); - }); - - context('Quests', () => { - it('buys a Quest scroll'); - - it('does not buy Quests without enough Gold'); - - it('does not buy nonexistent Quests'); - - it('does not buy Gem-premium Quests'); - }); - - context('Enchanted Armoire', () => { - let YIELD_EQUIPMENT = 0.5; - let YIELD_FOOD = 0.7; - let YIELD_EXP = 0.9; - - let fullArmoire = {}; - - _(shared.content.gearTypes).each((type) => { - _(shared.content.gear.tree[type].armoire).each((gearObject) => { - let armoireKey = gearObject.key; - - fullArmoire[armoireKey] = true; - }).value(); - }).value(); - - beforeEach(() => { - user.achievements.ultimateGearSets = { rogue: true }; - user.flags.armoireOpened = true; - user.stats.exp = 0; - user.items.food = {}; - }); - - context('failure conditions', () => { - it('does not open if user does not have enough gold', (done) => { - user.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.stats.gp = 50; - - user.ops.buy({params: {key: 'armoire'}}, (response) => { - expect(response.message).to.eql('Not Enough Gold'); - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - done(); - }); - }); - - it('does not open without Ultimate Gear achievement', (done) => { - user.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false}; - - user.ops.buy({params: {key: 'armoire'}}, (response) => { - expect(response.message).to.eql('You can\'t buy this item'); - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - done(); - }); - }); - }); - - context('non-gear awards', () => { - it('gives Experience', () => { - user.fns.predictableRandom.returns(YIELD_EXP); - - user.ops.buy({params: {key: 'armoire'}}); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(46); - expect(user.stats.gp).to.eql(100); - }); - - it('gives food', () => { - let honey = shared.content.food.Honey; - - user.fns.randomVal.returns(honey); - user.fns.predictableRandom.returns(YIELD_FOOD); - - user.ops.buy({params: {key: 'armoire'}}); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.eql({Honey: 1}); - expect(user.stats.exp).to.eql(0); - expect(user.stats.gp).to.eql(100); - }); - - it('does not give equipment if all equipment has been found', () => { - user.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.items.gear.owned = fullArmoire; - user.stats.gp = 150; - - user.ops.buy({params: {key: 'armoire'}}); - - expect(user.items.gear.owned).to.eql(fullArmoire); - let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(0); - - expect(user.stats.exp).to.eql(30); - expect(user.stats.gp).to.eql(50); - }); - }); - - context('gear awards', () => { - beforeEach(() => { - let shield = shared.content.gear.tree.shield.armoire.gladiatorShield; - - user.fns.randomVal.returns(shield); - }); - - it('always drops equipment the first time', () => { - delete user.flags.armoireOpened; - user.fns.predictableRandom.returns(YIELD_EXP); - - user.ops.buy({params: {key: 'armoire'}}); - - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - shield_armoire_gladiatorShield: true, - }); - - let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(_.size(fullArmoire) - 1); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - expect(user.stats.gp).to.eql(100); - }); - - it('gives more equipment', () => { - user.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.items.gear.owned = { - weapon_warrior_0: true, - head_armoire_hornedIronHelm: true, - }; - user.stats.gp = 200; - - user.ops.buy({params: {key: 'armoire'}}); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true}); - let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(_.size(fullArmoire) - 2); - expect(user.stats.gp).to.eql(100); - }); - }); - }); -}); diff --git a/test/common/user.fns.ultimateGear.test.js b/test/common/user.fns.ultimateGear.test.js deleted file mode 100644 index a95cb403d9..0000000000 --- a/test/common/user.fns.ultimateGear.test.js +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable camelcase */ - -let shared = require('../../common/script/index.js'); - -shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; - -require('./test_helper'); - -describe('User.fns.ultimateGear', () => { - it('sets armoirEnabled when partial achievement already achieved', () => { - let items = { - gear: { - owned: { - toObject: () => { - return { - armor_warrior_5: true, - shield_warrior_5: true, - head_warrior_5: true, - weapon_warrior_6: true, - }; - }, - }, - }, - }; - - let user = shared.wrap({ - items, - achievements: { - ultimateGearSets: {}, - }, - flags: {}, - }); - - user.fns.ultimateGear(); - expect(user.flags.armoireEnabled).to.equal(true); - }); -}); diff --git a/test/common/user.fns.updateStats.test.js b/test/common/user.fns.updateStats.test.js deleted file mode 100644 index fbad57e531..0000000000 --- a/test/common/user.fns.updateStats.test.js +++ /dev/null @@ -1,134 +0,0 @@ -import { - generateUser, -} from '../helpers/common.helper'; - -describe('user.fns.updateStats', () => { - let user; - - beforeEach(() => { - user = generateUser({}); - }); - - context('No Hp', () => { - it('returns 0 if user\'s hp is 0', () => { - let stats = { - hp: 0, - }; - - expect(user.fns.updateStats(stats)).to.eql(0); - }); - - it('returns 0 if user\'s hp is less than 0', () => { - let stats = { - hp: -5, - }; - - expect(user.fns.updateStats(stats)).to.eql(0); - }); - - it('sets user\'s hp to 0 if it is less than 0', () => { - let stats = { - hp: -5, - }; - - user.fns.updateStats(stats); - - expect(user.stats.hp).to.eql(0); - }); - }); - - context('Stat Allocation', () => { - it('adds only attribute points up to user\'s level', () => { - let stats = { - exp: 261, - }; - - user.stats.lvl = 10; - - user.fns.updateStats(stats); - - expect(user.stats.points).to.eql(11); - }); - - it('adds an attibute point when user\'s stat points are less than max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 24; - - user.fns.updateStats(stats); - - expect(user.stats.points).to.eql(1); - }); - - it('does not add an attibute point when user\'s stat points are equal to max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 25; - - user.fns.updateStats(stats); - - expect(user.stats.points).to.eql(0); - }); - - it('does not add an attibute point when user\'s stat points + unallocated points are equal to max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 15; - user.stats.points = 10; - - user.fns.updateStats(stats); - - expect(user.stats.points).to.eql(10); - }); - - it('only awards stat points up to level 100 if user is missing unallocated stat points and is over level 100', () => { - let stats = { - exp: 5581, - }; - - user.stats.lvl = 104; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 15; - user.stats.points = 0; - - user.fns.updateStats(stats); - - expect(user.stats.points).to.eql(10); - }); - - // @TODO: Set up sinon sandbox - xit('auto allocates stats if automaticAllocation is turned on', () => { - sandbox.stub(user.fns, 'autoAllocate'); - - let stats = { - exp: 261, - }; - - user.stats.lvl = 10; - - user.fns.updateStats(stats); - - expect(user.fns.autoAllocate).to.be.calledOnce; - }); - }); -}); diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js deleted file mode 100644 index 8c7899cf50..0000000000 --- a/test/common/user.ops.buyMysterySet.test.js +++ /dev/null @@ -1,77 +0,0 @@ -/* eslint-disable camelcase */ - -let shared = require('../../common/script/index.js'); - -describe('user.ops.buyMysterySet', () => { - let user; - - beforeEach(() => { - user = { - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - }, - }, - purchased: { - plan: { - consecutive: { - trinkets: 0, - }, - }, - }, - }; - - shared.wrap(user); - }); - - context('Mystery Sets', () => { - context('failure conditions', () => { - it('does not grant mystery sets without Mystic Hourglasses', (done) => { - user.ops.buyMysterySet({params: {key: '201501'}}, (response) => { - expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - done(); - }); - }); - - it('does not grant mystery set that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.gear.owned = { - weapon_warrior_0: true, - weapon_mystery_301404: true, - armor_mystery_301404: true, - head_mystery_301404: true, - eyewear_mystery_301404: true, - }; - - user.ops.buyMysterySet({params: {key: '301404'}}, (response) => { - expect(response.message).to.eql('Mystery set not found, or set already owned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - }); - }); - }); - - context('successful purchases', () => { - it('buys Steampunk Accessories Set', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - - user.ops.buyMysterySet({params: {key: '301404'}}, () => { - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - weapon_mystery_301404: true, - armor_mystery_301404: true, - head_mystery_301404: true, - eyewear_mystery_301404: true, - }); - - done(); - }); - }); - }); - }); -}); - diff --git a/test/common/user.ops.equip.test.js b/test/common/user.ops.equip.test.js deleted file mode 100644 index 62f7956e02..0000000000 --- a/test/common/user.ops.equip.test.js +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable camelcase */ - -import sinon from 'sinon'; // eslint-disable-line no-shadow -import {assert} from 'sinon'; -import i18n from '../../common/script/i18n'; -import shared from '../../common/script/index.js'; -import content from '../../common/script/content/index'; - -describe('user.ops.equip', () => { - let user; - let spy; - - beforeEach(() => { - user = { - items: { - gear: { - owned: { - weapon_warrior_0: true, - weapon_warrior_1: true, - weapon_warrior_2: true, - weapon_wizard_1: true, - weapon_wizard_2: true, - shield_base_0: true, - shield_warrior_1: true, - }, - equipped: { - weapon: 'weapon_warrior_0', - shield: 'shield_base_0', - }, - }, - }, - preferences: {}, - stats: {gp: 200}, - achievements: {}, - flags: {}, - }; - - shared.wrap(user); - spy = sinon.spy(); - }); - - context('Gear', () => { - it('should not send a message if a weapon is equipped while only having zero or one weapons equipped', () => { - // user.ops.equip always calls the callback, even if it isn't sending a message - // so we need to check to see if a single null message was sent. - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - - // one-handed to one-handed - user.ops.equip({params: {key: 'weapon_warrior_2'}}, spy); - - assert.calledOnce(spy); - assert.calledWith(spy, null); - spy.reset(); - - // one-handed to two-handed - user.ops.equip({params: {key: 'weapon_wizard_1'}}, spy); - assert.calledOnce(spy); - assert.calledWith(spy, null); - spy.reset(); - - // two-handed to two-handed - user.ops.equip({params: {key: 'weapon_wizard_2'}}, spy); - assert.calledOnce(spy); - assert.calledWith(spy, null); - spy.reset(); - - // two-handed to one-handed - user.ops.equip({params: {key: 'weapon_warrior_2'}}, spy); - assert.calledOnce(spy); - assert.calledWith(spy, null); - spy.reset(); - }); - - it('should send messages if equipping a two-hander causes the off-hander to be unequipped', () => { - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - user.ops.equip({params: {key: 'shield_warrior_1'}}); - - // equipping two-hander - user.ops.equip({params: {key: 'weapon_wizard_1'}}, spy); - let weapon = content.gear.flat.weapon_wizard_1; - let item = content.gear.flat.shield_warrior_1; - let message = i18n.t('messageTwoHandedEquip', {twoHandedText: weapon.text(null), offHandedText: item.text(null)}); - - assert.calledOnce(spy); - assert.calledWith(spy, {code: 200, message}); - }); - - it('should send messages if equipping an off-hand item causes a two-handed weapon to be unequipped', () => { - // equipping two-hander - user.ops.equip({params: {key: 'weapon_wizard_1'}}); - let weapon = content.gear.flat.weapon_wizard_1; - let shield = content.gear.flat.shield_warrior_1; - - user.ops.equip({params: {key: 'shield_warrior_1'}}, spy); - - let message = i18n.t('messageTwoHandedUnequip', {twoHandedText: weapon.text(null), offHandedText: shield.text(null)}); - - assert.calledOnce(spy); - assert.calledWith(spy, {code: 200, message}); - }); - }); -}); diff --git a/test/common/user.ops.hatch.js b/test/common/user.ops.hatch.js deleted file mode 100644 index 573103a360..0000000000 --- a/test/common/user.ops.hatch.js +++ /dev/null @@ -1,129 +0,0 @@ -let shared = require('../../common/script/index.js'); - -describe('user.ops.hatch', () => { - let user; - - beforeEach(() => { - user = { - items: { - eggs: {}, - hatchingPotions: {}, - pets: {}, - }, - }; - - shared.wrap(user); - }); - - context('Pet Hatching', () => { - context('failure conditions', () => { - it('does not allow hatching without specifying egg and potion', (done) => { - user.ops.hatch({params: {}}, (response) => { - expect(response.message).to.eql('Please specify query.egg & query.hatchingPotion'); - expect(user.items.pets).to.be.empty; - done(); - }); - }); - - it('does not allow hatching if user lacks specified egg', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.ops.hatch({params: {egg: 'Dragon', hatchingPotion: 'Base'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); - expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({Wolf: 1}); - expect(user.items.hatchingPotions).to.eql({Base: 1}); - done(); - }); - }); - - it('does not allow hatching if user lacks specified hatching potion', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Golden'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); - expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({Wolf: 1}); - expect(user.items.hatchingPotions).to.eql({Base: 1}); - done(); - }); - }); - - it('does not allow hatching if user already owns target pet', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {'Wolf-Base': 10}; - user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageAlreadyPet')); - expect(user.items.pets).to.eql({'Wolf-Base': 10}); - expect(user.items.eggs).to.eql({Wolf: 1}); - expect(user.items.hatchingPotions).to.eql({Base: 1}); - done(); - }); - }); - - it('does not allow hatching quest pet egg using premium potion', (done) => { - user.items.eggs = {Cheetah: 1}; - user.items.hatchingPotions = {Spooky: 1}; - user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Spooky'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageInvalidEggPotionCombo')); - expect(user.items.pets).to.be.empty; - expect(user.items.eggs).to.eql({Cheetah: 1}); - expect(user.items.hatchingPotions).to.eql({Spooky: 1}); - done(); - }); - }); - }); - - context('successful hatching', () => { - it('hatches a basic pet', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Base': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - done(); - }); - }); - - it('hatches a quest pet', (done) => { - user.items.eggs = {Cheetah: 1}; - user.items.hatchingPotions = {Base: 1}; - user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Base'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Cheetah-Base': 5}); - expect(user.items.eggs).to.eql({Cheetah: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - done(); - }); - }); - - it('hatches a premium pet', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Spooky: 1}; - user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Spooky'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Spooky': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Spooky: 0}); - done(); - }); - }); - - it('hatches a pet previously raised to a mount', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {'Wolf-Base': -1}; - user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { - expect(response.message).to.eql(shared.i18n.t('messageHatched')); - expect(user.items.pets).to.eql({'Wolf-Base': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - done(); - }); - }); - }); - }); -}); diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js deleted file mode 100644 index dbb6f877e2..0000000000 --- a/test/common/user.ops.hourglassPurchase.test.js +++ /dev/null @@ -1,122 +0,0 @@ -let shared = require('../../common/script/index.js'); - -describe('user.ops.hourglassPurchase', () => { - let user; - - beforeEach(() => { - user = { - items: { - pets: {}, - mounts: {}, - hatchingPotions: {}, - }, - purchased: { - plan: { - consecutive: { - trinkets: 0, - }, - }, - }, - }; - - shared.wrap(user); - }); - - context('Time Travel Stable', () => { - context('failure conditions', () => { - it('does not allow purchase of unsupported item types', (done) => { - user.ops.hourglassPurchase({params: {type: 'hatchingPotions', key: 'Base'}}, (response) => { - expect(response.message).to.eql('Item type not supported for purchase with Mystic Hourglass. Allowed types: pets,mounts'); - expect(user.items.hatchingPotions).to.eql({}); - done(); - }); - }); - - it('does not grant pets without Mystic Hourglasses', (done) => { - user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); - expect(user.items.pets).to.eql({}); - done(); - }); - }); - - it('does not grant mounts without Mystic Hourglasses', (done) => { - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); - expect(user.items.mounts).to.eql({}); - done(); - }); - }); - - it('does not grant pet that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.pets = { - 'MantisShrimp-Base': true, - }; - - user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('Pet already owned.'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - }); - }); - - it('does not grant mount that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.mounts = { - 'MantisShrimp-Base': true, - }; - - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('Mount already owned.'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - }); - }); - - it('does not grant pet that is not part of the Time Travel Stable', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - - user.ops.hourglassPurchase({params: {type: 'pets', key: 'Wolf-Veteran'}}, (response) => { - expect(response.message).to.eql('Pet not available for purchase with Mystic Hourglass.'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - }); - }); - - it('does not grant mount that is not part of the Time Travel Stable', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'Orca-Base'}}, (response) => { - expect(response.message).to.eql('Mount not available for purchase with Mystic Hourglass.'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - }); - }); - }); - - context('successful purchases', () => { - it('buys a pet', (done) => { - user.purchased.plan.consecutive.trinkets = 2; - - user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); - done(); - }); - }); - - it('buys a mount', (done) => { - user.purchased.plan.consecutive.trinkets = 2; - - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { - expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); - done(); - }); - }); - }); - }); -}); diff --git a/test/common/user.ops.test.js b/test/common/user.ops.test.js deleted file mode 100644 index f4e4bb82c9..0000000000 --- a/test/common/user.ops.test.js +++ /dev/null @@ -1,34 +0,0 @@ -let shared = require('../../common/script/index.js'); - -describe('user.ops', () => { - let user; - - beforeEach(() => { - user = { - items: { - gear: { }, - special: { }, - }, - achievements: { }, - flags: { }, - }; - - shared.wrap(user); - }); - - describe('readCard', () => { - it('removes card from invitation array', () => { - user.items.special.valentineReceived = ['Leslie']; - user.ops.readCard({ params: { cardType: 'valentine' } }); - - expect(user.items.special.valentineReceived).to.be.empty; - }); - - it('removes the first card from invitation array', () => { - user.items.special.valentineReceived = ['Leslie', 'Vicky']; - user.ops.readCard({ params: { cardType: 'valentine' } }); - - expect(user.items.special.valentineReceived).to.eql(['Vicky']); - }); - }); -}); diff --git a/test/helpers/api-integration/api-classes.js b/test/helpers/api-integration/api-classes.js index 3fe7adbca7..f584826520 100644 --- a/test/helpers/api-integration/api-classes.js +++ b/test/helpers/api-integration/api-classes.js @@ -4,7 +4,7 @@ import { requester } from './requester'; import { getDocument as getDocumentFromMongo, updateDocument as updateDocumentInMongo, -} from './mongo'; +} from '../mongo'; import { assign, each, @@ -59,6 +59,29 @@ export class ApiGroup extends ApiObject { this._docType = 'groups'; } + + async addChat (chat) { + let group = this; + + if (!chat) { + chat = { + id: 'Test_ID', + text: 'Test message', + flagCount: 0, + timestamp: Date(), + likes: {}, + flags: {}, + uuid: group.leader, + contributor: {}, + backer: {}, + user: group.leader, + }; + } + + let update = { chat }; + + return await this.update(update); + } } export class ApiChallenge extends ApiObject { diff --git a/test/helpers/api-integration/mongo.js b/test/helpers/api-integration/mongo.js deleted file mode 100644 index 52be5263b4..0000000000 --- a/test/helpers/api-integration/mongo.js +++ /dev/null @@ -1,92 +0,0 @@ -/* eslint-disable no-use-before-define */ - -import { MongoClient as mongo } from 'mongodb'; - -const DB_URI = 'mongodb://localhost/habitrpg_test'; - -// Useful for checking things that have been deleted, -// but you no longer have access to, -// like private parties or users -export async function checkExistence (collectionName, id) { - let db = await connectToMongo(); - - return new Promise((resolve, reject) => { - let collection = db.collection(collectionName); - - collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { - if (findError) return reject(findError); - - let exists = docs.length > 0; - - db.close(); - resolve(exists); - }); - }); -} - -// Specifically helpful for the GET /groups tests, -// resets the db to an empty state and creates a tavern document -export async function resetHabiticaDB () { - let db = await connectToMongo(); - - return new Promise((resolve, reject) => { - db.dropDatabase((dbErr) => { - if (dbErr) return reject(dbErr); - let groups = db.collection('groups'); - - groups.insertOne({ - _id: 'habitrpg', - chat: [], - leader: '9', - name: 'HabitRPG', - type: 'guild', - privacy: 'public', - members: [], - }, (insertErr) => { - if (insertErr) return reject(insertErr); - - db.close(); - resolve(); - }); - }); - }); -} - -export async function updateDocument (collectionName, doc, update) { - let db = await connectToMongo(); - - let collection = db.collection(collectionName); - - return new Promise((resolve) => { - collection.updateOne({ _id: doc._id }, { $set: update }, (updateErr) => { - if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`); - db.close(); - resolve(); - }); - }); -} - -export async function getDocument (collectionName, doc) { - let db = await connectToMongo(); - - let collection = db.collection(collectionName); - - return new Promise((resolve) => { - collection.findOne({ _id: doc._id }, (lookupErr, found) => { - if (lookupErr) throw new Error(`Error looking up ${collectionName}: ${lookupErr}`); - db.close(); - resolve(found); - }); - }); -} - -export function connectToMongo () { - return new Promise((resolve, reject) => { - mongo.connect(DB_URI, (err, db) => { - if (err) return reject(err); - - resolve(db); - }); - }); -} - diff --git a/test/helpers/api-integration/requester.js b/test/helpers/api-integration/requester.js index 209d312ba1..312942c194 100644 --- a/test/helpers/api-integration/requester.js +++ b/test/helpers/api-integration/requester.js @@ -1,14 +1,18 @@ /* eslint-disable no-use-before-define */ import superagent from 'superagent'; +import nconf from 'nconf'; +import { isEmpty, cloneDeep } from 'lodash'; -const API_TEST_SERVER_PORT = 3003; +const API_TEST_SERVER_PORT = nconf.get('PORT'); let apiVersion; -// Sets up an abject that can make all REST requests +// Sets up an object that can make all REST requests // If a user is passed in, the uuid and api token of // the user are used to make the requests -export function requester (user = {}, additionalSets) { +export function requester (user = {}, additionalSets = {}) { + additionalSets = cloneDeep(additionalSets); // cloning because it could be modified later to set cookie + return { get: _requestMaker(user, 'get', additionalSets), post: _requestMaker(user, 'post', additionalSets), @@ -21,12 +25,21 @@ requester.setApiVersion = (version) => { apiVersion = version; }; -function _requestMaker (user, method, additionalSets) { +function _requestMaker (user, method, additionalSets = {}) { if (!apiVersion) throw new Error('apiVersion not set'); return (route, send, query) => { return new Promise((resolve, reject) => { - let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/${apiVersion}${route}`) + let url = `http://localhost:${API_TEST_SERVER_PORT}`; + + // do not prefix with api/apiVersion requests to top level routes like dataexport, payments and emails + if (route.indexOf('/email') === 0 || route.indexOf('/export') === 0 || route.indexOf('/paypal') === 0 || route.indexOf('/amazon') === 0 || route.indexOf('/stripe') === 0) { + url += `${route}`; + } else { + url += `/api/${apiVersion}${route}`; + } + + let request = superagent[method](url) .accept('application/json'); if (user && user._id && user.apiToken) { @@ -35,7 +48,7 @@ function _requestMaker (user, method, additionalSets) { .set('x-api-key', user.apiToken); } - if (additionalSets) { + if (!isEmpty(additionalSets)) { request.set(additionalSets); } @@ -46,14 +59,58 @@ function _requestMaker (user, method, additionalSets) { if (err) { if (!err.response) return reject(err); - return reject({ - code: err.status, - text: err.response.body.err, - }); + let parsedError = _parseError(err); + + return reject(parsedError); } - resolve(response.body); + resolve(_parseRes(response)); }); }); }; } + +function _parseRes (res) { + let contentType = res.headers['content-type'] || ''; + let contentDisposition = res.headers['content-disposition'] || ''; + + if (contentType.indexOf('json') === -1) { // not a json response + return res.text; + } + + if (contentDisposition.indexOf('attachment') !== -1) { + return res.body; + } + + if (apiVersion === 'v2') { + return res.body; + } else if (apiVersion === 'v3') { + if (res.body.message) { + return { + data: res.body.data, + message: res.body.message, + }; + } else { + return res.body.data; + } + } +} + +function _parseError (err) { + let parsedError; + + if (apiVersion === 'v2') { + parsedError = { + code: err.status, + text: err.response.body.err, + }; + } else if (apiVersion === 'v3') { + parsedError = { + code: err.status, + error: err.response.body.error, + message: err.response.body.message, + }; + } + + return parsedError; +} diff --git a/test/helpers/api-integration/translate.js b/test/helpers/api-integration/translate.js index 4507d8fceb..3ef7d68541 100644 --- a/test/helpers/api-integration/translate.js +++ b/test/helpers/api-integration/translate.js @@ -1,5 +1,5 @@ import i18n from '../../../common/script/i18n'; -i18n.translations = require('../../../website/src/libs/i18n.js').translations; +i18n.translations = require('../../../website/server/libs/api-v3/i18n').translations; // Use this to verify error messages returned by the server // That way, if the translated string changes, the test @@ -16,4 +16,3 @@ export function translate (key, variables) { return translatedString; } - diff --git a/test/helpers/api-integration/v2/index.js b/test/helpers/api-integration/v2/index.js index 1d0baeea4b..8828b9b8ed 100644 --- a/test/helpers/api-integration/v2/index.js +++ b/test/helpers/api-integration/v2/index.js @@ -4,5 +4,5 @@ requester.setApiVersion('v2'); export { requester }; export { translate } from '../translate'; -export { checkExistence, resetHabiticaDB } from '../mongo'; +export { checkExistence, resetHabiticaDB } from '../../mongo'; export * from './object-generators'; diff --git a/test/helpers/api-integration/v2/object-generators.js b/test/helpers/api-integration/v2/object-generators.js index e10a4daa9a..3cd3c7d1d7 100644 --- a/test/helpers/api-integration/v2/object-generators.js +++ b/test/helpers/api-integration/v2/object-generators.js @@ -1,7 +1,8 @@ import { times, + map, } from 'lodash'; -import Q from 'q'; +import Bluebird from 'bluebird'; import { v4 as generateUUID } from 'uuid'; import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; import { requester } from '../requester'; @@ -41,11 +42,29 @@ export async function generateGroup (leader, details = {}, update = {}) { details.privacy = details.privacy || 'private'; details.name = details.name || 'test group'; + let members; + + if (details.members) { + members = details.members; + delete details.members; + } + let group = await leader.post('/groups', details); let apiGroup = new ApiGroup(group); - await apiGroup.update(update); + const groupMembershipTypes = { + party: { 'party._id': group._id}, + guild: { guilds: [group._id] }, + }; + await Bluebird.all( + map(members, (member) => { + return member.update(groupMembershipTypes[group.type]); + }) + ); + + await apiGroup.update(update); + await apiGroup.sync(); return apiGroup; } @@ -72,20 +91,20 @@ export async function createAndPopulateGroup (settings = {}) { let groupLeader = await generateUser(leaderDetails); let group = await generateGroup(groupLeader, groupDetails); - let members = await Q.all( + const groupMembershipTypes = { + party: { 'party._id': group._id}, + guild: { guilds: [group._id] }, + }; + + let members = await Bluebird.all( times(numberOfMembers, () => { - return generateUser(); + return generateUser(groupMembershipTypes[group.type]); }) ); - let memberIds = members.map((member) => { - return member._id; - }); - memberIds.push(groupLeader._id); + await group.update({ memberCount: numberOfMembers + 1}); - await group.update({ members: memberIds }); - - let invitees = await Q.all( + let invitees = await Bluebird.all( times(numberOfInvites, () => { return generateUser(); }) @@ -97,7 +116,7 @@ export async function createAndPopulateGroup (settings = {}) { }); }); - await Q.all(invitationPromises); + await Bluebird.all(invitationPromises); return { groupLeader, diff --git a/test/helpers/api-integration/v3/index.js b/test/helpers/api-integration/v3/index.js new file mode 100644 index 0000000000..6ae15d7ca6 --- /dev/null +++ b/test/helpers/api-integration/v3/index.js @@ -0,0 +1,11 @@ +/* eslint-disable no-use-before-define */ + +// Import requester function, set it up for v2, export it +import { requester } from '../requester'; +requester.setApiVersion('v3'); +export { requester }; + +export { translate } from '../translate'; +export { checkExistence, resetHabiticaDB } from '../../mongo'; +export * from './object-generators'; +export { sleep } from '../../sleep'; diff --git a/test/helpers/api-integration/v3/object-generators.js b/test/helpers/api-integration/v3/object-generators.js new file mode 100644 index 0000000000..f257c3543f --- /dev/null +++ b/test/helpers/api-integration/v3/object-generators.js @@ -0,0 +1,157 @@ +import { + times, +} from 'lodash'; +import Bluebird from 'bluebird'; +import { v4 as generateUUID } from 'uuid'; +import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; +import { requester } from '../requester'; +import * as Tasks from '../../../../website/server/models/task'; + +// Creates a new user and returns it +// If you need the user to have specific requirements, +// such as a balance > 0, just pass in the adjustment +// to the update object. If you want to adjust a nested +// paramter, 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 = generateUUID(); + let password = 'password'; + let email = `${username}@example.com`; + + let user = await requester().post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + + let apiUser = new ApiUser(user); + + await apiUser.update(update); + + return apiUser; +} + +export async function generateHabit (update = {}) { + let type = 'habit'; + let task = new Tasks[type](update); + await task.save({ validateBeforeSave: false }); + return task; +} + +export async function generateDaily (update = {}) { + let type = 'daily'; + let task = new Tasks[type](update); + await task.save({ validateBeforeSave: false }); + return task; +} + +export async function generateReward (update = {}) { + let type = 'reward'; + let task = new Tasks[type](update); + await task.save({ validateBeforeSave: false }); + return task; +} + +export async function generateTodo (update = {}) { + let type = 'todo'; + let task = new Tasks[type](update); + await task.save({ validateBeforeSave: false }); + return task; +} + +// Generates a new group. Requires a user object, which +// will will become the groups leader. Takes a details argument +// for the initial group creation and an update argument which +// will update the group via the db +export async function generateGroup (leader, details = {}, update = {}) { + details.type = details.type || 'party'; + details.privacy = details.privacy || 'private'; + details.name = details.name || 'test group'; + + let group = await leader.post('/groups', details); + let apiGroup = new ApiGroup(group); + + await apiGroup.update(update); + + return apiGroup; +} + +// This is generate group + the ability to create +// real users to populate it. The settings object +// takes in: +// members: Number - the number of group members to create. Defaults to 0. +// inivtes: Number - the number of users to create and invite to the group. Defaults to 0. +// groupDetails: Object - how to initialize the group +// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user +// can create the group +// +// Returns an object with +// members: an array of user objects that correspond to the members of the group +// invitees: an array of user objects that correspond to the invitees of the group +// leader: the leader user object +// group: the group object +export async function createAndPopulateGroup (settings = {}) { + let numberOfMembers = settings.members || 0; + let numberOfInvites = settings.invites || 0; + let groupDetails = settings.groupDetails; + let leaderDetails = settings.leaderDetails || { balance: 10 }; + + let groupLeader = await generateUser(leaderDetails); + let group = await generateGroup(groupLeader, groupDetails); + + const groupMembershipTypes = { + party: { 'party._id': group._id}, + guild: { guilds: [group._id] }, + }; + + let members = await Bluebird.all( + times(numberOfMembers, () => { + return generateUser(groupMembershipTypes[group.type]); + }) + ); + + await group.update({ memberCount: numberOfMembers + 1}); + + let invitees = await Bluebird.all( + times(numberOfInvites, () => { + return generateUser(); + }) + ); + + let invitationPromises = invitees.map((invitee) => { + return groupLeader.post(`/groups/${group._id}/invite`, { + uuids: [invitee._id], + }); + }); + + await Bluebird.all(invitationPromises); + + return { + groupLeader, + group, + members, + invitees, + }; +} + +// Generates a new challenge. Requires an ApiUser object and a +// group-like object (can just be {_id: 'your-group-id'}). The group +// will will become the group that owns the challenge. It takes an +// optional details argument for the initial challenge creation and an +// optional update argument which will update the challenge via the db +export async function generateChallenge (challengeCreator, group, details = {}, update = {}) { + details.group = group._id; + details.name = details.name || 'a challenge'; + details.shortName = details.shortName || 'aChallenge'; + details.prize = details.prize || 0; + details.official = details.official || false; + + let challenge = await challengeCreator.post('/challenges', details); + let apiChallenge = new ApiChallenge(challenge); + + await apiChallenge.update(update); + + return apiChallenge; +} diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js new file mode 100644 index 0000000000..ec20ae763e --- /dev/null +++ b/test/helpers/api-unit.helper.js @@ -0,0 +1,104 @@ +import '../../website/server/libs/api-v3/i18n'; +import mongoose from 'mongoose'; +import { defaultsDeep as defaults } from 'lodash'; +import { model as User } from '../../website/server/models/user'; +import { model as Group } from '../../website/server/models/group'; +import mongo from './mongo'; // eslint-disable-line +import moment from 'moment'; +import i18n from '../../common/script/i18n'; +import * as Tasks from '../../website/server/models/task'; + +afterEach((done) => { + sandbox.restore(); + mongoose.connection.db.dropDatabase(done); +}); + +export { sleep } from './sleep'; + +export function generateUser (options = {}) { + return new User(options).toObject(); +} + +export function generateGroup (options = {}) { + return new Group(options).toObject(); +} + +export function generateRes (options = {}) { + let defaultRes = { + render: sandbox.stub(), + send: sandbox.stub(), + status: sandbox.stub().returnsThis(), + sendStatus: sandbox.stub().returnsThis(), + json: sandbox.stub(), + locals: { + user: generateUser(options.localsUser), + group: generateGroup(options.localsGroup), + }, + set: sandbox.stub(), + t (string) { + return i18n.t(string); + }, + }; + + return defaults(options, defaultRes); +} + +export function generateReq (options = {}) { + let defaultReq = { + body: {}, + query: {}, + headers: {}, + header: sandbox.stub().returns(null), + }; + + return defaults(options, defaultReq); +} + +export function generateNext (func) { + return func || sandbox.stub(); +} + +export function generateHistory (days) { + let history = []; + let now = Number(moment().toDate()); + + while (days > 0) { + history.push({ + value: days, + date: Number(moment(now).subtract(days, 'days').toDate()), + }); + days--; + } + + return history; +} + +export function generateTodo (user) { + let todo = { + text: 'test todo', + type: 'todo', + value: 0, + completed: false, + }; + + let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap + task.userId = user._id; + task.save(); + + return task; +} + +export function generateDaily (user) { + let daily = { + text: 'test daily', + type: 'daily', + value: 0, + completed: false, + }; + + let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap + task.userId = user._id; + task.save(); + + return task; +} diff --git a/test/helpers/api-v3-integration.helper.js b/test/helpers/api-v3-integration.helper.js new file mode 100644 index 0000000000..4f703efdd6 --- /dev/null +++ b/test/helpers/api-v3-integration.helper.js @@ -0,0 +1 @@ +module.exports = require('./api-integration/v3'); diff --git a/test/helpers/common.helper.js b/test/helpers/common.helper.js index 96064b8142..4cd82ca4b4 100644 --- a/test/helpers/common.helper.js +++ b/test/helpers/common.helper.js @@ -1,13 +1,13 @@ import mongoose from 'mongoose'; import { wrap as wrapUser } from '../../common/script/index'; -import { model as User } from '../../website/src/models/user'; +import { model as User } from '../../website/server/models/user'; import { DailySchema, HabitSchema, RewardSchema, TodoSchema, -} from '../../website/src/models/task'; +} from '../../website/server/models/task'; export function generateUser (options = {}) { let user = new User(options).toObject(); diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js index be87f06696..c1ac3c5657 100644 --- a/test/helpers/content.helper.js +++ b/test/helpers/content.helper.js @@ -1,7 +1,6 @@ require('./globals.helper'); - import i18n from '../../common/script/i18n'; -i18n.translations = require('../../website/src/libs/i18n.js').translations; +i18n.translations = require('../../website/server/libs/api-v3/i18n').translations; export const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.'; export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; diff --git a/test/helpers/globals.helper.js b/test/helpers/globals.helper.js index 21f60b7f46..253d07e1d4 100644 --- a/test/helpers/globals.helper.js +++ b/test/helpers/globals.helper.js @@ -1,10 +1,39 @@ /* eslint-disable no-undef */ +/* eslint-disable global-require */ +/* eslint-disable no-process-env */ + +import Bluebird from 'bluebird'; + //------------------------------ // Global modules //------------------------------ - global._ = require('lodash'); global.chai = require('chai'); chai.use(require('sinon-chai')); chai.use(require('chai-as-promised')); global.expect = chai.expect; +global.sinon = require('sinon'); +global.sandbox = sinon.sandbox.create(); +global.Promise = Bluebird; + +import nconf from 'nconf'; +import mongoose from 'mongoose'; + +//------------------------------ +// Load nconf for unit tests +//------------------------------ +if (process.env.LOAD_SERVER === '0') { // when the server is in a different process we simply connect to mongoose + require('../../website/server/libs/api-v3/setupNconf')('./config.json'); + // Use Q promises instead of mpromise in mongoose + mongoose.Promise = Bluebird; + mongoose.connect(nconf.get('TEST_DB_URI')); +} else { // When running tests and the server in the same process + require('../../website/server/libs/api-v3/setupNconf')('./config.json.example'); + nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI')); + nconf.set('NODE_ENV', 'test'); + nconf.set('IS_TEST', true); + // We require src/server and npt src/index because + // 1. nconf is already setup + // 2. we don't need clustering + require('../../website/server/server'); +} diff --git a/test/helpers/mongo.js b/test/helpers/mongo.js new file mode 100644 index 0000000000..9ad5cc8700 --- /dev/null +++ b/test/helpers/mongo.js @@ -0,0 +1,110 @@ +import mongoose from 'mongoose'; +import { TAVERN_ID } from '../../website/server/models/group'; + +// Useful for checking things that have been deleted, +// but you no longer have access to, +// like private parties or users +export async function checkExistence (collectionName, id) { + return new Promise((resolve, reject) => { + let collection = mongoose.connection.db.collection(collectionName); + + collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { + if (findError) return reject(findError); + + let exists = docs.length > 0; + + resolve(exists); + }); + }); +} + +// Specifically helpful for the GET /groups tests, +// resets the db to an empty state and creates a tavern document +export async function resetHabiticaDB () { + return new Promise((resolve, reject) => { + mongoose.connection.db.dropDatabase((dbErr) => { + if (dbErr) return reject(dbErr); + let groups = mongoose.connection.db.collection('groups'); + let users = mongoose.connection.db.collection('users'); + + users.count({_id: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0'}, (err, count) => { + if (err) return reject(err); + if (count > 0) return resolve(); + + // create the leader for the tavern + users.insertOne({ + _id: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', + apiToken: TAVERN_ID, + auth: { + local: { + username: 'username', + lowerCaseUsername: 'username', + email: 'username@email.com', + salt: 'salt', + hashed_password: 'hashed_password', // eslint-disable-line camelcase + }, + }, + }, (insertErr) => { + if (insertErr) return reject(insertErr); + + // For some mysterious reason after a dropDatabase there can still be a group... + groups.count({_id: TAVERN_ID}, (err2, count2) => { + if (err2) return reject(err2); + if (count2 > 0) return resolve(); + + groups.insertOne({ + _id: TAVERN_ID, + chat: [], + leader: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', // Siena Leslie + name: 'HabitRPG', + type: 'guild', + privacy: 'public', + }, (insertErr2) => { + if (insertErr2) return reject(insertErr2); + + resolve(); + }); + }); + }); + }); + }); + }); +} + +export async function updateDocument (collectionName, doc, update) { + let collection = mongoose.connection.db.collection(collectionName); + + return new Promise((resolve) => { + collection.updateOne({ _id: doc._id }, { $set: 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); + + return new Promise((resolve) => { + collection.findOne({ _id: doc._id }, (lookupErr, found) => { + if (lookupErr) throw new Error(`Error looking up ${collectionName}: ${lookupErr}`); + resolve(found); + }); + }); +} + +before((done) => { + mongoose.connection.on('open', (err) => { + if (err) return done(err); + resetHabiticaDB() + .then(() => done()) + .catch(done); + }); +}); + +after((done) => { + mongoose.connection.db.dropDatabase((err) => { + if (err) return done(err); + mongoose.connection.close(done); + }); +}); diff --git a/test/helpers/sleep.js b/test/helpers/sleep.js new file mode 100644 index 0000000000..f8dd9ab165 --- /dev/null +++ b/test/helpers/sleep.js @@ -0,0 +1,7 @@ +export async function sleep (seconds) { + let milliseconds = seconds * 1000; + + return new Promise((resolve) => { + setTimeout(resolve, milliseconds); + }); +} diff --git a/test/migrations/20150605_ultimate_achievement_backfill.coffee b/test/migrations/20150605_ultimate_achievement_backfill.coffee index b5646020b0..689b49cb67 100644 --- a/test/migrations/20150605_ultimate_achievement_backfill.coffee +++ b/test/migrations/20150605_ultimate_achievement_backfill.coffee @@ -2,7 +2,7 @@ TEST_DB = process.env.DB_NAME = 'habitrpg_migration_test' process.env.NODE_DB_URI = 'mongodb://localhost/' + TEST_DB -app = require('../../website/src/server') +app = require('../../website/server/server') sh = require('shelljs') runMigration = -> diff --git a/test/mocha.opts b/test/mocha.opts index a2323400d5..69ce4ab86d 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -6,5 +6,4 @@ --globals io -r babel-polyfill --compilers js:babel-register ---require test/api-legacy/api-helper --require ./test/helpers/globals.helper diff --git a/test/server_side/analytics.test.js b/test/server_side/analytics.test.js index 7cde7aede8..5257303242 100644 --- a/test/server_side/analytics.test.js +++ b/test/server_side/analytics.test.js @@ -30,7 +30,7 @@ describe('analytics', function() { }); describe('init', function() { - var analytics = rewire('../../website/src/libs/analytics'); + var analytics = rewire('../../website/server/libs/api-v2/analytics'); it('throws an error if no options are passed in', function() { expect(analytics).to.throw('No options provided'); @@ -62,7 +62,7 @@ describe('analytics', function() { describe('track', function() { var analyticsData, event_type; - var analytics = rewire('../../website/src/libs/analytics'); + var analytics = rewire('../../website/server/libs/api-v2/analytics'); var initializedAnalytics; beforeEach(function() { @@ -370,7 +370,7 @@ describe('analytics', function() { var purchaseData; - var analytics = rewire('../../website/src/libs/analytics'); + var analytics = rewire('../../website/server/libs/api-v2/analytics'); var initializedAnalytics; beforeEach(function() { diff --git a/test/server_side/controllers/groups.test.js b/test/server_side/controllers/groups.test.js index 353fb581fe..bf12df321b 100644 --- a/test/server_side/controllers/groups.test.js +++ b/test/server_side/controllers/groups.test.js @@ -3,12 +3,12 @@ var chai = require("chai"); chai.use(require("sinon-chai")); var expect = chai.expect; -var Q = require('q'); -var Group = require('../../../website/src/models/group').model; -var groupsController = require('../../../website/src/controllers/api-v2/groups'); +var Bluebird = require('bluebird'); +var Group = require('../../../website/server/models/group').model; +var groupsController = require('../../../website/server/controllers/api-v2/groups'); describe('Groups Controller', function() { - var utils = require('../../../website/src/libs/utils'); + var utils = require('../../../website/server/libs/api-v2/utils'); describe('#invite', function() { var res, req, user, group; @@ -69,7 +69,7 @@ describe('Groups Controller', function() { }); context('emails', function() { - var EmailUnsubscription = require('../../../website/src/models/emailUnsubscription').model; + var EmailUnsubscription = require('../../../website/server/models/emailUnsubscription').model; var execStub, selectStub; beforeEach(function() { @@ -301,7 +301,7 @@ describe('Groups Controller', function() { }); afterEach(function() { - Q.all.restore(); + Promise.all.restore(); }); context('error conditions', function() { @@ -342,7 +342,7 @@ describe('Groups Controller', function() { }); it('sends 500 if group cannot save', function() { - Q.all.returns({ + Promise.all.returns({ done: sinon.stub().callsArgWith(1, {err: 'save error'}) }); var nextSpy = sinon.spy(); diff --git a/test/server_side/controllers/user.test.js b/test/server_side/controllers/user.test.js index 2be7f7215a..59589df511 100644 --- a/test/server_side/controllers/user.test.js +++ b/test/server_side/controllers/user.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var userController = rewire('../../../website/src/controllers/api-v2/user'); +var userController = rewire('../../../website/server/controllers/api-v2/user'); describe('User Controller', function() { @@ -359,7 +359,7 @@ describe('User Controller', function() { }); it('sends webhooks', function() { - var webhook = require('../../../website/src/libs/webhook'); + var webhook = require('../../../website/server/libs/webhook'); sinon.spy(webhook, 'sendTaskWebhook'); userController.score(req, res); @@ -384,7 +384,7 @@ describe('User Controller', function() { }); context('save callback dealing with non challenge tasks', function() { - var Challenge = require('../../../website/src/models/challenge').model; + var Challenge = require('../../../website/server/models/challenge').model; beforeEach(function() { user.save.yields(null, user); @@ -446,7 +446,7 @@ describe('User Controller', function() { }); context('save callback dealing with challenge tasks', function() { - var Challenge = require('../../../website/src/models/challenge').model; + var Challenge = require('../../../website/server/models/challenge').model; var chal; beforeEach(function() { diff --git a/test/server_side/webhooks.test.js b/test/server_side/webhooks.test.js index ef6636bf93..621d0daba3 100644 --- a/test/server_side/webhooks.test.js +++ b/test/server_side/webhooks.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var webhook = rewire('../../website/src/libs/webhook'); +var webhook = rewire('../../website/server/libs/api-v2/webhook'); describe('webhooks', function() { var postSpy; diff --git a/test/spec/chatServicesSpec.js b/test/spec/chatServicesSpec.js deleted file mode 100644 index e65c36be68..0000000000 --- a/test/spec/chatServicesSpec.js +++ /dev/null @@ -1,90 +0,0 @@ -'use strict'; - -describe('Chat Service', function() { - var $httpBackend, $http, chat, user; - - beforeEach(function() { - module(function($provide) { - var usr = specHelper.newUser(); - $provide.value('User', {user:usr}); - }); - - inject(function(_$httpBackend_, Chat, User) { - $httpBackend = _$httpBackend_; - chat = Chat; - user = User; - }); - }); - - describe('utils', function() { - it('calls post chat endpoint', function() { - var payload = { - gid: 'habitrpg', - message: 'Chat', - previousMsg: 'previous-msg-id' - } - - $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat?message=Chat&previousMsg=previous-msg-id').respond(); - chat.utils.postChat(payload, undefined); - $httpBackend.flush(); - }); - - it('calls like chat endpoint', function() { - var payload = { - gid: 'habitrpg', - messageId: 'msg-id' - } - - $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/like').respond(); - chat.utils.like(payload, undefined); - $httpBackend.flush(); - }); - - it('calls delete chat endpoint', function() { - var payload = { - gid: 'habitrpg', - messageId: 'msg-id' - } - - $httpBackend.expectDELETE('/api/v2/groups/habitrpg/chat/msg-id').respond(); - chat.utils.deleteChatMessage(payload, undefined); - $httpBackend.flush(); - }); - - it('calls flag chat endpoint', function() { - var payload = { - gid: 'habitrpg', - messageId: 'msg-id' - } - - $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/flag').respond(); - chat.utils.flagChatMessage(payload, undefined); - $httpBackend.flush(); - }); - - it('calls clear flags endpoint', function() { - var payload = { - gid: 'habitrpg', - messageId: 'msg-id' - } - - $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/clearflags').respond(); - chat.utils.clearFlagCount(payload, undefined); - $httpBackend.flush(); - }); - }); - - describe('seenMessage(gid)', function() { - it('calls chat seen endpoint', function() { - $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/seen').respond(); - chat.seenMessage('habitrpg'); - $httpBackend.flush(); - }); - - it('removes newMessages for a specific guild from user object', function() { - user.user.newMessages = {habitrpg: "foo"}; - chat.seenMessage('habitrpg'); - expect(user.user.newMessages.habitrpg).to.not.exist; - }); - }); -}); diff --git a/test/spec/controllers/authCtrlSpec.js b/test/spec/controllers/authCtrlSpec.js index b1f87d4d91..75ea898fca 100644 --- a/test/spec/controllers/authCtrlSpec.js +++ b/test/spec/controllers/authCtrlSpec.js @@ -25,7 +25,7 @@ describe('Auth Controller', function() { describe('logging in', function() { it('should log in users with correct uname / pass', function() { - $httpBackend.expectPOST('/api/v2/user/auth/local').respond({id: 'abc', token: 'abc'}); + $httpBackend.expectPOST('/api/v3/user/auth/local/login').respond({data: {id: 'abc', apiToken: 'abc'}}); scope.auth(); $httpBackend.flush(); expect(user.authenticate).to.be.calledOnce; @@ -33,7 +33,7 @@ describe('Auth Controller', function() { }); it('should not log in users with incorrect uname / pass', function() { - $httpBackend.expectPOST('/api/v2/user/auth/local').respond(404, ''); + $httpBackend.expectPOST('/api/v3/user/auth/local/login').respond(404, ''); scope.auth(); $httpBackend.flush(); expect(user.authenticate).to.not.be.called; diff --git a/test/spec/controllers/challengesCtrlSpec.js b/test/spec/controllers/challengesCtrlSpec.js index 0ebebeba44..ec63ee20c4 100644 --- a/test/spec/controllers/challengesCtrlSpec.js +++ b/test/spec/controllers/challengesCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Challenges Controller', function() { - var rootScope, scope, user, User, ctrl, groups, members, notification, state; + var rootScope, scope, user, User, ctrl, groups, members, notification, state, challenges, tasks, tavernId; beforeEach(function() { module(function($provide) { @@ -14,7 +14,7 @@ describe('Challenges Controller', function() { $provide.value('User', User); }); - inject(function($rootScope, $controller, _$state_, _Groups_, _Members_, _Notification_){ + inject(function($rootScope, $controller, _$state_, _Groups_, _Members_, _Notification_, _Challenges_, _Tasks_, _TAVERN_ID_){ scope = $rootScope.$new(); rootScope = $rootScope; @@ -23,10 +23,13 @@ describe('Challenges Controller', function() { ctrl = $controller('ChallengesCtrl', {$scope: scope, User: User}); + challenges = _Challenges_; + tasks = _Tasks_; groups = _Groups_; members = _Members_; notification = _Notification_; state = _$state_; + tavernId = _TAVERN_ID_; }); }); @@ -39,30 +42,36 @@ describe('Challenges Controller', function() { description: 'You are the owner and member', leader: user._id, members: [user], - _isMember: true + _isMember: true, + _id: 'ownMem-id', }); ownNotMem = specHelper.newChallenge({ description: 'You are the owner, but not a member', leader: user._id, members: [], - _isMember: false + _isMember: false, + _id: 'ownNotMem-id', }); notOwnMem = specHelper.newChallenge({ description: 'Not owner but a member', leader: {_id:"test"}, members: [user], - _isMember: true + _isMember: true, + _id: 'notOwnMem-id', }); notOwnNotMem = specHelper.newChallenge({ description: 'Not owner or member', leader: {_id:"test"}, members: [], - _isMember: false + _isMember: false, + _id: 'notOwnNotMem-id', }); + user.challenges = [ownMem._id, notOwnMem._id]; + scope.search = { group: _.transform(groups, function(m,g){m[g._id]=true;}) }; @@ -209,6 +218,17 @@ describe('Challenges Controller', function() { }); describe('addTask', function() { + var challenge; + + beforeEach(function () { + challenge = specHelper.newChallenge({ + description: 'You are the owner and member', + leader: user._id, + members: [user], + _isMember: true + }); + }); + it('adds default task to array', function() { var taskArray = []; var listDef = { @@ -216,26 +236,27 @@ describe('Challenges Controller', function() { type: 'todo' } - scope.addTask(taskArray, listDef); + scope.addTask(taskArray, listDef, challenge); - expect(taskArray.length).to.eql(1); - expect(taskArray[0].text).to.eql('new todo text'); - expect(taskArray[0].type).to.eql('todo'); + expect(challenge['todos'].length).to.eql(1); + expect(challenge['todos'][0].text).to.eql('new todo text'); + expect(challenge['todos'][0].type).to.eql('todo'); }); it('adds the task to the front of the array', function() { var previousTask = specHelper.newTodo({ text: 'previous task' }); - var taskArray = [previousTask]; + var taskArray = []; + challenge['todos'] = [previousTask]; var listDef = { newTask: 'new todo', type: 'todo' } - scope.addTask(taskArray, listDef); + scope.addTask(taskArray, listDef, challenge); - expect(taskArray.length).to.eql(2); - expect(taskArray[0].text).to.eql('new todo'); - expect(taskArray[1].text).to.eql('previous task'); + expect(challenge['todos'].length).to.eql(2); + expect(challenge['todos'][0].text).to.eql('new todo'); + expect(challenge['todos'][1].text).to.eql('previous task'); }); it('removes text from new task input box', function() { @@ -245,7 +266,7 @@ describe('Challenges Controller', function() { type: 'todo' } - scope.addTask(taskArray, listDef); + scope.addTask(taskArray, listDef, challenge); expect(listDef.newTask).to.not.exist; }); @@ -260,31 +281,37 @@ describe('Challenges Controller', function() { }); describe('removeTask', function() { - var task, list; + var task, challenge; beforeEach(function() { sandbox.stub(window, 'confirm'); task = specHelper.newTodo(); - list = [task]; + challenge = specHelper.newChallenge({ + description: 'You are the owner and member', + leader: user._id, + members: [user], + _isMember: true + }); + challenge['todos'] = [task]; }); it('asks user to confirm deletion', function() { - scope.removeTask(task, list); + scope.removeTask(task, challenge); expect(window.confirm).to.be.calledOnce; }); it('does not remove task from list if not confirmed', function() { window.confirm.returns(false); - scope.removeTask(task, list); + scope.removeTask(task, challenge); - expect(list).to.include(task); + expect(challenge['todos']).to.include(task); }); it('removes task from list', function() { window.confirm.returns(true); - scope.removeTask(task, list); + scope.removeTask(task, challenge); - expect(list).to.not.include(task); + expect(challenge['todos']).to.not.include(task); }); }); @@ -301,16 +328,23 @@ describe('Challenges Controller', function() { context('challenge owner interactions', function() { describe("save challenge", function() { - var alert; + var alert, createChallengeSpy, challengeResponse, taskChallengeCreateSpy; beforeEach(function(){ alert = sandbox.stub(window, "alert"); + createChallengeSpy = sinon.stub(challenges, 'createChallenge'); + challengeResponse = {data: {data: {_id: 'new-challenge'}}}; + createChallengeSpy.returns(Promise.resolve(challengeResponse)); + + taskChallengeCreateSpy = sinon.stub(tasks, 'createChallengeTasks'); + var taskResponse = {data: {data: []}}; + taskChallengeCreateSpy.returns(Promise.resolve(taskResponse)); }); - it("opens an alert box if challenge.group is not specified", function() - { + it("opens an alert box if challenge.group is not specified", function() { var challenge = specHelper.newChallenge({ name: 'Challenge without a group', + shortName: 'chal without group', group: null }); @@ -323,6 +357,7 @@ describe('Challenges Controller', function() { it("opens an alert box if isNew and user does not have enough gems", function() { var challenge = specHelper.newChallenge({ name: 'Challenge without enough gems', + shortName: 'chal without gem', prize: 5 }); @@ -334,81 +369,84 @@ describe('Challenges Controller', function() { }); it("saves the challenge if user does not have enough gems, but the challenge is not new", function() { + var updateChallengeSpy = sinon.spy(challenges, 'updateChallenge'); + var challenge = specHelper.newChallenge({ _id: 'challenge-has-id-so-its-not-new', name: 'Challenge without enough gems', + shortName: 'chal without gem', prize: 5, - $save: sandbox.spy() // stub $save }); scope.maxPrize = 0; scope.save(challenge); - expect(challenge.$save).to.be.calledOnce; + expect(updateChallengeSpy).to.be.calledOnce; expect(alert).to.not.be.called; }); it("saves the challenge if user has enough gems and challenge is new", function() { var challenge = specHelper.newChallenge({ name: 'Challenge without enough gems', + shortName: 'chal without gem', prize: 5, - $save: sandbox.spy() // stub $save }); scope.maxPrize = 5; scope.save(challenge); - expect(challenge.$save).to.be.calledOnce; + expect(createChallengeSpy).to.be.calledOnce; expect(alert).to.not.be.called; }); - it('saves challenge and then proceeds to detail page', function() { - var saveSpy = sandbox.stub(); - saveSpy.yields({_id: 'challenge-id'}); + it('saves challenge and then proceeds to detail page', function(done) { sandbox.stub(state, 'transitionTo'); var challenge = specHelper.newChallenge({ - $save: saveSpy // stub $save + name: 'Challenge', + shortName: 'chal', }); - scope.save(challenge); - - expect(state.transitionTo).to.be.calledOnce; - expect(state.transitionTo).to.be.calledWith( - 'options.social.challenges.detail', - { cid: 'challenge-id' }, - { - reload: true, inherit: false, notify: true - } - ); - }); - - it('saves new challenge and syncs User', function() { - var saveSpy = sandbox.stub(); - saveSpy.yields({_id: 'new-challenge'}); - - var challenge = specHelper.newChallenge({ - $save: saveSpy // stub $save - }); + setTimeout(function() { + expect(createChallengeSpy).to.be.calledOnce; + expect(state.transitionTo).to.be.calledWith( + 'options.social.challenges.detail', + { cid: 'new-challenge' }, + { + reload: true, inherit: false, notify: true + } + ); + done(); + }, 1000); scope.save(challenge); - - expect(User.sync).to.be.calledOnce; }); - it('saves new challenge and syncs User', function() { - var saveSpy = sandbox.stub(); - saveSpy.yields({_id: 'new-challenge'}); + it('saves new challenge and syncs User', function(done) { + var challenge = specHelper.newChallenge(); + challenge.shortName = 'chal'; + + setTimeout(function() { + expect(User.sync).to.be.calledOnce; + done(); + }, 1000); + + scope.save(challenge); + }); + + it('saves new challenge and syncs User', function(done) { sinon.stub(notification, 'text'); - var challenge = specHelper.newChallenge({ - $save: saveSpy // stub $save - }); + var challenge = specHelper.newChallenge(); + challenge.shortName = 'chal'; + + setTimeout(function() { + expect(notification.text).to.be.calledOnce; + expect(notification.text).to.be.calledWith(window.env.t('challengeCreated')); + done(); + }, 1000); scope.save(challenge); - - expect(notification.text).to.be.calledOnce; - expect(notification.text).to.be.calledWith(window.env.t('challengeCreated')); }); }); @@ -456,7 +494,7 @@ describe('Challenges Controller', function() { it('defaults to tavern if no group can be set as default', function() { scope.create(); - expect(scope.newChallenge.group).to.eql('habitrpg'); + expect(scope.newChallenge.group).to.eql(tavernId); }); it('calculates maxPrize', function() { @@ -478,7 +516,7 @@ describe('Challenges Controller', function() { expect(chal.todos).to.eql([]); expect(chal.rewards).to.eql([]); expect(chal.leader).to.eql('unique-user-id'); - expect(chal.group).to.eql('habitrpg'); + expect(chal.group).to.eql(tavernId); expect(chal.timestamp).to.be.greaterThan(0); expect(chal.official).to.eql(false); }); @@ -489,7 +527,7 @@ describe('Challenges Controller', function() { it('returns true if user has no gems', function() { User.user.balance = 0; scope.newChallenge = specHelper.newChallenge({ - group: 'habitrpg' + group: tavernId }); var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge(); @@ -499,7 +537,7 @@ describe('Challenges Controller', function() { it('returns false if user has gems', function() { User.user.balance = .25; scope.newChallenge = specHelper.newChallenge({ - group: 'habitrpg' + group: tavernId }); var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge(); @@ -627,15 +665,16 @@ describe('Challenges Controller', function() { context('User interactions', function() { describe('join', function() { - it('calls challenge.$join', function(){ + it('calls challenge join', function(){ + var joinChallengeSpy = sinon.spy(challenges, 'joinChallenge'); + var challenge = specHelper.newChallenge({ _id: 'challenge-to-join', - $join: sandbox.spy() }); scope.join(challenge); - expect(challenge.$join).to.be.calledOnce; + expect(joinChallengeSpy).to.be.calledOnce; }); }); @@ -669,7 +708,6 @@ describe('Challenges Controller', function() { describe('leave', function() { var challenge = specHelper.newChallenge({ _id: 'challenge-to-leave', - $leave: sandbox.spy() }); var clickEvent = { @@ -685,11 +723,12 @@ describe('Challenges Controller', function() { expect(scope.selectedChal).to.not.exist; }); - it('calls challenge.$leave when anything but cancel is chosen', function() { + it('calls challenge leave when anything but cancel is chosen', function() { + var leaveChallengeSpy = sinon.spy(challenges, 'leaveChallenge'); scope.clickLeave(challenge, clickEvent); - scope.leave('not-cancel'); - expect(challenge.$leave).to.be.calledOnce; + scope.leave('not-cancel', challenge); + expect(leaveChallengeSpy).to.be.calledOnce; }); }); }); @@ -698,31 +737,36 @@ describe('Challenges Controller', function() { beforeEach(function() { sandbox.stub(members, 'selectMember'); sandbox.stub(rootScope, 'openModal'); + members.selectMember.returns(Promise.resolve()); }); describe('sendMessageToChallengeParticipant', function() { - it('opens private-message modal', function() { - members.selectMember.yields(); + it('opens private-message modal', function(done) { scope.sendMessageToChallengeParticipant(user._id); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith( - 'private-message', - { controller: 'MemberModalCtrl' } - ); + setTimeout(function() { + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith( + 'private-message', + { controller: 'MemberModalCtrl' } + ); + done(); + }, 1000); }); }); describe('sendGiftToChallengeParticipant', function() { - it('opens send-gift modal', function() { - members.selectMember.yields(); + it('opens send-gift modal', function(done) { scope.sendGiftToChallengeParticipant(user._id); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith( - 'send-gift', - { controller: 'MemberModalCtrl' } - ); + setTimeout(function() { + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith( + 'send-gift', + { controller: 'MemberModalCtrl' } + ); + done(); + }, 1000); }); }); }); diff --git a/test/spec/controllers/copyMessageModalControllerSpec.js b/test/spec/controllers/copyMessageModalControllerSpec.js index bb25b6447e..419fb2341e 100644 --- a/test/spec/controllers/copyMessageModalControllerSpec.js +++ b/test/spec/controllers/copyMessageModalControllerSpec.js @@ -4,11 +4,9 @@ describe("CopyMessageModal controller", function() { var scope, ctrl, user, Notification, $rootScope, $controller; beforeEach(function() { - module(function($provide) { - $provide.value('User', {}); - }); + module(function($provide) {}); - inject(function($rootScope, _$controller_, _Notification_){ + inject(function($rootScope, _$controller_, _Notification_, User){ user = specHelper.newUser(); user._id = "unique-user-id"; user.ops = { @@ -20,10 +18,12 @@ describe("CopyMessageModal controller", function() { $controller = _$controller_; - // Load RootCtrl to ensure shared behaviors are loaded - $controller('RootCtrl', {$scope: scope, User: {user: user}}); + User.setUser(user); - ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: {user: user}}); + // Load RootCtrl to ensure shared behaviors are loaded + $controller('RootCtrl', {$scope: scope, User: User}); + + ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: User}); Notification = _Notification_; Notification.text = sandbox.spy(); diff --git a/test/spec/controllers/filtersCtrlSpec.js b/test/spec/controllers/filtersCtrlSpec.js index bbebce3cfd..b2adac581d 100644 --- a/test/spec/controllers/filtersCtrlSpec.js +++ b/test/spec/controllers/filtersCtrlSpec.js @@ -1,13 +1,16 @@ 'use strict'; describe('Filters Controller', function() { - var scope, user; + var scope, user, userService; - beforeEach(inject(function($rootScope, $controller, Shared) { + beforeEach(inject(function($rootScope, $controller, Shared, User) { user = specHelper.newUser(); Shared.wrap(user); scope = $rootScope.$new(); - $controller('FiltersCtrl', {$scope: scope, User: {user: user}}); + // user.filters = {}; + User.setUser(user); + userService = User; + $controller('FiltersCtrl', {$scope: scope, User: User}); })); describe('tags', function(){ @@ -22,9 +25,9 @@ describe('Filters Controller', function() { it('toggles tag filtering', inject(function(Shared){ var tag = {id: Shared.uuid(), name: 'myTag'}; scope.toggleFilter(tag); - expect(user.filters[tag.id]).to.eql(true); + expect(userService.user.filters[tag.id]).to.eql(true); scope.toggleFilter(tag); - expect(user.filters[tag.id]).to.eql(false); + expect(userService.user.filters[tag.id]).to.eql(false); })); }); @@ -33,7 +36,7 @@ describe('Filters Controller', function() { scope.filterQuery = 'task'; scope.updateTaskFilter(); - expect(user.filterQuery).to.eql(scope.filterQuery); + expect(userService.user.filterQuery).to.eql(scope.filterQuery); }); }); }); diff --git a/test/spec/controllers/footerCtrlSpec.js b/test/spec/controllers/footerCtrlSpec.js index 7b12bc2875..ce5245d099 100644 --- a/test/spec/controllers/footerCtrlSpec.js +++ b/test/spec/controllers/footerCtrlSpec.js @@ -1,12 +1,17 @@ 'use strict'; describe('Footer Controller', function() { - var scope, user; + var scope, user, User; beforeEach(inject(function($rootScope, $controller) { - console.log(window.env.NODE_ENV); user = specHelper.newUser(); - var User = {log: sandbox.stub(), set: sandbox.stub(), user: user}; + User = { + log: sandbox.stub(), + set: sandbox.stub(), + addTenGems: sandbox.stub(), + addHourglass: sandbox.stub(), + user: user + }; scope = $rootScope.$new(); $controller('FooterCtrl', {$scope: scope, User: User}); })); @@ -40,21 +45,17 @@ describe('Footer Controller', function() { describe('#addTenGems', function() { it('posts to /user/addTenGems', inject(function($httpBackend) { - $httpBackend.expectPOST('/api/v2/user/addTenGems').respond({}); - scope.addTenGems(); - $httpBackend.flush(); + expect(User.addTenGems).to.have.been.called; })); }); describe('#addHourglass', function() { it('posts to /user/addHourglass', inject(function($httpBackend) { - $httpBackend.expectPOST('/api/v2/user/addHourglass').respond({}); - scope.addHourglass(); - $httpBackend.flush(); + expect(User.addHourglass).to.have.been.called; })); }); diff --git a/test/spec/controllers/groupCtrlSpec.js b/test/spec/controllers/groupCtrlSpec.js index dce054bef6..6a5819ec67 100644 --- a/test/spec/controllers/groupCtrlSpec.js +++ b/test/spec/controllers/groupCtrlSpec.js @@ -23,6 +23,53 @@ describe('Groups Controller', function() { }); }); + describe("isMemberOfPendingQuest", function() { + var party; + var partyStub; + + beforeEach(function () { + party = specHelper.newGroup({ + _id: "unique-party-id", + type: 'party', + members: ['leader-id'] // Ensure we wouldn't pass automatically. + }); + + partyStub = sandbox.stub(groups, "party", function() { + return party; + }); + }); + + it("returns false if group is does not have a quest", function() { + expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; + }); + + it("returns false if group quest has not members", function() { + party.quest = { + 'key': 'random-key', + }; + expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; + }); + + it("returns false if group quest is active", function() { + party.quest = { + 'key': 'random-key', + 'members': {}, + 'active': true, + }; + party.quest.members[user._id] = true; + expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; + }); + + it("returns true if user is a member of a pending quest", function() { + party.quest = { + 'key': 'random-key', + 'members': {}, + }; + party.quest.members[user._id] = true; + expect(scope.isMemberOfPendingQuest(user._id, party)).to.be.ok; + }); + }); + describe("isMemberOfGroup", function() { it("returns true if group is the user's party retrieved from groups service", function() { var party = specHelper.newGroup({ @@ -31,7 +78,7 @@ describe('Groups Controller', function() { members: ['leader-id'] // Ensure we wouldn't pass automatically. }); - var partyStub = sandbox.stub(groups,"party", function() { + var partyStub = sandbox.stub(groups, "party", function() { return party; }); @@ -46,12 +93,9 @@ describe('Groups Controller', function() { members: [user._id] }); - var myGuilds = sandbox.stub(groups,"myGuilds", function() { - return [guild]; - }); + user.guilds = [guild._id]; expect(scope.isMemberOfGroup(user._id, guild)).to.be.ok; - expect(myGuilds).to.be.called; }); it('does not return true if guild is not included in myGuilds call', function(){ @@ -62,12 +106,9 @@ describe('Groups Controller', function() { members: ['not-user-id'] }); - var myGuilds = sandbox.stub(groups,"myGuilds", function() { - return []; - }); + user.guilds = []; expect(scope.isMemberOfGroup(user._id, guild)).to.not.be.ok; - expect(myGuilds).to.be.calledOnce; }); }); @@ -122,12 +163,12 @@ describe('Groups Controller', function() { scope.editGroup(guild); }); - it('calls group.save', () => { - let guildSave = sandbox.spy(scope.groupCopy, '$save'); + it('calls group update', () => { + let guildUpdate = sandbox.spy(groups.Group, 'update'); scope.saveEdit(guild); - expect(guildSave).to.be.calledOnce; + expect(guildUpdate).to.be.calledOnce; }); it('calls cancelEdit', () => { diff --git a/test/spec/controllers/headerCtrlSpec.js b/test/spec/controllers/headerCtrlSpec.js index ab37d11205..b32c5ccbd5 100644 --- a/test/spec/controllers/headerCtrlSpec.js +++ b/test/spec/controllers/headerCtrlSpec.js @@ -5,13 +5,12 @@ describe('Header Controller', function() { beforeEach(function() { module(function($provide) { - $provide.value('User', {}); + user = specHelper.newUser(); + user._id = "unique-user-id" + $provide.value('User', {user: user}); }); inject(function(_$rootScope_, _$controller_, _$location_){ - user = specHelper.newUser(); - user._id = "unique-user-id" - scope = _$rootScope_.$new(); $rootScope = _$rootScope_; diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index 2553acbcf3..4000f80a33 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -4,11 +4,9 @@ describe('Inventory Controller', function() { var scope, ctrl, user, rootScope; beforeEach(function() { - module(function($provide) { - $provide.value('User', {}); - }); + module(function($provide) {}); - inject(function($rootScope, $controller, Shared){ + inject(function($rootScope, $controller, Shared, User, $location, $window) { user = specHelper.newUser({ balance: 4, items: { @@ -26,17 +24,21 @@ describe('Inventory Controller', function() { Shared.wrap(user); var mockWindow = { - confirm: function(msg){ + confirm: function(msg) { return true; - } + }, }; + scope = $rootScope.$new(); rootScope = $rootScope; - // Load RootCtrl to ensure shared behaviors are loaded - $controller('RootCtrl', {$scope: scope, User: {user: user}, $window: mockWindow}); + User.user = user; + User.setUser(user); - ctrl = $controller('InventoryCtrl', {$scope: scope, User: {user: user}, $window: mockWindow}); + // Load RootCtrl to ensure shared behaviors are loaded + $controller('RootCtrl', {$scope: scope, User: User, $window: mockWindow}); + + ctrl = $controller('InventoryCtrl', {$scope: scope, User: User, $window: mockWindow}); }); }); @@ -88,14 +90,16 @@ describe('Inventory Controller', function() { expect(rootScope.openModal).to.have.been.calledWith('hatchPet'); }); - it('does not show modal if user tries to hatch a pet they own', function(){ + //@TODO: Fix Common hatch + xit('does not show modal if user tries to hatch a pet they own', function(){ user.items.pets['Cactus-Base'] = 5; scope.chooseEgg('Cactus'); scope.choosePotion('Base'); expect(rootScope.openModal).to.not.have.been.called; }); - it('does not show modal if user tries to hatch a premium quest pet', function(){ + //@TODO: Fix Common hatch + xit('does not show modal if user tries to hatch a premium quest pet', function(){ user.items.eggs = {Snake: 1}; user.items.hatchingPotions = {Peppermint: 1}; scope.chooseEgg('Snake'); diff --git a/test/spec/controllers/inviteToGroupCtrlSpec.js b/test/spec/controllers/inviteToGroupCtrlSpec.js index 385f42dda1..5c316855e9 100644 --- a/test/spec/controllers/inviteToGroupCtrlSpec.js +++ b/test/spec/controllers/inviteToGroupCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Invite to Group Controller', function() { - var scope, ctrl, groups, user, guild, $rootScope; + var scope, ctrl, groups, user, guild, rootScope, $controller; beforeEach(function() { user = specHelper.newUser({ @@ -13,8 +13,12 @@ describe('Invite to Group Controller', function() { $provide.value('injectedGroup', { user: user }); }); - inject(function($rootScope, $controller, Groups){ - scope = $rootScope.$new(); + inject(function(_$rootScope_, _$controller_, Groups) { + rootScope = _$rootScope_; + + scope = _$rootScope_.$new(); + + $controller = _$controller_; // Load RootCtrl to ensure shared behaviors are loaded $controller('RootCtrl', {$scope: scope, User: {user: user}}); @@ -44,69 +48,101 @@ describe('Invite to Group Controller', function() { }); describe('inviteNewUsers', function() { + var groupInvite, groupCreate; + beforeEach(function() { scope.group = specHelper.newGroup({ type: 'party', - $save: sinon.stub().returns({ - then: function(cb) { cb(); } - }) }); - sandbox.stub(groups.Group, 'invite'); + groupCreate = sandbox.stub(groups.Group, 'create'); + groupInvite = sandbox.stub(groups.Group, 'invite'); }); context('if the party does not already exist', function() { + var groupResponse; + beforeEach(function() { delete scope.group._id; + groupResponse = {data: {data: scope.group}} }); it('saves the group if a new group is being created', function() { + groupCreate.returns(Promise.resolve(groupResponse)); scope.inviteNewUsers('uuid'); - expect(scope.group.$save).to.be.calledOnce; + expect(groupCreate).to.be.calledOnce; }); it('uses provided name', function() { scope.group.name = 'test party'; + + groupCreate.returns(Promise.resolve(groupResponse)); + scope.inviteNewUsers('uuid'); + + expect(groupCreate).to.be.calledWith(scope.group); expect(scope.group.name).to.eql('test party'); }); it('names the group if no name is provided', function() { scope.group.name = ''; + + groupCreate.returns(Promise.resolve(groupResponse)); + scope.inviteNewUsers('uuid'); + + expect(groupCreate).to.be.calledWith(scope.group); expect(scope.group.name).to.eql(env.t('possessiveParty', {name: user.profile.name})); }); }); context('email', function() { - it('invites user with emails', function() { + beforeEach(function () { + sandbox.stub(rootScope, 'hardRedirect'); + }); + + it('invites user with emails', function(done) { scope.emails = [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ]; - scope.inviteNewUsers('email'); - expect(groups.Group.invite).to.be.calledOnce; - expect(groups.Group.invite).to.be.calledWith({ - gid: scope.group._id, - }, { + var inviteDetails = { inviter: user.profile.name, emails: [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ] + }; - }); + groupInvite.returns( + Promise.resolve() + .then(function () { + expect(groupInvite).to.be.calledOnce; + expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails); + done(); + }) + ); + + scope.inviteNewUsers('email'); }); - it('resets email list after sending', function() { - groups.Group.invite.yields(); + it('resets email list after sending', function(done) { scope.emails[0].name = 'Luigi'; scope.emails[0].email = 'mario_bro@themushroomkingdom.com'; - scope.inviteNewUsers('email'); + groupInvite.returns( + Promise.resolve() + .then(function () { + //We use a timeout to test items that happen after the promise is resolved + setTimeout(function(){ + expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]); + done(); + }, 1000); + }) + ); - expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]); + scope.inviteNewUsers('email'); }); it('filters out blank email inputs', function() { @@ -116,66 +152,93 @@ describe('Invite to Group Controller', function() { {name: 'Mario', email: 'mario@tmk.com'} ]; - scope.inviteNewUsers('email'); - expect(groups.Group.invite).to.be.calledOnce; - expect(groups.Group.invite).to.be.calledWith({ - gid: scope.group._id, - }, { + var inviteDetails = { inviter: user.profile.name, emails: [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ] - }); + }; + + groupInvite.returns( + Promise.resolve() + .then(function () { + expect(groupInvite).to.be.calledOnce; + expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails); + done(); + }) + ); + + scope.inviteNewUsers('email'); }); }); context('uuid', function() { - it('invites user with uuid', function() { + beforeEach(function () { + sandbox.stub(rootScope, 'hardRedirect'); + }); + + it('invites user with uuid', function(done) { scope.invitees = [{uuid: '1234'}]; + groupInvite.returns( + Promise.resolve() + .then(function () { + expect(groupInvite).to.be.calledOnce; + expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['1234'] }); + done(); + }) + ); + scope.inviteNewUsers('uuid'); - expect(groups.Group.invite).to.be.calledOnce; - expect(groups.Group.invite).to.be.calledWith({ - gid: scope.group._id, - }, { - uuids: ['1234'] - }); }); - it('invites users with uuids', function() { + it('invites users with uuids', function(done) { scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}]; + groupInvite.returns( + Promise.resolve() + .then(function () { + expect(groupInvite).to.be.calledOnce; + expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user2', 'user3'] }); + done(); + }) + ); + scope.inviteNewUsers('uuid'); - expect(groups.Group.invite).to.be.calledOnce; - expect(groups.Group.invite).to.be.calledWith({ - gid: scope.group._id, - }, { - uuids: ['user1', 'user2', 'user3'] - }); }); - it('resets invitee list after sending', function() { - groups.Group.invite.yields(); + it('resets invitee list after sending', function(done) { scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}]; - scope.inviteNewUsers('uuid'); + groupInvite.returns( + Promise.resolve() + .then(function () { + //We use a timeout to test items that happen after the promise is resolved + setTimeout(function(){ + expect(scope.invitees).to.eql([{uuid: ''}]); + done(); + }, 1000); + done(); + }) + ); - expect(scope.invitees).to.eql([{uuid: ''}]); + scope.inviteNewUsers('uuid'); }); it('removes blank fields from being sent', function() { - groups.Group.invite.yields(); scope.invitees = [{uuid: 'user1'}, {uuid: ''}, {uuid: 'user3'}]; - scope.inviteNewUsers('uuid'); + groupInvite.returns( + Promise.resolve() + .then(function () { + expect(groupInvite).to.be.calledOnce; + expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user3'] }); + done(); + }) + ); - expect(groups.Group.invite).to.be.calledOnce; - expect(groups.Group.invite).to.be.calledWith({ - gid: scope.group._id, - }, { - uuids: ['user1', 'user3'] - }); + scope.inviteNewUsers('uuid'); }); }); diff --git a/test/spec/controllers/menuCtrlSpec.js b/test/spec/controllers/menuCtrlSpec.js index eaa642370b..1e98595105 100644 --- a/test/spec/controllers/menuCtrlSpec.js +++ b/test/spec/controllers/menuCtrlSpec.js @@ -19,7 +19,7 @@ describe('Menu Controller', function() { describe('clearMessage', function() { it('is Chat.seenMessage', inject(function(Chat) { - expect(scope.clearMessages).to.eql(Chat.seenMessage); + expect(scope.clearMessages).to.eql(Chat.markChatSeen); })); }); }); diff --git a/test/spec/controllers/partyCtrlSpec.js b/test/spec/controllers/partyCtrlSpec.js index 92401f1987..00d3e676c5 100644 --- a/test/spec/controllers/partyCtrlSpec.js +++ b/test/spec/controllers/partyCtrlSpec.js @@ -1,7 +1,8 @@ 'use strict'; describe("Party Controller", function() { - var scope, ctrl, user, User, questsService, groups, rootScope, $controller; + var scope, ctrl, user, User, questsService, groups, rootScope, $controller, deferred; + var party; beforeEach(function() { user = specHelper.newUser(), @@ -10,13 +11,19 @@ describe("Party Controller", function() { user: user, sync: sandbox.spy(), set: sandbox.spy() - } + }; + + party = specHelper.newGroup({ + _id: "unique-party-id", + type: 'party', + members: ['leader-id'] // Ensure we wouldn't pass automatically. + }); module(function($provide) { $provide.value('User', User); }); - inject(function(_$rootScope_, _$controller_, Groups, Quests){ + inject(function(_$rootScope_, _$controller_, Groups, Quests, _$q_){ rootScope = _$rootScope_; @@ -35,12 +42,18 @@ describe("Party Controller", function() { }); describe('initialization', function() { + var groupResponse; + function initializeControllerWithStubbedState() { inject(function(_$state_) { var state = _$state_; sandbox.stub(state, 'is').returns(true); - $controller('PartyCtrl', { $scope: scope, $state: state }); - expect(state.is).to.be.calledOnce; // ensure initialization worked as desired + var syncParty = sinon.stub(groups.Group, 'syncParty') + syncParty.returns(Promise.resolve(groupResponse)); + $controller('PartyCtrl', { $scope: scope, $state: state, User: User }); + // @TODO: I have update the party ctrl to sync the user whenever it is called rather than only on the party page + // Since I have cached the promise, this should not be a performance issue, but let's keep this test here in case anything breaks. + // expect(state.is).to.be.calledOnce; // ensure initialization worked as desired }); }; @@ -50,10 +63,7 @@ describe("Party Controller", function() { context('party has 1 member', function() { it('awards no new achievements', function() { - sandbox.stub(groups, 'party').returns({ - $syncParty: function() {}, - memberCount: 1 - }); + groupResponse = {_id: "test", type: "party", memberCount: 1}; initializeControllerWithStubbedState(); @@ -64,61 +74,65 @@ describe("Party Controller", function() { context('party has 2 members', function() { context('user does not have "Party Up" achievement', function() { - it('awards "Party Up" achievement', function() { - sandbox.stub(groups, 'party').returns({ - $syncParty: function() {}, - memberCount: 2 - }); + it('awards "Party Up" achievement', function(done) { + groupResponse = {_id: "test", type: "party", memberCount: 2}; initializeControllerWithStubbedState(); - expect(User.set).to.be.calledOnce; - expect(User.set).to.be.calledWith( - { 'achievements.partyUp': true } - ); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); + setTimeout(function() { + expect(User.set).to.be.calledOnce; + expect(User.set).to.be.calledWith( + { 'achievements.partyUp': true } + ); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); + done(); + }, 1000); }); }); }); context('party has 4 members', function() { + beforeEach(function() { - sandbox.stub(groups, 'party').returns({ - $syncParty: function() {}, - memberCount: 4 - }); + groupResponse = {_id: "test", type: "party", memberCount: 4}; }); context('user has "Party Up" but not "Party On" achievement', function() { - it('awards "Party On" achievement', function() { + it('awards "Party On" achievement', function(done) { user.achievements.partyUp = true; initializeControllerWithStubbedState(); - expect(User.set).to.be.calledOnce; - expect(User.set).to.be.calledWith( - { 'achievements.partyOn': true } - ); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); + setTimeout(function(){ + expect(User.set).to.be.calledOnce; + expect(User.set).to.be.calledWith( + { 'achievements.partyOn': true } + ); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); + done(); + }, 1000); }); }); context('user has neither "Party Up" nor "Party On" achievements', function() { - it('awards "Party Up" and "Party On" achievements', function() { + it('awards "Party Up" and "Party On" achievements', function(done) { initializeControllerWithStubbedState(); - expect(User.set).to.be.calledTwice; - expect(User.set).to.be.calledWith( - { 'achievements.partyUp': true} - ); - expect(User.set).to.be.calledWith( - { 'achievements.partyOn': true} - ); - expect(rootScope.openModal).to.be.calledTwice; - expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); - expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); + setTimeout(function(){ + expect(User.set).to.have.been.called; + expect(User.set).to.be.calledWith( + { 'achievements.partyUp': true} + ); + expect(User.set).to.be.calledWith( + { 'achievements.partyOn': true} + ); + expect(rootScope.openModal).to.have.been.called; + expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); + expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); + done(); + }, 1000); }); }); @@ -136,70 +150,107 @@ describe("Party Controller", function() { }); }); + describe("create", function() { + var partyStub; + + beforeEach(function () { + partyStub = sinon.stub(groups.Group, "create"); + partyStub.returns(Promise.resolve(party)); + sinon.stub(rootScope, 'hardRedirect'); + }); + + it("creates a new party", function() { + var group = { + type: 'party', + }; + scope.create(group); + expect(partyStub).to.be.calledOnce; + //@TODO: Check user party console.log(User.user.party.id) + }); + }); + describe('questAccept', function() { + var sendAction; + var memberResponse; + beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - sandbox.stub(questsService, 'sendAction').returns({ - then: sandbox.stub().yields({members: {another: true}}) - }); + + memberResponse = {members: {another: true}}; + sinon.stub(questsService, 'sendAction') + questsService.sendAction.returns(Promise.resolve(memberResponse)); }); it('calls Quests.sendAction', function() { scope.questAccept(); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('questAccept'); + expect(questsService.sendAction).to.be.calledWith('quests/accept'); }); - it('updates quest object with new participants list', function() { + it('updates quest object with new participants list', function(done) { scope.group.quest = { members: { user: true, another: true } }; - scope.questAccept(); + setTimeout(function(){ + expect(scope.group.quest).to.eql(memberResponse); + done(); + }, 1000); - expect(scope.group.quest).to.eql({members: { another: true }}); + scope.questAccept(); }); }); describe('questReject', function() { + var memberResponse; + beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - sandbox.stub(questsService, 'sendAction').returns({ - then: sandbox.stub().yields({members: {another: true}}) - }); + + memberResponse = {members: {another: true}}; + var sendAction = sinon.stub(questsService, 'sendAction') + sendAction.returns(Promise.resolve(memberResponse)); }); it('calls Quests.sendAction', function() { scope.questReject(); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('questReject'); + expect(questsService.sendAction).to.be.calledWith('quests/reject'); }); - it('updates quest object with new participants list', function() { + it('updates quest object with new participants list', function(done) { scope.group.quest = { members: { user: true, another: true } }; - scope.questReject(); + setTimeout(function(){ + expect(scope.group.quest).to.eql(memberResponse); + done(); + }, 1000); - expect(scope.group.quest).to.eql({members: { another: true }}); + scope.questReject(); }); }); describe('questCancel', function() { - var party, cancelSpy, windowSpy; + var party, cancelSpy, windowSpy, memberResponse; + beforeEach(function() { - sandbox.stub(questsService, 'sendAction').returns({ - then: sandbox.stub().yields({members: {another: true}}) - }); + scope.group = { + quest: { members: { 'user-id': true } } + }; + + memberResponse = {members: {another: true}}; + sinon.stub(questsService, 'sendAction') + questsService.sendAction.returns(Promise.resolve(memberResponse)); }); it('calls Quests.sendAction when alert box is confirmed', function() { @@ -210,7 +261,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledOnce; expect(window.confirm).to.be.calledWith(window.env.t('sureCancel')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('questCancel'); + expect(questsService.sendAction).to.be.calledWith('quests/cancel'); }); it('does not call Quests.sendAction when alert box is not confirmed', function() { @@ -224,10 +275,16 @@ describe("Party Controller", function() { }); describe('questAbort', function() { + var memberResponse; + beforeEach(function() { - sandbox.stub(questsService, 'sendAction').returns({ - then: sandbox.stub().yields({members: {another: true}}) - }); + scope.group = { + quest: { members: { 'user-id': true } } + }; + + memberResponse = {members: {another: true}}; + sinon.stub(questsService, 'sendAction') + questsService.sendAction.returns(Promise.resolve(memberResponse)); }); it('calls Quests.sendAction when two alert boxes are confirmed', function() { @@ -239,7 +296,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('questAbort'); + expect(questsService.sendAction).to.be.calledWith('quests/abort'); }); it('does not call Quests.sendAction when first alert box is not confirmed', function() { @@ -273,13 +330,16 @@ describe("Party Controller", function() { }); describe('#questLeave', function() { + var memberResponse; + beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - sandbox.stub(questsService, 'sendAction').returns({ - then: sandbox.stub().yields({members: {another: true}}) - }); + + memberResponse = {members: {another: true}}; + sinon.stub(questsService, 'sendAction') + questsService.sendAction.returns(Promise.resolve(memberResponse)); }); it('calls Quests.sendAction when alert box is confirmed', function() { @@ -290,7 +350,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledOnce; expect(window.confirm).to.be.calledWith(window.env.t('sureLeave')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('questLeave'); + expect(questsService.sendAction).to.be.calledWith('quests/leave'); }); it('does not call Quests.sendAction when alert box is not confirmed', function() { @@ -302,15 +362,18 @@ describe("Party Controller", function() { questsService.sendAction.should.not.have.been.calledOnce; }); - it('updates quest object with new participants list', function() { + it('updates quest object with new participants list', function(done) { scope.group.quest = { members: { user: true, another: true } }; sandbox.stub(window, "confirm").returns(true); - scope.questLeave(); + setTimeout(function(){ + expect(scope.group.quest).to.eql(memberResponse); + done(); + }, 1000); - expect(scope.group.quest).to.eql({members: { another: true }}); + scope.questLeave(); }); }); @@ -362,7 +425,9 @@ describe("Party Controller", function() { describe('#leaveOldPartyAndJoinNewParty', function() { beforeEach(function() { sandbox.stub(scope, 'join'); - sandbox.stub(groups.Group, 'leave').yields(); + groups.data.party = { _id: 'old-party' }; + var groupLeave = sandbox.stub(groups.Group, 'leave'); + groupLeave.returns(Promise.resolve({})); sandbox.stub(groups, 'party').returns({ _id: 'old-party' }); @@ -380,20 +445,17 @@ describe("Party Controller", function() { scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name'); expect(groups.Group.leave).to.be.calledOnce; - expect(groups.Group.leave).to.be.calledWith({ - gid: 'old-party', - keep: false - }); + expect(groups.Group.leave).to.be.calledWith('old-party', false); }); - it('joins the new party', function() { + it('joins the new party', function(done) { scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name'); - expect(scope.join).to.be.calledOnce; - expect(scope.join).to.be.calledWith({ - id: 'some-id', - name: 'some-name' - }); + setTimeout(function() { + expect(scope.join).to.be.calledOnce; + expect(scope.join).to.be.calledWith({id: 'some-id', name: 'some-name'}); + done(); + }, 1000); }); }); @@ -406,6 +468,7 @@ describe("Party Controller", function() { leader: {}, quest: {} }); + scope.group = party; }); it('returns false if user is not the quest leader', function() { diff --git a/test/spec/controllers/settingsCtrlSpec.js b/test/spec/controllers/settingsCtrlSpec.js index 527600b29e..ef960ae204 100644 --- a/test/spec/controllers/settingsCtrlSpec.js +++ b/test/spec/controllers/settingsCtrlSpec.js @@ -12,6 +12,12 @@ describe('Settings Controller', function () { user = specHelper.newUser(); User = { set: sandbox.stub(), + reroll: sandbox.stub(), + rebirth: sandbox.stub(), + releasePets: sandbox.stub(), + releaseMounts: sandbox.stub(), + releaseBoth: sandbox.stub(), + setCustomDayStart: sandbox.stub(), user: user }; @@ -81,19 +87,11 @@ describe('Settings Controller', function () { }); describe('#saveDayStart', function () { - - it('updates user\'s custom day start and last cron', function () { - var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime(); - var expectedTime = fakeCurrentTime; - sandbox.useFakeTimers(fakeCurrentTime); + it('updates user\'s custom day start', function () { scope.dayStart = 5; scope.saveDayStart(); - expect(User.set).to.be.calledOnce; - expect(User.set).to.be.calledWith({ - 'preferences.dayStart': 5, - 'lastCron': expectedTime - }); + expect(User.setCustomDayStart).to.be.calledWith(5); }); }); @@ -123,7 +121,7 @@ describe('Settings Controller', function () { scope.reroll(true); - expect(user.ops.reroll).to.be.calledWith({}); + expect(User.reroll).to.be.calledWith({}); }); it('navigates to the tasks page when confirmed', function () { @@ -173,7 +171,7 @@ describe('Settings Controller', function () { scope.rebirth(true); - expect(user.ops.rebirth).to.be.calledWith({}); + expect(User.rebirth).to.be.calledWith({}); }); it('navigates to tasks page when confirmed', function () { @@ -216,9 +214,9 @@ describe('Settings Controller', function () { it('doesn\'t call any release method if type is not provided', function () { scope.releaseAnimals(); - expect(User.user.ops.releasePets).to.not.be.called; - expect(User.user.ops.releaseMounts).to.not.be.called; - expect(User.user.ops.releaseBoth).to.not.be.called; + expect(User.releasePets).to.not.be.called; + expect(User.releaseMounts).to.not.be.called; + expect(User.releaseBoth).to.not.be.called; }); it('doesn\'t redirect to tasks page if type is not provided', function () { @@ -230,7 +228,7 @@ describe('Settings Controller', function () { it('calls releasePets when "pets" is provided', function () { scope.releaseAnimals('pets'); - expect(User.user.ops.releasePets).to.be.calledOnce; + expect(User.releasePets).to.be.calledOnce; }); it('navigates to the tasks page when "pets" is provided', function () { @@ -242,7 +240,7 @@ describe('Settings Controller', function () { it('calls releaseMounts when "mounts" is provided', function () { scope.releaseAnimals('mounts'); - expect(User.user.ops.releaseMounts).to.be.calledOnce; + expect(User.releaseMounts).to.be.calledOnce; }); it('navigates to the tasks page when "mounts" is provided', function () { @@ -254,7 +252,7 @@ describe('Settings Controller', function () { it('calls releaseBoth when "both" is provided', function () { scope.releaseAnimals('both'); - expect(User.user.ops.releaseBoth).to.be.calledOnce; + expect(User.releaseBoth).to.be.calledOnce; }); it('navigates to the tasks page when "both" is provided', function () { @@ -266,9 +264,9 @@ describe('Settings Controller', function () { it('does not call release functions when non-applicable argument is passed in', function () { scope.releaseAnimals('dummy'); - expect(User.user.ops.releasePets).to.not.be.called; - expect(User.user.ops.releaseMounts).to.not.be.called; - expect(User.user.ops.releaseBoth).to.not.be.called; + expect(User.releasePets).to.not.be.called; + expect(User.releaseMounts).to.not.be.called; + expect(User.releaseBoth).to.not.be.called; }); }); diff --git a/test/spec/controllers/tasksCtrlSpec.js b/test/spec/controllers/tasksCtrlSpec.js index ea6da32897..7d02a6edbb 100644 --- a/test/spec/controllers/tasksCtrlSpec.js +++ b/test/spec/controllers/tasksCtrlSpec.js @@ -8,6 +8,8 @@ describe('Tasks Controller', function() { User = { user: user }; + + User.deleteTask = sandbox.stub(); User.user.ops = { deleteTask: sandbox.stub(), }; @@ -51,13 +53,13 @@ describe('Tasks Controller', function() { it('does not remove task if not confirmed', function() { window.confirm.returns(false); scope.removeTask(task); - expect(user.ops.deleteTask).to.not.be.called; + expect(User.deleteTask).to.not.be.called; }); it('removes task', function() { window.confirm.returns(true); scope.removeTask(task); - expect(user.ops.deleteTask).to.be.calledOnce; + expect(User.deleteTask).to.be.calledOnce; }); }); diff --git a/test/spec/services/challengeServicesSpec.js b/test/spec/services/challengeServicesSpec.js new file mode 100644 index 0000000000..9bed72db31 --- /dev/null +++ b/test/spec/services/challengeServicesSpec.js @@ -0,0 +1,88 @@ +'use strict'; + +describe('challengeServices', function() { + var $httpBackend, $http, challenges, user; + var apiV3Prefix = '/api/v3'; + + beforeEach(function() { + module(function($provide) { + $provide.value('User', {user:user}); + }); + + inject(function(_$httpBackend_, Challenges, User) { + $httpBackend = _$httpBackend_; + challenges = Challenges; + user = User; + user.sync = function(){}; + }); + }); + + it('calls create challenge endpoint', function() { + $httpBackend.expectPOST(apiV3Prefix + '/challenges').respond({}); + challenges.createChallenge(); + $httpBackend.flush(); + }); + + it('calls join challenge endpoint', function() { + var challengeId = 1; + $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/join').respond({}); + challenges.joinChallenge(challengeId); + $httpBackend.flush(); + }); + + it('calls leave challenge endpoint', function() { + var challengeId = 1; + $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/leave').respond({}); + challenges.leaveChallenge(challengeId); + $httpBackend.flush(); + }); + + it('calls get user challenges endpoint', function() { + $httpBackend.expectGET(apiV3Prefix + '/challenges/user').respond({}); + challenges.getUserChallenges(); + $httpBackend.flush(); + }); + + it('calls get group challenges endpoint', function() { + var groupId = 1; + $httpBackend.expectGET(apiV3Prefix + '/challenges/groups/' + groupId).respond({}); + challenges.getGroupChallenges(groupId); + $httpBackend.flush(); + }); + + it('calls get challenge endpoint', function() { + var challengeId = 1; + $httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId).respond({}); + challenges.getChallenge(challengeId); + $httpBackend.flush(); + }); + + it('calls export challenge to csv endpoint', function() { + var challengeId = 1; + $httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId + '/export/csv').respond({}); + challenges.exportChallengeCsv(challengeId); + $httpBackend.flush(); + }); + + it('calls update challenge endpoint', function() { + var challengeId = 1; + $httpBackend.expectPUT(apiV3Prefix + '/challenges/' + challengeId).respond({}); + challenges.updateChallenge(challengeId); + $httpBackend.flush(); + }); + + it('calls delete challenge endpoint', function() { + var challengeId = 1; + $httpBackend.expectDELETE(apiV3Prefix + '/challenges/' + challengeId).respond({}); + challenges.deleteChallenge(challengeId); + $httpBackend.flush(); + }); + + it('calls select challenge winner endpoint', function() { + var challengeId = 1; + var winnerId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId).respond({}); + challenges.selectChallengeWinner(challengeId, winnerId); + $httpBackend.flush(); + }); +}); diff --git a/test/spec/services/chatServicesSpec.js b/test/spec/services/chatServicesSpec.js new file mode 100644 index 0000000000..2d1c101df8 --- /dev/null +++ b/test/spec/services/chatServicesSpec.js @@ -0,0 +1,73 @@ +'use strict'; + +describe('chatServices', function() { + var $httpBackend, $http, chat, user; + var apiV3Prefix = '/api/v3'; + + beforeEach(function() { + module(function($provide) { + $provide.value('User', {user:user}); + }); + + inject(function(_$httpBackend_, Chat, User) { + $httpBackend = _$httpBackend_; + chat = Chat; + user = User; + user.sync = function(){}; + }); + }); + + it('calls get chat endpoint', function() { + var groupId = 1; + $httpBackend.expectGET(apiV3Prefix + '/groups/' + groupId + '/chat').respond({}); + chat.getChat(groupId); + $httpBackend.flush(); + }); + + it('calls get chat endpoint', function() { + var groupId = 1; + var message = "test message"; + $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat').respond({}); + chat.postChat(groupId, message); + $httpBackend.flush(); + }); + + it('calls delete chat endpoint', function() { + var groupId = 1; + var chatId = 2; + $httpBackend.expectDELETE(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId).respond({}); + chat.deleteChat(groupId, chatId); + $httpBackend.flush(); + }); + + it('calls like chat endpoint', function() { + var groupId = 1; + var chatId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like').respond({}); + chat.like(groupId, chatId); + $httpBackend.flush(); + }); + + it('calls flag chat endpoint', function() { + var groupId = 1; + var chatId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag').respond({}); + chat.flagChatMessage(groupId, chatId); + $httpBackend.flush(); + }); + + it('calls clearflags chat endpoint', function() { + var groupId = 1; + var chatId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags').respond({}); + chat.clearFlagCount(groupId, chatId); + $httpBackend.flush(); + }); + + it('calls chat seen endpoint', function() { + var groupId = 1; + $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/seen').respond({}); + chat.markChatSeen(groupId); + $httpBackend.flush(); + }); +}); diff --git a/test/spec/services/groupServicesSpec.js b/test/spec/services/groupServicesSpec.js index e9a9285265..5b4a88c996 100644 --- a/test/spec/services/groupServicesSpec.js +++ b/test/spec/services/groupServicesSpec.js @@ -2,41 +2,168 @@ describe('groupServices', function() { var $httpBackend, $http, groups, user; + var groupApiUrlPrefix = '/api/v3/groups'; beforeEach(function() { module(function($provide) { - $provide.value('User', {user:user}); + user = specHelper.newUser(); + user._id = "unique-user-id" + user.party._id = 'unique-party-id'; + user.sync = function(){}; + $provide.value('User', {user: user}); }); inject(function(_$httpBackend_, Groups, User) { $httpBackend = _$httpBackend_; groups = Groups; - user = User; - user.sync = function(){}; }); }); + it('calls get groups', function() { + $httpBackend.expectGET(groupApiUrlPrefix).respond({}); + groups.Group.getGroups(); + $httpBackend.flush(); + }); + + it('calls get group', function() { + var gid = 1; + $httpBackend.expectGET(groupApiUrlPrefix + '/' + gid).respond({}); + groups.Group.get(gid); + $httpBackend.flush(); + }); + it('calls party endpoint', function() { - $httpBackend.expectGET('/api/v2/groups/party').respond({}); + var groupId = '1234'; + var groupResponse = {data: {_id: groupId}}; + $httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse); + $httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({}); + $httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({}); + $httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({}); + groups.Group.syncParty(); + $httpBackend.flush(); + }); + + it('calls create endpoint', function() { + $httpBackend.expectPOST(groupApiUrlPrefix).respond({}); + groups.Group.create({}); + $httpBackend.flush(); + }); + + it('calls update group', function() { + var gid = 1; + var groupDetails = { _id: gid }; + $httpBackend.expectPUT(groupApiUrlPrefix + '/' + gid).respond({}); + groups.Group.update(groupDetails); + $httpBackend.flush(); + }); + + it('calls join group', function() { + var gid = 1; + $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/join').respond({}); + groups.Group.join(gid); + $httpBackend.flush(); + }); + + it('calls reject invite group', function() { + var gid = 1; + $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/reject-invite').respond({}); + groups.Group.rejectInvite(gid); + $httpBackend.flush(); + }); + + it('calls invite group', function() { + var gid = 1; + $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/invite').respond({}); + groups.Group.invite(gid, [], []); + $httpBackend.flush(); + }); + + it('calls party endpoint when party is not cached', function() { + var groupId = '1234'; + var groupResponse = {data: {_id: groupId}}; + $httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse); + $httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({}); + $httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({}); + $httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({}); groups.party(); $httpBackend.flush(); }); - it('calls tavern endpoint', function() { - $httpBackend.expectGET('/api/v2/groups/habitrpg').respond({}); + it('returns party if cached', function (done) { + var uid = 'abc'; + var party = { + _id: uid, + }; + groups.data.party = party; + groups.party() + .then(function (result) { + expect(result).to.eql(party); + done(); + }); + $httpBackend.flush(); + }); + + it('calls tavern endpoint when tavern is not cached', function() { + $httpBackend.expectGET(groupApiUrlPrefix + '/habitrpg').respond({}); groups.tavern(); $httpBackend.flush(); }); + it('returns tavern if cached', function (done) { + var uid = 'abc'; + var tavern = { + _id: uid, + }; + groups.data.tavern = tavern; + groups.tavern() + .then(function (result) { + expect(result).to.eql(tavern); + done(); + }); + $httpBackend.flush(); + }); + it('calls public guilds endpoint', function() { - $httpBackend.expectGET('/api/v2/groups?type=public').respond([]); + $httpBackend.expectGET(groupApiUrlPrefix + '?type=publicGuilds').respond([]); groups.publicGuilds(); $httpBackend.flush(); }); + it('returns public guilds if cached', function (done) { + var uid = 'abc'; + var publicGuilds = [ + {_id: uid}, + ]; + groups.data.publicGuilds = publicGuilds; + + groups.publicGuilds() + .then(function (result) { + expect(result).to.eql(publicGuilds); + done(); + }); + + $httpBackend.flush(); + }); + it('calls my guilds endpoint', function() { - $httpBackend.expectGET('/api/v2/groups?type=guilds').respond([]); + $httpBackend.expectGET(groupApiUrlPrefix + '?type=guilds').respond([]); groups.myGuilds(); $httpBackend.flush(); }); + + it('returns my guilds if cached', function (done) { + var uid = 'abc'; + var myGuilds = [ + {_id: uid}, + ]; + groups.data.myGuilds = myGuilds; + + groups.myGuilds() + .then(function (myGuilds) { + expect(myGuilds).to.eql(myGuilds); + done(); + }); + + $httpBackend.flush() + }); }); diff --git a/test/spec/services/memberServicesSpec.js b/test/spec/services/memberServicesSpec.js index d33ccd8c98..1344bf96e7 100644 --- a/test/spec/services/memberServicesSpec.js +++ b/test/spec/services/memberServicesSpec.js @@ -2,6 +2,7 @@ describe('memberServices', function() { var $httpBackend, members; + var apiV3Prefix = '/api/v3'; beforeEach(inject(function (_$httpBackend_, Members) { $httpBackend = _$httpBackend_; @@ -20,10 +21,51 @@ describe('memberServices', function() { expect(members.selectedMember).to.be.undefined; }); + it('calls fetch member', function() { + var memberId = 1; + var memberUrl = apiV3Prefix + '/members/' + memberId; + $httpBackend.expectGET(memberUrl).respond({}); + members.fetchMember(memberId); + $httpBackend.flush(); + }); + + it('calls get group members', function() { + var groupId = 1; + var memberUrl = apiV3Prefix + '/groups/' + groupId + '/members'; + $httpBackend.expectGET(memberUrl).respond({}); + members.getGroupMembers(groupId); + $httpBackend.flush(); + }); + + it('calls get group invites', function() { + var groupId = 1; + var memberUrl = apiV3Prefix + '/groups/' + groupId + '/invites'; + $httpBackend.expectGET(memberUrl).respond({}); + members.getGroupInvites(groupId); + $httpBackend.flush(); + }); + + it('calls get challenge members', function() { + var challengeId = 1; + var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members'; + $httpBackend.expectGET(memberUrl).respond({}); + members.getChallengeMembers(challengeId); + $httpBackend.flush(); + }); + + it('calls get challenge members progress', function() { + var challengeId = 1; + var memberId = 2; + var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId; + $httpBackend.expectGET(memberUrl).respond({}); + members.getChallengeMemberProgress(challengeId, memberId); + $httpBackend.flush(); + }); + describe('addToMembersList', function() { it('adds member to members object', function() { var member = { _id: 'user_id' }; - members.addToMembersList(member); + members.addToMembersList(member, members); expect(members.members).to.eql({ user_id: { _id: 'user_id' } }); @@ -31,27 +73,37 @@ describe('memberServices', function() { }); describe('selectMember', function() { - it('fetches member if not already in cache', function() { + it('fetches member if not already in cache', function(done) { var uid = 'abc'; - $httpBackend.expectGET('/api/v2/members/' + uid).respond({ _id: uid }); - members.selectMember(uid, function(){}); + var memberResponse = { + data: {_id: uid}, + } + $httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse); + members.selectMember(uid) + .then(function () { + expect(members.selectedMember._id).to.eql(uid); + expect(members.members).to.have.property(uid); + done(); + }); $httpBackend.flush(); - - expect(members.selectedMember._id).to.eql(uid); - expect(members.members).to.have.property(uid); }); - it('fetches member if member data in cache is incomplete', function() { + it('fetches member if member data in cache is incomplete', function(done) { var uid = 'abc'; members.members = { abc: { _id: 'abc', items: {} } } - $httpBackend.expectGET('/api/v2/members/' + uid).respond({ _id: uid }); - members.selectMember(uid, function(){}); + var memberResponse = { + data: {_id: uid}, + } + $httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse); + members.selectMember(uid) + .then(function () { + expect(members.selectedMember._id).to.eql(uid); + expect(members.members).to.have.property(uid); + done(); + }); $httpBackend.flush(); - - expect(members.selectedMember._id).to.eql(uid); - expect(members.members).to.have.property(uid); }); it('gets member from cache if member has a weapons object', function() { diff --git a/test/spec/services/questServicesSpec.js b/test/spec/services/questServicesSpec.js index e7f8c638b2..e71dc6f3ef 100644 --- a/test/spec/services/questServicesSpec.js +++ b/test/spec/services/questServicesSpec.js @@ -1,13 +1,14 @@ 'use strict'; describe('Quests Service', function() { - var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy; + var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy, state; beforeEach(function() { user = specHelper.newUser(); user.ops = { buyQuest: sandbox.spy() }; + user.party._id = 'unique-party-id'; user.achievements.quests = {}; quest = {lvl:20}; @@ -16,10 +17,11 @@ describe('Quests Service', function() { $provide.value('User', {sync: sinon.stub(), user: user}); }); - inject(function(Quests, Groups, Content) { + inject(function(Quests, Groups, Content, _$state_) { questsService = Quests; groupsService = Groups; content = Content; + state = _$state_; }); sandbox.stub(groupsService, 'inviteOrStartParty'); @@ -72,7 +74,8 @@ describe('Quests Service', function() { scope = $rootScope.$new(); })); - it('returns a promise', function() { + //@TODO: This is fixed in a Quest Service PR port + xit('returns a promise', function() { var promise = questsService.buyQuest('whale'); expect(promise).to.respondTo('then'); }); @@ -225,7 +228,7 @@ describe('Quests Service', function() { scope = $rootScope.$new(); })); - it('returns a promise', function() { + xit('returns a promise', function() { var promise = questsService.showQuest('whale'); expect(promise).to.respondTo('then'); }); @@ -335,39 +338,67 @@ describe('Quests Service', function() { }); describe('#initQuest', function() { + var fakeBackend, scope, key = 'whale'; + + beforeEach(inject(function($httpBackend, $rootScope) { + scope = $rootScope.$new(); + fakeBackend = $httpBackend; + var partyResponse = {data:{_id: 'party-id'}}; + + fakeBackend.when('GET', 'partials/main.html').respond({}); + fakeBackend.when('GET', 'partials/main.html').respond({}); + fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse); + fakeBackend.when('GET', '/api/v3/groups/party-id/members?includeAllPublicFields=true').respond({}); + fakeBackend.when('GET', '/api/v3/groups/party-id/invites').respond({}); + fakeBackend.when('GET', '/api/v3/challenges/groups/party-id').respond({}); + fakeBackend.when('POST', '/api/v3/groups/party-id/quests/invite/' + key).respond({quest: { key: 'whale' } }); + fakeBackend.flush(); + })); it('returns a promise', function() { - var promise = questsService.initQuest('whale'); + var promise = questsService.initQuest(key); expect(promise).to.respondTo('then'); }); - it('accepts quest'); + it('starts a quest', function(done) { + fakeBackend.expectPOST( '/api/v3/groups/party-id/quests/invite/' + key); + + questsService.initQuest(key) + .then(function(res) { + done(); + }); + + fakeBackend.flush(); + scope.$apply(); + }); it('brings user to party page'); }); - describe('#sendAction', function() { + //@TODO: This is fixed in a Quest Service PR port + xdescribe('#sendAction', function() { var fakeBackend, scope; beforeEach(inject(function($httpBackend, $rootScope) { scope = $rootScope.$new(); fakeBackend = $httpBackend; + var partyResponse = {data:{_id: 'party-id'}}; fakeBackend.when('GET', 'partials/main.html').respond({}); - fakeBackend.when('GET', '/api/v2/groups/party').respond({_id: 'party-id'}); - fakeBackend.when('POST', '/api/v2/groups/party-id/questReject').respond({quest: { key: 'whale' } }); + fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse); + fakeBackend.when('POST', '/api/v3/groups/party-id/quests/reject').respond({quest: { key: 'whale' } }); fakeBackend.flush(); })); it('returns a promise', function() { - var promise = questsService.sendAction('questReject'); + var promise = questsService.sendAction('quests/reject'); expect(promise).to.respondTo('then'); }); it('calls specified quest endpoint', function(done) { - fakeBackend.expectPOST('/api/v2/groups/party-id/questReject'); + fakeBackend.expectPOST('/api/v3/groups/party-id/quests/reject'); - questsService.sendAction('questReject') + questsService.sendAction('quests/reject') .then(function(res) { expect(res.key).to.eql('whale'); done(); @@ -378,7 +409,7 @@ describe('Quests Service', function() { }); it('syncs User', function() { - questsService.sendAction('questReject') + questsService.sendAction('quests/reject') .then(function(res) { expect(User.sync).to.be.calledOnce; done(); diff --git a/test/spec/services/statServicesSpec.js b/test/spec/services/statServicesSpec.js index 81e13645de..a1d99809c2 100644 --- a/test/spec/services/statServicesSpec.js +++ b/test/spec/services/statServicesSpec.js @@ -76,7 +76,11 @@ describe('Stats Service', function() { "armor" : "armor_warrior_1" }; var user = { - _statsComputed: { str: 50 }, + fns: { + statsComputed: function () { + return { str: 50 }; + }, + }, stats: { lvl: 10, buffs: { str: 10 }, @@ -252,7 +256,8 @@ describe('Stats Service', function() { describe('mpDisplay', function() { it('displays mp as "mp / totalMP"', function() { - user._statsComputed = { maxMP: 100 }; + user.fns = {}; + user.fns.statsComputed = function () { return { maxMP: 100 } }; user.stats.mp = 30; var mpDisplay = statCalc.mpDisplay(user); @@ -260,7 +265,8 @@ describe('Stats Service', function() { }); it('Rounds mp down when given a decimal', function() { - user._statsComputed = { maxMP: 100 }; + user.fns = {}; + user.fns.statsComputed = function () { return { maxMP: 100 } }; user.stats.mp = 30.99; var mpDisplay = statCalc.mpDisplay(user); diff --git a/test/spec/services/tagServicesSpec.js b/test/spec/services/tagServicesSpec.js new file mode 100644 index 0000000000..119c814e09 --- /dev/null +++ b/test/spec/services/tagServicesSpec.js @@ -0,0 +1,52 @@ +'use strict'; + +describe('Tags Service', function() { + var rootScope, tags, user, $httpBackend; + var apiV3Prefix = 'api/v3/tags'; + + beforeEach(function() { + module(function($provide) { + user = specHelper.newUser(); + $provide.value('User', {user: user}); + }); + + inject(function(_$httpBackend_, _$rootScope_, Tags, User) { + $httpBackend = _$httpBackend_; + rootScope = _$rootScope_; + tags = Tags; + }); + }); + + it('calls get tags endpoint', function() { + $httpBackend.expectGET(apiV3Prefix).respond({}); + tags.getTags(); + $httpBackend.flush(); + }); + + it('calls post tags endpoint', function() { + $httpBackend.expectPOST(apiV3Prefix).respond({}); + tags.createTag(); + $httpBackend.flush(); + }); + + it('calls get tag endpoint', function() { + var tagId = 1; + $httpBackend.expectGET(apiV3Prefix + '/' + tagId).respond({}); + tags.getTag(tagId); + $httpBackend.flush(); + }); + + it('calls update tag endpoint', function() { + var tagId = 1; + $httpBackend.expectPUT(apiV3Prefix + '/' + tagId).respond({}); + tags.updateTag(tagId, {}); + $httpBackend.flush(); + }); + + it('calls delete tag endpoint', function() { + var tagId = 1; + $httpBackend.expectDELETE(apiV3Prefix + '/' + tagId).respond({}); + tags.deleteTag(tagId); + $httpBackend.flush(); + }); +}); diff --git a/test/spec/services/taskServicesSpec.js b/test/spec/services/taskServicesSpec.js index 212ba816f9..59d1a5d49e 100644 --- a/test/spec/services/taskServicesSpec.js +++ b/test/spec/services/taskServicesSpec.js @@ -1,22 +1,155 @@ 'use strict'; describe('Tasks Service', function() { - var rootScope, tasks, user; + var rootScope, tasks, user, $httpBackend; + var apiV3Prefix = '/api/v3/tasks'; beforeEach(function() { - module(function($provide) { user = specHelper.newUser(); $provide.value('User', {user: user}); }); - inject(function(_$rootScope_, Tasks, User) { + inject(function(_$httpBackend_, _$rootScope_, Tasks, User) { + $httpBackend = _$httpBackend_; rootScope = _$rootScope_; rootScope.charts = {}; tasks = Tasks; }); }); + it('calls get user tasks endpoint', function() { + $httpBackend.expectGET(apiV3Prefix + '/user').respond({}); + tasks.getUserTasks(); + $httpBackend.flush(); + }); + + it('calls post user tasks endpoint', function() { + $httpBackend.expectPOST(apiV3Prefix + '/user').respond({}); + tasks.createUserTasks(); + $httpBackend.flush(); + }); + + it('calls get challenge tasks endpoint', function() { + var challengeId = 1; + $httpBackend.expectGET(apiV3Prefix + '/challenge/' + challengeId).respond({}); + tasks.getChallengeTasks(challengeId); + $httpBackend.flush(); + }); + + it('calls create challenge tasks endpoint', function() { + var challengeId = 1; + $httpBackend.expectPOST(apiV3Prefix + '/challenge/' + challengeId).respond({}); + tasks.createChallengeTasks(challengeId, {}); + $httpBackend.flush(); + }); + + it('calls get task endpoint', function() { + var taskId = 1; + $httpBackend.expectGET(apiV3Prefix + '/' + taskId).respond({}); + tasks.getTask(taskId); + $httpBackend.flush(); + }); + + it('calls update task endpoint', function() { + var taskId = 1; + $httpBackend.expectPUT(apiV3Prefix + '/' + taskId).respond({}); + tasks.updateTask(taskId, {}); + $httpBackend.flush(); + }); + + it('calls delete task endpoint', function() { + var taskId = 1; + $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId).respond({}); + tasks.deleteTask(taskId); + $httpBackend.flush(); + }); + + it('calls score task endpoint', function() { + var taskId = 1; + var direction = "down"; + $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/score/' + direction).respond({}); + tasks.scoreTask(taskId, direction); + $httpBackend.flush(); + }); + + it('calls move task endpoint', function() { + var taskId = 1; + var position = 0; + $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/move/to/' + position).respond({}); + tasks.moveTask(taskId, position); + $httpBackend.flush(); + }); + + it('calls add check list item endpoint', function() { + var taskId = 1; + $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist').respond({}); + tasks.addChecklistItem(taskId, {}); + $httpBackend.flush(); + }); + + it('calls score check list item endpoint', function() { + var taskId = 1; + var itemId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist/' + itemId + '/score').respond({}); + tasks.scoreCheckListItem(taskId, itemId); + $httpBackend.flush(); + }); + + it('calls update check list item endpoint', function() { + var taskId = 1; + var itemId = 2; + $httpBackend.expectPUT(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({}); + tasks.updateChecklistItem(taskId, itemId, {}); + $httpBackend.flush(); + }); + + it('calls remove check list item endpoint', function() { + var taskId = 1; + var itemId = 2; + $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({}); + tasks.removeChecklistItem(taskId, itemId); + $httpBackend.flush(); + }); + + it('calls add tag to list item endpoint', function() { + var taskId = 1; + var tagId = 2; + $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({}); + tasks.addTagToTask(taskId, tagId); + $httpBackend.flush(); + }); + + it('calls remove tag to list item endpoint', function() { + var taskId = 1; + var tagId = 2; + $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({}); + tasks.removeTagFromTask(taskId, tagId); + $httpBackend.flush(); + }); + + it('calls unlinkOneTask endpoint', function() { + var taskId = 1; + var keep = "keep"; + $httpBackend.expectPOST(apiV3Prefix + '/unlink-one/' + taskId + '?keep=' + keep).respond({}); + tasks.unlinkOneTask(taskId); + $httpBackend.flush(); + }); + + it('calls unlinkAllTasks endpoint', function() { + var challengeId = 1; + var keep = "keep-all"; + $httpBackend.expectPOST(apiV3Prefix + '/unlink-all/' + challengeId + '?keep=' + keep).respond({}); + tasks.unlinkAllTasks(challengeId); + $httpBackend.flush(); + }); + + it('calls clear completed todo task endpoint', function() { + $httpBackend.expectPOST(apiV3Prefix + '/clearCompletedTodos').respond({}); + tasks.clearCompletedTodos(); + $httpBackend.flush(); + }); + describe('editTask', function() { var task; @@ -26,35 +159,35 @@ describe('Tasks Service', function() { }); it('toggles the _editing property', function() { - tasks.editTask(task); + tasks.editTask(task, user); expect(task._editing).to.eql(true); - tasks.editTask(task); + tasks.editTask(task, user); expect(task._editing).to.eql(false); }); it('sets _tags to true by default', function() { - tasks.editTask(task); + tasks.editTask(task, user); expect(task._tags).to.eql(true); }); it('sets _tags to false if preference for collapsed tags is turned on', function() { user.preferences.tagsCollapsed = true; - tasks.editTask(task); + tasks.editTask(task, user); expect(task._tags).to.eql(false); }); it('sets _advanced to true by default', function(){ user.preferences.advancedCollapsed = true; - tasks.editTask(task); + tasks.editTask(task, user); expect(task._advanced).to.eql(false); }); it('sets _advanced to false if preference for collapsed advance menu is turned on', function() { user.preferences.advancedCollapsed = false; - tasks.editTask(task); + tasks.editTask(task, user); expect(task._advanced).to.eql(true); }); @@ -62,7 +195,7 @@ describe('Tasks Service', function() { it('closes task chart if it exists', function() { rootScope.charts[task.id] = true; - tasks.editTask(task); + tasks.editTask(task, user); expect(rootScope.charts[task.id]).to.eql(false); }); }); @@ -82,24 +215,22 @@ describe('Tasks Service', function() { expect(clonedTask.attribute).to.eql(task.attribute); }); - it('does not clone original task\'s id or _id', function() { + it('does not clone original task\'s _id', function() { var task = specHelper.newTask(); var clonedTask = tasks.cloneTask(task); - expect(clonedTask.id).to.exist; - expect(clonedTask.id).to.not.eql(task.id); expect(clonedTask._id).to.exist; expect(clonedTask._id).to.not.eql(task._id); }); it('does not clone original task\'s dateCreated attribute', function() { var task = specHelper.newTask({ - dateCreated: new Date(2014, 5, 1, 1, 1, 1, 1), + createdAt: new Date(2014, 5, 1, 1, 1, 1, 1), }); var clonedTask = tasks.cloneTask(task); - expect(clonedTask.dateCreated).to.exist; - expect(clonedTask.dateCreated).to.not.eql(task.dateCreated); + expect(clonedTask.createdAt).to.exist; + expect(clonedTask.createdAt).to.not.eql(task.createdAt); }); it('does not clone original task\'s value', function() { diff --git a/test/spec/services/userServicesSpec.js b/test/spec/services/userServicesSpec.js index 2f34507e32..7bb5b7aac9 100644 --- a/test/spec/services/userServicesSpec.js +++ b/test/spec/services/userServicesSpec.js @@ -36,12 +36,12 @@ describe('userServices', function() { expect(user_id).to.eql(user.user); }); - it('alerts when not authenticated', function(){ + xit('alerts when not authenticated', function(){ user.log(); expect($window.alert).to.have.been.calledWith("Not authenticated, can't sync, go to settings first."); }); - it('puts items in que queue', function(){ + xit('puts items in que queue', function(){ user.log({}); //TODO where does that null comes from? expect(user.settings.sync.queue).to.eql([null, {}]); diff --git a/test/spec/specHelper.js b/test/spec/specHelper.js index 9324fa5d8b..cbacac1988 100644 --- a/test/spec/specHelper.js +++ b/test/spec/specHelper.js @@ -28,6 +28,9 @@ var specHelper = {}; var user = { _id: 'unique-user-id', + profile: { + name: 'dummy-name', + }, auth: { timestamps: {} }, stats: stats, items: items, diff --git a/website/public/500.html b/website/client/500.html similarity index 100% rename from website/public/500.html rename to website/client/500.html diff --git a/website/public/apple-touch-icon-114-precomposed.png b/website/client/apple-touch-icon-114-precomposed.png similarity index 100% rename from website/public/apple-touch-icon-114-precomposed.png rename to website/client/apple-touch-icon-114-precomposed.png diff --git a/website/public/apple-touch-icon-144-precomposed.png b/website/client/apple-touch-icon-144-precomposed.png similarity index 100% rename from website/public/apple-touch-icon-144-precomposed.png rename to website/client/apple-touch-icon-144-precomposed.png diff --git a/website/public/apple-touch-icon-57-precomposed.png b/website/client/apple-touch-icon-57-precomposed.png similarity index 100% rename from website/public/apple-touch-icon-57-precomposed.png rename to website/client/apple-touch-icon-57-precomposed.png diff --git a/website/public/apple-touch-icon-72-precomposed.png b/website/client/apple-touch-icon-72-precomposed.png similarity index 100% rename from website/public/apple-touch-icon-72-precomposed.png rename to website/client/apple-touch-icon-72-precomposed.png diff --git a/website/public/apple-touch-icon-precomposed.png b/website/client/apple-touch-icon-precomposed.png similarity index 100% rename from website/public/apple-touch-icon-precomposed.png rename to website/client/apple-touch-icon-precomposed.png diff --git a/website/public/cake.png b/website/client/cake.png similarity index 100% rename from website/public/cake.png rename to website/client/cake.png diff --git a/website/public/community-guidelines-images/backCorner.png b/website/client/community-guidelines-images/backCorner.png similarity index 100% rename from website/public/community-guidelines-images/backCorner.png rename to website/client/community-guidelines-images/backCorner.png diff --git a/website/public/community-guidelines-images/beingHabitican.png b/website/client/community-guidelines-images/beingHabitican.png similarity index 100% rename from website/public/community-guidelines-images/beingHabitican.png rename to website/client/community-guidelines-images/beingHabitican.png diff --git a/website/public/community-guidelines-images/consequences.png b/website/client/community-guidelines-images/consequences.png similarity index 100% rename from website/public/community-guidelines-images/consequences.png rename to website/client/community-guidelines-images/consequences.png diff --git a/website/public/community-guidelines-images/contributing.png b/website/client/community-guidelines-images/contributing.png similarity index 100% rename from website/public/community-guidelines-images/contributing.png rename to website/client/community-guidelines-images/contributing.png diff --git a/website/public/community-guidelines-images/github.gif b/website/client/community-guidelines-images/github.gif similarity index 100% rename from website/public/community-guidelines-images/github.gif rename to website/client/community-guidelines-images/github.gif diff --git a/website/public/community-guidelines-images/infractions.png b/website/client/community-guidelines-images/infractions.png similarity index 100% rename from website/public/community-guidelines-images/infractions.png rename to website/client/community-guidelines-images/infractions.png diff --git a/website/public/community-guidelines-images/intro.png b/website/client/community-guidelines-images/intro.png similarity index 100% rename from website/public/community-guidelines-images/intro.png rename to website/client/community-guidelines-images/intro.png diff --git a/website/public/community-guidelines-images/moderators.png b/website/client/community-guidelines-images/moderators.png similarity index 100% rename from website/public/community-guidelines-images/moderators.png rename to website/client/community-guidelines-images/moderators.png diff --git a/website/public/community-guidelines-images/publicGuilds.png b/website/client/community-guidelines-images/publicGuilds.png similarity index 100% rename from website/public/community-guidelines-images/publicGuilds.png rename to website/client/community-guidelines-images/publicGuilds.png diff --git a/website/public/community-guidelines-images/publicSpaces.png b/website/client/community-guidelines-images/publicSpaces.png similarity index 100% rename from website/public/community-guidelines-images/publicSpaces.png rename to website/client/community-guidelines-images/publicSpaces.png diff --git a/website/public/community-guidelines-images/restoration.png b/website/client/community-guidelines-images/restoration.png similarity index 100% rename from website/public/community-guidelines-images/restoration.png rename to website/client/community-guidelines-images/restoration.png diff --git a/website/public/community-guidelines-images/staff.png b/website/client/community-guidelines-images/staff.png similarity index 100% rename from website/public/community-guidelines-images/staff.png rename to website/client/community-guidelines-images/staff.png diff --git a/website/public/community-guidelines-images/tavern.png b/website/client/community-guidelines-images/tavern.png similarity index 100% rename from website/public/community-guidelines-images/tavern.png rename to website/client/community-guidelines-images/tavern.png diff --git a/website/public/community-guidelines-images/trello.png b/website/client/community-guidelines-images/trello.png similarity index 100% rename from website/public/community-guidelines-images/trello.png rename to website/client/community-guidelines-images/trello.png diff --git a/website/public/community-guidelines-images/wiki.png b/website/client/community-guidelines-images/wiki.png similarity index 100% rename from website/public/community-guidelines-images/wiki.png rename to website/client/community-guidelines-images/wiki.png diff --git a/website/public/css/README.md b/website/client/css/README.md similarity index 100% rename from website/public/css/README.md rename to website/client/css/README.md diff --git a/website/public/css/alerts.styl b/website/client/css/alerts.styl similarity index 100% rename from website/public/css/alerts.styl rename to website/client/css/alerts.styl diff --git a/website/public/css/avatar.styl b/website/client/css/avatar.styl similarity index 100% rename from website/public/css/avatar.styl rename to website/client/css/avatar.styl diff --git a/website/public/css/challenges.styl b/website/client/css/challenges.styl similarity index 100% rename from website/public/css/challenges.styl rename to website/client/css/challenges.styl diff --git a/website/public/css/classes.styl b/website/client/css/classes.styl similarity index 100% rename from website/public/css/classes.styl rename to website/client/css/classes.styl diff --git a/website/public/css/customizer.styl b/website/client/css/customizer.styl similarity index 100% rename from website/public/css/customizer.styl rename to website/client/css/customizer.styl diff --git a/website/public/css/filters.styl b/website/client/css/filters.styl similarity index 100% rename from website/public/css/filters.styl rename to website/client/css/filters.styl diff --git a/website/public/css/footer.styl b/website/client/css/footer.styl similarity index 100% rename from website/public/css/footer.styl rename to website/client/css/footer.styl diff --git a/website/public/css/game-pane.styl b/website/client/css/game-pane.styl similarity index 100% rename from website/public/css/game-pane.styl rename to website/client/css/game-pane.styl diff --git a/website/public/css/global-colors.styl b/website/client/css/global-colors.styl similarity index 100% rename from website/public/css/global-colors.styl rename to website/client/css/global-colors.styl diff --git a/website/public/css/global-modules.styl b/website/client/css/global-modules.styl similarity index 100% rename from website/public/css/global-modules.styl rename to website/client/css/global-modules.styl diff --git a/website/public/css/header.styl b/website/client/css/header.styl similarity index 100% rename from website/public/css/header.styl rename to website/client/css/header.styl diff --git a/website/public/css/helpers.styl b/website/client/css/helpers.styl similarity index 100% rename from website/public/css/helpers.styl rename to website/client/css/helpers.styl diff --git a/website/public/css/index.styl b/website/client/css/index.styl similarity index 100% rename from website/public/css/index.styl rename to website/client/css/index.styl diff --git a/website/public/css/inventory.styl b/website/client/css/inventory.styl similarity index 100% rename from website/public/css/inventory.styl rename to website/client/css/inventory.styl diff --git a/website/public/css/items.styl b/website/client/css/items.styl similarity index 100% rename from website/public/css/items.styl rename to website/client/css/items.styl diff --git a/website/public/css/menu.styl b/website/client/css/menu.styl similarity index 100% rename from website/public/css/menu.styl rename to website/client/css/menu.styl diff --git a/website/public/css/no-script.styl b/website/client/css/no-script.styl similarity index 100% rename from website/public/css/no-script.styl rename to website/client/css/no-script.styl diff --git a/website/public/css/npcs.styl b/website/client/css/npcs.styl similarity index 100% rename from website/public/css/npcs.styl rename to website/client/css/npcs.styl diff --git a/website/public/css/options.styl b/website/client/css/options.styl similarity index 100% rename from website/public/css/options.styl rename to website/client/css/options.styl diff --git a/website/public/css/quests.styl b/website/client/css/quests.styl similarity index 100% rename from website/public/css/quests.styl rename to website/client/css/quests.styl diff --git a/website/public/css/scrollbars.styl b/website/client/css/scrollbars.styl similarity index 100% rename from website/public/css/scrollbars.styl rename to website/client/css/scrollbars.styl diff --git a/website/public/css/shared.styl b/website/client/css/shared.styl similarity index 100% rename from website/public/css/shared.styl rename to website/client/css/shared.styl diff --git a/website/public/css/static.styl b/website/client/css/static.styl similarity index 100% rename from website/public/css/static.styl rename to website/client/css/static.styl diff --git a/website/public/css/tasks.styl b/website/client/css/tasks.styl similarity index 100% rename from website/public/css/tasks.styl rename to website/client/css/tasks.styl diff --git a/website/public/css/variables/screen-size.styl b/website/client/css/variables/screen-size.styl similarity index 100% rename from website/public/css/variables/screen-size.styl rename to website/client/css/variables/screen-size.styl diff --git a/website/public/emails/images/10-days-recapture-v1.png b/website/client/emails/images/10-days-recapture-v1.png similarity index 100% rename from website/public/emails/images/10-days-recapture-v1.png rename to website/client/emails/images/10-days-recapture-v1.png diff --git a/website/public/emails/images/3-days-1-month-recapture-v1.png b/website/client/emails/images/3-days-1-month-recapture-v1.png similarity index 100% rename from website/public/emails/images/3-days-1-month-recapture-v1.png rename to website/client/emails/images/3-days-1-month-recapture-v1.png diff --git a/website/public/emails/images/PROMO-Enchanted-Armoire-v1.png b/website/client/emails/images/PROMO-Enchanted-Armoire-v1.png similarity index 100% rename from website/public/emails/images/PROMO-Enchanted-Armoire-v1.png rename to website/client/emails/images/PROMO-Enchanted-Armoire-v1.png diff --git a/website/public/emails/images/android-promo-v1.png b/website/client/emails/images/android-promo-v1.png similarity index 100% rename from website/public/emails/images/android-promo-v1.png rename to website/client/emails/images/android-promo-v1.png diff --git a/website/public/emails/images/iphone-promo-v1.png b/website/client/emails/images/iphone-promo-v1.png similarity index 100% rename from website/public/emails/images/iphone-promo-v1.png rename to website/client/emails/images/iphone-promo-v1.png diff --git a/website/public/emails/images/one-day-v1.png b/website/client/emails/images/one-day-v1.png similarity index 100% rename from website/public/emails/images/one-day-v1.png rename to website/client/emails/images/one-day-v1.png diff --git a/website/public/emails/images/spring-2015-00-v1.png b/website/client/emails/images/spring-2015-00-v1.png similarity index 100% rename from website/public/emails/images/spring-2015-00-v1.png rename to website/client/emails/images/spring-2015-00-v1.png diff --git a/website/public/emails/images/spring-2015-01-v1.png b/website/client/emails/images/spring-2015-01-v1.png similarity index 100% rename from website/public/emails/images/spring-2015-01-v1.png rename to website/client/emails/images/spring-2015-01-v1.png diff --git a/website/public/emails/images/subscription-begins-time-travelers-v1.png b/website/client/emails/images/subscription-begins-time-travelers-v1.png similarity index 100% rename from website/public/emails/images/subscription-begins-time-travelers-v1.png rename to website/client/emails/images/subscription-begins-time-travelers-v1.png diff --git a/website/public/emails/images/subscription-begins-v1.png b/website/client/emails/images/subscription-begins-v1.png similarity index 100% rename from website/public/emails/images/subscription-begins-v1.png rename to website/client/emails/images/subscription-begins-v1.png diff --git a/website/public/favicon.ico b/website/client/favicon.ico similarity index 100% rename from website/public/favicon.ico rename to website/client/favicon.ico diff --git a/website/public/favicon_192x192.png b/website/client/favicon_192x192.png similarity index 100% rename from website/public/favicon_192x192.png rename to website/client/favicon_192x192.png diff --git a/website/public/fontello/LICENSE.txt b/website/client/fontello/LICENSE.txt similarity index 100% rename from website/public/fontello/LICENSE.txt rename to website/client/fontello/LICENSE.txt diff --git a/website/public/fontello/README.txt b/website/client/fontello/README.txt similarity index 100% rename from website/public/fontello/README.txt rename to website/client/fontello/README.txt diff --git a/website/public/fontello/css/animation.css b/website/client/fontello/css/animation.css similarity index 100% rename from website/public/fontello/css/animation.css rename to website/client/fontello/css/animation.css diff --git a/website/public/fontello/css/fontelico-codes.css b/website/client/fontello/css/fontelico-codes.css similarity index 100% rename from website/public/fontello/css/fontelico-codes.css rename to website/client/fontello/css/fontelico-codes.css diff --git a/website/public/fontello/css/fontelico-embedded.css b/website/client/fontello/css/fontelico-embedded.css similarity index 100% rename from website/public/fontello/css/fontelico-embedded.css rename to website/client/fontello/css/fontelico-embedded.css diff --git a/website/public/fontello/css/fontelico-ie7-codes.css b/website/client/fontello/css/fontelico-ie7-codes.css similarity index 100% rename from website/public/fontello/css/fontelico-ie7-codes.css rename to website/client/fontello/css/fontelico-ie7-codes.css diff --git a/website/public/fontello/css/fontelico-ie7.css b/website/client/fontello/css/fontelico-ie7.css similarity index 100% rename from website/public/fontello/css/fontelico-ie7.css rename to website/client/fontello/css/fontelico-ie7.css diff --git a/website/public/fontello/css/fontelico.css b/website/client/fontello/css/fontelico.css similarity index 100% rename from website/public/fontello/css/fontelico.css rename to website/client/fontello/css/fontelico.css diff --git a/website/public/fontello/demo.html b/website/client/fontello/demo.html similarity index 100% rename from website/public/fontello/demo.html rename to website/client/fontello/demo.html diff --git a/website/public/fontello/font/fontelico.eot b/website/client/fontello/font/fontelico.eot similarity index 100% rename from website/public/fontello/font/fontelico.eot rename to website/client/fontello/font/fontelico.eot diff --git a/website/public/fontello/font/fontelico.svg b/website/client/fontello/font/fontelico.svg similarity index 100% rename from website/public/fontello/font/fontelico.svg rename to website/client/fontello/font/fontelico.svg diff --git a/website/public/fontello/font/fontelico.ttf b/website/client/fontello/font/fontelico.ttf similarity index 100% rename from website/public/fontello/font/fontelico.ttf rename to website/client/fontello/font/fontelico.ttf diff --git a/website/public/fontello/font/fontelico.woff b/website/client/fontello/font/fontelico.woff similarity index 100% rename from website/public/fontello/font/fontelico.woff rename to website/client/fontello/font/fontelico.woff diff --git a/website/public/front/README.md b/website/client/front/README.md similarity index 100% rename from website/public/front/README.md rename to website/client/front/README.md diff --git a/website/public/front/css/blockScroll.css b/website/client/front/css/blockScroll.css similarity index 100% rename from website/public/front/css/blockScroll.css rename to website/client/front/css/blockScroll.css diff --git a/website/public/front/css/bootstrap.min.css b/website/client/front/css/bootstrap.min.css similarity index 100% rename from website/public/front/css/bootstrap.min.css rename to website/client/front/css/bootstrap.min.css diff --git a/website/public/front/css/fixed-positioning.css b/website/client/front/css/fixed-positioning.css similarity index 100% rename from website/public/front/css/fixed-positioning.css rename to website/client/front/css/fixed-positioning.css diff --git a/website/public/front/fonts/glyphicons-halflings-regular.eot b/website/client/front/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from website/public/front/fonts/glyphicons-halflings-regular.eot rename to website/client/front/fonts/glyphicons-halflings-regular.eot diff --git a/website/public/front/fonts/glyphicons-halflings-regular.svg b/website/client/front/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from website/public/front/fonts/glyphicons-halflings-regular.svg rename to website/client/front/fonts/glyphicons-halflings-regular.svg diff --git a/website/public/front/fonts/glyphicons-halflings-regular.ttf b/website/client/front/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from website/public/front/fonts/glyphicons-halflings-regular.ttf rename to website/client/front/fonts/glyphicons-halflings-regular.ttf diff --git a/website/public/front/fonts/glyphicons-halflings-regular.woff b/website/client/front/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from website/public/front/fonts/glyphicons-halflings-regular.woff rename to website/client/front/fonts/glyphicons-halflings-regular.woff diff --git a/website/public/front/fonts/glyphicons-halflings-regular.woff2 b/website/client/front/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from website/public/front/fonts/glyphicons-halflings-regular.woff2 rename to website/client/front/fonts/glyphicons-halflings-regular.woff2 diff --git a/website/public/front/images/Feeding_Time.png b/website/client/front/images/Feeding_Time.png similarity index 100% rename from website/public/front/images/Feeding_Time.png rename to website/client/front/images/Feeding_Time.png diff --git a/website/public/front/images/Guilds Sample Screen.png b/website/client/front/images/Guilds Sample Screen.png similarity index 100% rename from website/public/front/images/Guilds Sample Screen.png rename to website/client/front/images/Guilds Sample Screen.png diff --git a/website/public/front/images/HabitRPGPromoPostCard6.png b/website/client/front/images/HabitRPGPromoPostCard6.png similarity index 100% rename from website/public/front/images/HabitRPGPromoPostCard6.png rename to website/client/front/images/HabitRPGPromoPostCard6.png diff --git a/website/public/front/images/HabitRPGPromoThin.png b/website/client/front/images/HabitRPGPromoThin.png similarity index 100% rename from website/public/front/images/HabitRPGPromoThin.png rename to website/client/front/images/HabitRPGPromoThin.png diff --git a/website/public/front/images/Habitica_banner_by_uncommoncriminal.png b/website/client/front/images/Habitica_banner_by_uncommoncriminal.png similarity index 100% rename from website/public/front/images/Habitica_banner_by_uncommoncriminal.png rename to website/client/front/images/Habitica_banner_by_uncommoncriminal.png diff --git a/website/public/front/images/Habitica_map_by_uncommoncriminal.png b/website/client/front/images/Habitica_map_by_uncommoncriminal.png similarity index 100% rename from website/public/front/images/Habitica_map_by_uncommoncriminal.png rename to website/client/front/images/Habitica_map_by_uncommoncriminal.png diff --git a/website/public/front/images/Healer.png b/website/client/front/images/Healer.png similarity index 100% rename from website/public/front/images/Healer.png rename to website/client/front/images/Healer.png diff --git a/website/public/front/images/Mount.png b/website/client/front/images/Mount.png similarity index 100% rename from website/public/front/images/Mount.png rename to website/client/front/images/Mount.png diff --git a/website/public/front/images/Mount_Body_Dragon-Golden.png b/website/client/front/images/Mount_Body_Dragon-Golden.png similarity index 100% rename from website/public/front/images/Mount_Body_Dragon-Golden.png rename to website/client/front/images/Mount_Body_Dragon-Golden.png diff --git a/website/public/front/images/Mount_Body_Dragon-Red.png b/website/client/front/images/Mount_Body_Dragon-Red.png similarity index 100% rename from website/public/front/images/Mount_Body_Dragon-Red.png rename to website/client/front/images/Mount_Body_Dragon-Red.png diff --git a/website/public/front/images/Mount_Body_Wolf-Base.png b/website/client/front/images/Mount_Body_Wolf-Base.png similarity index 100% rename from website/public/front/images/Mount_Body_Wolf-Base.png rename to website/client/front/images/Mount_Body_Wolf-Base.png diff --git a/website/public/front/images/Mount_Head_Dragon-Golden.png b/website/client/front/images/Mount_Head_Dragon-Golden.png similarity index 100% rename from website/public/front/images/Mount_Head_Dragon-Golden.png rename to website/client/front/images/Mount_Head_Dragon-Golden.png diff --git a/website/public/front/images/Mount_Head_Dragon-Red.png b/website/client/front/images/Mount_Head_Dragon-Red.png similarity index 100% rename from website/public/front/images/Mount_Head_Dragon-Red.png rename to website/client/front/images/Mount_Head_Dragon-Red.png diff --git a/website/public/front/images/Mount_Head_Wolf-Base.png b/website/client/front/images/Mount_Head_Wolf-Base.png similarity index 100% rename from website/public/front/images/Mount_Head_Wolf-Base.png rename to website/client/front/images/Mount_Head_Wolf-Base.png diff --git a/website/public/front/images/Party-Header.png b/website/client/front/images/Party-Header.png similarity index 100% rename from website/public/front/images/Party-Header.png rename to website/client/front/images/Party-Header.png diff --git a/website/public/front/images/Pet-Dragon-Red.png b/website/client/front/images/Pet-Dragon-Red.png similarity index 100% rename from website/public/front/images/Pet-Dragon-Red.png rename to website/client/front/images/Pet-Dragon-Red.png diff --git a/website/public/front/images/Pet-Fox-Red.png b/website/client/front/images/Pet-Fox-Red.png similarity index 100% rename from website/public/front/images/Pet-Fox-Red.png rename to website/client/front/images/Pet-Fox-Red.png diff --git a/website/public/front/images/Promo_springclasses2015.png b/website/client/front/images/Promo_springclasses2015.png similarity index 100% rename from website/public/front/images/Promo_springclasses2015.png rename to website/client/front/images/Promo_springclasses2015.png diff --git a/website/public/front/images/Quest_dilatory_drag'on.png b/website/client/front/images/Quest_dilatory_drag'on.png similarity index 100% rename from website/public/front/images/Quest_dilatory_drag'on.png rename to website/client/front/images/Quest_dilatory_drag'on.png diff --git a/website/public/front/images/Quest_dilatory_drag'onSmall.png b/website/client/front/images/Quest_dilatory_drag'onSmall.png similarity index 100% rename from website/public/front/images/Quest_dilatory_drag'onSmall.png rename to website/client/front/images/Quest_dilatory_drag'onSmall.png diff --git a/website/public/front/images/Rogue.png b/website/client/front/images/Rogue.png similarity index 100% rename from website/public/front/images/Rogue.png rename to website/client/front/images/Rogue.png diff --git a/website/public/front/images/SAMPLEadventurers.png b/website/client/front/images/SAMPLEadventurers.png similarity index 100% rename from website/public/front/images/SAMPLEadventurers.png rename to website/client/front/images/SAMPLEadventurers.png diff --git a/website/public/front/images/TVreward.png b/website/client/front/images/TVreward.png similarity index 100% rename from website/public/front/images/TVreward.png rename to website/client/front/images/TVreward.png diff --git a/website/public/front/images/VICE_by_Baconsaur.png b/website/client/front/images/VICE_by_Baconsaur.png similarity index 100% rename from website/public/front/images/VICE_by_Baconsaur.png rename to website/client/front/images/VICE_by_Baconsaur.png diff --git a/website/public/front/images/Warrior.png b/website/client/front/images/Warrior.png similarity index 100% rename from website/public/front/images/Warrior.png rename to website/client/front/images/Warrior.png diff --git a/website/public/front/images/Wizard.png b/website/client/front/images/Wizard.png similarity index 100% rename from website/public/front/images/Wizard.png rename to website/client/front/images/Wizard.png diff --git a/website/public/front/images/achievement-perfect.png b/website/client/front/images/achievement-perfect.png similarity index 100% rename from website/public/front/images/achievement-perfect.png rename to website/client/front/images/achievement-perfect.png diff --git a/website/public/front/images/achievement-triadbingo.png b/website/client/front/images/achievement-triadbingo.png similarity index 100% rename from website/public/front/images/achievement-triadbingo.png rename to website/client/front/images/achievement-triadbingo.png diff --git a/website/public/front/images/avatar/Warrior.png b/website/client/front/images/avatar/Warrior.png similarity index 100% rename from website/public/front/images/avatar/Warrior.png rename to website/client/front/images/avatar/Warrior.png diff --git a/website/public/front/images/avatar/avatar.png b/website/client/front/images/avatar/avatar.png similarity index 100% rename from website/public/front/images/avatar/avatar.png rename to website/client/front/images/avatar/avatar.png diff --git a/website/public/front/images/avatar/avatarstatic.png b/website/client/front/images/avatar/avatarstatic.png similarity index 100% rename from website/public/front/images/avatar/avatarstatic.png rename to website/client/front/images/avatar/avatarstatic.png diff --git a/website/public/front/images/avatar/hair_bangs_1_brown.png b/website/client/front/images/avatar/hair_bangs_1_brown.png similarity index 100% rename from website/public/front/images/avatar/hair_bangs_1_brown.png rename to website/client/front/images/avatar/hair_bangs_1_brown.png diff --git a/website/public/front/images/avatar/head_0.png b/website/client/front/images/avatar/head_0.png similarity index 100% rename from website/public/front/images/avatar/head_0.png rename to website/client/front/images/avatar/head_0.png diff --git a/website/public/front/images/avatar/head_warrior_3.png b/website/client/front/images/avatar/head_warrior_3.png similarity index 100% rename from website/public/front/images/avatar/head_warrior_3.png rename to website/client/front/images/avatar/head_warrior_3.png diff --git a/website/public/front/images/avatar/head_warrior_5.png b/website/client/front/images/avatar/head_warrior_5.png similarity index 100% rename from website/public/front/images/avatar/head_warrior_5.png rename to website/client/front/images/avatar/head_warrior_5.png diff --git a/website/public/front/images/avatar/shield_warrior_3.png b/website/client/front/images/avatar/shield_warrior_3.png similarity index 100% rename from website/public/front/images/avatar/shield_warrior_3.png rename to website/client/front/images/avatar/shield_warrior_3.png diff --git a/website/public/front/images/avatar/shield_warrior_5.png b/website/client/front/images/avatar/shield_warrior_5.png similarity index 100% rename from website/public/front/images/avatar/shield_warrior_5.png rename to website/client/front/images/avatar/shield_warrior_5.png diff --git a/website/public/front/images/avatar/skin_f5a76e.png b/website/client/front/images/avatar/skin_f5a76e.png similarity index 100% rename from website/public/front/images/avatar/skin_f5a76e.png rename to website/client/front/images/avatar/skin_f5a76e.png diff --git a/website/public/front/images/avatar/slim_armor_warrior_3.png b/website/client/front/images/avatar/slim_armor_warrior_3.png similarity index 100% rename from website/public/front/images/avatar/slim_armor_warrior_3.png rename to website/client/front/images/avatar/slim_armor_warrior_3.png diff --git a/website/public/front/images/avatar/slim_armor_warrior_5.png b/website/client/front/images/avatar/slim_armor_warrior_5.png similarity index 100% rename from website/public/front/images/avatar/slim_armor_warrior_5.png rename to website/client/front/images/avatar/slim_armor_warrior_5.png diff --git a/website/public/front/images/avatar/slim_shirt_black.png b/website/client/front/images/avatar/slim_shirt_black.png similarity index 100% rename from website/public/front/images/avatar/slim_shirt_black.png rename to website/client/front/images/avatar/slim_shirt_black.png diff --git a/website/public/front/images/avatar/weapon_healer_6.png b/website/client/front/images/avatar/weapon_healer_6.png similarity index 100% rename from website/public/front/images/avatar/weapon_healer_6.png rename to website/client/front/images/avatar/weapon_healer_6.png diff --git a/website/public/front/images/avatar/weapon_warrior_3.png b/website/client/front/images/avatar/weapon_warrior_3.png similarity index 100% rename from website/public/front/images/avatar/weapon_warrior_3.png rename to website/client/front/images/avatar/weapon_warrior_3.png diff --git a/website/public/front/images/avatar/weapon_warrior_5.png b/website/client/front/images/avatar/weapon_warrior_5.png similarity index 100% rename from website/public/front/images/avatar/weapon_warrior_5.png rename to website/client/front/images/avatar/weapon_warrior_5.png diff --git a/website/public/front/images/blackish_fox_by_kellllly-d7pzd46.png b/website/client/front/images/blackish_fox_by_kellllly-d7pzd46.png similarity index 100% rename from website/public/front/images/blackish_fox_by_kellllly-d7pzd46.png rename to website/client/front/images/blackish_fox_by_kellllly-d7pzd46.png diff --git a/website/public/front/images/coding_by_phoneix_faerie.png b/website/client/front/images/coding_by_phoneix_faerie.png similarity index 100% rename from website/public/front/images/coding_by_phoneix_faerie.png rename to website/client/front/images/coding_by_phoneix_faerie.png diff --git a/website/public/front/images/devices.png b/website/client/front/images/devices.png similarity index 100% rename from website/public/front/images/devices.png rename to website/client/front/images/devices.png diff --git a/website/public/front/images/explosion.jpg b/website/client/front/images/explosion.jpg similarity index 100% rename from website/public/front/images/explosion.jpg rename to website/client/front/images/explosion.jpg diff --git a/website/public/front/images/explosion.png b/website/client/front/images/explosion.png similarity index 100% rename from website/public/front/images/explosion.png rename to website/client/front/images/explosion.png diff --git a/website/public/front/images/habitrpg_pixel.png b/website/client/front/images/habitrpg_pixel.png similarity index 100% rename from website/public/front/images/habitrpg_pixel.png rename to website/client/front/images/habitrpg_pixel.png diff --git a/website/public/front/images/icon175x175.png b/website/client/front/images/icon175x175.png similarity index 100% rename from website/public/front/images/icon175x175.png rename to website/client/front/images/icon175x175.png diff --git a/website/public/front/images/intro.jpg b/website/client/front/images/intro.jpg similarity index 100% rename from website/public/front/images/intro.jpg rename to website/client/front/images/intro.jpg diff --git a/website/public/front/images/intro.psd b/website/client/front/images/intro.psd similarity index 100% rename from website/public/front/images/intro.psd rename to website/client/front/images/intro.psd diff --git a/website/public/front/images/misc/Pet_Food_Cake_Base.png b/website/client/front/images/misc/Pet_Food_Cake_Base.png similarity index 100% rename from website/public/front/images/misc/Pet_Food_Cake_Base.png rename to website/client/front/images/misc/Pet_Food_Cake_Base.png diff --git a/website/public/front/images/misc/inventory_quest_scroll_harpy.png b/website/client/front/images/misc/inventory_quest_scroll_harpy.png similarity index 100% rename from website/public/front/images/misc/inventory_quest_scroll_harpy.png rename to website/client/front/images/misc/inventory_quest_scroll_harpy.png diff --git a/website/public/front/images/misc/rebirth_orb.png b/website/client/front/images/misc/rebirth_orb.png similarity index 100% rename from website/public/front/images/misc/rebirth_orb.png rename to website/client/front/images/misc/rebirth_orb.png diff --git a/website/public/front/images/misc/shop_gold.png b/website/client/front/images/misc/shop_gold.png similarity index 100% rename from website/public/front/images/misc/shop_gold.png rename to website/client/front/images/misc/shop_gold.png diff --git a/website/public/front/images/misc/shop_potion.png b/website/client/front/images/misc/shop_potion.png similarity index 100% rename from website/public/front/images/misc/shop_potion.png rename to website/client/front/images/misc/shop_potion.png diff --git a/website/public/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png b/website/client/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png similarity index 100% rename from website/public/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png rename to website/client/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png diff --git a/website/public/front/images/party/AnnaCosplay.png b/website/client/front/images/party/AnnaCosplay.png similarity index 100% rename from website/public/front/images/party/AnnaCosplay.png rename to website/client/front/images/party/AnnaCosplay.png diff --git a/website/public/front/images/party/Ariel_cosplay.png b/website/client/front/images/party/Ariel_cosplay.png similarity index 100% rename from website/public/front/images/party/Ariel_cosplay.png rename to website/client/front/images/party/Ariel_cosplay.png diff --git a/website/public/front/images/party/Big_Daddy_(BioShock).png b/website/client/front/images/party/Big_Daddy_(BioShock).png similarity index 100% rename from website/public/front/images/party/Big_Daddy_(BioShock).png rename to website/client/front/images/party/Big_Daddy_(BioShock).png diff --git a/website/public/front/images/party/Cosplay_Daenerys_Targaryen.png b/website/client/front/images/party/Cosplay_Daenerys_Targaryen.png similarity index 100% rename from website/public/front/images/party/Cosplay_Daenerys_Targaryen.png rename to website/client/front/images/party/Cosplay_Daenerys_Targaryen.png diff --git a/website/public/front/images/party/GrimReaper.png b/website/client/front/images/party/GrimReaper.png similarity index 100% rename from website/public/front/images/party/GrimReaper.png rename to website/client/front/images/party/GrimReaper.png diff --git a/website/public/front/images/party/HomeStuckLusus.png b/website/client/front/images/party/HomeStuckLusus.png similarity index 100% rename from website/public/front/images/party/HomeStuckLusus.png rename to website/client/front/images/party/HomeStuckLusus.png diff --git a/website/public/front/images/presslogos/Cnetlogo.png b/website/client/front/images/presslogos/Cnetlogo.png similarity index 100% rename from website/public/front/images/presslogos/Cnetlogo.png rename to website/client/front/images/presslogos/Cnetlogo.png diff --git a/website/public/front/images/presslogos/Fast-Company-logo.png b/website/client/front/images/presslogos/Fast-Company-logo.png similarity index 100% rename from website/public/front/images/presslogos/Fast-Company-logo.png rename to website/client/front/images/presslogos/Fast-Company-logo.png diff --git a/website/public/front/images/presslogos/Forbes_logo.png b/website/client/front/images/presslogos/Forbes_logo.png similarity index 100% rename from website/public/front/images/presslogos/Forbes_logo.png rename to website/client/front/images/presslogos/Forbes_logo.png diff --git a/website/public/front/images/presslogos/GitHub_Logo.png b/website/client/front/images/presslogos/GitHub_Logo.png similarity index 100% rename from website/public/front/images/presslogos/GitHub_Logo.png rename to website/client/front/images/presslogos/GitHub_Logo.png diff --git a/website/public/front/images/presslogos/discover_logo.png b/website/client/front/images/presslogos/discover_logo.png similarity index 100% rename from website/public/front/images/presslogos/discover_logo.png rename to website/client/front/images/presslogos/discover_logo.png diff --git a/website/public/front/images/presslogos/ionic-logo-blog.png b/website/client/front/images/presslogos/ionic-logo-blog.png similarity index 100% rename from website/public/front/images/presslogos/ionic-logo-blog.png rename to website/client/front/images/presslogos/ionic-logo-blog.png diff --git a/website/public/front/images/presslogos/ionic-logo-horizontal-transparent.png b/website/client/front/images/presslogos/ionic-logo-horizontal-transparent.png similarity index 100% rename from website/public/front/images/presslogos/ionic-logo-horizontal-transparent.png rename to website/client/front/images/presslogos/ionic-logo-horizontal-transparent.png diff --git a/website/public/front/images/presslogos/kickstarter-logo.png b/website/client/front/images/presslogos/kickstarter-logo.png similarity index 100% rename from website/public/front/images/presslogos/kickstarter-logo.png rename to website/client/front/images/presslogos/kickstarter-logo.png diff --git a/website/public/front/images/presslogos/landing_slack_hash_wordmark_logo.png b/website/client/front/images/presslogos/landing_slack_hash_wordmark_logo.png similarity index 100% rename from website/public/front/images/presslogos/landing_slack_hash_wordmark_logo.png rename to website/client/front/images/presslogos/landing_slack_hash_wordmark_logo.png diff --git a/website/public/front/images/presslogos/lifehacker.png b/website/client/front/images/presslogos/lifehacker.png similarity index 100% rename from website/public/front/images/presslogos/lifehacker.png rename to website/client/front/images/presslogos/lifehacker.png diff --git a/website/public/front/images/presslogos/logo_webstorm.png b/website/client/front/images/presslogos/logo_webstorm.png similarity index 100% rename from website/public/front/images/presslogos/logo_webstorm.png rename to website/client/front/images/presslogos/logo_webstorm.png diff --git a/website/public/front/images/presslogos/makeuseof.png b/website/client/front/images/presslogos/makeuseof.png similarity index 100% rename from website/public/front/images/presslogos/makeuseof.png rename to website/client/front/images/presslogos/makeuseof.png diff --git a/website/public/front/images/presslogos/nyt-logo.png b/website/client/front/images/presslogos/nyt-logo.png similarity index 100% rename from website/public/front/images/presslogos/nyt-logo.png rename to website/client/front/images/presslogos/nyt-logo.png diff --git a/website/public/front/images/presslogos/slack.png b/website/client/front/images/presslogos/slack.png similarity index 100% rename from website/public/front/images/presslogos/slack.png rename to website/client/front/images/presslogos/slack.png diff --git a/website/public/front/images/presslogos/trello-logo-blue.png b/website/client/front/images/presslogos/trello-logo-blue.png similarity index 100% rename from website/public/front/images/presslogos/trello-logo-blue.png rename to website/client/front/images/presslogos/trello-logo-blue.png diff --git a/website/public/front/images/quest_vice3.png b/website/client/front/images/quest_vice3.png similarity index 100% rename from website/public/front/images/quest_vice3.png rename to website/client/front/images/quest_vice3.png diff --git a/website/public/front/images/screenshot.png b/website/client/front/images/screenshot.png similarity index 100% rename from website/public/front/images/screenshot.png rename to website/client/front/images/screenshot.png diff --git a/website/public/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png b/website/client/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png similarity index 100% rename from website/public/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png rename to website/client/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png diff --git a/website/public/front/images/testimonial_by_Streak.png b/website/client/front/images/testimonial_by_Streak.png similarity index 100% rename from website/public/front/images/testimonial_by_Streak.png rename to website/client/front/images/testimonial_by_Streak.png diff --git a/website/public/front/images/testimonials/16bitFil.png b/website/client/front/images/testimonials/16bitFil.png similarity index 100% rename from website/public/front/images/testimonials/16bitFil.png rename to website/client/front/images/testimonials/16bitFil.png diff --git a/website/public/front/images/testimonials/AlexandraSo.png b/website/client/front/images/testimonials/AlexandraSo.png similarity index 100% rename from website/public/front/images/testimonials/AlexandraSo.png rename to website/client/front/images/testimonials/AlexandraSo.png diff --git a/website/public/front/images/testimonials/Althaire.png b/website/client/front/images/testimonials/Althaire.png similarity index 100% rename from website/public/front/images/testimonials/Althaire.png rename to website/client/front/images/testimonials/Althaire.png diff --git a/website/public/front/images/testimonials/AndeeLiao.png b/website/client/front/images/testimonials/AndeeLiao.png similarity index 100% rename from website/public/front/images/testimonials/AndeeLiao.png rename to website/client/front/images/testimonials/AndeeLiao.png diff --git a/website/public/front/images/testimonials/Brenna.png b/website/client/front/images/testimonials/Brenna.png similarity index 100% rename from website/public/front/images/testimonials/Brenna.png rename to website/client/front/images/testimonials/Brenna.png diff --git a/website/public/front/images/testimonials/Drag0nsilver.png b/website/client/front/images/testimonials/Drag0nsilver.png similarity index 100% rename from website/public/front/images/testimonials/Drag0nsilver.png rename to website/client/front/images/testimonials/Drag0nsilver.png diff --git a/website/public/front/images/testimonials/Drei-M.png b/website/client/front/images/testimonials/Drei-M.png similarity index 100% rename from website/public/front/images/testimonials/Drei-M.png rename to website/client/front/images/testimonials/Drei-M.png diff --git a/website/public/front/images/testimonials/Elmi.png b/website/client/front/images/testimonials/Elmi.png similarity index 100% rename from website/public/front/images/testimonials/Elmi.png rename to website/client/front/images/testimonials/Elmi.png diff --git a/website/public/front/images/testimonials/EvaGantz.png b/website/client/front/images/testimonials/EvaGantz.png similarity index 100% rename from website/public/front/images/testimonials/EvaGantz.png rename to website/client/front/images/testimonials/EvaGantz.png diff --git a/website/public/front/images/testimonials/Helcura.png b/website/client/front/images/testimonials/Helcura.png similarity index 100% rename from website/public/front/images/testimonials/Helcura.png rename to website/client/front/images/testimonials/Helcura.png diff --git a/website/public/front/images/testimonials/InfH.png b/website/client/front/images/testimonials/InfH.png similarity index 100% rename from website/public/front/images/testimonials/InfH.png rename to website/client/front/images/testimonials/InfH.png diff --git a/website/public/front/images/testimonials/Kai.png b/website/client/front/images/testimonials/Kai.png similarity index 100% rename from website/public/front/images/testimonials/Kai.png rename to website/client/front/images/testimonials/Kai.png diff --git a/website/public/front/images/testimonials/Kazui.png b/website/client/front/images/testimonials/Kazui.png similarity index 100% rename from website/public/front/images/testimonials/Kazui.png rename to website/client/front/images/testimonials/Kazui.png diff --git a/website/public/front/images/testimonials/Zelah_Meyer.png b/website/client/front/images/testimonials/Zelah_Meyer.png similarity index 100% rename from website/public/front/images/testimonials/Zelah_Meyer.png rename to website/client/front/images/testimonials/Zelah_Meyer.png diff --git a/website/public/front/images/testimonials/autumnesquirrel.png b/website/client/front/images/testimonials/autumnesquirrel.png similarity index 100% rename from website/public/front/images/testimonials/autumnesquirrel.png rename to website/client/front/images/testimonials/autumnesquirrel.png diff --git a/website/public/front/images/testimonials/frabjabulous.png b/website/client/front/images/testimonials/frabjabulous.png similarity index 100% rename from website/public/front/images/testimonials/frabjabulous.png rename to website/client/front/images/testimonials/frabjabulous.png diff --git a/website/public/front/images/testimonials/galarix.png b/website/client/front/images/testimonials/galarix.png similarity index 100% rename from website/public/front/images/testimonials/galarix.png rename to website/client/front/images/testimonials/galarix.png diff --git a/website/public/front/images/testimonials/gwyn.blath.png b/website/client/front/images/testimonials/gwyn.blath.png similarity index 100% rename from website/public/front/images/testimonials/gwyn.blath.png rename to website/client/front/images/testimonials/gwyn.blath.png diff --git a/website/public/front/images/testimonials/irishfeet123.png b/website/client/front/images/testimonials/irishfeet123.png similarity index 100% rename from website/public/front/images/testimonials/irishfeet123.png rename to website/client/front/images/testimonials/irishfeet123.png diff --git a/website/public/front/images/testimonials/skysailor.png b/website/client/front/images/testimonials/skysailor.png similarity index 100% rename from website/public/front/images/testimonials/skysailor.png rename to website/client/front/images/testimonials/skysailor.png diff --git a/website/public/front/images/testimonials/supermouse35.png b/website/client/front/images/testimonials/supermouse35.png similarity index 100% rename from website/public/front/images/testimonials/supermouse35.png rename to website/client/front/images/testimonials/supermouse35.png diff --git a/website/public/front/images/testimonials/tonitonirocca.png b/website/client/front/images/testimonials/tonitonirocca.png similarity index 100% rename from website/public/front/images/testimonials/tonitonirocca.png rename to website/client/front/images/testimonials/tonitonirocca.png diff --git a/website/public/front/images/uses/achievement-bkgd.png b/website/client/front/images/uses/achievement-bkgd.png similarity index 100% rename from website/public/front/images/uses/achievement-bkgd.png rename to website/client/front/images/uses/achievement-bkgd.png diff --git a/website/public/front/images/uses/clipart-rosemonkeyct-meditation.png b/website/client/front/images/uses/clipart-rosemonkeyct-meditation.png similarity index 100% rename from website/public/front/images/uses/clipart-rosemonkeyct-meditation.png rename to website/client/front/images/uses/clipart-rosemonkeyct-meditation.png diff --git a/website/public/front/images/uses/clipart-rosemonkeyct-meditation.psd b/website/client/front/images/uses/clipart-rosemonkeyct-meditation.psd similarity index 100% rename from website/public/front/images/uses/clipart-rosemonkeyct-meditation.psd rename to website/client/front/images/uses/clipart-rosemonkeyct-meditation.psd diff --git a/website/public/front/images/uses/clipart-rosemonkeyct-reading.png b/website/client/front/images/uses/clipart-rosemonkeyct-reading.png similarity index 100% rename from website/public/front/images/uses/clipart-rosemonkeyct-reading.png rename to website/client/front/images/uses/clipart-rosemonkeyct-reading.png diff --git a/website/public/front/images/uses/coding.png b/website/client/front/images/uses/coding.png similarity index 100% rename from website/public/front/images/uses/coding.png rename to website/client/front/images/uses/coding.png diff --git a/website/public/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png b/website/client/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png similarity index 100% rename from website/public/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png rename to website/client/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png diff --git a/website/public/front/images/uses/consequences.png b/website/client/front/images/uses/consequences.png similarity index 100% rename from website/public/front/images/uses/consequences.png rename to website/client/front/images/uses/consequences.png diff --git a/website/public/front/images/uses/dusting-bkgd.png b/website/client/front/images/uses/dusting-bkgd.png similarity index 100% rename from website/public/front/images/uses/dusting-bkgd.png rename to website/client/front/images/uses/dusting-bkgd.png diff --git a/website/public/front/images/uses/dusting_by_leephon.png b/website/client/front/images/uses/dusting_by_leephon.png similarity index 100% rename from website/public/front/images/uses/dusting_by_leephon.png rename to website/client/front/images/uses/dusting_by_leephon.png diff --git a/website/public/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png b/website/client/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png similarity index 100% rename from website/public/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png rename to website/client/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png diff --git a/website/public/front/images/uses/meditation-bkgd.png b/website/client/front/images/uses/meditation-bkgd.png similarity index 100% rename from website/public/front/images/uses/meditation-bkgd.png rename to website/client/front/images/uses/meditation-bkgd.png diff --git a/website/public/front/images/uses/publicSpaces.png b/website/client/front/images/uses/publicSpaces.png similarity index 100% rename from website/public/front/images/uses/publicSpaces.png rename to website/client/front/images/uses/publicSpaces.png diff --git a/website/public/front/images/uses/reading.png b/website/client/front/images/uses/reading.png similarity index 100% rename from website/public/front/images/uses/reading.png rename to website/client/front/images/uses/reading.png diff --git a/website/public/front/js/blockScroll.js b/website/client/front/js/blockScroll.js similarity index 100% rename from website/public/front/js/blockScroll.js rename to website/client/front/js/blockScroll.js diff --git a/website/public/front/js/bootstrap.min.js b/website/client/front/js/bootstrap.min.js similarity index 100% rename from website/public/front/js/bootstrap.min.js rename to website/client/front/js/bootstrap.min.js diff --git a/website/public/front/js/skrollr.min.js b/website/client/front/js/skrollr.min.js similarity index 100% rename from website/public/front/js/skrollr.min.js rename to website/client/front/js/skrollr.min.js diff --git a/website/public/front/landingv1Wireframe.jpg b/website/client/front/landingv1Wireframe.jpg similarity index 100% rename from website/public/front/landingv1Wireframe.jpg rename to website/client/front/landingv1Wireframe.jpg diff --git a/website/public/front/staticstyle.css b/website/client/front/staticstyle.css similarity index 100% rename from website/public/front/staticstyle.css rename to website/client/front/staticstyle.css diff --git a/website/public/front/style.css b/website/client/front/style.css similarity index 100% rename from website/public/front/style.css rename to website/client/front/style.css diff --git a/website/public/google280633b772b94345.html b/website/client/google280633b772b94345.html similarity index 100% rename from website/public/google280633b772b94345.html rename to website/client/google280633b772b94345.html diff --git a/website/public/google8ca65b6ff3506fb8.html b/website/client/google8ca65b6ff3506fb8.html similarity index 100% rename from website/public/google8ca65b6ff3506fb8.html rename to website/client/google8ca65b6ff3506fb8.html diff --git a/website/public/googlef3b1402b0e28338a.html b/website/client/googlef3b1402b0e28338a.html similarity index 100% rename from website/public/googlef3b1402b0e28338a.html rename to website/client/googlef3b1402b0e28338a.html diff --git a/website/public/js/.eslintrc b/website/client/js/.eslintrc similarity index 100% rename from website/public/js/.eslintrc rename to website/client/js/.eslintrc diff --git a/website/public/js/app.js b/website/client/js/app.js similarity index 73% rename from website/public/js/app.js rename to website/client/js/app.js index 6b39f45d63..86801e6777 100644 --- a/website/public/js/app.js +++ b/website/client/js/app.js @@ -26,6 +26,7 @@ window.habitrpg = angular.module('habitrpg', .constant("STORAGE_USER_ID", 'habitrpg-user') .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) + .constant("TAVERN_ID", window.habitrpgShared.TAVERN_ID) //.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'STORAGE_SETTINGS_ID', @@ -150,12 +151,25 @@ window.habitrpg = angular.module('habitrpg', url: '/:gid', templateUrl: 'partials/options.social.guilds.detail.html', title: env.t('titleGuilds'), - controller: ['$scope', 'Groups', 'Chat', '$stateParams', - function($scope, Groups, Chat, $stateParams){ - Groups.Group.get({gid:$stateParams.gid}, function(group){ - $scope.group = group; - Chat.seenMessage(group._id); - }); + controller: ['$scope', 'Groups', 'Chat', '$stateParams', 'Members', 'Challenges', + function($scope, Groups, Chat, $stateParams, Members, Challenges){ + Groups.Group.get($stateParams.gid) + .then(function (response) { + $scope.group = response.data.data; + Chat.markChatSeen($scope.group._id); + Members.getGroupMembers($scope.group._id) + .then(function (response) { + $scope.group.members = response.data.data; + }); + Members.getGroupInvites($scope.group._id) + .then(function (response) { + $scope.group.invites = response.data.data; + }); + Challenges.getGroupChallenges($scope.group._id) + .then(function (response) { + $scope.group.challenges = response.data.data; + }); + }); }] }) @@ -171,33 +185,69 @@ window.habitrpg = angular.module('habitrpg', url: '/:cid', templateUrl: 'partials/options.social.challenges.detail.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Challenges', '$stateParams', - function($scope, Challenges, $stateParams){ - $scope.obj = $scope.challenge = Challenges.Challenge.get({cid:$stateParams.cid}, function(){ - $scope.challenge._locked = true; - }); + controller: ['$scope', 'Challenges', '$stateParams', 'Tasks', 'Members', + function ($scope, Challenges, $stateParams, Tasks, Members) { + Challenges.getChallenge($stateParams.cid) + .then(function (response) { + $scope.obj = $scope.challenge = response.data.data; + $scope.challenge._locked = true; + return Tasks.getChallengeTasks($scope.challenge._id); + }) + .then(function (response) { + var tasks = response.data.data; + tasks.forEach(function (element, index, array) { + if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = []; + $scope.challenge[element.type + 's'].push(element); + }) + + return Members.getChallengeMembers($scope.challenge._id); + }) + .then(function (response) { + $scope.challenge.members = response.data.data; + }); }] }) .state('options.social.challenges.edit', { url: '/:cid/edit', templateUrl: 'partials/options.social.challenges.detail.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Challenges', '$stateParams', - function($scope, Challenges, $stateParams){ - $scope.obj = $scope.challenge = Challenges.Challenge.get({cid:$stateParams.cid}, function(){ - $scope.challenge._locked = false; - }); + controller: ['$scope', 'Challenges', '$stateParams', 'Tasks', + function ($scope, Challenges, $stateParams, Tasks) { + Challenges.getChallenge($stateParams.cid) + .then(function (response) { + $scope.obj = $scope.challenge = response.data.data; + $scope.challenge._locked = false; + return Tasks.getChallengeTasks($scope.challenge._id); + }) + .then(function (response) { + var tasks = response.data.data; + tasks.forEach(function (element, index, array) { + if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = []; + $scope.challenge[element.type + 's'].push(element); + }) + }); }] }) .state('options.social.challenges.detail.member', { url: '/:uid', templateUrl: 'partials/options.social.challenges.detail.member.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Challenges', '$stateParams', - function($scope, Challenges, $stateParams){ - $scope.obj = Challenges.Challenge.getMember({cid:$stateParams.cid, uid:$stateParams.uid}, function(){ - $scope.obj._locked = true; - }); + controller: ['$scope', 'Members', '$stateParams', + function($scope, Members, $stateParams){ + Members.getChallengeMemberProgress($stateParams.cid, $stateParams.uid) + .then(function(response) { + $scope.obj = response.data.data; + + $scope.obj.habits = []; + $scope.obj.todos = []; + $scope.obj.dailys = []; + $scope.obj.rewards = []; + $scope.obj.tasks.forEach(function (element, index, array) { + $scope.obj[element.type + 's'].push(element) + }); + + $scope.obj._locked = true; + }); }] }) @@ -281,6 +331,7 @@ window.habitrpg = angular.module('habitrpg', }); var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)); + if (settings && settings.auth) { $httpProvider.defaults.headers.common['Content-Type'] = 'application/json;charset=utf-8'; $httpProvider.defaults.headers.common['x-api-user'] = settings.auth.apiId; diff --git a/website/public/js/controllers/authCtrl.js b/website/client/js/controllers/authCtrl.js similarity index 71% rename from website/public/js/controllers/authCtrl.js rename to website/client/js/controllers/authCtrl.js index 5b486677f5..13bc8ca1e6 100644 --- a/website/public/js/controllers/authCtrl.js +++ b/website/client/js/controllers/authCtrl.js @@ -17,10 +17,10 @@ angular.module('habitrpg') var runAuth = function(id, token) { User.authenticate(id, token, function(err) { if(!err) $scope.registrationInProgress = false; - $window.location.href = ('/' + window.location.hash); Analytics.login(); Analytics.updateUser(); Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); + $window.location.href = ('/' + window.location.hash); }); }; @@ -28,8 +28,12 @@ angular.module('habitrpg') $scope.registrationInProgress = false; if (status === 0) { $window.alert(window.env.t('noReachServer')); - } else if (!!data && !!data.err) { - $window.alert(data.err); + } else if (status === 400 && data.errors && _.isArray(data.errors)) { // bad requests + data.errors.forEach(function (err) { + $window.alert(err.message); + }); + } else if (!!data && !!data.error) { + $window.alert(data.message); } else { $window.alert(window.env.t('errorUpCase') + ' ' + status); } @@ -46,10 +50,18 @@ angular.module('habitrpg') $scope.registrationInProgress = true; - var url = ApiUrl.get() + "/api/v2/register"; - if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code; - $http.post(url, scope.registerVals).success(function(data, status, headers, config) { - runAuth(data.id, data.apiToken); + var url = ApiUrl.get() + "/api/v3/user/auth/local/register"; + if (location.search && location.search.indexOf('Invite=') !== -1) { // matches groupInvite and partyInvite + url += location.search; + } + + if($rootScope.selectedLanguage) { + var toAppend = url.indexOf('?') !== -1 ? '&' : '?'; + url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code; + } + + $http.post(url, scope.registerVals).success(function(res, status, headers, config) { + runAuth(res.data._id, res.data.apiToken); }).error(errorAlert); }; @@ -58,13 +70,14 @@ angular.module('habitrpg') username: $scope.loginUsername || $('#loginForm input[name="username"]').val(), password: $scope.loginPassword || $('#loginForm input[name="password"]').val() }; - $http.post(ApiUrl.get() + "/api/v2/user/auth/local", data) - .success(function(data, status, headers, config) { - runAuth(data.id, data.token); + //@TODO: Move all the $http methods to a service + $http.post(ApiUrl.get() + "/api/v3/user/auth/local/login", data) + .success(function(res, status, headers, config) { + runAuth(res.data.id, res.data.apiToken); }).error(errorAlert); }; - $scope.playButtonClick = function(){ + $scope.playButtonClick = function() { Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'}) if (User.authenticated()) { window.location.href = ('/' + window.location.hash); @@ -80,7 +93,7 @@ angular.module('habitrpg') if(email == null || email.length == 0) { alert(window.env.t('invalidEmail')); } else { - $http.post(ApiUrl.get() + '/api/v2/user/reset-password', {email:email}) + $http.post(ApiUrl.get() + '/api/v3/user/reset-password', {email:email}) .success(function(){ alert(window.env.t('newPassSent')); }) @@ -98,12 +111,12 @@ angular.module('habitrpg') $scope.socialLogin = function(network){ hello(network).login({scope:'email'}).then(function(auth){ - $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) - .success(function(data, status, headers, config) { - runAuth(data.id, data.token); + $http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth) + .success(function(res, status, headers, config) { + runAuth(res.data.id, res.data.apiToken); }).error(errorAlert); }, function( e ){ - alert("Signin error: " + e.error.message ); + alert("Signin error: " + e.message ); }); }; diff --git a/website/public/js/controllers/autoCompleteCtrl.js b/website/client/js/controllers/autoCompleteCtrl.js similarity index 100% rename from website/public/js/controllers/autoCompleteCtrl.js rename to website/client/js/controllers/autoCompleteCtrl.js diff --git a/website/public/js/controllers/challengesCtrl.js b/website/client/js/controllers/challengesCtrl.js similarity index 61% rename from website/public/js/controllers/challengesCtrl.js rename to website/client/js/controllers/challengesCtrl.js index 30aac589d5..bcc62eca72 100644 --- a/website/public/js/controllers/challengesCtrl.js +++ b/website/client/js/controllers/challengesCtrl.js @@ -1,5 +1,5 @@ -habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', '$stateParams', 'Members', 'Tasks', - function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state, $stateParams, Members, Tasks) { +habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', '$stateParams', 'Members', 'Tasks', 'TAVERN_ID', + function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state, $stateParams, Members, Tasks, TAVERN_ID) { // Use presence of cid to determine whether to show a list or a single // challenge @@ -10,7 +10,11 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', _getChallenges(); // FIXME $scope.challenges needs to be resolved first (see app.js) - $scope.groups = Groups.Group.query({type:'party,guilds,tavern'}); + $scope.groups = []; + Groups.Group.getGroups('party,guilds,tavern') + .then(function (response) { + $scope.groups = response.data.data; + }); // override score() for tasks listed in challenges-editing pages, so that nothing happens $scope.score = function(){} @@ -26,13 +30,16 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', }); }; + $scope.isUserMemberOf = function (challenge) { + return User.user.challenges.indexOf(challenge._id) !== -1; + } + $scope.editTask = Tasks.editTask; /** * Create */ $scope.create = function() { - //If the user has one filter selected, assume that the user wants to default to that group var defaultGroup; //Our filters contain all groups, but we only want groups that have atleast one challenge @@ -41,19 +48,19 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', var filterCount = 0; for ( var i = 0; i < len; i += 1 ) { - if ( $scope.search.group[groupsWithChallenges[i]] == true ) { + if ($scope.search.group[groupsWithChallenges[i]] === true) { filterCount += 1; defaultGroup = groupsWithChallenges[i]; } - if (filterCount > 1) { - defaultGroup = $scope.groups[0]._id + + if (filterCount >= 1 && defaultGroup) { break; } } - if(!defaultGroup) defaultGroup = 'habitrpg'; + if(!defaultGroup) defaultGroup = TAVERN_ID; - $scope.obj = $scope.newChallenge = new Challenges.Challenge({ + $scope.obj = $scope.newChallenge = { name: '', description: '', habits: [], @@ -65,7 +72,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', timestamp: +(new Date), members: [], official: false - }); + }; _calculateMaxPrize(defaultGroup); }; @@ -82,10 +89,12 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', }; _(clonedTasks).each(function(val, type) { - challenge[type + 's'].forEach(_cloneTaskAndPush); + if (challenge[type + 's']) { + challenge[type + 's'].forEach(_cloneTaskAndPush); + } }).value(); - $scope.obj = $scope.newChallenge = new Challenges.Challenge({ + $scope.obj = $scope.newChallenge = { name: challenge.name, shortName: challenge.shortName, description: challenge.description, @@ -97,7 +106,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', group: challenge.group._id, official: challenge.official, prize: challenge.prize - }); + }; function _cloneTaskAndPush(taskToClone) { var task = Tasks.cloneTask(taskToClone); @@ -111,22 +120,45 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.save = function(challenge) { if (!challenge.group) return alert(window.env.t('selectGroup')); + if (!challenge.shortName || challenge.shortName.length < 3) return alert(window.env.t('shortNameTooShort')); + var isNew = !challenge._id; if(isNew && challenge.prize > $scope.maxPrize) { return alert(window.env.t('challengeNotEnoughGems')); } - challenge.$save(function(_challenge){ - if (isNew) { - Notification.text(window.env.t('challengeCreated')); - User.sync(); - } + if (isNew) { + var _challenge; + Challenges.createChallenge(challenge) + .then(function (response) { + _challenge = response.data.data; + Notification.text(window.env.t('challengeCreated')); - $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { - reload: true, inherit: false, notify: true - }); - }); + var challengeTasks = []; + challengeTasks = challengeTasks.concat(challenge.todos); + challengeTasks = challengeTasks.concat(challenge.habits); + challengeTasks = challengeTasks.concat(challenge.dailys); + challengeTasks = challengeTasks.concat(challenge.rewards); + + return Tasks.createChallengeTasks(_challenge._id, challengeTasks); + }) + .then(function (response) { + $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { + reload: true, inherit: false, notify: true + }); + User.sync(); + }); + } else { + Challenges.updateChallenge(challenge._id, challenge) + .then(function (response) { + var _challenge = response.data.data; + $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { + reload: true, inherit: false, notify: true + }); + User.sync(); + }); + } }; /** @@ -136,7 +168,6 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.newChallenge = null; }; - /** * Close Challenge * ------------------ @@ -148,27 +179,34 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', challenge.winner = undefined; }; + //@TODO: change to $scope.remove $scope["delete"] = function(challenge) { var warningMsg; - if(challenge.group._id == 'habitrpg') { + + if(challenge.group._id == TAVERN_ID) { warningMsg = window.env.t('sureDelChaTavern'); } else { warningMsg = window.env.t('sureDelCha'); } + if (!confirm(warningMsg)) return; - challenge.$delete(function(){ - $scope.popoverEl.popover('destroy'); - _backToChallenges(); - }); + + Challenges.deleteChallenge(challenge._id) + .then(function (response) { + $scope.popoverEl.popover('destroy'); + _backToChallenges(); + }); }; $scope.selectWinner = function(challenge) { if (!challenge.winner) return; if (!confirm(window.env.t('youSure'))) return; - challenge.$close({uid:challenge.winner}, function(){ - $scope.popoverEl.popover('destroy'); - _backToChallenges(); - }) + + Challenges.selectChallengeWinner(challenge._id, challenge.winner) + .then(function (response) { + $scope.popoverEl.popover('destroy'); + _backToChallenges(); + }); } $scope.close = function(challenge, $event) { @@ -203,19 +241,36 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', //------------------------------------------------------------ // Tasks //------------------------------------------------------------ - - $scope.addTask = function(addTo, listDef) { + function addTask (addTo, listDef, challenge) { var task = Shared.taskDefaults({text: listDef.newTask, type: listDef.type}); - addTo.unshift(task); - //User.log({op: "addTask", data: task}); //TODO persist + //If the challenge has not been created, we bulk add tasks on save + if (challenge._id) Tasks.createChallengeTasks(challenge._id, task); + if (!challenge[task.type + 's']) challenge[task.type + 's'] = []; + challenge[task.type + 's'].unshift(task); delete listDef.newTask; }; - $scope.removeTask = function(task, list) { + $scope.addTask = function(addTo, listDef, challenge) { + if (listDef.bulk) { + var tasks = listDef.newTask.split(/[\n\r]+/); + //Reverse the order of tasks so the tasks will appear in the order the user entered them + tasks.reverse(); + _.each(tasks, function(t) { + listDef.newTask = t; + addTask(addTo, listDef, challenge); + }); + listDef.bulk = false; + } else { + addTask(addTo, listDef, challenge); + } + } + + $scope.removeTask = function(task, challenge) { if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return; - //TODO persist - // User.log({op: "delTask", data: task}); - _.remove(list, task); + //We only pass to the api if the challenge exists, otherwise, the tasks only exist on the client + if (challenge._id) Tasks.deleteTask(task._id); + var index = challenge[task.type + 's'].indexOf(task); + challenge[task.type + 's'].splice(index, 1); }; $scope.saveTask = function(task){ @@ -223,28 +278,48 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', // TODO persist } + $scope.toggleBulk = function(list) { + if (typeof list.bulk === 'undefined') { + list.bulk = false; + } + list.bulk = !list.bulk; + list.focus = true; + }; + /* -------------------------- Subscription -------------------------- */ - $scope.join = function(challenge){ - challenge.$join(function(){ - _getChallenges() - User.log({}); - }); - + $scope.join = function (challenge) { + Challenges.joinChallenge(challenge._id) + .then(function (response) { + User.user.challenges.push(challenge._id); + _getChallenges(); + return Tasks.getUserTasks(); + }) + .then(function (response) { + var tasks = response.data.data; + User.syncUserTasks(tasks); + }); } - $scope.leave = function(keep) { + $scope.leave = function(keep, challenge) { if (keep == 'cancel') { $scope.selectedChal = undefined; } else { - $scope.selectedChal.$leave({keep:keep}, function(){ - _getChallenges() - User.log({}); - }); + Challenges.leaveChallenge($scope.selectedChal._id, keep) + .then(function (response) { + var index = User.user.challenges.indexOf($scope.selectedChal._id); + delete User.user.challenges[index]; + _getChallenges(); + return Tasks.getUserTasks(); + }) + .then(function (response) { + var tasks = response.data.data; + User.syncUserTasks(tasks); + }); } $scope.popoverEl.popover('destroy'); } @@ -283,7 +358,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', _calculateMaxPrize(gid); - if (gid == 'habitrpg') { + if (gid == TAVERN_ID) { $scope.newChallenge.prize = 1; } }) @@ -306,7 +381,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.insufficientGemsForTavernChallenge = function() { var balance = User.user.balance || 0; - var isForTavern = $scope.newChallenge.group == 'habitrpg'; + var isForTavern = $scope.newChallenge.group == TAVERN_ID; if (isForTavern) { return balance <= 0; @@ -316,21 +391,24 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', } $scope.sendMessageToChallengeParticipant = function(uid) { - Members.selectMember(uid, function(){ - $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); - }); + Members.selectMember(uid) + .then(function () { + $rootScope.openModal('private-message', {controller:'MemberModalCtrl'}); + }); }; $scope.sendGiftToChallengeParticipant = function(uid) { - Members.selectMember(uid, function(){ - $rootScope.openModal('send-gift',{controller:'MemberModalCtrl'}) - }); + Members.selectMember(uid) + .then(function () { + $rootScope.openModal('send-gift', {controller:'MemberModalCtrl'}); + }); }; $scope.filterInitialChallenges = function() { - $scope.groupsFilter = _.uniq(_.pluck($scope.challenges, 'group'), function(g){return g._id}); + $scope.groupsFilter = _.uniq(_.compact(_.pluck($scope.challenges, 'group')), function(g) {return g._id}); + $scope.search = { - group: _.transform($scope.groups, function(m,g){m[g._id]=true;}), + group: _.transform($scope.groups, function(m,g) { m[g._id] = true;}), _isMember: "either", _isOwner: "either" }; @@ -361,14 +439,14 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', return groupBalance; } - function _shouldShowChallenge(chal) { + function _shouldShowChallenge (chal) { // Have to check that the leader object exists first in the // case where a challenge's leader deletes their account var userIsOwner = (chal.leader && chal.leader._id) === User.user.id; - var groupSelected = $scope.search.group[chal.group._id]; + var groupSelected = $scope.search.group[chal.group ? chal.group._id : null]; var checkOwner = $scope.search._isOwner === 'either' || (userIsOwner === $scope.search._isOwner); - var checkMember = $scope.search._isMember === 'either' || (chal._isMember === $scope.search._isMember); + var checkMember = $scope.search._isMember === 'either' || ($scope.isUserMemberOf(chal) === $scope.search._isMember); return groupSelected && checkOwner && checkMember; } @@ -377,24 +455,24 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.popoverEl.popover('destroy'); $scope.cid = null; $state.go('options.social.challenges'); - $scope.challenges = Challenges.Challenge.query(); - User.log({}); + _getChallenges(); } - // Fetch single challenge if a cid is present; fetch multiple challenges // otherwise function _getChallenges() { if ($scope.cid) { - Challenges.Challenge.get({cid: $scope.cid}, function(challenge) { - $scope.challenges = [challenge]; - }); + Challenges.getChallenge($scope.cid) + .then(function (response) { + var challenge = response.data.data; + $scope.challenges = [challenge]; + }); } else { - Challenges.Challenge.query(function(challenges){ - $scope.challenges = challenges; - $scope.filterInitialChallenges(); - }); + Challenges.getUserChallenges() + .then(function(response){ + $scope.challenges = response.data.data; + $scope.filterInitialChallenges(); + }); } }; - }]); diff --git a/website/public/js/controllers/chatCtrl.js b/website/client/js/controllers/chatCtrl.js similarity index 62% rename from website/public/js/controllers/chatCtrl.js rename to website/client/js/controllers/chatCtrl.js index 8ba5b2a15c..c7c29d6502 100644 --- a/website/public/js/controllers/chatCtrl.js +++ b/website/client/js/controllers/chatCtrl.js @@ -27,66 +27,72 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A if (_.isEmpty(message) || $scope._sending) return; $scope._sending = true; var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false; - Chat.utils.postChat({gid: group._id, message:message, previousMsg: previousMsg}, undefined, function(data){ - if(data.chat){ - group.chat = data.chat; - }else if(data.message){ - group.chat.unshift(data.message); - } - $scope.message.content = ''; - $scope._sending = false; - if (group.type == 'party') { - Analytics.updateUser({'partyID':group.id,'partySize':group.memberCount}); - } - if (group.privacy == 'public'){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name}); - } else { - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); - } - }, function(err){ - $scope._sending = false; - }); + Chat.postChat(group._id, message, previousMsg) + .then(function(response) { + var message = response.data.data.message; + + group.chat.unshift(message); + + $scope.message.content = ''; + $scope._sending = false; + + if (group.type == 'party') { + Analytics.updateUser({'partyID': group.id, 'partySize': group.memberCount}); + } + + if (group.privacy == 'public'){ + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name}); + } else { + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); + } + }, function(err){ + $scope._sending = false; + }); } $scope.deleteChatMessage = function(group, message){ if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){ var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false; - if(confirm('Are you sure you want to delete this message?')){ - Chat.utils.deleteChatMessage({gid:group._id, messageId:message.id, previousMsg:previousMsg}, undefined, function(data){ - if(data.chat) group.chat = data.chat; - - var i = _.findIndex(group.chat, {id: message.id}); - if(i !== -1) group.chat.splice(i, 1); - }); + if (confirm('Are you sure you want to delete this message?')) { + Chat.deleteChat(group._id, message.id, previousMsg) + .then(function (response) { + var i = _.findIndex(group.chat, {id: message.id}); + if(i !== -1) group.chat.splice(i, 1); + }); } } } - $scope.likeChatMessage = function(group,message) { + $scope.likeChatMessage = function(group, message) { if (message.uuid == User.user._id) return Notification.text(window.env.t('foreverAlone')); + if (!message.likes) message.likes = {}; + if (message.likes[User.user._id]) { delete message.likes[User.user._id]; } else { message.likes[User.user._id] = true; } - Chat.utils.like({ gid:group._id, messageId:message.id }, undefined); + + Chat.like(group._id, message.id); } $scope.flagChatMessage = function(groupId,message) { if(!message.flags) message.flags = {}; - if(message.flags[User.user._id]) + + if (message.flags[User.user._id]) { Notification.text(window.env.t('abuseAlreadyReported')); - else { + } else { $scope.abuseObject = message; $scope.groupId = groupId; - Members.selectMember(message.uuid, function(){ - $rootScope.openModal('abuse-flag',{ - controller:'MemberModalCtrl', - scope: $scope + Members.selectMember(message.uuid) + .then(function () { + $rootScope.openModal('abuse-flag',{ + controller:'MemberModalCtrl', + scope: $scope + }); }); - }); } }; @@ -108,15 +114,21 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A }); }; - $scope.sync = function(group){ - if(group.type == 'party') { - group.$syncParty(); // Syncs the whole party, not just 15 members + function handleGroupResponse (response) { + $scope.group = response; + if (!$scope.group._id) $scope.group = response.data.data; + }; + + $scope.sync = function(group) { + if (group.name === Groups.TAVERN_NAME) { + Groups.tavern(true).then(handleGroupResponse); + } else if (group._id === User.user.party._id) { + Groups.party(true).then(handleGroupResponse); } else { - group.$get(); + Groups.Group.get(group._id).then(handleGroupResponse); } - // When the user clicks fetch recent messages we need to update - // that the user has seen the new messages - Chat.seenMessage(group._id); + + Chat.markChatSeen(group._id); } // List of Ordering options for the party members list diff --git a/website/public/js/controllers/copyMessageModalCtrl.js b/website/client/js/controllers/copyMessageModalCtrl.js similarity index 89% rename from website/public/js/controllers/copyMessageModalCtrl.js rename to website/client/js/controllers/copyMessageModalCtrl.js index 60d07ec152..1e58237454 100644 --- a/website/public/js/controllers/copyMessageModalCtrl.js +++ b/website/client/js/controllers/copyMessageModalCtrl.js @@ -9,7 +9,7 @@ habitrpg.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification', notes: $scope.notes }; - User.user.ops.addTask({body:newTask}); + User.addTask({body:newTask}); Notification.text(window.env.t('messageAddedAsToDo')); $scope.$close(); diff --git a/website/public/js/controllers/filtersCtrl.js b/website/client/js/controllers/filtersCtrl.js similarity index 81% rename from website/public/js/controllers/filtersCtrl.js rename to website/client/js/controllers/filtersCtrl.js index cfdc45658d..4a3ed6e59c 100644 --- a/website/public/js/controllers/filtersCtrl.js +++ b/website/client/js/controllers/filtersCtrl.js @@ -14,7 +14,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', _.each(User.user.tags, function(tag){ // Send an update op for each changed tag (excluding new tags & deleted tags, this if() packs a punch) if (tagsSnap[tag.id] && tagsSnap[tag.id].name != tag.name) - User.user.ops.updateTag({params:{id:tag.id},body:{name:tag.name}}); + User.updateTag({params:{id:tag.id}, body:{name:tag.name}}); }) $scope._editing = false; } else { @@ -25,7 +25,12 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', }; $scope.toggleFilter = function(tag) { - user.filters[tag.id] = !user.filters[tag.id]; + if (!user.filters[tag.id]) { + user.filters[tag.id] = true; + } else { + user.filters[tag.id] = !user.filters[tag.id]; + } + // no longer persisting this, it was causing a lot of confusion - users thought they'd permanently lost tasks // Note: if we want to persist for just this computer, easy method is: // User.save(); @@ -37,7 +42,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', $scope.updateTaskFilter(); $scope.createTag = function() { - User.user.ops.addTag({body:{name:$scope._newTag.name, id:Shared.uuid()}}); + User.addTag({body:{name: $scope._newTag.name, id: Shared.uuid()}}); $scope._newTag.name = ''; }; }]); diff --git a/website/public/js/controllers/footerCtrl.js b/website/client/js/controllers/footerCtrl.js similarity index 64% rename from website/public/js/controllers/footerCtrl.js rename to website/client/js/controllers/footerCtrl.js index cd0b9ac182..2d07aff430 100644 --- a/website/public/js/controllers/footerCtrl.js +++ b/website/client/js/controllers/footerCtrl.js @@ -7,8 +7,8 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { $scope.loadWidgets = Social.loadWidgets; if(env.isStaticPage){ - $scope.languages = env.avalaibleLanguages; - $scope.selectedLanguage = _.find(env.avalaibleLanguages, {code: env.language.code}); + $scope.languages = env.availableLanguages; + $scope.selectedLanguage = _.find(env.availableLanguages, {code: env.language.code}); $rootScope.selectedLanguage = $scope.selectedLanguage; @@ -80,21 +80,16 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { $scope.addMissedDay = function(numberOfDays){ if (!confirm("Are you sure you want to reset the day by " + numberOfDays + " day(s)?")) return; - var dayBefore = moment(User.user.lastCron).subtract(numberOfDays, 'days').toDate(); - User.set({'lastCron': dayBefore}); - Notification.text('-' + numberOfDays + ' day(s), remember to refresh'); + + User.setCron(numberOfDays); }; $scope.addTenGems = function(){ - $http.post(ApiUrl.get() + '/api/v2/user/addTenGems').success(function(){ - User.log({}); - }) + User.addTenGems(); }; $scope.addHourglass = function(){ - $http.post(ApiUrl.get() + '/api/v2/user/addHourglass').success(function(){ - User.log({}); - }) + User.addHourglass(); }; $scope.addGold = function(){ @@ -123,10 +118,63 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { }); }; - $scope.addBossQuestProgressUp = function(){ - User.set({ - 'party.quest.progress.up': User.user.party.quest.progress.up + 1000 - }); + $scope.addQuestProgress = function(){ + $http({ + method: "POST", + url: 'api/v3/debug/quest-progress' + }) + .then(function (response) { + Notification.text('Quest progress increased'); + User.sync(); + }) + }; + + $scope.makeAdmin = function () { + User.makeAdmin(); + }; + + $scope.openModifyInventoryModal = function () { + $rootScope.openModal('modify-inventory', {controller: 'FooterCtrl', scope: $scope }); + $scope.showInv = { }; + $scope.inv = { + gear: {}, + special: {}, + pets: {}, + mounts: {}, + eggs: {}, + hatchingPotions: {}, + food: {}, + quests: {}, + }; + $scope.setAllItems = function (type, value) { + var set = $scope.inv[type]; + + for (var item in set) { + if (set.hasOwnProperty(item)) { + set[item] = value; + } + } + }; + }; + + $scope.modifyInventory = function () { + $http({ + method: "POST", + url: 'api/v3/debug/modify-inventory', + data: { + gear: $scope.showInv.gear ? $scope.inv.gear : null, + special: $scope.showInv.special ? $scope.inv.special : null, + pets: $scope.showInv.pets ? $scope.inv.pets : null, + mounts: $scope.showInv.mounts ? $scope.inv.mounts : null, + eggs: $scope.showInv.eggs ? $scope.inv.eggs : null, + hatchingPotions: $scope.showInv.hatchingPotions ? $scope.inv.hatchingPotions : null, + food: $scope.showInv.food ? $scope.inv.food : null, + quests: $scope.showInv.quests ? $scope.inv.quests : null, + } + }) + .then(function (response) { + Notification.text('Inventory updated. Refresh or sync.'); + }) }; } }]) diff --git a/website/public/js/controllers/groupsCtrl.js b/website/client/js/controllers/groupsCtrl.js similarity index 72% rename from website/public/js/controllers/groupsCtrl.js rename to website/client/js/controllers/groupsCtrl.js index e7865afb55..cafbe3b158 100644 --- a/website/public/js/controllers/groupsCtrl.js +++ b/website/client/js/controllers/groupsCtrl.js @@ -2,30 +2,28 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification', function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) { - - $scope.isMemberOfPendingQuest = function(userid, group) { + $scope.isMemberOfPendingQuest = function (userid, group) { if (!group.quest || !group.quest.members) return false; if (group.quest.active) return false; // quest is started, not pending return userid in group.quest.members && group.quest.members[userid] != false; }; - $scope.isMemberOfRunningQuest = function(userid, group) { + $scope.isMemberOfRunningQuest = function (userid, group) { if (!group.quest || !group.quest.members) return false; if (!group.quest.active) return false; // quest is pending, not started return group.quest.members[userid]; }; - $scope.isMemberOfGroup = function(userid, group){ - + $scope.isMemberOfGroup = function (userid, group) { // If the group is a guild, just check for an intersection with the // current user's guilds, rather than checking the members of the group. if(group.type === 'guild') { - return _.detect(Groups.myGuilds(), function(g) { return g._id === group._id }); + return _.detect(User.user.guilds, function(guildId) { return guildId === group._id }); } // Similarly, if we're dealing with the user's current party, return true. if(group.type === 'party') { - var currentParty = Groups.party(); + var currentParty = group; if(currentParty._id && currentParty._id === group._id) return true; } @@ -34,12 +32,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' return ~(memberIds.indexOf(userid)); }; - $scope.isMember = function(user, group){ + $scope.isMember = function (user, group) { return ~(group.members.indexOf(user._id)); }; $scope.Members = Members; - $scope._editing = {group:false}; + + $scope._editing = {group: false}; $scope.groupCopy = {}; $scope.editGroup = function (group) { @@ -47,7 +46,6 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' group._editing = true; }; - $scope.saveEdit = function (group) { var newLeader = $scope.groupCopy._newLeader && $scope.groupCopy._newLeader._id; @@ -57,7 +55,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' angular.copy($scope.groupCopy, group); - group.$save(); + Groups.Group.update(group); $scope.cancelEdit(group); }; @@ -69,13 +67,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.deleteAllMessages = function() { if (confirm(window.env.t('confirmDeleteAllMessages'))) { - User.user.ops.clearPMs({}); + User.clearPMs(); } }; // ------ Modals ------ - $scope.clickMember = function(uid, forceShow) { + $scope.clickMember = function (uid, forceShow) { if (User.user._id == uid && !forceShow) { if ($state.is('tasks')) { $state.go('options.profile.avatar'); @@ -85,14 +83,14 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } else { // We need the member information up top here, but then we pass it down to the modal controller // down below. Better way of handling this? - Members.selectMember(uid, function(){ - $rootScope.openModal('member', {controller:'MemberModalCtrl', windowClass:'profile-modal', size:'lg'}); - }); + Members.selectMember(uid) + .then(function () { + $rootScope.openModal('member', {controller: 'MemberModalCtrl', windowClass: 'profile-modal', size: 'lg'}); + }); } }; - - $scope.removeMember = function(group, member, isMember){ + $scope.removeMember = function (group, member, isMember) { // TODO find a better way to do this (share data with remove member modal) $scope.removeMemberData = { group: group, @@ -102,13 +100,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $rootScope.openModal('remove-member', {scope: $scope}); }; - $scope.confirmRemoveMember = function(confirm){ - if(confirm){ - Groups.Group.removeMember({ - gid: $scope.removeMemberData.group._id, - uuid: $scope.removeMemberData.member._id, - message: $scope.removeMemberData.message, - }, undefined, function(){ + $scope.confirmRemoveMember = function (confirm) { + if (confirm) { + Groups.Group.removeMember( + $scope.removeMemberData.group._id, + $scope.removeMemberData.member._id, + $scope.removeMemberData.message + ).then(function (response) { if($scope.removeMemberData.isMember){ _.pull($scope.removeMemberData.group.members, $scope.removeMemberData.member); }else{ @@ -117,15 +115,16 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.removeMemberData = undefined; }); - }else{ + } else { $scope.removeMemberData = undefined; } }; - $scope.openInviteModal = function(group){ + $scope.openInviteModal = function (group) { if (group.type !== 'party' && group.type !== 'guild') { return console.log('Invalid group type.') } + $rootScope.openModal('invite-' + group.type, { controller:'InviteToGroupCtrl', resolve: { @@ -136,10 +135,10 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }; - $scope.quickReply = function(uid) { - Members.selectMember(uid, function(){ - $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); - }); + $scope.quickReply = function (uid) { + Members.selectMember(uid) + .then(function (response) { + $rootScope.openModal('private-message', {controller: 'MemberModalCtrl'}); + }); } - }]); diff --git a/website/client/js/controllers/guildsCtrl.js b/website/client/js/controllers/guildsCtrl.js new file mode 100644 index 0000000000..821b9aebba --- /dev/null +++ b/website/client/js/controllers/guildsCtrl.js @@ -0,0 +1,127 @@ +'use strict'; + +habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', + function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { + $scope.groups = { + guilds: [], + public: [], + }; + + Groups.myGuilds() + .then(function (guilds) { + $scope.groups.guilds = guilds; + }); + + Groups.publicGuilds() + .then(function (guilds) { + $scope.groups.public = guilds; + }); + + $scope.type = 'guild'; + $scope.text = window.env.t('guild'); + + var newGroup = function(){ + return {type:'guild', privacy:'private'}; + } + $scope.newGroup = newGroup() + + $scope.create = function(group){ + if (User.user.balance < 1) { + return $rootScope.openModal('buyGems', {track:"Gems > Create Group"}); + } + + if (confirm(window.env.t('confirmGuild'))) { + Groups.Group.create(group) + .then(function (response) { + var createdGroup = response.data.data; + if (createdGroup.privacy == 'public') { + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy, 'groupName':createdGroup.name}) + } else { + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy}) + } + $rootScope.hardRedirect('/#/options/groups/guilds/' + createdGroup._id); + }); + } + } + + $scope.join = function (group) { + var groupId = group._id; + + // If we don't have the _id property, we are joining from an invitation + // which contains a id property of the group + if (group.id && !group._id) { + groupId = group.id; + } + + Groups.Group.join(groupId) + .then(function (response) { + var joinedGroup = response.data.data; + + User.user.guilds.push(joinedGroup._id); + + if (joinedGroup.privacy == 'public') { + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy, 'groupName': joinedGroup.name}) + } else { + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy}) + } + + $location.path('/options/groups/guilds/' + joinedGroup._id); + }); + } + + $scope.reject = function(invitationToReject) { + var index = _.findIndex(User.user.invitations.guilds, function(invite) { return invite.id === invitationToReject.id; }); + User.user.invitations.guilds = User.user.invitations.guilds.splice(0, index); + Groups.Group.rejectInvite(invitationToReject.id); + } + + $scope.leave = function(keep) { + if (keep == 'cancel') { + $scope.selectedGroup = undefined; + $scope.popoverEl.popover('destroy'); + } else { + Groups.Group.leave($scope.selectedGroup._id, keep) + .success(function (data) { + var index = User.user.guilds.indexOf($scope.selectedGroup._id); + delete User.user.guilds[index]; + $scope.selectedGroup = undefined; + $location.path('/options/groups/guilds'); + }); + } + } + + $scope.clickLeave = function(group, $event){ + $scope.selectedGroup = group; + $scope.popoverEl = $($event.target).closest('.btn'); + + var html, title; + + Challenges.getGroupChallenges(group._id) + .then(function(response) { + var challenges = _.pluck(_.filter(response.data.data, function(c) { + return c.group._id == group._id; + }), '_id'); + + if (_.intersection(challenges, User.user.challenges).length > 0) { + html = $compile( + '
' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveGroupCha'); + } else { + html = $compile( + '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveGroup') + } + + $scope.popoverEl.popover('destroy').popover({ + html: true, + placement: 'top', + trigger: 'manual', + title: title, + content: html + }).popover('show'); + }); + } + } + ]); diff --git a/website/public/js/controllers/hallCtrl.js b/website/client/js/controllers/hallCtrl.js similarity index 66% rename from website/public/js/controllers/hallCtrl.js rename to website/client/js/controllers/hallCtrl.js index fe385a316c..3aad20a782 100644 --- a/website/public/js/controllers/hallCtrl.js +++ b/website/client/js/controllers/hallCtrl.js @@ -2,10 +2,12 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Hero = $resource(ApiUrl.get() + '/api/v2/hall/heroes/:uid', {uid:'@_id'}); + var Hero = $resource(ApiUrl.get() + '/api/v3/hall/heroes/:uid', {uid:'@_id'}); $scope.hero = undefined; $scope.loadHero = function(uuid){ - $scope.hero = Hero.get({uid:uuid}); + Hero.query({uid:uuid}, function (heroData) { + $scope.hero = heroData.data; + }); } $scope.saveHero = function(hero) { $scope.hero.contributor.admin = ($scope.hero.contributor.level > 7) ? true : false; @@ -13,10 +15,14 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notifica Notification.text("User updated"); $scope.hero = undefined; $scope._heroID = undefined; - $scope.heroes = Hero.query(); + Hero.query({}, function (heroesData) { + $scope.heroes = heroesData.data; + }); }) } - $scope.heroes = Hero.query(); + Hero.query({}, function (heroesData) { + $scope.heroes = heroesData.data; + }); $scope.populateContributorInput = function(id) { $scope._heroID = id; @@ -27,14 +33,14 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notifica habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Patron = $resource(ApiUrl.get() + '/api/v2/hall/patrons/:uid', {uid:'@_id'}); + var Patron = $resource(ApiUrl.get() + '/api/v3/hall/patrons/:uid', {uid:'@_id'}); var page = 0; $scope.patrons = []; $scope.loadMore = function(){ - Patron.query({page: page++}, function(patrons){ - $scope.patrons = $scope.patrons.concat(patrons); + Patron.query({page: page++}, function(patronsData){ + $scope.patrons = $scope.patrons.concat(patronsData.data); }) } $scope.loadMore(); diff --git a/website/public/js/controllers/headerCtrl.js b/website/client/js/controllers/headerCtrl.js similarity index 79% rename from website/public/js/controllers/headerCtrl.js rename to website/client/js/controllers/headerCtrl.js index 68000da3b9..390d2c92a9 100644 --- a/website/public/js/controllers/headerCtrl.js +++ b/website/client/js/controllers/headerCtrl.js @@ -8,15 +8,19 @@ habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User', $scope.inviteOrStartParty = Groups.inviteOrStartParty; - $scope.party = Groups.party(function(){ - var triggerResort = function() { - $scope.partyMinusSelf = resortParty(); - }; + function handlePartyResponse (party) { + $scope.party = party; - triggerResort(); - $scope.$watch('user.party.order', triggerResort); - $scope.$watch('user.party.orderAscending', triggerResort); - }); + var triggerResort = function() { + $scope.partyMinusSelf = resortParty(); + }; + + triggerResort(); + $scope.$watch('user.party.order', triggerResort); + $scope.$watch('user.party.orderAscending', triggerResort); + } + + Groups.party().then(handlePartyResponse, handlePartyResponse); function resortParty() { var result = _.sortBy( diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/client/js/controllers/inventoryCtrl.js similarity index 92% rename from website/public/js/controllers/inventoryCtrl.js rename to website/client/js/controllers/inventoryCtrl.js index 80c80bf3d4..41fa416abf 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/client/js/controllers/inventoryCtrl.js @@ -99,7 +99,7 @@ habitrpg.controller("InventoryCtrl", var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined; if (selected) { var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined; - user.ops.sell({params:{type:type, key: $scope[selected].key}}); + User.sell({params:{type:type, key: $scope[selected].key}}); if (user.items[type][$scope[selected].key] < 1) { $scope[selected] = null; } @@ -118,7 +118,7 @@ habitrpg.controller("InventoryCtrl", var userHasPet = user.items.pets[egg.key + '-' + potion.key] > 0; var isPremiumPet = Content.hatchingPotions[potion.key].premium && !Content.dropEggs[egg.key]; - user.ops.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); + User.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); if (!user.preferences.suppressModals.hatchPet && !userHasPet && !isPremiumPet) { $scope.hatchedPet = { @@ -128,11 +128,13 @@ habitrpg.controller("InventoryCtrl", eggKey: egg.key, pet: 'Pet-' + egg.key + '-' + potion.key }; + $rootScope.openModal('hatchPet', { scope: $scope, size: 'sm' }); } + $scope.selectedEgg = null; $scope.selectedPotion = null; @@ -170,7 +172,7 @@ habitrpg.controller("InventoryCtrl", } else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text()}))) { return; } - User.user.ops.feed({params:{pet: pet, food: food.key}}); + User.feed({params:{pet: pet, food: food.key}}); $scope.selectedFood = null; _updateDropAnimalCount(user.items); @@ -196,12 +198,12 @@ habitrpg.controller("InventoryCtrl", // Selecting Pet } else { - User.user.ops.equip({params:{type: 'pet', key: pet}}); + User.equip({params:{type: 'pet', key: pet}}); } } $scope.chooseMount = function(egg, potion) { - User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}}); + User.equip({params:{type: 'mount', key: egg + '-' + potion}}); } $scope.getSeasonalShopArray = function(set){ @@ -228,7 +230,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.equipped){ var itemKey = user.items.gear.equipped[item]; if (user.items.gear.owned[itemKey]) { - user.ops.equip({params: {key: itemKey}}); + User.equip({params: {type: 'equipped', key: itemKey}}); } } break; @@ -237,7 +239,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.costume){ var itemKey = user.items.gear.costume[item]; if (user.items.gear.owned[itemKey]) { - user.ops.equip({params: {type:"costume", key: itemKey}}); + User.equip({params: {type:"costume", key: itemKey}}); } } break; @@ -245,17 +247,17 @@ habitrpg.controller("InventoryCtrl", case "petMountBackground": var pet = user.items.currentPet; if (pet) { - user.ops.equip({params:{type: 'pet', key: pet}}); + User.equip({params:{type: 'pet', key: pet}}); } var mount = user.items.currentMount; if (mount) { - user.ops.equip({params:{type: 'mount', key: mount}}); + User.equip({params:{type: 'mount', key: mount}}); } var background = user.preferences.background; if (background) { - User.user.ops.unlock({query:{path:"background."+background}}); + User.unlock({query:{path:"background."+background}}); } break; @@ -308,9 +310,9 @@ habitrpg.controller("InventoryCtrl", }; $scope.clickTimeTravelItem = function(type,key) { - if (user.purchased.plan.consecutive.trinkets < 1) return user.ops.hourglassPurchase({params:{type:type,key:key}}); + if (user.purchased.plan.consecutive.trinkets < 1) return User.hourglassPurchase({params:{type:type,key:key}}); if (!window.confirm(window.env.t('hourglassBuyItemConfirm'))) return; - user.ops.hourglassPurchase({params:{type:type,key:key}}); + User.hourglassPurchase({params:{type:type,key:key}}); }; function _updateDropAnimalCount(items) { diff --git a/website/public/js/controllers/inviteToGroupCtrl.js b/website/client/js/controllers/inviteToGroupCtrl.js similarity index 63% rename from website/public/js/controllers/inviteToGroupCtrl.js rename to website/client/js/controllers/inviteToGroupCtrl.js index f4a74dbac9..a986cd768c 100644 --- a/website/public/js/controllers/inviteToGroupCtrl.js +++ b/website/client/js/controllers/inviteToGroupCtrl.js @@ -1,6 +1,7 @@ 'use strict'; -habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', function($scope, User, Groups, injectedGroup, $http, Notification) { +habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', + function($scope, $rootScope, User, Groups, injectedGroup, $http, Notification) { $scope.group = injectedGroup; $scope.inviter = User.user.profile.name; @@ -17,8 +18,12 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedG $scope.inviteNewUsers = function(inviteMethod) { if (!$scope.group._id) { $scope.group.name = $scope.group.name || env.t('possessiveParty', {name: User.user.profile.name}); - return $scope.group.$save() - .then(function(res) { + + return Groups.Group.create($scope.group) + .then(function(response) { + $scope.group = response.data.data; + User.sync(); + Groups.data.party = $scope.group; _inviteByMethod(inviteMethod); }); } @@ -39,12 +44,21 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedG return console.log('Invalid invite method.') } - Groups.Group.invite({gid: $scope.group._id}, invitationDetails, function(){ - Notification.text(window.env.t('invitationsSent')); - _resetInvitees(); - }, function(){ - _resetInvitees(); - }); + Groups.Group.invite($scope.group._id, invitationDetails) + .then(function() { + Notification.text(window.env.t('invitationsSent')); + _resetInvitees(); + var redirectTo = '/#/options/groups/' + if ($scope.group.type === 'party') { + redirectTo += 'party'; + } else { + redirectTo += ('guilds/' + $scope.group._id); + } + + $rootScope.hardRedirect(redirectTo); + }, function(){ + _resetInvitees(); + }); } function _getOnlyUuids() { @@ -65,7 +79,6 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedG function _resetInvitees() { var emptyEmails = [{name:"",email:""},{name:"",email:""}]; var emptyInvitees = [{uuid: ''}]; - $scope.emails = emptyEmails; $scope.invitees = emptyInvitees; } diff --git a/website/public/js/controllers/memberModalCtrl.js b/website/client/js/controllers/memberModalCtrl.js similarity index 50% rename from website/public/js/controllers/memberModalCtrl.js rename to website/client/js/controllers/memberModalCtrl.js index 54f0bb054a..6e8b96d0ae 100644 --- a/website/public/js/controllers/memberModalCtrl.js +++ b/website/client/js/controllers/memberModalCtrl.js @@ -20,45 +20,49 @@ habitrpg }); $scope.sendPrivateMessage = function(uuid, message){ - // Don't do anything if the user somehow gets here without a message. if (!message) return; - $http.post('/api/v2/members/'+uuid+'/message',{message:message}).success(function(){ - Notification.text(window.env.t('messageSentAlert')); - $rootScope.User.sync(); - $scope.$close(); - }); + Members.sendPrivateMessage(message, uuid) + .then(function (response) { + Notification.text(window.env.t('messageSentAlert')); + $rootScope.User.sync(); + $scope.$close(); + }); }; + //@TODO: We don't send subscriptions so the structure has changed in the back. Update this when we update the views. $scope.gift = { type: 'gems', - gems: {amount:0, fromBalance:true}, - subscription: {key:''}, - message:'' + gems: {amount: 0, fromBalance: true}, + subscription: {key: ''}, + message: '' }; - $scope.sendGift = function(uuid, gift){ - $http.post('/api/v2/members/'+uuid+'/gift', gift).success(function(){ - Notification.text('Gift sent!') - $rootScope.User.sync(); - $scope.$close(); - }) + $scope.sendGift = function (uuid) { + Members.transferGems($scope.gift.message, uuid, $scope.gift.gems.amount) + .then(function (response) { + Notification.text(window.env.t('sentGems')); + $rootScope.User.sync(); + $scope.$close(); + }); }; $scope.reportAbuse = function(reporter, message, groupId) { message.flags[reporter._id] = true; - Chat.utils.flagChatMessage({gid: groupId, messageId: message.id}, undefined, function(data){ - Notification.text(window.env.t('abuseReported')); - $scope.$close(); - }); + Chat.flagChatMessage(groupId, message.id) + .then(function(data){ + Notification.text(window.env.t('abuseReported')); + $scope.$close(); + }); }; $scope.clearFlagCount = function(message, groupId) { - Chat.utils.clearFlagCount({gid: groupId, messageId: message.id}, undefined, function(data){ - message.flagCount = 0; - Notification.text("Flags cleared"); - $scope.$close(); - }); + Chat.clearFlagCount(groupId, message.id) + .then(function(data){ + message.flagCount = 0; + Notification.text("Flags cleared"); + $scope.$close(); + }); } } ]); diff --git a/website/public/js/controllers/menuCtrl.js b/website/client/js/controllers/menuCtrl.js similarity index 97% rename from website/public/js/controllers/menuCtrl.js rename to website/client/js/controllers/menuCtrl.js index 5549c527dd..761c118986 100644 --- a/website/public/js/controllers/menuCtrl.js +++ b/website/client/js/controllers/menuCtrl.js @@ -26,7 +26,7 @@ angular.module('habitrpg') } } - $scope.clearMessages = Chat.seenMessage; + $scope.clearMessages = Chat.markChatSeen; $scope.clearCards = Chat.clearCards; $scope.iconClasses = function() { diff --git a/website/public/js/controllers/notificationCtrl.js b/website/client/js/controllers/notificationCtrl.js similarity index 97% rename from website/public/js/controllers/notificationCtrl.js rename to website/client/js/controllers/notificationCtrl.js index ac3fab9e6a..16a2c3a991 100644 --- a/website/public/js/controllers/notificationCtrl.js +++ b/website/client/js/controllers/notificationCtrl.js @@ -68,7 +68,7 @@ habitrpg.controller('NotificationCtrl', } $rootScope.$watch('user.stats.lvl', function(after, before) { - if (after <= before) return; + if (after <= before) return; Notification.lvl(); $rootScope.playSound('Level_Up'); if (User.user._tmp && User.user._tmp.drop && (User.user._tmp.drop.type === 'Quest')) return; @@ -127,7 +127,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(env.t('messageDropFood', {dropArticle: after.article, dropText: text, dropNotes: notes}), after); } else if (after.type === 'Quest') { $rootScope.selectedQuest = Content.quests[after.key]; - $rootScope.openModal('questDrop', {controller:'PartyCtrl',size:'sm'}); + $rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'}); } else if (after.notificationType === 'Mystery') { text = Content.gear.flat[after.key].text(); Notification.drop(env.t('messageDropMysteryItem', {dropText: text}), after); @@ -180,9 +180,13 @@ habitrpg.controller('NotificationCtrl', $rootScope.openModal('questInvitation', {controller:'PartyCtrl'}); }); - $rootScope.$on('responseError', function(ev, error){ + $rootScope.$on('responseError500', function(ev, error){ Notification.error(error); }); + $rootScope.$on('responseError', function(ev, error){ + Notification.error(error, true); + }); + $rootScope.$on('responseText', function(ev, error){ Notification.text(error); }); diff --git a/website/client/js/controllers/partyCtrl.js b/website/client/js/controllers/partyCtrl.js new file mode 100644 index 0000000000..64070734bf --- /dev/null +++ b/website/client/js/controllers/partyCtrl.js @@ -0,0 +1,213 @@ +'use strict'; + +habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', + function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social) { + + var user = User.user; + + $scope.type = 'party'; + $scope.text = window.env.t('party'); + + $scope.inviteOrStartParty = Groups.inviteOrStartParty; + $scope.loadWidgets = Social.loadWidgets; + + Groups.Group.syncParty() + .then(function successCallback(group) { + $rootScope.party = $scope.group = group; + checkForNotifications(); + }, function errorCallback(response) { + $rootScope.party = $scope.group = $scope.newGroup = { type: 'party' }; + }); + + function checkForNotifications () { + // Checks if user's party has reached 2 players for the first time. + if(!user.achievements.partyUp + && $scope.group.memberCount >= 2) { + User.set({'achievements.partyUp':true}); + $rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'}); + } + + // Checks if user's party has reached 4 players for the first time. + if(!user.achievements.partyOn + && $scope.group.memberCount >= 4) { + User.set({'achievements.partyOn':true}); + $rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'}); + } + } + + if ($scope.group && $scope.group._id) { + Chat.markChatSeen($scope.group._id); + } + + $scope.create = function(group) { + if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name}); + Groups.Group.create(group) + .then(function(response) { + $rootScope.party = $scope.group = response.data.data; + User.sync(); + Groups.data.party = $scope.group; + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'party', 'privacy':'private'}); + Analytics.updateUser({'party.id': $scope.group ._id, 'partySize': 1}); + }); + }; + + $scope.join = function (party) { + Groups.Group.join(party.id) + .then(function (response) { + $rootScope.party = $scope.group = response.data.data; + User.sync(); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); + Analytics.updateUser({'partyID': party.id}); + $rootScope.hardRedirect('/#/options/groups/party'); + }); + }; + + // TODO: refactor guild and party leave into one function + $scope.leave = function (keep) { + if (keep == 'cancel') { + $scope.selectedGroup = undefined; + $scope.popoverEl.popover('destroy'); + } else { + Groups.Group.leave($scope.selectedGroup._id, keep) + .then(function (response) { + Analytics.updateUser({'partySize':null,'partyID':null}); + User.sync().then(function () { + $rootScope.hardRedirect('/#/options/groups/party'); + }); + }); + } + }; + + // TODO: refactor guild and party clickLeave into one function + $scope.clickLeave = function(group, $event){ + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'}); + $scope.selectedGroup = group; + $scope.popoverEl = $($event.target).closest('.btn'); + var html, title; + html = $compile('' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
')($scope); + title = window.env.t('leavePartyCha'); + + //TODO: Move this to challenge service + Challenges.getGroupChallenges(group._id) + .then(function(response) { + var challenges = _.pluck(_.filter(response.data.data, function(c) { + return c.group._id == group._id; + }), '_id'); + + if (_.intersection(challenges, User.user.challenges).length > 0) { + html = $compile( + '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leavePartyCha'); + } else { + html = $compile( + '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveParty'); + } + + $scope.popoverEl.popover('destroy').popover({ + html: true, + placement: 'top', + trigger: 'manual', + title: title, + content: html + }).popover('show'); + }); + }; + + $scope.clickStartQuest = function () { + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'}); + var hasQuests = _.find(User.user.items.quests, function(quest) { + return quest > 0; + }); + + if (hasQuests){ + $rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" }); + } else { + $rootScope.$state.go('options.inventory.quests'); + } + }; + + $scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) { + if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) { + Groups.Group.leave(Groups.data.party._id, false) + .then(function() { + $rootScope.party = $scope.group = { + loadingNewParty: true + }; + $scope.join({ id: newPartyId, name: newPartyName }); + }); + } + } + + $scope.reject = function(party) { + Groups.Group.rejectInvite(party.id); + User.set({'invitations.party':{}}); + } + + $scope.questInit = function() { + var key = $rootScope.selectedQuest.key; + + Quests.initQuest(key).then(function() { + $rootScope.selectedQuest = undefined; + $scope.$close(); + }); + }; + + $scope.questCancel = function(){ + if (!confirm(window.env.t('sureCancel'))) return; + + Quests.sendAction('quests/cancel') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questAbort = function(){ + if (!confirm(window.env.t('sureAbort'))) return; + if (!confirm(window.env.t('doubleSureAbort'))) return; + + Quests.sendAction('quests/abort') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questLeave = function(){ + if (!confirm(window.env.t('sureLeave'))) return; + + Quests.sendAction('quests/leave') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questAccept = function(){ + Quests.sendAction('quests/accept') + .then(function(quest) { + $scope.group.quest = quest; + }); + }; + + $scope.questForceStart = function(){ + Quests.sendAction('quests/force-start') + .then(function(quest) { + $scope.group.quest = quest; + }); + }; + + $scope.questReject = function(){ + Quests.sendAction('quests/reject') + .then(function(quest) { + $scope.group.quest = quest; + }); + }; + + $scope.canEditQuest = function() { + var isQuestLeader = $scope.group.quest && $scope.group.quest.leader === User.user._id; + + return isQuestLeader; + }; + } + ]); diff --git a/website/public/js/controllers/rootCtrl.js b/website/client/js/controllers/rootCtrl.js similarity index 86% rename from website/public/js/controllers/rootCtrl.js rename to website/client/js/controllers/rootCtrl.js index eacc0d2cce..b4fee4076a 100644 --- a/website/public/js/controllers/rootCtrl.js +++ b/website/client/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics','TAVERN_ID', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics, TAVERN_ID) { var user = User.user; var initSticky = _.once(function(){ @@ -21,10 +21,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { - User.user.ops.update && User.set({'inbox.newMessages':0}); + User.clearNewMessages(); } }); + $rootScope.TAVERN_ID = TAVERN_ID; $rootScope.User = User; $rootScope.user = user; $rootScope.moment = window.moment; @@ -218,11 +219,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ key: itemKey }; - user.ops.equip({ params: equipParams }); + User.equip({ params: equipParams }); } $rootScope.purchase = function(type, item){ - if (type == 'special') return user.ops.buySpecialSpell({params:{key:item.key}}); + if (type == 'special') return User.buySpecialSpell({params:{key:item.key}}); var gems = user.balance * 4; var price = item.value; @@ -248,7 +249,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ message += window.env.t('buyThis', {text: itemName, price: price, gems: gems}); if ($window.confirm(message)) - user.ops.purchase({params:{type:type,key:item.key}}); + User.purchase({params:{type:type,key:item.key}}); }; function _canBuyEquipment(itemKey) { @@ -277,31 +278,51 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ if (spell.target == 'self') { $scope.castEnd(null, 'self'); } else if (spell.target == 'party') { - var party = Groups.party(); - party = (_.isArray(party) ? party : []).concat(User.user); - $scope.castEnd(party, 'party'); + Groups.party() + .then(function (party) { + party = (_.isArray(party) ? party : []).concat(User.user); + $scope.castEnd(party, 'party'); + }) + .catch(function (party) { // not in a party, act as a solo party + if (party && party.type === 'party') { + party = [User.user]; + $scope.castEnd(party, 'party'); + } + }); + } else if (spell.target == 'tasks') { + var tasks = User.user.habits.concat(User.user.dailys).concat(User.user.rewards).concat(User.user.todos); + // exclude challenge tasks + tasks = tasks.filter(function (task) { + if (!task.challenge) return true; + return (!task.challenge.id || task.challenge.broken); + }); + $scope.castEnd(tasks, 'tasks'); } } $scope.castEnd = function(target, type, $event){ if (!$rootScope.applyingAction) return 'No applying action'; $event && ($event.stopPropagation(),$event.preventDefault()); + if ($scope.spell.target != type) return Notification.text(window.env.t('invalidTarget')); $scope.spell.cast(User.user, target); User.save(); var spell = $scope.spell; - var targetId = (type == 'party' || type == 'self') ? '' : type == 'task' ? target.id : target._id; + var targetId = target ? target._id : null; $scope.spell = null; $rootScope.applyingAction = false; - $http.post(ApiUrl.get() + '/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId) - .success(function(){ + var spellUrl = ApiUrl.get() + '/api/v3/user/class/cast/' + spell.key; + if (targetId) spellUrl += '?targetId=' + targetId; + + $http.post(spellUrl) + .success(function(){ // TODO response will always include the modified data, no need to sync! var msg = window.env.t('youCast', {spell: spell.text()}); switch (type) { - case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break; - case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break; - case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break; + case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break; + case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break; + case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break; } Notification.markdown(msg); User.sync(); diff --git a/website/public/js/controllers/settingsCtrl.js b/website/client/js/controllers/settingsCtrl.js similarity index 89% rename from website/public/js/controllers/settingsCtrl.js rename to website/client/js/controllers/settingsCtrl.js index 3672a4d703..7e78a8b095 100644 --- a/website/public/js/controllers/settingsCtrl.js +++ b/website/client/js/controllers/settingsCtrl.js @@ -77,14 +77,11 @@ habitrpg.controller('SettingsCtrl', }; $scope.saveDayStart = function() { - User.set({ - 'preferences.dayStart': Math.floor($scope.dayStart), - 'lastCron': +new Date - }); + User.setCustomDayStart(Math.floor($scope.dayStart)); }; $scope.language = window.env.language; - $scope.avalaibleLanguages = window.env.avalaibleLanguages; + $scope.availableLanguages = window.env.availableLanguages; $scope.changeLanguage = function(){ $rootScope.$on('userSynced', function(){ @@ -99,7 +96,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.user.ops.reroll({}); + User.reroll({}); $rootScope.$state.go('tasks'); } } @@ -124,7 +121,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.user.ops.rebirth({}); + User.rebirth({}); $rootScope.$state.go('tasks'); } } @@ -146,7 +143,7 @@ habitrpg.controller('SettingsCtrl', } $scope.changeUser = function(attr, updates){ - $http.post(ApiUrl.get() + '/api/v2/user/change-'+attr, updates) + $http.put(ApiUrl.get() + '/api/v3/user/auth/update-'+attr, updates) .success(function(){ alert(window.env.t(attr+'Success')); _.each(updates, function(v,k){updates[k]=null;}); @@ -175,21 +172,25 @@ habitrpg.controller('SettingsCtrl', } $scope.reset = function(){ - User.user.ops.reset({}); + User.reset({}); + User.sync(); $rootScope.$state.go('tasks'); } - $scope['delete'] = function(){ - $http['delete'](ApiUrl.get() + '/api/v2/user') - .success(function(res, code){ - if (res.err) return alert(res.err); - localStorage.clear(); - window.location.href = '/logout'; - }); + $scope['delete'] = function(password) { + $http({ + url: ApiUrl.get() + '/api/v3/user', + method: 'DELETE', + data: {password: password}, + }) + .then(function(res, code) { + localStorage.clear(); + window.location.href = '/logout'; + }); } $scope.enterCoupon = function(code) { - $http.post(ApiUrl.get() + '/api/v2/user/coupon/' + code).success(function(res,code){ + $http.post(ApiUrl.get() + '/api/v3/coupons/enter/' + code).success(function(res,code){ if (code!==200) return; User.sync(); Notification.text(env.t('promoCodeApplied')); @@ -201,7 +202,7 @@ habitrpg.controller('SettingsCtrl', .success(function(res,code){ $scope._codes = {}; if (code!==200) return; - window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.user.apiToken; + window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.settings.auth.apiToken; }) } @@ -235,7 +236,7 @@ habitrpg.controller('SettingsCtrl', var releaseFunction = RELEASE_ANIMAL_TYPES[type]; if (releaseFunction) { - User.user.ops[releaseFunction]({}); + User[releaseFunction]({}); $rootScope.$state.go('tasks'); } } @@ -246,19 +247,19 @@ habitrpg.controller('SettingsCtrl', $scope.hasWebhooks = _.size(webhooks); }) $scope.addWebhook = function(url) { - User.user.ops.addWebhook({body:{url:url, id:Shared.uuid()}}); + User.addWebhook({body:{url:url, id:Shared.uuid()}}); $scope._newWebhook.url = ''; } $scope.saveWebhook = function(id,webhook) { delete webhook._editing; - User.user.ops.updateWebhook({params:{id:id}, body:webhook}); + User.updateWebhook({params:{id:id}, body:webhook}); } $scope.deleteWebhook = function(id) { - User.user.ops.deleteWebhook({params:{id:id}}); + User.deleteWebhook({params:{id:id}}); } $scope.applyCoupon = function(coupon){ - $http.get(ApiUrl.get() + '/api/v2/coupons/valid-discount/'+coupon) + $http.get(ApiUrl.get() + '/api/v3/coupons/validate/'+coupon) .success(function(){ Notification.text("Coupon applied!"); var subs = Content.subscriptionBlocks; diff --git a/website/public/js/controllers/sortableInventoryCtrl.js b/website/client/js/controllers/sortableInventoryCtrl.js similarity index 100% rename from website/public/js/controllers/sortableInventoryCtrl.js rename to website/client/js/controllers/sortableInventoryCtrl.js diff --git a/website/public/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js similarity index 71% rename from website/public/js/controllers/tasksCtrl.js rename to website/client/js/controllers/tasksCtrl.js index 619b0a8b3f..fb44d19cb3 100644 --- a/website/public/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -24,20 +24,23 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N if (direction === 'down') $rootScope.playSound('Minus_Habit'); else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } - User.user.ops.score({params:{id: task.id, direction:direction}}); + User.score({params:{task: task, direction:direction}}); Analytics.updateUser(); Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); }; - function addTask(addTo, listDef, task) { - var newTask = { - text: task, - type: listDef.type, - tags: _.transform(User.user.filters, function(m,v,k){ - if (v) m[k]=v; - }) - }; - User.user.ops.addTask({body:newTask}); + function addTask(addTo, listDef, tasks) { + tasks = _.isArray(tasks) ? tasks : [tasks]; + + User.addTask({ + body: tasks.map(function (task) { + return { + text: task, + type: listDef.type, + tags: _.keys(User.user.filters), + } + }), + }); } $scope.addTask = function(addTo, listDef) { @@ -45,9 +48,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N var tasks = listDef.newTask.split(/[\n\r]+/); //Reverse the order of tasks so the tasks will appear in the order the user entered them tasks.reverse(); - _.each(tasks, function(t) { - addTask(addTo, listDef, t); - }); + addTask(addTo, listDef, tasks); listDef.bulk = false; } else { addTask(addTo, listDef, listDef.newTask); @@ -70,14 +71,16 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N /** * Add the new task to the actions log */ - $scope.clearDoneTodos = function() {}; + $scope.clearDoneTodos = function() { + Tasks.clearCompletedTodos(); + }; /** * Pushes task to top or bottom of list */ $scope.pushTask = function(task, index, location) { var to = (location === 'bottom' || $scope.ctrlPressed) ? -1 : 0; - User.user.ops.sortTask({params:{id:task.id},query:{from:index, to:to}}) + User.sortTask({params:{id: task._id, taskType: task.type}, query:{from:index, to:to}}) }; /** @@ -93,16 +96,22 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.removeTask = function(task) { if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return; - User.user.ops.deleteTask({params:{id:task.id}}) + User.deleteTask({params:{id: task._id, taskType: task.type}}) }; $scope.saveTask = function(task, stayOpen, isSaveAndClose) { - if (task.checklist) - task.checklist = _.filter(task.checklist,function(i){return !!i.text}); - User.user.ops.updateTask({params:{id:task.id},body:task}); + if (task.checklist) { + task.checklist = _.filter(task.checklist, function (i) { + return !!i.text + }); + } + User.updateTask(task, {body: task}); if (!stayOpen) task._editing = false; - if (isSaveAndClose) - $("#task-" + task.id).parent().children('.popover').removeClass('in'); + + if (isSaveAndClose) { + $("#task-" + task._id).parent().children('.popover').removeClass('in'); + } + if (task.type == 'habit') Guide.goto('intro', 3); }; @@ -120,11 +129,17 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N }; $scope.unlink = function(task, keep) { - // TODO move this to userServices, turn userSerivces.user into ng-resource - $http.post(ApiUrl.get() + '/api/v2/user/tasks/' + task.id + '/unlink?keep=' + keep) - .success(function(){ - User.log({}); - }); + if (keep.search('-all') !== -1) { // unlink all tasks + Tasks.unlinkAllTasks(task.challenge.id, keep) + .success(function () { + User.sync({}); + }); + } else { // unlink a task + Tasks.unlinkOneTask(task._id, keep) + .success(function () { + User.sync({}); + }); + } }; /* @@ -134,6 +149,16 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ $scope._today = moment().add({days: 1}); + $scope.loadedCompletedTodos = function () { + if (Tasks.loadedCompletedTodos === true) return; + + Tasks.getUserTasks(true) + .then(function (response) { + User.user.todos = User.user.todos.concat(response.data.data); + Tasks.loadedCompletedTodos = true; + }); + } + /* ------------------------ Dailies @@ -154,53 +179,60 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ function focusChecklist(task,index) { window.setTimeout(function(){ - $('#task-'+task.id+' .checklist-form input[type="text"]')[index].focus(); + $('#task-'+task._id+' .checklist-form input[type="text"]')[index].focus(); }); } + $scope.addChecklist = function(task) { - task.checklist = [{completed:false,text:""}]; + task.checklist = [{completed:false, text:""}]; focusChecklist(task,0); } - $scope.addChecklistItem = function(task,$event,$index) { + + $scope.addChecklistItem = function(task, $event, $index) { if (!task.checklist[$index].text) { // Don't allow creation of an empty checklist item // TODO Provide UI feedback that this item is still blank - } else if ($index == task.checklist.length-1){ - User.user.ops.updateTask({params:{id:task.id},body:task}); // don't preen the new empty item + } else if ($index == task.checklist.length - 1) { + Tasks.addChecklistItem(task._id, task.checklist[$index]); task.checklist.push({completed:false,text:''}); focusChecklist(task,task.checklist.length-1); } else { - $scope.saveTask(task,true); - focusChecklist(task,$index+1); + $scope.saveTask(task, true); + focusChecklist(task, $index + 1); } } - $scope.removeChecklistItem = function(task,$event,$index,force){ + + $scope.removeChecklistItem = function(task, $event, $index, force){ // Remove item if clicked on trash icon if (force) { - task.checklist.splice($index,1); - $scope.saveTask(task,true); + Tasks.removeChecklistItem(task._id, task.checklist[$index].id); + task.checklist.splice($index, 1); } else if (!task.checklist[$index].text) { // User deleted all the text and is now wishing to delete the item // saveTask will prune the empty item - $scope.saveTask(task,true); + Tasks.removeChecklistItem(task._id, task.checklist[$index].id); // Move focus if the list is still non-empty if ($index > 0) - focusChecklist(task,$index-1); + focusChecklist(task, $index-1); // Don't allow the backspace key to navigate back now that the field is gone $event.preventDefault(); } } + $scope.swapChecklistItems = function(task, oldIndex, newIndex) { var toSwap = task.checklist.splice(oldIndex, 1)[0]; task.checklist.splice(newIndex, 0, toSwap); $scope.saveTask(task, true); } + $scope.navigateChecklist = function(task,$index,$event){ focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1); } + $scope.checklistCompletion = function(checklist){ return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0) } + $scope.collapseChecklist = function(task) { task.collapseChecklist = !task.collapseChecklist; $scope.saveTask(task,true); @@ -221,10 +253,9 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.buy = function(item) { playRewardSound(item); - User.user.ops.buy({params:{key:item.key}}); + User.buy({params:{key:item.key}}); }; - /* ------------------------ Hiding Tasks @@ -258,4 +289,21 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $rootScope.playSound('Reward'); } } + + /* + ------------------------ + Tags + ------------------------ + */ + + $scope.updateTaskTags = function (tagId, task) { + var tagIndex = task.tags.indexOf(tagId); + if (tagIndex === -1) { + Tasks.addTagToTask(task._id, tagId); + task.tags.push(tagId); + } else { + Tasks.removeTagFromTask(task._id, tagId); + task.tags.splice(tagIndex, 0); + } + } }]); diff --git a/website/client/js/controllers/tavernCtrl.js b/website/client/js/controllers/tavernCtrl.js new file mode 100644 index 0000000000..77056e07a2 --- /dev/null +++ b/website/client/js/controllers/tavernCtrl.js @@ -0,0 +1,18 @@ +'use strict'; + +habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User', 'Challenges', + function($scope, Groups, User, Challenges) { + Groups.tavern() + .then(function (tavern) { + $scope.group = tavern; + Challenges.getGroupChallenges($scope.group._id) + .then(function (response) { + $scope.group.challenges = response.data.data; + }); + }) + + $scope.toggleUserTier = function($event) { + $($event.target).next().toggle(); + } + } + ]); diff --git a/website/public/js/controllers/userCtrl.js b/website/client/js/controllers/userCtrl.js similarity index 90% rename from website/public/js/controllers/userCtrl.js rename to website/client/js/controllers/userCtrl.js index e345d8ba2e..b62207b945 100644 --- a/website/public/js/controllers/userCtrl.js +++ b/website/client/js/controllers/userCtrl.js @@ -17,17 +17,17 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ }); $scope.allocate = function(stat){ - User.user.ops.allocate({query:{stat:stat}}); + User.allocate({query:{stat:stat}}); } $scope.changeClass = function(klass){ if (!klass) { if (!confirm(window.env.t('sureReset'))) return; - return User.user.ops.changeClass({}); + return User.changeClass({}); } - User.user.ops.changeClass({query:{class:klass}}); + User.changeClass({query:{class:klass}}); $scope.selectedClass = undefined; Shared.updateStore(User.user); Guide.goto('classes', 0,true); @@ -46,7 +46,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ } $scope.acknowledgeHealthWarning = function(){ - User.user.ops.update && User.set({'flags.warnedLowHealth':true}); + User.set({'flags.warnedLowHealth':true}); } /** @@ -69,7 +69,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return; if (User.user.balance < cost) return $rootScope.openModal('buyGems'); } - User.user.ops.unlock({query:{path:path}}) + User.unlock({query:{path:path}}) } $scope.ownsSet = function(type,_set) { diff --git a/website/public/js/directives/close-menu.directive.js b/website/client/js/directives/close-menu.directive.js similarity index 100% rename from website/public/js/directives/close-menu.directive.js rename to website/client/js/directives/close-menu.directive.js diff --git a/website/public/js/directives/expand-menu.directive.js b/website/client/js/directives/expand-menu.directive.js similarity index 100% rename from website/public/js/directives/expand-menu.directive.js rename to website/client/js/directives/expand-menu.directive.js diff --git a/website/public/js/directives/focus-element.directive.js b/website/client/js/directives/focus-element.directive.js similarity index 100% rename from website/public/js/directives/focus-element.directive.js rename to website/client/js/directives/focus-element.directive.js diff --git a/website/public/js/directives/from-now.directive.js b/website/client/js/directives/from-now.directive.js similarity index 100% rename from website/public/js/directives/from-now.directive.js rename to website/client/js/directives/from-now.directive.js diff --git a/website/public/js/directives/habitrpg-tasks.directive.js b/website/client/js/directives/habitrpg-tasks.directive.js similarity index 100% rename from website/public/js/directives/habitrpg-tasks.directive.js rename to website/client/js/directives/habitrpg-tasks.directive.js diff --git a/website/public/js/directives/hrpg-sort-checklist.directive.js b/website/client/js/directives/hrpg-sort-checklist.directive.js similarity index 100% rename from website/public/js/directives/hrpg-sort-checklist.directive.js rename to website/client/js/directives/hrpg-sort-checklist.directive.js diff --git a/website/public/js/directives/hrpg-sort-tags.directive.js b/website/client/js/directives/hrpg-sort-tags.directive.js similarity index 88% rename from website/public/js/directives/hrpg-sort-tags.directive.js rename to website/client/js/directives/hrpg-sort-tags.directive.js index 5b42bc778f..9a9e3d49bb 100644 --- a/website/public/js/directives/hrpg-sort-tags.directive.js +++ b/website/client/js/directives/hrpg-sort-tags.directive.js @@ -16,10 +16,10 @@ ui.item.data('startIndex', ui.item.index()); }, stop: function (event, ui) { - User.user.ops.sortTag({ + User.sortTag({ query: { from: ui.item.data('startIndex'), - to:ui.item.index() + to: ui.item.index() } }); } diff --git a/website/public/js/directives/hrpg-sort-tasks.directive.js b/website/client/js/directives/hrpg-sort-tasks.directive.js similarity index 89% rename from website/public/js/directives/hrpg-sort-tasks.directive.js rename to website/client/js/directives/hrpg-sort-tasks.directive.js index 820fccfbe8..0ce42d82eb 100644 --- a/website/public/js/directives/hrpg-sort-tasks.directive.js +++ b/website/client/js/directives/hrpg-sort-tasks.directive.js @@ -20,8 +20,8 @@ stop: function (event, ui) { var task = angular.element(ui.item[0]).scope().task; var startIndex = ui.item.data('startIndex'); - User.user.ops.sortTask({ - params: { id: task.id }, + User.sortTask({ + params: { id: task._id, taskType: task.type }, query: { from: startIndex, to: ui.item.index() diff --git a/website/public/js/directives/popover-html-popup.directive.js b/website/client/js/directives/popover-html-popup.directive.js similarity index 100% rename from website/public/js/directives/popover-html-popup.directive.js rename to website/client/js/directives/popover-html-popup.directive.js diff --git a/website/public/js/directives/popover-html.directive.js b/website/client/js/directives/popover-html.directive.js similarity index 100% rename from website/public/js/directives/popover-html.directive.js rename to website/client/js/directives/popover-html.directive.js diff --git a/website/public/js/directives/when-scrolled.directive.js b/website/client/js/directives/when-scrolled.directive.js similarity index 100% rename from website/public/js/directives/when-scrolled.directive.js rename to website/client/js/directives/when-scrolled.directive.js diff --git a/website/public/js/env.js b/website/client/js/env.js similarity index 100% rename from website/public/js/env.js rename to website/client/js/env.js diff --git a/website/public/js/filters/money.js b/website/client/js/filters/money.js similarity index 100% rename from website/public/js/filters/money.js rename to website/client/js/filters/money.js diff --git a/website/public/js/filters/roundLargeNumbers.js b/website/client/js/filters/roundLargeNumbers.js similarity index 100% rename from website/public/js/filters/roundLargeNumbers.js rename to website/client/js/filters/roundLargeNumbers.js diff --git a/website/public/js/filters/taskOrdering.js b/website/client/js/filters/taskOrdering.js similarity index 100% rename from website/public/js/filters/taskOrdering.js rename to website/client/js/filters/taskOrdering.js diff --git a/website/public/js/filters/timezoneOffsetToUtc.js b/website/client/js/filters/timezoneOffsetToUtc.js similarity index 100% rename from website/public/js/filters/timezoneOffsetToUtc.js rename to website/client/js/filters/timezoneOffsetToUtc.js diff --git a/website/public/js/services/analyticsServices.js b/website/client/js/services/analyticsServices.js similarity index 100% rename from website/public/js/services/analyticsServices.js rename to website/client/js/services/analyticsServices.js diff --git a/website/client/js/services/challengeServices.js b/website/client/js/services/challengeServices.js new file mode 100644 index 0000000000..e5f4cfa2b8 --- /dev/null +++ b/website/client/js/services/challengeServices.js @@ -0,0 +1,99 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Challenges', ['ApiUrl', '$resource', '$http', + function(ApiUrl, $resource, $http) { + var apiV3Prefix = '/api/v3'; + + function createChallenge (challengeData) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/challenges', + data: challengeData, + }); + } + + function joinChallenge (challengeId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/challenges/' + challengeId + '/join', + }); + } + + function leaveChallenge (challengeId, keep) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/challenges/' + challengeId + '/leave', + data: { + keep: keep, + } + }); + } + + function getUserChallenges () { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/user', + }); + } + + function getGroupChallenges (groupId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/groups/' + groupId, + }); + } + + function getChallenge (challengeId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/' + challengeId, + }); + } + + function exportChallengeCsv (challengeId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/' + challengeId + '/export/csv', + }); + } + + function updateChallenge (challengeId, updateData) { + + var challengeDataToSend = _.omit(updateData, ['tasks', 'habits', 'todos', 'rewards', 'group']); + if (challengeDataToSend.leader && challengeDataToSend.leader._id) challengeDataToSend.leader = challengeDataToSend.leader._id; + + return $http({ + method: 'PUT', + url: apiV3Prefix + '/challenges/' + challengeId, + data: challengeDataToSend, + }); + } + + function deleteChallenge (challengeId) { + return $http({ + method: 'DELETE', + url: apiV3Prefix + '/challenges/' + challengeId, + }); + } + + function selectChallengeWinner (challengeId, winnerId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId, + }); + } + + return { + createChallenge: createChallenge, + joinChallenge: joinChallenge, + leaveChallenge: leaveChallenge, + getUserChallenges: getUserChallenges, + getGroupChallenges: getGroupChallenges, + getChallenge: getChallenge, + exportChallengeCsv: exportChallengeCsv, + updateChallenge: updateChallenge, + deleteChallenge: deleteChallenge, + selectChallengeWinner: selectChallengeWinner, + } + }]); diff --git a/website/client/js/services/chatServices.js b/website/client/js/services/chatServices.js new file mode 100644 index 0000000000..4020c9dd92 --- /dev/null +++ b/website/client/js/services/chatServices.js @@ -0,0 +1,87 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Chat', ['$http', 'ApiUrl', 'User', + function($http, ApiUrl, User) { + var apiV3Prefix = '/api/v3'; + + function getChat (groupId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/groups/' + groupId + '/chat', + }); + } + + function postChat (groupId, message, previousMsg) { + var url = apiV3Prefix + '/groups/' + groupId + '/chat'; + + if (previousMsg) { + url += '?previousMsg=' + previousMsg; + } + + return $http({ + method: 'POST', + url: url, + data: { + message: message, + } + }); + } + + function deleteChat (groupId, chatId, previousMsg) { + var url = apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId; + + if (previousMsg) { + url += '?previousMsg=' + previousMsg; + } + + return $http({ + method: 'DELETE', + url: url, + }); + } + + function like (groupId, chatId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like', + }); + } + + function flagChatMessage (groupId, chatId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag', + }); + } + + function clearFlagCount (groupId, chatId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags', + }); + } + + function markChatSeen (groupId) { + if (User.user.newMessages) delete User.user.newMessages[groupId]; + return $http({ + method: 'POST', + url: apiV3Prefix + '/groups/' + groupId + '/chat/seen', + }); + } + + function clearCards () { + User.user._wrapped && User.set({'flags.cardReceived':false}); + } + + return { + getChat: getChat, + postChat: postChat, + deleteChat: deleteChat, + like: like, + flagChatMessage: flagChatMessage, + clearFlagCount: clearFlagCount, + markChatSeen: markChatSeen, + clearCards: clearCards, + } + }]); diff --git a/website/client/js/services/groupServices.js b/website/client/js/services/groupServices.js new file mode 100644 index 0000000000..916efe50b7 --- /dev/null +++ b/website/client/js/services/groupServices.js @@ -0,0 +1,234 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Groups', [ '$location', '$rootScope', '$http', 'Analytics', 'ApiUrl', 'Challenges', '$q', 'User', 'Members', + function($location, $rootScope, $http, Analytics, ApiUrl, Challenges, $q, User, Members) { + var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined }; + var groupApiURLPrefix = "/api/v3/groups"; + var TAVERN_NAME = 'HabitRPG'; + + var Group = {}; + + //@TODO: Add paging + Group.getGroups = function(type) { + var url = groupApiURLPrefix; + if (type) { + url += '?type=' + type; + } + + return $http({ + method: 'GET', + url: url, + }); + }; + + Group.get = function(gid) { + return $http({ + method: 'GET', + url: groupApiURLPrefix + '/' + gid, + }); + }; + + Group.syncParty = function() { + return party(); + }; + + Group.create = function(groupDetails) { + return $http({ + method: "POST", + url: groupApiURLPrefix, + data: groupDetails, + }); + }; + + Group.update = function(groupDetails) { + //@TODO: Check for what has changed? + + //Remove populated fields + var groupDetailsToSend = _.omit(groupDetails, ['challenges', 'members', 'invites']); + if (groupDetailsToSend.leader && groupDetailsToSend.leader._id) groupDetailsToSend.leader = groupDetailsToSend.leader._id; + + return $http({ + method: "PUT", + url: groupApiURLPrefix + '/' + groupDetailsToSend._id, + data: groupDetailsToSend, + }); + }; + + Group.join = function(gid) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/join', + }); + }; + + Group.rejectInvite = function(gid) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/reject-invite', + }); + }; + + Group.leave = function(gid, keep) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/leave', + data: { + keep: keep, + } + }); + }; + + Group.removeMember = function(gid, memberId, message) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/removeMember/' + memberId, + data: { + message: message, + }, + }); + }; + + Group.invite = function(gid, invitationDetails) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/invite', + data: { + uuids: invitationDetails.uuids, + emails: invitationDetails.emails, + }, + }); + }; + + Group.inviteToQuest = function(gid, key) { + return $http({ + method: "POST", + url: groupApiURLPrefix + '/' + gid + '/quests/invite/' + key, + }); + }; + + //On page load, multiple controller request the party. + //So, we cache the promise until the first result is returned + var _cachedPartyPromise; + function party (forceUpdate) { + if (_cachedPartyPromise && !forceUpdate) return _cachedPartyPromise.promise; + _cachedPartyPromise = $q.defer(); + + if (!User.user.party._id) { + data.party = { type: 'party' }; + _cachedPartyPromise.reject(data.party); + } + + if (!data.party || forceUpdate) { + Group.get('party') + .then(function (response) { + data.party = response.data.data; + Members.getGroupMembers(data.party._id, true) + .then(function (response) { + data.party.members = response.data.data; + return Members.getGroupInvites(data.party._id); + }) + .then(function (response) { + data.party.invites = response.data.data; + return Challenges.getGroupChallenges(data.party._id) + }) + .then(function (response) { + data.party.challenges = response.data.data; + _cachedPartyPromise.resolve(data.party); + }); + }, function (response) { + data.party = { type: 'party' }; + _cachedPartyPromise.reject(data.party); + }) + .finally(function() { + _cachePartyPromise = null; + }); + } else { + _cachedPartyPromise.resolve(data.party); + } + + return _cachedPartyPromise.promise; + } + + function publicGuilds () { + var deferred = $q.defer(); + + if (!data.publicGuilds) { + Group.getGroups('publicGuilds') + .then(function (response) { + data.publicGuilds = response.data.data; + deferred.resolve(data.publicGuilds); + }, function (response) { + deferred.reject(response); + }); + } else { + deferred.resolve(data.publicGuilds); + } + + return deferred.promise; + //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them + } + + function myGuilds () { + var deferred = $q.defer(); + + if (!data.myGuilds) { + Group.getGroups('guilds') + .then(function (response) { + data.myGuilds = response.data.data; + deferred.resolve(data.myGuilds); + }, function (response) { + deferred.reject(response); + }); + } else { + deferred.resolve(data.myGuilds); + } + + return deferred.promise; + } + + function tavern (forceUpdate) { + var deferred = $q.defer(); + + if (!data.tavern || forceUpdate) { + Group.get('habitrpg') + .then(function (response) { + data.tavern = response.data.data; + deferred.resolve(data.tavern); + }, function (response) { + deferred.reject(response); + }); + } else { + deferred.resolve(data.tavern); + } + + return deferred.promise; + } + + function inviteOrStartParty (group) { + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'}); + if (group.type === "party" || $location.$$path === "/options/groups/party") { + group.type = 'party'; + $rootScope.openModal('invite-party', { + controller:'InviteToGroupCtrl', + resolve: { + injectedGroup: function(){ return group; } + } + }); + } else { + $location.path("/options/groups/party"); + } + } + + return { + TAVERN_NAME: TAVERN_NAME, + party: party, + publicGuilds: publicGuilds, + myGuilds: myGuilds, + tavern: tavern, + inviteOrStartParty: inviteOrStartParty, + + data: data, + Group: Group, + }; + }]); diff --git a/website/public/js/services/guideServices.js b/website/client/js/services/guideServices.js similarity index 97% rename from website/public/js/services/guideServices.js rename to website/client/js/services/guideServices.js index ee0be9d2c8..7f3ce9e499 100644 --- a/website/public/js/services/guideServices.js +++ b/website/client/js/services/guideServices.js @@ -241,7 +241,7 @@ function($rootScope, User, $timeout, $state, Analytics) { }); var goto = function(chapter, page, force) { - if (chapter == 'intro') User.set({'flags.welcomed': true}); + if (chapter == 'intro' && User.user.flags.welcomed != true) User.set({'flags.welcomed': true}); if (page === -1) page = 0; var curr = User.user.flags.tour[chapter]; if (page != curr+1 && !force) return; @@ -264,8 +264,8 @@ function($rootScope, User, $timeout, $state, Analytics) { } //Init and show the welcome tour (only after user is pulled from server & wrapped). - var watcher = $rootScope.$watch('User.user.ops.update', function(updateFn){ - if (!updateFn) return; // only run after user has been wrapped + var watcher = $rootScope.$watch('User.user._wrapped', function(wrapped){ + if (!wrapped) return; // only run after user has been wrapped watcher(); // deregister watcher if (window.env.IS_MOBILE) return; // Don't show tour immediately on mobile devices if (User.user.flags.welcomed == false) { diff --git a/website/client/js/services/memberServices.js b/website/client/js/services/memberServices.js new file mode 100644 index 0000000000..be1eab4841 --- /dev/null +++ b/website/client/js/services/memberServices.js @@ -0,0 +1,128 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Members', [ '$rootScope', 'Shared', 'ApiUrl', '$http', '$q', + function($rootScope, Shared, ApiUrl, $http, $q) { + var members = {}; + var selectedMember = {}; + var apiV3Prefix = '/api/v3'; + + function fetchMember (memberId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/members/' + memberId, + }); + } + + //@TODO: Add paging + function getGroupMembers (groupId, includeAllPublicFields) { + var url = apiV3Prefix + '/groups/' + groupId + '/members'; + + if (includeAllPublicFields) { + url += '?includeAllPublicFields=true'; + } + + return $http({ + method: 'GET', + url: url, + }); + } + + function getGroupInvites (groupId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/groups/' + groupId + '/invites', + }); + } + + function getChallengeMembers (challengeId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/' + challengeId + '/members', + }); + } + + function getChallengeMemberProgress (challengeId, memberId) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId, + }); + } + + function sendPrivateMessage (message, toUserId) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/members/send-private-message', + data: { + message: message, + toUserId: toUserId, + } + }); + } + + function transferGems (message, toUserId, gemAmount) { + return $http({ + method: 'POST', + url: apiV3Prefix + '/members/transfer-gems', + data: { + message: message, + toUserId: toUserId, + gemAmount: gemAmount, + } + }); + } + + function selectMember (uid) { + var self = this; + var deferred = $q.defer(); + var memberIsReady = _checkIfMemberIsReady(members[uid]); + + if (memberIsReady) { + _prepareMember(members[uid], self); + deferred.resolve(); + } else { + fetchMember(uid) + .then(function (response) { + var member = response.data.data; + addToMembersList(member); // lazy load for later + _prepareMember(member, self); + deferred.resolve(); + }); + } + + return deferred.promise; + } + + function addToMembersList (member) { + if (member._id) { + members[member._id] = member; + } + } + + function _checkIfMemberIsReady (member) { + return member && member.items && member.items.weapon; + } + + function _prepareMember(member, self) { + Shared.wrap(member, false); + self.selectedMember = members[member._id]; + } + + $rootScope.$on('userUpdated', function(event, user){ + addToMembersList(user); + }) + + return { + members: members, + addToMembersList: addToMembersList, + selectedMember: undefined, + selectMember: selectMember, + fetchMember: fetchMember, + getGroupMembers: getGroupMembers, + getGroupInvites: getGroupInvites, + getChallengeMembers: getChallengeMembers, + getChallengeMemberProgress: getChallengeMemberProgress, + sendPrivateMessage: sendPrivateMessage, + transferGems: transferGems, + } + }]); diff --git a/website/public/js/services/notificationServices.js b/website/client/js/services/notificationServices.js similarity index 95% rename from website/public/js/services/notificationServices.js rename to website/client/js/services/notificationServices.js index f4556f6989..096c1643af 100644 --- a/website/public/js/services/notificationServices.js +++ b/website/client/js/services/notificationServices.js @@ -54,8 +54,8 @@ angular.module("habitrpg").factory("Notification", _notify(_sign(val) + " " + _round(val) + " " + window.env.t('experience'), 'xp', 'glyphicon glyphicon-star'); } - function error(error){ - _notify(error, "danger", 'glyphicon glyphicon-exclamation-sign'); + function error(error, canHide){ + _notify(error, "danger", 'glyphicon glyphicon-exclamation-sign', canHide); } function gp(val, bonus) { @@ -107,14 +107,14 @@ angular.module("habitrpg").factory("Notification", // Used to stack notifications, must be outside of _notify var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60}; - function _notify(html, type, icon) { + function _notify(html, type, icon, canHide) { var notice = $.pnotify({ type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit') text: html, opacity: 1, addclass: 'alert-' + type, delay: 7000, - hide: (type == 'error' || type == 'danger') ? false : true, + hide: ((type == 'error' || type == 'danger') && !canHide) ? false : true, mouse_reset: false, width: "250px", stack: stack_topright, diff --git a/website/public/js/services/paymentServices.js b/website/client/js/services/paymentServices.js similarity index 95% rename from website/public/js/services/paymentServices.js rename to website/client/js/services/paymentServices.js index a3fc832d24..6797da7bf9 100644 --- a/website/public/js/services/paymentServices.js +++ b/website/client/js/services/paymentServices.js @@ -37,7 +37,7 @@ function($rootScope, User, $http, Content) { $http.post(url, res).success(function() { window.location.reload(true); }).error(function(res) { - alert(res.err); + alert(res.message); }); } }); @@ -55,7 +55,7 @@ function($rootScope, User, $http, Content) { $http.post(url, data).success(function() { window.location.reload(true); }).error(function(data) { - alert(data.err); + alert(data.message); }); } }); @@ -127,12 +127,12 @@ function($rootScope, User, $http, Content) { var url = '/amazon/createOrderReferenceId' $http.post(url, { billingAgreementId: Payments.amazonPayments.billingAgreementId - }).success(function(data){ + }).success(function(res){ Payments.amazonPayments.loggedIn = true; - Payments.amazonPayments.orderReferenceId = data.orderReferenceId; + Payments.amazonPayments.orderReferenceId = res.data.orderReferenceId; Payments.amazonPayments.initWidgets(); }).error(function(res){ - alert(res.err); + alert(res.message); }); } }, @@ -146,7 +146,7 @@ function($rootScope, User, $http, Content) { var url = '/amazon/verifyAccessToken' $http.post(url, response).error(function(res){ - alert(res.err); + alert(res.message); }); }); }, @@ -232,7 +232,7 @@ function($rootScope, User, $http, Content) { Payments.amazonPayments.reset(); window.location.reload(true); }).error(function(res){ - alert(res.err); + alert(res.message); Payments.amazonPayments.reset(); }); }else if(Payments.amazonPayments.type === 'subscription'){ @@ -246,7 +246,7 @@ function($rootScope, User, $http, Content) { Payments.amazonPayments.reset(); window.location.reload(true); }).error(function(res){ - alert(res.err); + alert(res.message); Payments.amazonPayments.reset(); }); } @@ -262,7 +262,7 @@ function($rootScope, User, $http, Content) { paymentMethod = paymentMethod.toLowerCase(); } - window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.user.apiToken; + window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken; } Payments.encodeGift = function(uuid, gift){ diff --git a/website/public/js/services/questServices.js b/website/client/js/services/questServices.js similarity index 80% rename from website/public/js/services/questServices.js rename to website/client/js/services/questServices.js index a69ae800d8..5033bad33d 100644 --- a/website/public/js/services/questServices.js +++ b/website/client/js/services/questServices.js @@ -1,25 +1,16 @@ 'use strict'; -(function(){ - angular - .module('habitrpg') - .factory('Quests', questsFactory); - - questsFactory.$inject = [ - '$http', - '$state', - '$q', - 'ApiUrl', - 'Content', - 'Groups', - 'User', - 'Analytics' - ]; - +angular.module('habitrpg') +.factory('Quests', ['$http', '$state','$q', 'ApiUrl', 'Content', 'Groups', 'User', 'Analytics', function questsFactory($http, $state, $q, ApiUrl, Content, Groups, User, Analytics) { var user = User.user; - var party = Groups.party(); + var party; + + Groups.party() + .then(function (partyFound) { + party = partyFound; + }); function lockQuest(quest,ignoreLevel) { if (!ignoreLevel){ @@ -106,20 +97,21 @@ function initQuest(key) { return $q(function(resolve, reject) { - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName': key}); - Analytics.updateUser({'partyID':party._id,'partySize':party.memberCount}); - party.$startQuest({key:key}, function(){ - party.$syncParty(); - $state.go('options.social.party'); - resolve(); - }); + Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'quest', 'owner':true, 'response':'accept', 'questName': key}); + Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount}); + Groups.Group.inviteToQuest(party._id, key) + .then(function(response) { + party.quest = response.data.data; + Groups.data.party = party; + $state.go('options.social.party'); + resolve(); + }); }); } function sendAction(action) { return $q(function(resolve, reject) { - - $http.post(ApiUrl.get() + '/api/v2/groups/' + party._id + '/' + action) + $http.post(ApiUrl.get() + '/api/v3/groups/' + party._id + '/' + action) .then(function(response) { User.sync(); @@ -129,6 +121,7 @@ }); var quest = response.data.quest; + if (!quest) quest = response.data.data; resolve(quest); });; }); @@ -142,5 +135,4 @@ showQuest: showQuest, initQuest: initQuest } - } -}()); + }]); diff --git a/website/public/js/services/sharedServices.js b/website/client/js/services/sharedServices.js similarity index 100% rename from website/public/js/services/sharedServices.js rename to website/client/js/services/sharedServices.js diff --git a/website/public/js/services/socialServices.js b/website/client/js/services/socialServices.js similarity index 100% rename from website/public/js/services/socialServices.js rename to website/client/js/services/socialServices.js diff --git a/website/public/js/services/statServices.js b/website/client/js/services/statServices.js similarity index 94% rename from website/public/js/services/statServices.js rename to website/client/js/services/statServices.js index f2db805fb6..b716dbcaf7 100644 --- a/website/public/js/services/statServices.js +++ b/website/client/js/services/statServices.js @@ -22,7 +22,7 @@ } function classBonus(user, stat) { - var computedStats = user._statsComputed; + var computedStats = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed() : null; if(computedStats) { var bonus = computedStats[stat] @@ -95,7 +95,7 @@ function mpDisplay(user) { var remainingMP = Math.floor(user.stats.mp); - var totalMP = user._statsComputed.maxMP; + var totalMP = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed().maxMP : null; var display = _formatOutOfTotalDisplay(remainingMP, totalMP); return display; diff --git a/website/client/js/services/tagsServices.js b/website/client/js/services/tagsServices.js new file mode 100644 index 0000000000..2a430282b6 --- /dev/null +++ b/website/client/js/services/tagsServices.js @@ -0,0 +1,60 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Tags', ['$rootScope', '$http', + function tagsFactory($rootScope, $http) { + + function getTags () { + return $http({ + method: 'GET', + url: 'api/v3/tags', + }); + }; + + function createTag (tagDetails) { + return $http({ + method: 'POST', + url: 'api/v3/tags', + data: tagDetails, + }); + }; + + function getTag (tagId) { + return $http({ + method: 'GET', + url: 'api/v3/tags/' + tagId, + }); + }; + + function updateTag (tagId, tagDetails) { + return $http({ + method: 'PUT', + url: 'api/v3/tags/' + tagId, + data: tagDetails, + }); + }; + + function sortTag (tagId, to) { + return $http({ + method: 'POST', + url: 'api/v3/reorder-tags', + data: {tagId: tagId, to: to}, + }); + }; + + function deleteTag (tagId) { + return $http({ + method: 'DELETE', + url: 'api/v3/tags/' + tagId, + }); + }; + + return { + getTags: getTags, + createTag: createTag, + getTag: getTag, + updateTag: updateTag, + sortTag: sortTag, + deleteTag: deleteTag, + }; + }]); diff --git a/website/client/js/services/taskServices.js b/website/client/js/services/taskServices.js new file mode 100644 index 0000000000..e7eeef1a93 --- /dev/null +++ b/website/client/js/services/taskServices.js @@ -0,0 +1,205 @@ +'use strict'; + +var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt']; + +angular.module('habitrpg') +.factory('Tasks', ['$rootScope', 'Shared', '$http', + function tasksFactory($rootScope, Shared, $http) { + + function getUserTasks (getCompletedTodos) { + var url = '/api/v3/tasks/user'; + + if (getCompletedTodos) url += '?type=completedTodos'; + + return $http({ + method: 'GET', + url: url, + }); + }; + + function createUserTasks (taskDetails) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/user', + data: taskDetails, + }); + }; + + function getChallengeTasks (challengeId) { + return $http({ + method: 'GET', + url: '/api/v3/tasks/challenge/' + challengeId, + }); + }; + + function createChallengeTasks (challengeId, taskDetails) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/challenge/' + challengeId, + data: taskDetails, + }); + }; + + function getTask (taskId) { + return $http({ + method: 'GET', + url: '/api/v3/tasks/' + taskId, + }); + }; + + function updateTask (taskId, taskDetails) { + return $http({ + method: 'PUT', + url: '/api/v3/tasks/' + taskId, + data: taskDetails, + }); + }; + + function deleteTask (taskId) { + return $http({ + method: 'DELETE', + url: '/api/v3/tasks/' + taskId, + }); + }; + + function scoreTask (taskId, direction) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/score/' + direction, + }); + }; + + function moveTask (taskId, position) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/move/to/' + position, + }); + }; + + function addChecklistItem (taskId, checkListItem) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/checklist', + data: checkListItem, + }); + }; + + function scoreCheckListItem (taskId, itemId) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId + '/score', + }); + }; + + function updateChecklistItem (taskId, itemId, itemDetails) { + return $http({ + method: 'PUT', + url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId, + data: itemDetails, + }); + }; + + function removeChecklistItem (taskId, itemId) { + return $http({ + method: 'DELETE', + url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId, + }); + }; + + function addTagToTask (taskId, tagId) { + return $http({ + method: 'POST', + url: '/api/v3/tasks/' + taskId + '/tags/' + tagId, + }); + }; + + function removeTagFromTask (taskId, tagId) { + return $http({ + method: 'DELETE', + url: '/api/v3/tasks/' + taskId + '/tags/' + tagId, + }); + }; + + function unlinkOneTask (taskId, keep) { // single task + if (!keep) { + keep = "keep"; + } + + return $http({ + method: 'POST', + url: '/api/v3/tasks/unlink-one/' + taskId + '?keep=' + keep, + }); + }; + + function unlinkAllTasks (challengeId, keep) { // all tasks + if (!keep) { + keep = "keep-all"; + } + + return $http({ + method: 'POST', + url: '/api/v3/tasks/unlink-all/' + challengeId + '?keep=' + keep, + }); + }; + + function clearCompletedTodos () { + return $http({ + method: 'POST', + url: '/api/v3/tasks/clearCompletedTodos', + }); + }; + + function editTask(task, user) { + task._editing = !task._editing; + task._tags = !user.preferences.tagsCollapsed; + task._advanced = !user.preferences.advancedCollapsed; + if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false; + } + + function cloneTask(task) { + var clonedTask = _.cloneDeep(task); + clonedTask = _cleanUpTask(clonedTask); + + return Shared.taskDefaults(clonedTask); + } + + function _cleanUpTask(task) { + var cleansedTask = _.omit(task, TASK_KEYS_TO_REMOVE); + + // Copy checklists but reset to uncomplete and assign new id + _(cleansedTask.checklist).forEach(function(item) { + item.completed = false; + item.id = Shared.uuid(); + }).value(); + + if (cleansedTask.type !== 'reward') { + delete cleansedTask.value; + } + + return cleansedTask; + } + + return { + getUserTasks: getUserTasks, + loadedCompletedTodos: false, + createUserTasks: createUserTasks, + getChallengeTasks: getChallengeTasks, + createChallengeTasks: createChallengeTasks, + getTask: getTask, + updateTask: updateTask, + deleteTask: deleteTask, + scoreTask: scoreTask, + moveTask: moveTask, + addChecklistItem: addChecklistItem, + scoreCheckListItem: scoreCheckListItem, + updateChecklistItem: updateChecklistItem, + removeChecklistItem: removeChecklistItem, + addTagToTask: addTagToTask, + removeTagFromTask: removeTagFromTask, + unlinkOneTask: unlinkOneTask, + unlinkAllTasks: unlinkAllTasks, + clearCompletedTodos: clearCompletedTodos, + editTask: editTask, + cloneTask: cloneTask + }; + }]); diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js new file mode 100644 index 0000000000..70b8821c6d --- /dev/null +++ b/website/client/js/services/userServices.js @@ -0,0 +1,583 @@ +'use strict'; + +angular.module('habitrpg') + .service('ApiUrl', ['API_URL', function(currentApiUrl) { + this.setApiUrl = function(newUrl){ + currentApiUrl = newUrl; + }; + + this.get = function(){ + return currentApiUrl; + }; + }]) + +/** + * Services that persists and retrieves user from localStorage. + */ + .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Notification', 'ApiUrl', 'Tasks', 'Tags', + function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Notification, ApiUrl, Tasks, Tags) { + var authenticated = false; + var defaultSettings = { + auth: { apiId: '', apiToken: ''}, + sync: { + queue: [], //here OT will be queued up, this is NOT call-back queue! + sent: [] //here will be OT which have been sent, but we have not got reply from server yet. + }, + fetching: false, // whether fetch() was called or no. this is to avoid race conditions + online: false + }; + var settings = {}; //habit mobile settings (like auth etc.) to be stored here + var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate + + var userNotifications = { + // "party.order" : env.t("updatedParty"), + // "party.orderAscending" : env.t("updatedParty") + // party.order notifications are not currently needed because the party avatars are resorted immediately now + }; // this is a list of notifications to send to the user when changes are made, along with the message. + + //first we populate user with schema + user.apiToken = user._id = ''; // we use id / apitoken to determine if registered + + //than we try to load localStorage + if (localStorage.getItem(STORAGE_USER_ID)) { + _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); + } + + user._wrapped = false; + + function syncUserTasks (tasks) { + user.habits = []; + user.todos = []; + user.dailys = []; + user.rewards = []; + + // Order tasks based on tasksOrder + var groupedTasks = _(tasks) + .groupBy('type') + .forEach(function (tasksOfType, type) { + var order = user.tasksOrder[type + 's']; + var orderedTasks = new Array(tasksOfType.length); + var unorderedTasks = []; // what we want to add later + + tasksOfType.forEach(function (task, index) { + var taskId = task._id; + var i = order[index] === taskId ? index : order.indexOf(taskId); + if (i === -1) { + unorderedTasks.push(task); + } else { + orderedTasks[i] = task; + } + }); + + // Remove empty values from the array and add any unordered task + user[type + 's'] = _.compact(orderedTasks).concat(unorderedTasks); + }).value(); + } + + function sync() { + return $http({ + method: "GET", + url: '/api/v3/user/', + }) + .then(function (response) { + if (response.data.message) Notification.text(response.data.message); + + _.extend(user, response.data.data); + + $rootScope.$emit('userUpdated', user); + + if (!user._wrapped) { + // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, + // they update the user in the browser and then send the request to the server, where the same operation is + // replicated. We need to wrap each op to provide a callback to send that operation + $window.habitrpgShared.wrap(user); + _.each(user.ops, function(op,k){ + user.ops[k] = function(req){ + try { + op(req); + } catch (err) { + Notification.text(err.message); + return; + } + } + }); + } + + return Tasks.getUserTasks(); + }) + .then(function (response) { + var tasks = response.data.data; + syncUserTasks(tasks); + save(); + $rootScope.$emit('userSynced'); + }); + } + sync(); + + var save = function () { + localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); + }; + + function callOpsFunctionAndRequest (opName, endPoint, method, paramString, opData) { + if (!opData) opData = {}; + + var clientResponse; + + try { + var args = [user]; + if (opName === 'rebirth' || opName === 'reroll' || opName === 'reset') { + args.push(user.habits.concat(user.dailys).concat(user.rewards).concat(user.todos)); + } + + args.push(opData); + clientResponse = $window.habitrpgShared.ops[opName].apply(null, args); + } catch (err) { + Notification.text(err.message); + return; + } + + var clientMessage = clientResponse[1]; + + if (clientMessage) { + Notification.text(clientMessage); + } + + var url = '/api/v3/user/' + endPoint; + if (paramString) { + url += '/' + paramString + } + + var body = {}; + if (opData.body) body = opData.body; + + var queryString = ''; + if (opData.query) queryString = '?' + $.param(opData.query) + + $http({ + method: method, + url: url + queryString, + body: body, + }) + .then(function (response) { + if (response.data.message && response.data.message !== clientMessage) { + Notification.text(response.data.message); + } + + save(); + }) + } + + function setUser(updates) { + for (var key in updates) { + _.set(user, key, updates[key]); + } + } + + var userServices = { + user: user, + + //@TODO: WE need a new way to set the user from tests + setUser: function (userInc) { + user = userInc; + }, + + allocate: function (data) { + callOpsFunctionAndRequest('allocate', 'allocate', "POST",'', data); + }, + + allocateNow: function () { + callOpsFunctionAndRequest('allocateNow', 'allocate-now', "POST"); + }, + + changeClass: function (data) { + callOpsFunctionAndRequest('changeClass', 'change-class', "POST",'', data); + }, + + disableClasses: function () { + callOpsFunctionAndRequest('disableClasses', 'disable-classes', "POST"); + }, + + revive: function (data) { + callOpsFunctionAndRequest('revive', 'revive', "POST"); + }, + + addTask: function (data) { + if (_.isArray(data.body)) { + data.body.forEach(function (task) { + user.ops.addTask({body: task}); + }); + } else { + user.ops.addTask(data); + } + save(); + Tasks.createUserTasks(data.body); + }, + + score: function (data) { + try { + $window.habitrpgShared.ops.scoreTask({user: user, task: data.params.task, direction: data.params.direction}, data.params); + } catch (err) { + Notification.text(err.message); + return; + } + save(); + + Tasks.scoreTask(data.params.task._id, data.params.direction).then(function (res) { + var tmp = res.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses + var drop = tmp.drop; + + if (drop) user._tmp.drop = drop; + }); + }, + + sortTask: function (data) { + user.ops.sortTask(data); + save(); + Tasks.moveTask(data.params.id, data.query.to); + }, + + updateTask: function (task, data) { + $window.habitrpgShared.ops.updateTask(task, data); + save(); + Tasks.updateTask(task._id, data.body); + }, + + deleteTask: function (data) { + user.ops.deleteTask(data); + save(); + Tasks.deleteTask(data.params.id); + }, + + clearCompleted: function () { + user.ops.clearCompleted(user.todos); + save(); + Tasks.clearCompletedTodos(); + }, + + addTag: function(data) { + user.ops.addTag(data); + save(); + Tags.createTag(data.body); + }, + + updateTag: function(data) { + user.ops.updateTag(data); + save(); + Tags.updateTag(data.params.id, data.body); + }, + + sortTag: function (data) { + user.ops.sortTag(data); + Tags.sortTag(user.tags[data.query.from].id, data.query.to); + }, + + deleteTag: function(data) { + user.ops.deleteTag(data); + save(); + Tags.deleteTag(data.params.id); + }, + + addTenGems: function () { + $http({ + method: "POST", + url: 'api/v3/debug/add-ten-gems', + }) + .then(function (response) { + Notification.text('+10 Gems!'); + sync(); + }) + }, + + addHourglass: function () { + $http({ + method: "POST", + url: 'api/v3/debug/add-hourglass', + }) + .then(function (response) { + sync(); + }) + }, + + setCron: function (numberOfDays) { + var date = moment(user.lastCron).subtract(numberOfDays, 'days').toDate(); + + $http({ + method: "POST", + url: 'api/v3/debug/set-cron', + data: { + lastCron: date + } + }) + .then(function (response) { + Notification.text('-' + numberOfDays + ' day(s), remember to refresh'); + }); + }, + + setCustomDayStart: function (dayStart) { + $http({ + method: "POST", + url: 'api/v3/user/custom-day-start', + data: { + dayStart: dayStart + } + }) + .then(function (response) { + Notification.text(response.data.data.message); + sync(); + }); + }, + + makeAdmin: function () { + $http({ + method: "POST", + url: 'api/v3/debug/make-admin' + }) + .then(function (response) { + Notification.text('You are now an admin! Go to the Hall of Heroes to change your contributor level.'); + sync() + }); + }, + + clearNewMessages: function () { + callOpsFunctionAndRequest('markPmsRead', 'mark-pms-read', "POST"); + }, + + clearPMs: function () { + callOpsFunctionAndRequest('clearPMs', 'messages', "DELETE"); + }, + + deletePM: function (data) { + callOpsFunctionAndRequest('deletePM', 'messages', "DELETE", data.params.id, data); + }, + + buy: function (data) { + callOpsFunctionAndRequest('buy', 'buy', "POST", data.params.key, data); + }, + + buyQuest: function (data) { + callOpsFunctionAndRequest('buyQuest', 'buy-quest', "POST", data.params.key, data); + }, + + purchase: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('purchase', 'purchase', "POST", type + '/' + key, data); + }, + + buySpecialSpell: function (data) { + $window.habitrpgShared.ops['buySpecialSpell'](user, data); + var key = data.params.key; + + $http({ + method: "POST", + url: '/api/v3/user/' + 'buy-special-spell/' + key, + }) + .then(function (response) { + Notification.text(response.data.message); + }) + }, + + buyMysterySet: function (data) { + callOpsFunctionAndRequest('buyMysterySet', 'buy-mystery-set', "POST", data.params.key, data); + }, + + readCard: function (data) { + callOpsFunctionAndRequest('readCard', 'read-card', "POST", data.params.cardType, data); + }, + + openMysteryItem: function (data) { + callOpsFunctionAndRequest('openMysteryItem', 'open-mystery-item', "POST"); + }, + + sell: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('sell', 'sell', "POST", type + '/' + key, data); + }, + + hatch: function (data) { + var egg = data.params.egg; + var hatchingPotion = data.params.hatchingPotion; + callOpsFunctionAndRequest('hatch', 'hatch', "POST", egg + '/' + hatchingPotion, data); + }, + + feed: function (data) { + var pet = data.params.pet; + var food = data.params.food; + callOpsFunctionAndRequest('feed', 'feed', "POST", pet + '/' + food, data); + }, + + equip: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('equip', 'equip', "POST", type + '/' + key, data); + }, + + hourglassPurchase: function (data) { + var type = data.params.type; + var key = data.params.key; + callOpsFunctionAndRequest('purchaseHourglass', 'purchase-hourglass', "POST", type + '/' + key, data); + }, + + unlock: function (data) { + callOpsFunctionAndRequest('unlock', 'unlock', "POST", '', data); + }, + + set: function(updates) { + setUser(updates); + $http({ + method: "PUT", + url: '/api/v3/user', + data: updates, + }) + .then(function () { + save(); + $rootScope.$emit('userSynced'); + }) + }, + + reroll: function () { + callOpsFunctionAndRequest('reroll', 'reroll', "POST"); + }, + + rebirth: function () { + callOpsFunctionAndRequest('rebirth', 'rebirth', "POST"); + }, + + reset: function () { + callOpsFunctionAndRequest('reset', 'reset', "POST"); + }, + + releaseBoth: function () { + callOpsFunctionAndRequest('releaseBoth', 'release-both', "POST"); + }, + + releaseMounts: function () { + callOpsFunctionAndRequest('releaseMounts', 'release-mounts', "POST"); + }, + + releasePets: function () { + callOpsFunctionAndRequest('releasePets', 'release-pets', "POST"); + }, + + addWebhook: function (data) { + callOpsFunctionAndRequest('addWebhook', 'webhook', "POST", '', data, data.body); + }, + + updateWebhook: function (data) { + callOpsFunctionAndRequest('updateWebhook', 'webhook', "PUT", data.params.id, data, data.body); + }, + + deleteWebhook: function (data) { + callOpsFunctionAndRequest('deleteWebhook', 'webhook', "DELETE", data.params.id, data, data.body); + }, + + sleep: function () { + callOpsFunctionAndRequest('sleep', 'sleep', "POST"); + }, + + blockUser: function (data) { + callOpsFunctionAndRequest('blockUser', 'block', "POST", data.params.uuid, data); + }, + + online: function (status) { + if (status===true) { + settings.online = true; + // syncQueue(); + } else { + settings.online = false; + }; + }, + + authenticate: function (uuid, token, cb) { + if (uuid && token) { + var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) + $http.defaults.headers.common['x-api-user'] = uuid; + $http.defaults.headers.common['x-api-key'] = token; + $http.defaults.headers.common['x-user-timezoneOffset'] = offset; + authenticated = true; + settings.auth.apiId = uuid; + settings.auth.apiToken = token; + settings.online = true; + save(); + sync().then(function () { + if (user.preferences.timezoneOffset !== offset) + userServices.set({'preferences.timezoneOffset': offset}); + if (cb) cb(); + }); + } else { + alert('Please enter your ID and Token in settings.') + } + }, + + authenticated: function(){ + return this.settings.auth.apiId !== ""; + }, + + getBalanceInGems: function() { + var balance = user.balance || 0; + return balance * 4; + }, + + log: function (action, cb) { + //push by one buy one if an array passed in. + if (_.isArray(action)) { + action.forEach(function (a) { + settings.sync.queue.push(a); + }); + } else { + settings.sync.queue.push(action); + } + + save(); + }, + + sync: function(){ + userServices.log({}); + return sync(); + }, + + syncUserTasks: syncUserTasks, + + save: save, + + settings: settings + }; + + //load settings if we have them + if (localStorage.getItem(STORAGE_SETTINGS_ID)) { + //use extend here to make sure we keep object reference in other angular controllers + _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID))); + + //if settings were saved while fetch was in process reset the flag. + settings.fetching = false; + //create and load if not + } else { + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings)); + _.extend(settings, defaultSettings); + } + + //If user does not have ApiID that forward him to settings. + if (!settings.auth.apiId || !settings.auth.apiToken) { + //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=... + var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead + if (search.err) return alert(search.err); + if (search._id && search.apiToken) { + userServices.authenticate(search._id, search.apiToken, function(){ + $window.location.href = '/'; + }); + } else { + var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); + if (!isStaticOrSocial){ + localStorage.clear(); + $location.path('/logout'); + } + } + } else { + userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) + } + + return userServices; + } +]); diff --git a/website/public/js/static.js b/website/client/js/static.js similarity index 77% rename from website/public/js/static.js rename to website/client/js/static.js index 67a07df815..c8af2adf27 100644 --- a/website/public/js/static.js +++ b/website/client/js/static.js @@ -6,18 +6,21 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui. .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) -.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', 'Stats', function($scope, $location, $modal, $http, Stats){ +.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', 'Stats', 'Members', + function($scope, $location, $modal, $http, Stats, Members) { var memberId = $location.search()['memberId']; if (memberId) { - $http.get('/api/v2/members/'+memberId).success(function(data, status, headers, config){ - $scope.profile = window.habitrpgShared.wrap(data, false); - $scope.statCalc = Stats; - $scope.Content = window.habitrpgShared.content; - $modal.open({ - templateUrl: 'modals/member.html', - scope: $scope + Members.fetchMember(memberId) + .success(function(response) { + $scope.profile = response.data; + + $scope.statCalc = Stats; + $scope.Content = window.habitrpgShared.content; + $modal.open({ + templateUrl: 'modals/member.html', + scope: $scope + }); }); - }) } $http.defaults.headers.common['x-client'] = 'habitica-web'; diff --git a/website/public/logo.png b/website/client/logo.png similarity index 100% rename from website/public/logo.png rename to website/client/logo.png diff --git a/website/public/logo/HABITRPG logo version 1.psd b/website/client/logo/HABITRPG logo version 1.psd similarity index 100% rename from website/public/logo/HABITRPG logo version 1.psd rename to website/client/logo/HABITRPG logo version 1.psd diff --git a/website/public/logo/HABITRPG-logo-version-1.gif b/website/client/logo/HABITRPG-logo-version-1.gif similarity index 100% rename from website/public/logo/HABITRPG-logo-version-1.gif rename to website/client/logo/HABITRPG-logo-version-1.gif diff --git a/website/public/logo/habitrpg.jpg b/website/client/logo/habitrpg.jpg similarity index 100% rename from website/public/logo/habitrpg.jpg rename to website/client/logo/habitrpg.jpg diff --git a/website/public/logo/habitrpg_bl.eps b/website/client/logo/habitrpg_bl.eps similarity index 100% rename from website/public/logo/habitrpg_bl.eps rename to website/client/logo/habitrpg_bl.eps diff --git a/website/public/logo/habitrpg_pixel.png b/website/client/logo/habitrpg_pixel.png similarity index 100% rename from website/public/logo/habitrpg_pixel.png rename to website/client/logo/habitrpg_pixel.png diff --git a/website/public/manifest.json b/website/client/manifest.json similarity index 94% rename from website/public/manifest.json rename to website/client/manifest.json index 20da085a28..2c770893f1 100644 --- a/website/public/manifest.json +++ b/website/client/manifest.json @@ -31,7 +31,6 @@ "bower_components/jquery-ui/ui/minified/jquery.ui.widget.min.js", "bower_components/jquery-ui/ui/minified/jquery.ui.mouse.min.js", "bower_components/jquery-ui/ui/minified/jquery.ui.sortable.min.js", - "bower_components/smart-app-banner/smart-app-banner.js", "common/dist/scripts/habitrpg-shared.js", @@ -43,7 +42,6 @@ "js/services/sharedServices.js", "js/services/notificationServices.js", - "common/script/public/userServices.js", "common/script/public/directives.js", "js/services/analyticsServices.js", "js/services/groupServices.js", @@ -51,11 +49,13 @@ "js/services/memberServices.js", "js/services/guideServices.js", "js/services/taskServices.js", + "js/services/tagsServices.js", "js/services/challengeServices.js", "js/services/paymentServices.js", "js/services/questServices.js", "js/services/socialServices.js", "js/services/statServices.js", + "js/services/userServices.js", "js/filters/money.js", "js/filters/roundLargeNumbers.js", @@ -130,10 +130,13 @@ "js/static.js", "js/services/analyticsServices.js", "js/services/notificationServices.js", + "js/services/userServices.js", "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "common/script/public/userServices.js", + "js/services/taskServices.js", + "js/services/tagsServices.js", + "js/services/memberServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], @@ -167,7 +170,10 @@ "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "common/script/public/userServices.js", + "js/services/taskServices.js", + "js/services/tagsServices.js", + "js/services/userServices.js", + "js/services/memberServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], diff --git a/website/public/marketing/android_iphone.png b/website/client/marketing/android_iphone.png similarity index 100% rename from website/public/marketing/android_iphone.png rename to website/client/marketing/android_iphone.png diff --git a/website/public/marketing/animals.png b/website/client/marketing/animals.png similarity index 100% rename from website/public/marketing/animals.png rename to website/client/marketing/animals.png diff --git a/website/public/marketing/challenge.png b/website/client/marketing/challenge.png similarity index 100% rename from website/public/marketing/challenge.png rename to website/client/marketing/challenge.png diff --git a/website/public/marketing/devices.png b/website/client/marketing/devices.png similarity index 100% rename from website/public/marketing/devices.png rename to website/client/marketing/devices.png diff --git a/website/public/marketing/drops.png b/website/client/marketing/drops.png similarity index 100% rename from website/public/marketing/drops.png rename to website/client/marketing/drops.png diff --git a/website/public/marketing/education.png b/website/client/marketing/education.png similarity index 100% rename from website/public/marketing/education.png rename to website/client/marketing/education.png diff --git a/website/public/marketing/gear.png b/website/client/marketing/gear.png similarity index 100% rename from website/public/marketing/gear.png rename to website/client/marketing/gear.png diff --git a/website/public/marketing/guild.png b/website/client/marketing/guild.png similarity index 100% rename from website/public/marketing/guild.png rename to website/client/marketing/guild.png diff --git a/website/public/marketing/guild_small.png b/website/client/marketing/guild_small.png similarity index 100% rename from website/public/marketing/guild_small.png rename to website/client/marketing/guild_small.png diff --git a/website/public/marketing/integration.png b/website/client/marketing/integration.png similarity index 100% rename from website/public/marketing/integration.png rename to website/client/marketing/integration.png diff --git a/website/public/marketing/lefnire.png b/website/client/marketing/lefnire.png similarity index 100% rename from website/public/marketing/lefnire.png rename to website/client/marketing/lefnire.png diff --git a/website/public/marketing/promos/201403_Forest_Walker.png b/website/client/marketing/promos/201403_Forest_Walker.png similarity index 100% rename from website/public/marketing/promos/201403_Forest_Walker.png rename to website/client/marketing/promos/201403_Forest_Walker.png diff --git a/website/public/marketing/promos/April14SAMPLE2.png b/website/client/marketing/promos/April14SAMPLE2.png similarity index 100% rename from website/public/marketing/promos/April14SAMPLE2.png rename to website/client/marketing/promos/April14SAMPLE2.png diff --git a/website/public/marketing/screenshot.png b/website/client/marketing/screenshot.png similarity index 100% rename from website/public/marketing/screenshot.png rename to website/client/marketing/screenshot.png diff --git a/website/public/marketing/social_competitve.png b/website/client/marketing/social_competitve.png similarity index 100% rename from website/public/marketing/social_competitve.png rename to website/client/marketing/social_competitve.png diff --git a/website/public/marketing/wellness.png b/website/client/marketing/wellness.png similarity index 100% rename from website/public/marketing/wellness.png rename to website/client/marketing/wellness.png diff --git a/website/public/merch/stickermule-logo.png b/website/client/merch/stickermule-logo.png similarity index 100% rename from website/public/merch/stickermule-logo.png rename to website/client/merch/stickermule-logo.png diff --git a/website/public/merch/stickermule-logo.svg b/website/client/merch/stickermule-logo.svg similarity index 100% rename from website/public/merch/stickermule-logo.svg rename to website/client/merch/stickermule-logo.svg diff --git a/website/public/merch/stickermule.png b/website/client/merch/stickermule.png similarity index 100% rename from website/public/merch/stickermule.png rename to website/client/merch/stickermule.png diff --git a/website/public/merch/teespring-eu-logo.png b/website/client/merch/teespring-eu-logo.png similarity index 100% rename from website/public/merch/teespring-eu-logo.png rename to website/client/merch/teespring-eu-logo.png diff --git a/website/public/merch/teespring-eu.png b/website/client/merch/teespring-eu.png similarity index 100% rename from website/public/merch/teespring-eu.png rename to website/client/merch/teespring-eu.png diff --git a/website/public/merch/teespring-logo.png b/website/client/merch/teespring-logo.png similarity index 100% rename from website/public/merch/teespring-logo.png rename to website/client/merch/teespring-logo.png diff --git a/website/public/merch/teespring-logo.svg b/website/client/merch/teespring-logo.svg similarity index 100% rename from website/public/merch/teespring-logo.svg rename to website/client/merch/teespring-logo.svg diff --git a/website/public/merch/teespring.png b/website/client/merch/teespring.png similarity index 100% rename from website/public/merch/teespring.png rename to website/client/merch/teespring.png diff --git a/website/public/page-loader.gif b/website/client/page-loader.gif similarity index 100% rename from website/public/page-loader.gif rename to website/client/page-loader.gif diff --git a/website/public/presskit/Boss - Basi-List.png b/website/client/presskit/Boss - Basi-List.png similarity index 100% rename from website/public/presskit/Boss - Basi-List.png rename to website/client/presskit/Boss - Basi-List.png diff --git a/website/public/presskit/Boss - Battling the Ghost Stag.png b/website/client/presskit/Boss - Battling the Ghost Stag.png similarity index 100% rename from website/public/presskit/Boss - Battling the Ghost Stag.png rename to website/client/presskit/Boss - Battling the Ghost Stag.png diff --git a/website/public/presskit/Boss - Laundromancer.png b/website/client/presskit/Boss - Laundromancer.png similarity index 100% rename from website/public/presskit/Boss - Laundromancer.png rename to website/client/presskit/Boss - Laundromancer.png diff --git a/website/public/presskit/Boss - Necro-Vice.png b/website/client/presskit/Boss - Necro-Vice.png similarity index 100% rename from website/public/presskit/Boss - Necro-Vice.png rename to website/client/presskit/Boss - Necro-Vice.png diff --git a/website/public/presskit/Boss - SnackLess Monster.png b/website/client/presskit/Boss - SnackLess Monster.png similarity index 100% rename from website/public/presskit/Boss - SnackLess Monster.png rename to website/client/presskit/Boss - SnackLess Monster.png diff --git a/website/public/presskit/Boss - Stagnant Dishes.png b/website/client/presskit/Boss - Stagnant Dishes.png similarity index 100% rename from website/public/presskit/Boss - Stagnant Dishes.png rename to website/client/presskit/Boss - Stagnant Dishes.png diff --git a/website/public/presskit/Habitica Gryphon.png b/website/client/presskit/Habitica Gryphon.png similarity index 100% rename from website/public/presskit/Habitica Gryphon.png rename to website/client/presskit/Habitica Gryphon.png diff --git a/website/public/presskit/Habitica Logo - Android.png b/website/client/presskit/Habitica Logo - Android.png similarity index 100% rename from website/public/presskit/Habitica Logo - Android.png rename to website/client/presskit/Habitica Logo - Android.png diff --git a/website/public/presskit/Habitica Logo - Icon with Text.png b/website/client/presskit/Habitica Logo - Icon with Text.png similarity index 100% rename from website/public/presskit/Habitica Logo - Icon with Text.png rename to website/client/presskit/Habitica Logo - Icon with Text.png diff --git a/website/public/presskit/Habitica Logo - Icon.png b/website/client/presskit/Habitica Logo - Icon.png similarity index 100% rename from website/public/presskit/Habitica Logo - Icon.png rename to website/client/presskit/Habitica Logo - Icon.png diff --git a/website/public/presskit/Habitica Logo - Text.png b/website/client/presskit/Habitica Logo - Text.png similarity index 100% rename from website/public/presskit/Habitica Logo - Text.png rename to website/client/presskit/Habitica Logo - Text.png diff --git a/website/public/presskit/Habitica Logo - iOS.png b/website/client/presskit/Habitica Logo - iOS.png similarity index 100% rename from website/public/presskit/Habitica Logo - iOS.png rename to website/client/presskit/Habitica Logo - iOS.png diff --git a/website/public/presskit/Habitica Promo - Thin.png b/website/client/presskit/Habitica Promo - Thin.png similarity index 100% rename from website/public/presskit/Habitica Promo - Thin.png rename to website/client/presskit/Habitica Promo - Thin.png diff --git a/website/public/presskit/Habitica Promo.png b/website/client/presskit/Habitica Promo.png similarity index 100% rename from website/public/presskit/Habitica Promo.png rename to website/client/presskit/Habitica Promo.png diff --git a/website/public/presskit/Sample Screen - Boss (iOS).png b/website/client/presskit/Sample Screen - Boss (iOS).png similarity index 100% rename from website/public/presskit/Sample Screen - Boss (iOS).png rename to website/client/presskit/Sample Screen - Boss (iOS).png diff --git a/website/public/presskit/Sample Screen - Challenges.png b/website/client/presskit/Sample Screen - Challenges.png similarity index 100% rename from website/public/presskit/Sample Screen - Challenges.png rename to website/client/presskit/Sample Screen - Challenges.png diff --git a/website/public/presskit/Sample Screen - Equipment.png b/website/client/presskit/Sample Screen - Equipment.png similarity index 100% rename from website/public/presskit/Sample Screen - Equipment.png rename to website/client/presskit/Sample Screen - Equipment.png diff --git a/website/public/presskit/Sample Screen - Guilds.png b/website/client/presskit/Sample Screen - Guilds.png similarity index 100% rename from website/public/presskit/Sample Screen - Guilds.png rename to website/client/presskit/Sample Screen - Guilds.png diff --git a/website/public/presskit/Sample Screen - Level Up (iOS).png b/website/client/presskit/Sample Screen - Level Up (iOS).png similarity index 100% rename from website/public/presskit/Sample Screen - Level Up (iOS).png rename to website/client/presskit/Sample Screen - Level Up (iOS).png diff --git a/website/public/presskit/Sample Screen - Market.png b/website/client/presskit/Sample Screen - Market.png similarity index 100% rename from website/public/presskit/Sample Screen - Market.png rename to website/client/presskit/Sample Screen - Market.png diff --git a/website/public/presskit/Sample Screen - Party (iOS).png b/website/client/presskit/Sample Screen - Party (iOS).png similarity index 100% rename from website/public/presskit/Sample Screen - Party (iOS).png rename to website/client/presskit/Sample Screen - Party (iOS).png diff --git a/website/public/presskit/Sample Screen - Pets (iOS).png b/website/client/presskit/Sample Screen - Pets (iOS).png similarity index 100% rename from website/public/presskit/Sample Screen - Pets (iOS).png rename to website/client/presskit/Sample Screen - Pets (iOS).png diff --git a/website/public/presskit/Sample Screen - Tasks Page (iOS).png b/website/client/presskit/Sample Screen - Tasks Page (iOS).png similarity index 100% rename from website/public/presskit/Sample Screen - Tasks Page (iOS).png rename to website/client/presskit/Sample Screen - Tasks Page (iOS).png diff --git a/website/public/presskit/Sample Screen - Tasks Page.png b/website/client/presskit/Sample Screen - Tasks Page.png similarity index 100% rename from website/public/presskit/Sample Screen - Tasks Page.png rename to website/client/presskit/Sample Screen - Tasks Page.png diff --git a/website/public/presskit/World Boss - Dread Drag'on of Dilatory.png b/website/client/presskit/World Boss - Dread Drag'on of Dilatory.png similarity index 100% rename from website/public/presskit/World Boss - Dread Drag'on of Dilatory.png rename to website/client/presskit/World Boss - Dread Drag'on of Dilatory.png diff --git a/website/public/presskit/presskit.zip b/website/client/presskit/presskit.zip similarity index 100% rename from website/public/presskit/presskit.zip rename to website/client/presskit/presskit.zip diff --git a/website/public/refresh.png b/website/client/refresh.png similarity index 100% rename from website/public/refresh.png rename to website/client/refresh.png diff --git a/website/public/js/controllers/guildsCtrl.js b/website/public/js/controllers/guildsCtrl.js deleted file mode 100644 index df6b001448..0000000000 --- a/website/public/js/controllers/guildsCtrl.js +++ /dev/null @@ -1,94 +0,0 @@ -'use strict'; - -habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', - function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { - $scope.groups = { - guilds: Groups.myGuilds(), - "public": Groups.publicGuilds() - } - - $scope.type = 'guild'; - $scope.text = window.env.t('guild'); - var newGroup = function(){ - return new Groups.Group({type:'guild', privacy:'private'}); - } - $scope.newGroup = newGroup() - $scope.create = function(group){ - if (User.user.balance < 1) - return $rootScope.openModal('buyGems', {track:"Gems > Create Group"}); - - if (confirm(window.env.t('confirmGuild'))) { - group.$save(function(saved){ - if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} - else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})} - $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); - }); - } - } - - $scope.join = function(group){ - // If we're accepting an invitation, we don't have the actual group object, but a faux group object (for performance - // purposes) {id, name}. Let's trick ngResource into thinking we have a group, so we can call the same $join - // function (server calls .attachGroup(), which finds group by _id and handles this properly) - if (group.id && !group._id) { - group = new Groups.Group({_id:group.id}); - } - - group.$join(function(joined){ - if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} - else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})} - $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); - }) - } - - $scope.leave = function(keep) { - if (keep == 'cancel') { - $scope.selectedGroup = undefined; - $scope.popoverEl.popover('destroy'); - } else { - Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){ - $rootScope.hardRedirect('/#/options/groups/guilds'); - }); - } - } - - $scope.clickLeave = function(group, $event){ - $scope.selectedGroup = group; - $scope.popoverEl = $($event.target).closest('.btn'); - var html, title; - Challenges.Challenge.query(function(challenges) { - challenges = _.pluck(_.filter(challenges, function(c) { - return c.group._id == group._id; - }), '_id'); - - if (_.intersection(challenges, User.user.challenges).length > 0) { - html = $compile( - '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveGroupCha'); - } else { - html = $compile( - '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveGroup') - } - - $scope.popoverEl.popover('destroy').popover({ - html: true, - placement: 'top', - trigger: 'manual', - title: title, - content: html - }).popover('show'); - }); - } - - $scope.reject = function(guild){ - var i = _.findIndex(User.user.invitations.guilds, {id:guild.id}); - if (~i){ - User.user.invitations.guilds.splice(i, 1); - User.set({'invitations.guilds':User.user.invitations.guilds}); - } - } - } - ]); diff --git a/website/public/js/controllers/partyCtrl.js b/website/public/js/controllers/partyCtrl.js deleted file mode 100644 index 6494679911..0000000000 --- a/website/public/js/controllers/partyCtrl.js +++ /dev/null @@ -1,173 +0,0 @@ -'use strict'; - -habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', - function($rootScope,$scope,Groups,Chat,User,Challenges,$state,$compile,Analytics,Quests,Social) { - - var user = User.user; - - $scope.type = 'party'; - $scope.text = window.env.t('party'); - $scope.group = $rootScope.party = Groups.party(); - $scope.newGroup = new Groups.Group({type:'party'}); - $scope.inviteOrStartParty = Groups.inviteOrStartParty; - $scope.loadWidgets = Social.loadWidgets; - - if ($state.is('options.social.party')) { - $scope.group.$syncParty(); // Sync party automatically when navigating to party page - - // Checks if user's party has reached 2 players for the first time. - if(!user.achievements.partyUp - && $scope.group.memberCount >= 2) { - User.set({'achievements.partyUp':true}); - $rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'}); - } - - // Checks if user's party has reached 4 players for the first time. - if(!user.achievements.partyOn - && $scope.group.memberCount >= 4) { - User.set({'achievements.partyOn':true}); - $rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'}); - } - } - - Chat.seenMessage($scope.group._id); - - $scope.create = function(group){ - if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name}); - group.$save(function(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'}); - Analytics.updateUser({'partyID':group.id,'partySize':1}); - $rootScope.hardRedirect('/#/options/groups/party'); - }); - }; - - $scope.join = function(party){ - var group = new Groups.Group({_id: party.id, name: party.name}); - group.$join(function(){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); - Analytics.updateUser({'partyID':party.id}); - $rootScope.hardRedirect('/#/options/groups/party'); - }); - }; - - // TODO: refactor guild and party leave into one function - $scope.leave = function(keep) { - if (keep == 'cancel') { - $scope.selectedGroup = undefined; - $scope.popoverEl.popover('destroy'); - } else { - Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){ - Analytics.updateUser({'partySize':null,'partyID':null}); - $rootScope.hardRedirect('/#/options/groups/party'); - }); - } - }; - - // TODO: refactor guild and party clickLeave into one function - $scope.clickLeave = function(group, $event){ - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'}); - $scope.selectedGroup = group; - $scope.popoverEl = $($event.target).closest('.btn'); - var html, title; - Challenges.Challenge.query(function(challenges) { - challenges = _.pluck(_.filter(challenges, function(c) { - return c.group._id == group._id; - }), '_id'); - if (_.intersection(challenges, User.user.challenges).length > 0) { - html = $compile( - '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leavePartyCha'); - } else { - html = $compile( - '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveParty'); - } - $scope.popoverEl.popover('destroy').popover({ - html: true, - placement: 'top', - trigger: 'manual', - title: title, - content: html - }).popover('show'); - }); - }; - - $scope.clickStartQuest = function(){ - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'}); - var hasQuests = _.find(User.user.items.quests, function(quest) { - return quest > 0; - }); - - if (hasQuests){ - $rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" }); - } else { - $rootScope.$state.go('options.inventory.quests'); - } - }; - - $scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) { - if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) { - Groups.Group.leave({gid: Groups.party()._id, keep:false}, undefined, function() { - $scope.group = { - loadingNewParty: true - }; - $scope.join({ id: newPartyId, name: newPartyName }); - }); - } - } - - $scope.reject = function(){ - User.set({'invitations.party':{}}); - } - - $scope.questCancel = function(){ - if (!confirm(window.env.t('sureCancel'))) return; - - Quests.sendAction('questCancel') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questAbort = function(){ - if (!confirm(window.env.t('sureAbort'))) return; - if (!confirm(window.env.t('doubleSureAbort'))) return; - - Quests.sendAction('questAbort') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questLeave = function(){ - if (!confirm(window.env.t('sureLeave'))) return; - - Quests.sendAction('questLeave') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questAccept = function(){ - Quests.sendAction('questAccept') - .then(function(quest) { - $scope.group.quest = quest; - }); - }; - - $scope.questReject = function(){ - Quests.sendAction('questReject') - .then(function(quest) { - $scope.group.quest = quest; - }); - }; - - $scope.canEditQuest = function(party) { - var isQuestLeader = party.quest && party.quest.leader === User.user._id; - - return isQuestLeader; - }; - } - ]); diff --git a/website/public/js/controllers/tavernCtrl.js b/website/public/js/controllers/tavernCtrl.js deleted file mode 100644 index 995fa22ad0..0000000000 --- a/website/public/js/controllers/tavernCtrl.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User', - function($scope, Groups, User) { - $scope.group = Groups.tavern(); - $scope.toggleUserTier = function($event) { - $($event.target).next().toggle(); - } - } - ]); diff --git a/website/public/js/services/challengeServices.js b/website/public/js/services/challengeServices.js deleted file mode 100644 index 51b91de8c2..0000000000 --- a/website/public/js/services/challengeServices.js +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; - -/** - * Services that persists and retrieves user from localStorage. - */ - -angular.module('habitrpg').factory('Challenges', -['ApiUrl', '$resource', -function(ApiUrl, $resource) { - var Challenge = $resource(ApiUrl.get() + '/api/v2/challenges/:cid', - {cid:'@_id'}, - { - //'query': {method: "GET", isArray:false} - join: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/join'}, - leave: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/leave'}, - close: {method: "POST", params: {uid:''}, url: ApiUrl.get() + '/api/v2/challenges/:cid/close'}, - getMember: {method: "GET", url: ApiUrl.get() + '/api/v2/challenges/:cid/member/:uid'} - }); - - //var challenges = []; - - return { - Challenge: Challenge - //challenges: challenges - } -}]); diff --git a/website/public/js/services/chatServices.js b/website/public/js/services/chatServices.js deleted file mode 100644 index 7fe2533506..0000000000 --- a/website/public/js/services/chatServices.js +++ /dev/null @@ -1,33 +0,0 @@ -'use strict'; - -angular.module('habitrpg').factory('Chat', -['$resource', '$http', 'ApiUrl', 'User', -function($resource, $http, ApiUrl, User) { - var utils = $resource(ApiUrl.get() + '/api/v2/groups/:gid', - {gid:'@_id', messageId: '@_messageId'}, - { - postChat: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat'}, - like: {method: 'POST', isArray: true, url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/like'}, - deleteChatMessage: {method: "DELETE", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId'}, - flagChatMessage: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/flag'}, - clearFlagCount: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/clearflags'}, - }); - - var chatService = { - seenMessage: seenMessage, - clearCards: clearCards, - utils: utils - }; - - return chatService; - - function clearCards() { - User.user.ops.update && User.set({'flags.cardReceived':false}); - } - - function seenMessage(gid) { - // On enter, set chat message to "seen" - $http.post(ApiUrl.get() + '/api/v2/groups/'+gid+'/chat/seen'); - if (User.user.newMessages) delete User.user.newMessages[gid]; - } -}]); diff --git a/website/public/js/services/groupServices.js b/website/public/js/services/groupServices.js deleted file mode 100644 index 001a137d63..0000000000 --- a/website/public/js/services/groupServices.js +++ /dev/null @@ -1,92 +0,0 @@ -'use strict'; - -(function() { - angular - .module('habitrpg') - .factory('Groups', groupsFactory); - - groupsFactory.$inject = [ - '$location', - '$resource', - '$rootScope', - 'Analytics', - 'ApiUrl', - 'Challenges', - 'User' - ]; - - function groupsFactory($location, $resource, $rootScope, Analytics, ApiUrl, Challenges, User) { - - var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined}; - var Group = $resource(ApiUrl.get() + '/api/v2/groups/:gid', - {gid:'@_id', messageId: '@_messageId'}, - { - get: { - method: "GET", - isArray:false, - // Wrap challenges as ngResource so they have functions like $leave or $join - transformResponse: function(data) { - data = angular.fromJson(data); - _.each(data && data.challenges, function(c) { - angular.extend(c, Challenges.Challenge.prototype); - }); - return data; - } - }, - - syncParty: {method: "GET", url: '/api/v2/groups/party'}, - join: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/join'}, - leave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/leave'}, - invite: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/invite'}, - removeMember: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/removeMember'}, - startQuest: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAccept'} - }); - - function party(cb) { - if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); - return (cb) ? cb(party) : data.party; - } - - function publicGuilds() { - //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them - if (!data.publicGuilds) data.publicGuilds = Group.query({type:'public'}); - return data.publicGuilds; - } - - function myGuilds() { - if (!data.myGuilds) data.myGuilds = Group.query({type:'guilds'}); - return data.myGuilds; - } - - function tavern() { - if (!data.tavern) data.tavern = Group.get({gid:'habitrpg'}); - return data.tavern; - } - - function inviteOrStartParty(group) { - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'}); - if (group.type === "party" || $location.$$path === "/options/groups/party") { - group.type = 'party'; - $rootScope.openModal('invite-party', { - controller:'InviteToGroupCtrl', - resolve: { - injectedGroup: function(){ return group; } - } - }); - } else { - $location.path("/options/groups/party"); - } - } - - return { - party: party, - publicGuilds: publicGuilds, - myGuilds: myGuilds, - tavern: tavern, - inviteOrStartParty: inviteOrStartParty, - - data: data, - Group: Group - } - } -})(); diff --git a/website/public/js/services/memberServices.js b/website/public/js/services/memberServices.js deleted file mode 100644 index 4146a4ea1f..0000000000 --- a/website/public/js/services/memberServices.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; -(function(){ - angular - .module('habitrpg') - .factory('Members', membersFactory); - - membersFactory.$inject = [ - '$rootScope', - 'Shared', - 'ApiUrl', - '$resource' - ]; - - function membersFactory($rootScope, Shared, ApiUrl, $resource) { - var members = {}; - var fetchMember = $resource(ApiUrl.get() + '/api/v2/members/:uid', { uid: '@_id' }).get; - - function selectMember(uid, cb) { - - var self = this; - var memberIsReady = _checkIfMemberIsReady(members[uid]); - - if (memberIsReady) { - _prepareMember(self, members[uid], cb); - } else { - fetchMember({ uid: uid }, function(member) { - addToMembersList(member); // lazy load for later - _prepareMember(self, member, cb); - }); - } - } - - function addToMembersList(member){ - if (member._id) { - members[member._id] = member; - } - } - - function _checkIfMemberIsReady(member) { - return member && member.items && member.items.weapon; - } - - function _prepareMember(self, member, cb) { - Shared.wrap(member, false); - self.selectedMember = members[member._id]; - cb(); - } - - $rootScope.$on('userUpdated', function(event, user){ - addToMembersList(user); - }) - - return { - members: members, - addToMembersList: addToMembersList, - selectedMember: undefined, - selectMember: selectMember - } - } -}()); diff --git a/website/public/js/services/taskServices.js b/website/public/js/services/taskServices.js deleted file mode 100644 index bbe7f6f689..0000000000 --- a/website/public/js/services/taskServices.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict'; - -(function(){ - var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'dateCreated', 'history', 'id', 'streak']; - - angular - .module('habitrpg') - .factory('Tasks', tasksFactory); - - tasksFactory.$inject = [ - '$rootScope', - 'Shared', - 'User' - ]; - - function tasksFactory($rootScope, Shared, User) { - - function editTask(task) { - task._editing = !task._editing; - task._tags = !User.user.preferences.tagsCollapsed; - task._advanced = !User.user.preferences.advancedCollapsed; - if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false; - } - - function cloneTask(task) { - var clonedTask = _.cloneDeep(task); - clonedTask = _cleanUpTask(clonedTask); - - return Shared.taskDefaults(clonedTask); - } - - function _cleanUpTask(task) { - var cleansedTask = _.omit(task, TASK_KEYS_TO_REMOVE); - - // Copy checklists but reset to uncomplete and assign new id - _(cleansedTask.checklist).forEach(function(item) { - item.completed = false; - item.id = Shared.uuid(); - }).value(); - - if (cleansedTask.type !== 'reward') { - delete cleansedTask.value; - } - - return cleansedTask; - } - - return { - editTask: editTask, - cloneTask: cloneTask - }; - } -})(); diff --git a/website/src/controllers/api-v2/auth.js b/website/server/controllers/api-v2/auth.js similarity index 94% rename from website/src/controllers/api-v2/auth.js rename to website/server/controllers/api-v2/auth.js index f63c652814..cfa5c40a0d 100644 --- a/website/src/controllers/api-v2/auth.js +++ b/website/server/controllers/api-v2/auth.js @@ -3,14 +3,19 @@ var validator = require('validator'); var passport = require('passport'); var shared = require('../../../../common'); var async = require('async'); -var utils = require('../../libs/utils'); +var utils = require('../../libs/api-v2/utils'); var nconf = require('nconf'); var request = require('request'); var FirebaseTokenGenerator = require('firebase-token-generator'); -var User = require('../../models/user').model; -var EmailUnsubscription = require('../../models/emailUnsubscription').model; +import { + model as User, +} from '../../models/user'; +import { + model as EmailUnsubscription, +} from '../../models/emailUnsubscription'; + var analytics = utils.analytics; -var i18n = require('./../../libs/i18n'); +var i18n = require('./../../libs/api-v2/i18n'); var isProd = nconf.get('NODE_ENV') === 'production'; @@ -53,6 +58,7 @@ api.authWithSession = function(req, res, next) { //[todo] there is probably a mo }); }; +// TODO passing auth params as query params is not safe as they are logged by browser history, ... api.authWithUrl = function(req, res, next) { User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){ if (err) return next(err); @@ -126,8 +132,8 @@ api.registerUser = function(req, res, next) { analytics.track('register', analyticsData) user.save(function(err, savedUser){ + if (err) return cb(err); // Clean previous email preferences - // TODO when emails added to EmailUnsubcription they should use lowercase version EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){ utils.txnEmail(savedUser, 'welcome'); }); @@ -137,15 +143,13 @@ api.registerUser = function(req, res, next) { }] }, function(err, data) { if (err) return err.code ? res.status(err.code).json(err) : next(err); - res.status(200).json(data.register[0]); + data.register[0].getTransformedData(function(err, userTransformed){ + if(err) return next(err); + res.status(200).json(userTransformed); + }); }); }; -/* - Register new user with uname / password - */ - - api.loginLocal = function(req, res, next) { var username = req.body.username; var password = req.body.password; @@ -236,7 +240,7 @@ api.loginSocial = function(req, res, next) { api.deleteSocial = function(req,res,next){ if (!res.locals.user.auth.local.username) return res.status(401).json({err:"Account lacks another authentication method, can't detach Facebook"}); - //FIXME for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123 + //TODO for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123 //res.locals.user.auth.facebook = null; //res.locals.user.auth.save(function(err, saved){ User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){ @@ -347,7 +351,8 @@ api.changePassword = function(req, res, next) { }) }; -var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); +// DISABLED FOR API v2 +/*var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); api.getFirebaseToken = function(req, res, next) { var user = res.locals.user; // Expires 24 hours after now (60*60*24*1000) (in milliseconds) @@ -366,13 +371,10 @@ api.getFirebaseToken = function(req, res, next) { token: token, expires: expires }); -}; +};*/ -/* - Registers a new user. Only accepting username/password registrations, no Facebook -*/ - -api.setupPassport = function(router) { +// DISABLED FOR API v2 +/*api.setupPassport = function(router) { router.get('/logout', i18n.getUserLanguage, function(req, res) { req.logout(); @@ -380,4 +382,4 @@ api.setupPassport = function(router) { res.redirect('/'); }) -}; +};*/ diff --git a/website/server/controllers/api-v2/challenges.js b/website/server/controllers/api-v2/challenges.js new file mode 100644 index 0000000000..c5d716d88e --- /dev/null +++ b/website/server/controllers/api-v2/challenges.js @@ -0,0 +1,428 @@ +// @see ../routes for routing + +var _ = require('lodash'); +var nconf = require('nconf'); +var async = require('async'); +var shared = require('../../../../common'); +import { + model as User, +} from '../../models/user'; +import { + model as Group, + basicFields as basicGroupFields, + TAVERN_ID, +} from '../../models/group'; +import { + model as Challenge, +} from '../../models/challenge'; +import * as Tasks from '../../models/task'; +var logging = require('./../../libs/api-v2/logging'); +var csvStringify = require('csv-stringify'); +var utils = require('../../libs/api-v2/utils'); +var api = module.exports; +var pushNotify = require('./pushNotifications'); +import Bluebird from 'bluebird'; +import v3MembersController from '../api-v3/members'; +/* + ------------------------------------------------------------------------ + Challenges + ------------------------------------------------------------------------ +*/ + +var nameFields = 'profile.name'; + +api.list = async function(req, res, next) { + try { + var user = res.locals.user; + + let challenges = await Challenge.find({ + $or: [ + {_id: {$in: user.challenges}}, // Challenges where the user is participating + {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member + {leader: user._id}, // Challenges where I'm the leader + ], + _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit + }) + .sort('-official -timestamp') + // .populate('group', basicGroupFields) + // .populate('leader', nameFields) + .exec(); + + let resChals = challenges.map(challenge => { + let obj = challenge.toJSON(); + + obj._isMember = user.challenges.indexOf(challenge._id) !== -1; + return obj; + }); + + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + await Bluebird.all(resChals.map((chal, index) => { + return Bluebird.all([ + User.findById(chal.leader).select(nameFields).exec(), + Group.findById(chal.group).select(basicGroupFields).exec(), + ]).then(populatedData => { + resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null; + resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null; + }); + })); + + res.json(resChals); + } catch (err) { + next(err); + } +} + +// GET +api.get = async function(req, res, next) { + try { + let user = res.locals.user; + let challengeId = req.params.cid; + + let challenge = await Challenge.findById(challengeId) + // Don't populate the group as we'll fetch it manually later + // .populate('leader', nameFields) + .exec(); + if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); + + // Fetching basic group data + let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); + if (!group || !challenge.canView(user, group)) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); + + let leaderRes = await User.findById(challenge.leader).select('profile.name').exec(); + leaderRes = leaderRes ? leaderRes.toJSON({minimize: true}) : null; + + challenge.getTransformedData({ + populateMembers: 'profile.name', + cb (err, transformedChal) { + transformedChal.group = group.toJSON({minimize: true}); + transformedChal.leader = leaderRes; + transformedChal._isMember = user.challenges.indexOf(transformedChal._id) !== -1; + res.json(transformedChal); + } + }); + } catch (err) { + next(err); + } +} + +api.csv = function(req, res, next) { + var cid = req.params.cid; + req.params.challengeId = cid; + v3MembersController.exportChallengeCsv.handler(req, res, next).catch(next); +} + +api.getMember = function(req, res, next) { + var cid = req.params.cid; + var uid = req.params.uid; + + req.params.memberId = uid; + req.params.challengeId = cid; + v3MembersController.getChallengeMemberProgress.handler(req, res, next) + .then(result => { + let newResult = { + profile: { + name: result.profile.name, + }, + habits: [], + dailys: [], + todos: [], + rewards: [], + }; + + let tasks = result.tasks; + tasks.forEach(task => { + let taskObj = task.toJSONV2(); + newResult[taskObj.type + 's'].push(taskObj); + }); + + res.json(newResult); + }) + .catch(next); +} + +// CREATE +api.create = async function(req, res, next){ + try { + var user = res.locals.user; + + let groupId = req.body.group; + let prize = req.body.prize; + + let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); + if (!group) return res.status(404).json({err:"Group." + req.body.group + " not found"}); + if (!group.isMember(user)) return res.status(404).json({err:"Group." + req.body.group + " not found"}); + + if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { + return res.status(401).json({err:"Only the group leader can create challenges"}); + } + + if (group._id === TAVERN_ID && prize < 1) { + return res.status(401).json({err: 'Prize must be at least 1 Gem for public challenges.'}) + } + + if (prize > 0) { + let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; + let prizeCost = prize / 4; + + if (prizeCost > user.balance + groupBalance) { + return res.status(401).json({err: 'You can\'t afford this prize. Purchase more gems or lower the prize amount.'}); + } + + if (groupBalance >= prizeCost) { + // Group pays for all of prize + group.balance -= prizeCost; + } else if (groupBalance > 0) { + // User pays remainder of prize cost after group + let remainder = prizeCost - group.balance; + group.balance = 0; + user.balance -= remainder; + } else { + // User pays for all of prize + user.balance -= prizeCost; + } + } + + group.challengeCount += 1; + + req.body.leader = user._id; + req.body.official = user.contributor.admin && req.body.official; + let challenge = new Challenge(Challenge.sanitize(req.body)); + + // First validate challenge so we don't save group if it's invalid (only runs sync validators) + let challengeValidationErrors = challenge.validateSync(); + if (challengeValidationErrors) throw challengeValidationErrors; + + req.body.habits = req.body.habits || []; + req.body.todos = req.body.todos || []; + req.body.dailys = req.body.dailys || []; + req.body.rewards = req.body.rewards || []; + + var chalTasks = req.body.habits.concat(req.body.rewards) + .concat(req.body.dailys).concat(req.body.todos) + .map(v2Task => Tasks.Task.fromJSONV2(v2Task)); + + chalTasks = chalTasks.map(function(task) { + var newTask = new Tasks[task.type](Tasks.Task.sanitize(task)); + newTask.challenge.id = challenge._id; + return newTask.save(); + }); + + let results = await Bluebird.all([challenge.save({ + validateBeforeSave: false, // already validated + }), group.save()].concat(chalTasks)); + let savedChal = results[0]; + + await savedChal.syncToUser(user); // (it also saves the user) + + savedChal.getTransformedData({ + cb (err, transformedChal) { + res.status(201).json(transformedChal); + }, + }); + } catch (err) { + next(err); + } +} + +// UPDATE +api.update = function(req, res, next){ + var cid = req.params.cid; + var user = res.locals.user; + var before; + var updatedTasks; + + async.waterfall([ + function(cb){ + // We first need the original challenge data, since we're going to compare against new & decide to sync users + Challenge.findById(cid, cb); + }, + function(chal, cb){ + if(!chal) return cb({chal: null}); + + chal.getTasks(function(err, tasks){ + cb(err, { + chal: chal, + tasks: tasks + }); + }); + }, + function(_before, cb) { + if (!_before.chal) return cb('Challenge ' + cid + ' not found'); + if (_before.chal.leader != user._id && !user.contributor.admin) return cb({code: 401, err: shared.i18n.t('noPermissionEditChallenge', req.language)}); + // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some + // before-save / after-save comparison to determine if we need to sync to users + before = {chal: _before.chal, tasks: _before.tasks}; + var chalAttrs = _.pick(req.body, 'name shortName description date'.split(' ')); + async.parallel({ + chal: function(cb1){ + Challenge.findByIdAndUpdate(cid, {$set:chalAttrs}, {new: true}, cb1); + }, + tasks: function(cb1) { + // Convert to map of {id: task} so we can easily match them + var _beforeClonedTasks = _before.tasks; + updatedTasks = _.object(_.pluck(_beforeClonedTasks, '_id'), _beforeClonedTasks); + var newTasks = req.body.habits.concat(req.body.dailys) + .concat(req.body.todos).concat(req.body.rewards); + + var newTasksObj = _.object(_.pluck(newTasks, '_id'), newTasks); + async.forEachOf(newTasksObj, function(newTask, taskId, cb2){ + // some properties can't be changed + newTask = Tasks.Task.sanitize(newTask); + // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? + _.assign(updatedTasks[taskId], shared.ops.updateTask(updatedTasks[taskId].toObject(), {body: newTask})); + _before.chal.updateTask(updatedTasks[taskId]).then(cb2).catch(cb2); + }, cb1); + } + }, cb); + }, + ], function(err, saved){ + if(err) { + return err.code ? res.json(err.code, err) : next(err); + } + + saved.chal.getTransformedData({cb: function(err, newChal){ + if(err) return next(err); + res.json(newChal); + }}) + cid = user = before = null; + }); +} + +/** + * Delete & close + */ +api.delete = async function(req, res, next){ + try { + var user = res.locals.user; + var cid = req.params.cid; + + let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); + if (!challenge) return next('Challenge ' + cid + ' not found'); + if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge')); + + // Close channel in background, some ops are run in the background without `await`ing + await challenge.closeChal({broken: 'CHALLENGE_DELETED'}); + res.sendStatus(200); + } catch (err) { + next(err); + } +} + +/** + * Select Winner & Close + */ +api.selectWinner = async function(req, res, next) { + try { + if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'}); + + let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); + if (!challenge) return next('Challenge ' + req.params.cid + ' not found'); + if (!challenge.canModify(res.locals.user)) return next(shared.i18n.t('noPermissionCloseChallenge')); + + let winner = await User.findOne({_id: req.query.uid}).exec(); + if (!winner || winner.challenges.indexOf(challenge._id) === -1) return next('Winner ' + req.query.uid + ' not found.'); + + // Close channel in background, some ops are run in the background without `await`ing + await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner}); + res.respond(200, {}); + } catch (err) { + next(err); + } +} + +api.join = async function(req, res, next){ + try { + var user = res.locals.user; + var cid = req.params.cid; + + let challenge = await Challenge.findOne({ _id: cid }); + if (!challenge) return next(shared.i18n.t('challengeNotFound')); + if (challenge.isMember(user)) return next(shared.i18n.t('userAlreadyInChallenge')); + + let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); + if (!group || !challenge.hasAccess(user, group)) return next(shared.i18n.t('challengeNotFound')); + + challenge.memberCount += 1; + + // Add all challenge's tasks to user's tasks and save the challenge + await Bluebird.all([challenge.syncToUser(user), challenge.save()]); + + challenge.getTransformedData({ + cb (err, transformedChal) { + transformedChal._isMember = true; + res.json(transformedChal); + } + }); + } catch (e) { + next(e); + } +} + +api.leave = async function(req, res, next){ + try { + var user = res.locals.user; + var cid = req.params.cid; + // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided + var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; + + let challenge = await Challenge.findOne({ _id: cid }); + if (!challenge) return next(shared.i18n.t('challengeNotFound')); + + let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); + if (!group || !challenge.canView(user, group)) return next(shared.i18n.t('challengeNotFound')); + + if (!challenge.isMember(user)) return next(shared.i18n.t('challengeMemberNotFound')); + + challenge.memberCount -= 1; + + // Unlink challenge's tasks from user's tasks and save the challenge + await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]); + + challenge.getTransformedData({ + cb (err, transformedChal) { + transformedChal._isMember = false; + res.json(transformedChal); + } + }); + } catch (e) { + next(e); + } +} + +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; + +api.unlink = async function(req, res, next) { + try { + var user = res.locals.user; + var tid = req.params.id; + var cid; + if (!req.query.keep) + return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'}); + + let keep = req.query.keep; + let task = await Tasks.Task.findOne({ + _id: tid, + userId: user._id, + }).exec(); + + if (!task) return next(shared.i18n.t('taskNotFound')); + if (!task.challenge.id) return next(shared.i18n.t('cantOnlyUnlinkChalTask')); + + cid = task.challenge.id; + if (keep === 'keep') { + task.challenge = {}; + await task.save(); + } else { // remove + if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if + removeFromArray(user.tasksOrder[`${task.type}s`], tid); + await Bluebird.all([user.save(), task.remove()]); + } else { + await task.remove(); + } + } + + res.sendStatus(200); + } catch (e) { + next(e); + } +} diff --git a/website/src/controllers/api-v2/coupon.js b/website/server/controllers/api-v2/coupon.js similarity index 90% rename from website/src/controllers/api-v2/coupon.js rename to website/server/controllers/api-v2/coupon.js index a69c41e326..17cd289bd3 100644 --- a/website/src/controllers/api-v2/coupon.js +++ b/website/server/controllers/api-v2/coupon.js @@ -1,5 +1,7 @@ var _ = require('lodash'); -var Coupon = require('./../../models/coupon').model; +import { + model as Coupon, +} from '../../models/coupon'; var api = module.exports; var csvStringify = require('csv-stringify'); var async = require('async'); @@ -28,7 +30,7 @@ api.getCoupons = function(req,res,next) { res.set({ 'Content-Type': 'text/csv', - 'Content-disposition': `attachment; filename=habitica-coupons.csv`, + 'Content-disposition': 'attachment; filename=habitica-coupons.csv', }); csvStringify(output, (err, csv) => { if (err) return next(err); diff --git a/website/src/controllers/dataexport.js b/website/server/controllers/api-v2/dataexport.js similarity index 89% rename from website/src/controllers/dataexport.js rename to website/server/controllers/api-v2/dataexport.js index 8c16b9864f..f0aad2b35c 100644 --- a/website/src/controllers/dataexport.js +++ b/website/server/controllers/api-v2/dataexport.js @@ -5,15 +5,17 @@ var nconf = require('nconf'); var moment = require('moment'); var js2xmlparser = require("js2xmlparser"); var pd = require('pretty-data').pd; -var User = require('../models/user').model; +import { + model as User, +} from '../../models/user'; // Avatar screenshot/static-page includes -var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres -var AWS = require('aws-sdk'); -AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")}); -var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream -var bucket = nconf.get("S3:bucket"); -var request = require('request'); +//var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres +//var AWS = require('aws-sdk'); +//AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")}); +//var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream +//var bucket = nconf.get("S3:bucket"); +//var request = require('request'); /* ------------------------------------------------------------------------ @@ -42,7 +44,7 @@ dataexport.history = function(req, res) { res.set({ 'Content-Type': 'text/csv', - 'Content-disposition': `attachment; filename=habitica-tasks-history.csv`, + 'Content-disposition': 'attachment; filename=habitica-tasks-history.csv', }); csvStringify(output, (err, csv) => { diff --git a/website/src/controllers/api-v2/groups.js b/website/server/controllers/api-v2/groups.js similarity index 69% rename from website/src/controllers/api-v2/groups.js rename to website/server/controllers/api-v2/groups.js index e117aa40ad..31a21d7dc9 100644 --- a/website/src/controllers/api-v2/groups.js +++ b/website/server/controllers/api-v2/groups.js @@ -8,18 +8,31 @@ function clone(a) { var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); -var Q = require('q'); -var utils = require('./../../libs/utils'); +var Bluebird = require('bluebird'); +var utils = require('./../../libs/api-v2/utils'); var shared = require('../../../../common'); -var User = require('./../../models/user').model; -var Group = require('./../../models/group').model; -var Challenge = require('./../../models/challenge').model; -var EmailUnsubscription = require('./../../models/emailUnsubscription').model; + +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; + +import { + model as User, +} from './../../models/user'; +import { + model as Group, + TAVERN_ID, +} from './../../models/group'; +import { + model as Challenge, +} from './../../models/challenge'; +import { + model as EmailUnsubscription, +} from './../../models/emailUnsubscription'; + var isProd = nconf.get('NODE_ENV') === 'production'; var api = module.exports; -var pushNotify = require('./../pushNotifications'); +var pushNotify = require('./pushNotifications'); var analytics = utils.analytics; -var firebase = require('../../libs/firebase'); +var firebase = require('../../libs/api-v2/firebase'); /* ------------------------------------------------------------------------ @@ -72,31 +85,41 @@ api.list = function(req, res, next) { // unecessary given our ui-router setup party: function(cb){ if (!~type.indexOf('party')) return cb(null, {}); - Group.findOne({type: 'party', members: {'$in': [user._id]}}) + Group.findOne({_id: user.party._id, type: 'party'}) .select(groupFields).exec(function(err, party){ if (err) return cb(err); - cb(null, (party === null ? [] : [party])); // return as an array for consistent ngResource use + if (!party) return cb(null, []); + party.getTransformedData({cb: function (err, transformedParty) { + if (err) return cb(err); + cb(null, (transformedParty === null ? [] : [transformedParty])); // return as an array for consistent ngResource use + }}); }); }, guilds: function(cb) { if (!~type.indexOf('guilds')) return cb(null, []); - Group.find({members: {'$in': [user._id]}, type:'guild'}) - .select(groupFields).sort(sort).exec(cb); + Group.find({_id: {'$in': user.guilds}, type:'guild'}) + .select(groupFields).sort(sort).exec(function (err, guilds) { + if (err) return cb(err); + async.map(guilds, function (guild, cb1) { + guild.getTransformedData({cb: cb1}) + }, function(err, guildsTransormed) { + cb(err, guildsTransormed); + }); + }); }, 'public': function(cb) { if (!~type.indexOf('public')) return cb(null, []); Group.find({privacy: 'public'}) - .select(groupFields + ' members') + .select(groupFields) .sort(sort) .lean() .exec(function(err, groups){ if (err) return cb(err); _.each(groups, function(g){ // To save some client-side performance, don't send down the full members arr, just send down temp var _isMember - if (~g.members.indexOf(user._id)) g._isMember = true; - g.members = undefined; + if (user.guilds.indexOf(g._id) !== -1) g._isMember = true; }); cb(null, groups); }); @@ -105,9 +128,12 @@ api.list = function(req, res, next) { // unecessary given our ui-router setup tavern: function(cb) { if (!~type.indexOf('tavern')) return cb(null, {}); - Group.findById('habitrpg').select(groupFields).exec(function(err, tavern){ + Group.findById(TAVERN_ID).select(groupFields).exec(function(err, tavern){ if (err) return cb(err); - cb(null, [tavern]); // return as an array for consistent ngResource use + tavern.getTransformedData({cb: function (err, transformedTavern) { + if (err) return cb(err); + cb(null, ([transformedTavern])); // return as an array for consistent ngResource use + }}); }); } @@ -134,14 +160,26 @@ api.list = function(req, res, next) { api.get = function(req, res, next) { var user = res.locals.user; var gid = req.params.gid; + let isUserGuild = user.guilds.indexOf(gid) !== -1; - var q = (gid == 'party') - ? Group.findOne({type: 'party', members: {'$in': [user._id]}}) - : Group.findOne({$or:[ - {_id:gid, privacy:'public'}, - {_id:gid, privacy:'private', members: {$in:[user._id]}} // if the group is private, only return if they have access - ]}); - populateQuery(gid, q); + var q; + + if (gid === 'party' || gid === user.party._id) { + q = Group.findOne({_id: user.party._id, type: 'party'}) + } else { + + if (isUserGuild) { + q = Group.findOne({type: 'guild', _id: gid}); + } else if (gid === 'habitrpg') { + q = Group.findOne({_id: TAVERN_ID}); + } else { + q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); + } + } + + q.populate('leader', nameFields); + + //populateQuery(gid, q); q.exec(function(err, group){ if (err) return next(err); if(!group){ @@ -152,34 +190,27 @@ api.get = function(req, res, next) { return res.json(group); } - if (!user.contributor.admin) { - _purgeFlagInfoFromChat(group, user); - } - - //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group - var userInGroup = _.find(group.members, function(member){ return member._id == user._id; }); - //If the group is private or the group is a party, then the user must be a member of the group based on access restrictions above - if (group.privacy === 'private' || gid === 'party') { - //If the user is not in the group query, remove a user and add the current user - if (!userInGroup) { - group.members.splice(0,1); - group.members.push(user); - } - res.json(group); - } else if ( group.privacy === "public" ) { //The group is public, we must do an extra check to see if the user is already in the group query - //We must see how to check if a user is a member of a public group, so we requery - var q2 = Group.findOne({ _id: group._id, privacy:'public', members: {$in:[user._id]} }); - q2.exec(function(err, group2){ + group.getTransformedData({ + cb: function (err, transformedGroup) { if (err) return next(err); - if (group2 && !userInGroup) { - group.members.splice(0,1); - group.members.push(user); - } - res.json(group); - }); - } - gid = null; + if (!user.contributor.admin) { + _purgeFlagInfoFromChat(transformedGroup, user); + } + + //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group + var userInGroup = _.find(transformedGroup.members, function(member){ return member._id == user._id; }); + if ((gid === 'party' || isUserGuild) && !userInGroup) { + transformedGroup.members.splice(0,1); + transformedGroup.members.push(user); + } + + res.json(transformedGroup); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + populateInvites: nameFields, + populateChallenges: challengeFields, + }); }); }; @@ -187,10 +218,12 @@ api.get = function(req, res, next) { api.create = function(req, res, next) { var group = new Group(req.body); var user = res.locals.user; - group.members = [user._id]; + //group.members = [user._id]; group.leader = user._id; + if (!group.name) group.name = 'group name'; if(group.type === 'guild'){ + user.guilds.push(group._id); if(user.balance < 1) return res.status(401).json({err: shared.i18n.t('messageInsufficientGems')}); group.balance = 1; @@ -202,33 +235,31 @@ api.create = function(req, res, next) { function(saved,ct,cb){ firebase.updateGroupData(saved); firebase.addUserToGroup(saved._id, user._id); - saved.populate('members', nameFields, cb); + saved.getTransformedData({ + populateMembers: nameFields, + cb, + }) } - ],function(err,saved){ + ],function(err,groupTransformed){ if (err) return next(err); - res.json(saved); + res.json(groupTransformed); group = user = null; }); } else{ - async.waterfall([ - function(cb){ - Group.findOne({type:'party',members:{$in:[user._id]}},cb); - }, - function(found, cb){ - if (found) return cb(shared.i18n.t('messageGroupAlreadyInParty')); - group.save(cb); - }, - function(saved, count, cb){ - firebase.updateGroupData(saved); - firebase.addUserToGroup(saved._id, user._id); - saved.populate('members', nameFields, cb); - } - ], function(err, populated){ - if (err === shared.i18n.t('messageGroupAlreadyInParty')) return res.status(400).json({err:err}); + if (user.party._id) return res.status(400).json({err:shared.i18n.t('messageGroupAlreadyInParty')}); + user.party._id = group._id; + user.save(function (err) { if (err) return next(err); - group = user = null; - return res.json(populated); + group.save(function(err, saved) { + if (err) return next(err); + saved.getTransformedData({ + populateMembers: nameFields, + cb (err, groupTransformed) { + res.json(groupTransformed); + }, + }); + }); }) } } @@ -241,7 +272,7 @@ api.update = function(req, res, next) { return res.status(401).json({err: shared.i18n.t('messageGroupOnlyLeaderCanUpdate')}); 'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){ - group[attr] = req.body[attr]; + if (req.body[attr]) group[attr] = req.body[attr]; }); group.save(function(err, saved){ @@ -255,8 +286,11 @@ api.update = function(req, res, next) { // TODO remove from api object? api.attachGroup = function(req, res, next) { var user = res.locals.user; - var gid = req.params.gid; - var q = (gid == 'party') ? Group.findOne({type: 'party', members: {'$in': [res.locals.user._id]}}) : Group.findById(gid); + var gid = req.params.gid === 'party' ? user.party._id : req.params.gid; + if (gid === 'habitrpg') gid = TAVERN_ID; + + let q = Group.findOne({_id: gid}) + q.exec(function(err, group){ if(err) return next(err); if(!group) return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); @@ -274,13 +308,22 @@ api.getChat = function(req, res, next) { // TODO: This code is duplicated from api.get - pull it out into a function to remove duplication. var user = res.locals.user; var gid = req.params.gid; - var q = (gid == 'party') - ? Group.findOne({type: 'party', members: {$in:[user._id]}}) - : Group.findOne({$or:[ - {_id:gid, privacy:'public'}, - {_id:gid, privacy:'private', members: {$in:[user._id]}} - ]}); - populateQuery(gid, q); + + var q; + let isUserGuild = user.guilds.indexOf(gid) !== -1; + + if (gid === 'party' || gid === user.party._id) { + q = Group.findOne({_id: user.party._id, type: 'party'}) + } else { + if (isUserGuild) { + q = Group.findOne({type: 'guild', _id: gid}); + } else if (gid === 'habitrpg') { + q = Group.findOne({_id: TAVERN_ID}); + } else { + q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); + } + } + q.exec(function(err, group){ if (err) return next(err); if (!group && gid!=='party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); @@ -303,7 +346,7 @@ api.postChat = function(req, res, next) { var lastClientMsg = req.query.previousMsg; var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false; - group.sendChat(req.query.message, user); // FIXME this should be body, but ngResource is funky + group.sendChat(req.query.message, user); // TODO this should be body, but ngResource is funky if (group.type === 'party') { user.party.lastMessageSeen = group.chat[0].id; @@ -386,7 +429,7 @@ api.flagChatMessage = function(req, res, next){ {name: "GROUP_NAME", content: group.name}, {name: "GROUP_TYPE", content: group.type}, {name: "GROUP_ID", content: group._id}, - {name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, + {name: "GROUP_URL", content: group._id == TAVERN_ID ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, ]); return res.sendStatus(204); @@ -455,7 +498,9 @@ api.join = function(req, res, next) { if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) { User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter - user.invitations.party = undefined; // Clear invite + user.invitations.party = {}; // Clear invite + user.markModified('invitations.party'); + user.party._id = group._id; user.save(); // invite new user to pending quest if (group.quest.key && !group.quest.active) { @@ -464,20 +509,29 @@ api.join = function(req, res, next) { group.markModified('quest.members'); } isUserInvited = true; - } else if (group.type == 'guild' && user.invitations && user.invitations.guilds) { + } else if (group.type == 'guild') { var i = _.findIndex(user.invitations.guilds, {id:group._id}); if (~i){ isUserInvited = true; user.invitations.guilds.splice(i,1); + user.guilds.push(group._id); user.save(); }else{ isUserInvited = group.privacy === 'private' ? false : true; + if (isUserInvited) { + user.guilds.push(group._id); + user.save(); + } } } if(!isUserInvited) return res.status(401).json({err: shared.i18n.t('messageGroupRequiresInvite')}); - if (!_.contains(group.members, user._id)){ + if (group.memberCount === 0) { + group.leader = user._id; + } + + /*if (!_.contains(group.members, user._id)){ if (group.members.length === 0) { group.leader = user._id; } @@ -487,7 +541,7 @@ api.join = function(req, res, next) { if (group.invites.length > 0) { group.invites.splice(_.indexOf(group.invites, user._id), 1); } - } + }*/ async.series([ function(cb){ @@ -495,8 +549,12 @@ api.join = function(req, res, next) { }, function(cb){ firebase.addUserToGroup(group._id, user._id); - // TODO why query group once again? - populateQuery(group.type, Group.findById(group._id)).exec(cb); + group.getTransformedData({ + cb, + populateMembers: group.type === 'party' ? partyFields : nameFields, + populateInvites: nameFields, + populateChallenges: challengeFields, + }) } ], function(err, results){ if (err) return next(err); @@ -523,12 +581,9 @@ api.leave = function(req, res, next) { // When removing the user from challenges, should we keep the tasks? var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - group.leave(user, keep, function(err){ - if (err) return next(err); - user = group = keep = null; - - return res.sendStatus(204); - }); + group.leave(user, keep) + .then(() => res.sendStatus(204)) + .catch(next); }; var inviteByUUIDs = function(uuids, group, req, res, next){ @@ -538,7 +593,7 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ if (!invite) return cb({code:400,err:'User with id "' + uuid + '" not found'}); if (group.type == 'guild') { - if (_.contains(group.members,uuid)) + if (_.contains(invite.guilds, group._id)) return cb({code:400, err: "User already in that group"}); if (invite.invitations && invite.invitations.guilds && _.find(invite.invitations.guilds, {id:group._id})) return cb({code:400, err:"User already invited to that group"}); @@ -546,13 +601,10 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ } else if (group.type == 'party') { if (invite.invitations && !_.isEmpty(invite.invitations.party)) return cb({code: 400,err:"User already pending invitation."}); - Group.find({type: 'party', members: {$in: [uuid]}}, function(err, groups){ - if (err) return cb(err); - if (!_.isEmpty(groups) && groups[0].members.length > 1) { - return cb({code: 400, err: "User already in a party."}) - } - sendInvite(); - }); + if (invite.party && invite.party._id) { + return cb({code: 400, err: "User already in a party."}) + } + sendInvite(); } function sendInvite (){ @@ -567,7 +619,7 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ pushNotify.sendNotify(invite, shared.i18n.t('invitedParty'), group.name); } - group.invites.push(invite._id); + //group.invites.push(invite._id); async.series([ function(cb){ @@ -610,10 +662,17 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ }, function(cb) { // TODO pass group from save above don't find it again, or you have to find it again in order to run populate? - populateQuery(group.type, Group.findById(group._id)).exec(function(err, populatedGroup){ - if(err) return next(err); - - res.json(populatedGroup); + Group.findById(group._id).populate('leader', nameFields).exec(function (err, savedGroup) { + if (err) return next(err); + savedGroup.getTransformedData({ + cb: function (err, transformedGroup) { + if (err) return next(err); + res.json(transformedGroup); + }, + populateMembers: savedGroup.type === 'party' ? partyFields : nameFields, + populateInvites: nameFields, + populateChallenges: challengeFields, + }) }); } ]); @@ -681,10 +740,17 @@ var inviteByEmails = function(invites, group, req, res, next){ api.invite = function(req, res, next){ var group = res.locals.group; + let userParty = res.locals.user.party && res.locals.user.party._id; + let userGuilds = res.locals.user.guilds; - if (group.privacy === 'private' && !_.contains(group.members,res.locals.user._id)) { + if (group.type === 'party' && userParty !== group._id) { return res.status(401).json({err: "Only a member can invite new members!"}); } + + if (group.type === 'guild' && group.privacy === 'private' && !_.contains(userGuilds, group._id)) { + return res.status(401).json({err: "Only a member can invite new members!"}); + } + if (req.body.uuids) { inviteByUUIDs(req.body.uuids, group, req, res, next); } else if (req.body.emails) { @@ -720,28 +786,35 @@ api.removeMember = function(req, res, next){ return res.status(401).json({err: "You cannot remove yourself!"}); } - if(_.contains(group.members, uuid)){ - var update = {$pull:{members:uuid}}; - if (group.quest && group.quest.leader === uuid) { - update['$set'] = { - quest: { key: null, leader: null } - }; - } else if(group.quest && group.quest.members){ - // remove member from quest - update['$unset'] = {}; - update['$unset']['quest.members.' + uuid] = ""; - } - update['$inc'] = {memberCount: -1}; - Group.update({_id:group._id},update, function(err, saved){ - if (err) return next(err); + User.findById(uuid, function(err, removedUser){ + if (err) return next(err); + let isMember = group._id === removedUser.party._id || _.contains(removedUser.guilds, group._id); + let isInvited = group._id === removedUser.invitations.party._id || !!_.find(removedUser.invitations.guilds, {id: group._id}); - User.findById(uuid, function(err, removedUser){ - if(err) return next(err); + if(isMember){ + var update = {}; + if (group.quest && group.quest.leader === uuid) { + update['$set'] = { + quest: { key: null, leader: null } + }; + } else if(group.quest && group.quest.members){ + // remove member from quest + update['$unset'] = {}; + update['$unset']['quest.members.' + uuid] = ""; + } + update['$inc'] = {memberCount: -1}; + Group.update({_id:group._id},update, function(err, saved){ + if (err) return next(err); sendMessage(removedUser); //Mark removed users messages as seen var update = {$unset:{}}; + if (group.type === 'guild') { + update.$pull = {guilds: group._id}; + } else { + update.$unset.party = true; + } update.$unset['newMessages.' + group._id] = ''; if (group.quest && group.quest.active && group.quest.leader === uuid) { update['$inc'] = {}; @@ -754,12 +827,8 @@ api.removeMember = function(req, res, next){ group = uuid = null; return res.sendStatus(204); }); - }); - }else if(_.contains(group.invites, uuid)){ - User.findById(uuid, function(err,invited){ - if(err) return next(err); - - var invitations = invited.invitations; + }else if(isInvited){ + var invitations = removedUser.invitations; if(group.type === 'guild'){ invitations.guilds.splice(_.indexOf(invitations.guilds, group._id), 1); }else{ @@ -768,31 +837,32 @@ api.removeMember = function(req, res, next){ async.series([ function(cb){ - invited.save(cb); + removedUser.save(cb); }, - function(cb){ - Group.update({_id:group._id},{$pull:{invites:uuid}}, cb); - } ], function(err, results){ if (err) return next(err); // Sending an empty 204 because Group.update doesn't return the group // see http://mongoosejs.com/docs/api.html#model_Model.update - sendMessage(invited); + sendMessage(removedUser); group = uuid = null; return res.sendStatus(204); }); - - }); - }else{ - group = uuid = null; - return res.status(400).json({err: "User not found among group's members!"}); - } + }else{ + group = uuid = null; + return res.status(400).json({err: "User not found among group's members!"}); + } + }); } // ------------------------------------ // Quests // ------------------------------------ +function canStartQuestAutomatically (group) { + // If all members are either true (accepted) or false (rejected) return true + // If any member is null/undefined (undecided) return false + return _.every(group.quest.members, _.isBoolean); +} function questStart(req, res, next) { var group = res.locals.group; @@ -908,70 +978,103 @@ api.questAccept = function(req, res, next) { if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."}); if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'}); if (!user.items.quests[key]) return res.status(400).json({err: "You don't own that quest scroll"}); - group.quest.key = key; - group.quest.members = {}; - // Invite everyone. true means "accepted", false="rejected", undefined="pending". Once we click "start quest" - // or everyone has either accepted/rejected, then we store quest key in user object. - _.each(group.members, function(m){ - if (m == user._id) { - var analyticsData = { - category: 'behavior', - owner: true, - response: 'accept', - gaLabel: 'accept', - questName: key, - uuid: user._id, - }; - analytics.track('quest',analyticsData); - group.quest.members[m] = true; - group.quest.leader = user._id; - } else { - User.update({_id:m},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec(); - group.quest.members[m] = undefined; - } - }); + + let members; User.find({ - _id: { - $in: _.without(group.members, user._id) + 'party._id': group._id, + _id: {$ne: user._id}, + }).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') + .exec().then(membersF => { + members = membersF; + + group.markModified('quest'); + group.quest.key = key; + group.quest.leader = user._id; + group.quest.members = {}; + group.quest.members[user._id] = true; + + user.party.quest.RSVPNeeded = false; + user.party.quest.key = key; + + return User.update({ + 'party._id': group._id, + _id: {$ne: user._id}, + }, { + $set: { + 'party.quest.RSVPNeeded': true, + 'party.quest.key': key, + }, + }, {multi: true}).exec(); + }).then(() => { + _.each(members, (member) => { + group.quest.members[member._id] = null; + }); + + if (canStartQuestAutomatically(group)) { + group.startQuest(user).then(() => { + return Bluebird.all([group.save(), user.save()]) + }) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); + + } else { + Bluebird.all([group.save(), user.save()]) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); } - }, {auth: 1, preferences: 1, profile: 1, pushDevices: 1}, function(err, members){ - if(err) return next(err); - - var inviterVars = utils.getUserInfo(user, ['name', 'email']); - - var membersToEmail = members.filter(function(member){ - return member.preferences.emailNotifications.invitedQuest !== false; - }); - - utils.txnEmail(membersToEmail, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [ - {name: 'QUEST_NAME', content: quest.text()}, - {name: 'INVITER', content: inviterVars.name}, - {name: 'PARTY_URL', content: '/#/options/groups/party'} - ]); - - _.each(members, function(groupMember){ - pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() })); - }); - - questStart(req,res,next); - }); + }).catch(next); // Party member accepting the invitation } else { - if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'}); - var analyticsData = { - category: 'behavior', - owner: false, - response: 'accept', - gaLabel: 'accept', - questName: group.quest.key, - uuid: user._id, - }; - analytics.track('quest',analyticsData); + group.markModified('quest'); group.quest.members[user._id] = true; - User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false}}).exec(); - questStart(req,res,next); + user.party.quest.RSVPNeeded = false; + + if (canStartQuestAutomatically(group)) { + group.startQuest(user).then(() => { + return Bluebird.all([group.save(), user.save()]) + }) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); + + } else { + Bluebird.all([group.save(), user.save()]) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); + } } } @@ -979,84 +1082,102 @@ api.questReject = function(req, res, next) { var group = res.locals.group; var user = res.locals.user; - if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'}); - var analyticsData = { - category: 'behavior', - owner: false, - response: 'reject', - gaLabel: 'reject', - questName: group.quest.key, - uuid: user._id, - }; - analytics.track('quest',analyticsData); group.quest.members[user._id] = false; - User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec(); - questStart(req,res,next); + group.markModified('quest.members'); + + user.party.quest = Group.cleanQuestProgress(); + user.markModified('party.quest'); + + if (canStartQuestAutomatically(group)) { + group.startQuest(user).then(() => { + return Bluebird.all([group.save(), user.save()]) + }) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); + + } else { + Bluebird.all([group.save(), user.save()]) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }) + .catch(next); + } } api.questCancel = function(req, res, next){ + var group = res.locals.group; + + group.quest = Group.cleanGroupQuest(); + group.markModified('quest'); + + Bluebird.all([ + group.save(), + User.update( + {'party._id': group._id}, + {$set: {'party.quest': Group.cleanQuestProgress()}}, + {multi: true} + ), + ]).then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); + }).catch(next); + // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) // Quest scroll has not yet left quest owner's inventory so no need to return it. // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. - var group = res.locals.group; - async.parallel([ - function(cb){ - if (! group.quest.active) { - // Do not cancel active quests because this function does - // not do the clean-up required for that. - // TODO: return an informative error when quest is active - group.quest = {key:null,progress:{},leader:null}; - group.markModified('quest'); - group.save(cb); - _.each(group.members, function(m){ - User.update({_id:m}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec(); - }); - } - } - ], function(err){ - if (err) return next(err); - res.json(group); - group = null; - }) } api.questAbort = function(req, res, next){ - // Abort a quest AFTER it has begun (see questCancel for BEFORE) var group = res.locals.group; - async.parallel([ - function(cb){ - User.update( - {_id:{$in: _.keys(group.quest.members)}}, - { - $set: {'party.quest':Group.cleanQuestProgress()}, - $inc: {_v:1} - }, - {multi:true}, - cb); + + let memberUpdates = User.update({ + 'party._id': group._id, + }, { + $set: {'party.quest': Group.cleanQuestProgress()}, + $inc: {_v: 1}, // TODO update middleware + }, {multi: true}).exec(); + + let questLeaderUpdate = User.update({ + _id: group.quest.leader, + }, { + $inc: { + [`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader }, - // Refund party leader quest scroll - function(cb){ - if (group.quest.active) { - var update = {$inc:{}}; - update['$inc']['items.quests.' + group.quest.key] = 1; - User.update({_id:group.quest.leader}, update).exec(); - } - group.quest = {key:null,progress:{},leader:null}; - group.markModified('quest'); - group.save(cb); - }, function(cb){ - populateQuery(group.type, Group.findById(group._id)).exec(cb); - } - ], function(err, results){ - if (err) return next(err); + }).exec(); - var groupClone = clone(group); + group.quest = Group.cleanGroupQuest(); + group.markModified('quest'); - groupClone.members = results[2].members; - - res.json(groupClone); - group = null; + Bluebird.all([group.save(), memberUpdates, questLeaderUpdate]) + .then(results => { + results[0].getTransformedData({ + cb (err, groupTransformed) { + if (err) return next(err); + res.json(groupTransformed); + }, + populateMembers: group.type === 'party' ? partyFields : nameFields, + }); }) + .catch(next); } api.questLeave = function(req, res, next) { @@ -1076,16 +1197,16 @@ api.questLeave = function(req, res, next) { return res.status(403).json({ err: 'Quest leader cannot leave quest' }); } - delete group.quest.members[user._id]; + group.quest.members[user._id] = false; group.markModified('quest.members'); user.party.quest = Group.cleanQuestProgress(); user.markModified('party.quest'); - var groupSavePromise = Q.nbind(group.save, group); - var userSavePromise = Q.nbind(user.save, user); + var groupSavePromise = Bluebird.promisify(group.save, {context: group}); + var userSavePromise = Bluebird.promisify(user.save, {context: user}); - Q.all([groupSavePromise(), userSavePromise()]) + Bluebird.all([groupSavePromise(), userSavePromise()]) .done(function(values) { return res.sendStatus(204); }, function(error) { diff --git a/website/src/controllers/api-v2/hall.js b/website/server/controllers/api-v2/hall.js similarity index 96% rename from website/src/controllers/api-v2/hall.js rename to website/server/controllers/api-v2/hall.js index ec88894c62..05d3bf520c 100644 --- a/website/src/controllers/api-v2/hall.js +++ b/website/server/controllers/api-v2/hall.js @@ -2,8 +2,12 @@ var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); var shared = require('../../../../common'); -var User = require('./../../models/user').model; -var Group = require('./../../models/group').model; +import { + model as User, +} from '../../models/user'; +import { + model as Group, +} from '../../models/group'; var api = module.exports; api.ensureAdmin = function(req, res, next) { diff --git a/website/src/controllers/api-v2/members.js b/website/server/controllers/api-v2/members.js similarity index 90% rename from website/src/controllers/api-v2/members.js rename to website/server/controllers/api-v2/members.js index 01c136c472..232bb9ed73 100644 --- a/website/src/controllers/api-v2/members.js +++ b/website/server/controllers/api-v2/members.js @@ -1,13 +1,18 @@ -var User = require('mongoose').model('User'); -var groups = require('../../models/group'); -var partyFields = require('./groups').partyFields +import { + model as groups, + chatDefaults, +} from '../../models/group'; +import { + model as User, +} from '../../models/user'; +let partyFields = require('./groups').partyFields; var api = module.exports; var async = require('async'); var _ = require('lodash'); var shared = require('../../../../common'); -var utils = require('../../libs/utils'); +var utils = require('../../libs/api-v2/utils'); var nconf = require('nconf'); -var pushNotify = require('./../pushNotifications'); +var pushNotify = require('./pushNotifications'); var fetchMember = function(uuid, restrict){ return function(cb){ @@ -49,12 +54,12 @@ api.sendMessage = function(user, member, data){ } msg += data.message ? data.message : ''; } - shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user)); + shared.refPush(member.inbox.messages, chatDefaults(msg, user)); member.inbox.newMessages++; member._v++; member.markModified('inbox.messages'); - shared.refPush(user.inbox.messages, _.defaults({sent:true}, groups.chatDefaults(msg, member))); + shared.refPush(user.inbox.messages, _.defaults({sent:true}, chatDefaults(msg, member))); user.markModified('inbox.messages'); } diff --git a/website/src/controllers/pushNotifications.js b/website/server/controllers/api-v2/pushNotifications.js similarity index 98% rename from website/src/controllers/pushNotifications.js rename to website/server/controllers/api-v2/pushNotifications.js index d1c728f365..860cfbf56a 100644 --- a/website/src/controllers/pushNotifications.js +++ b/website/server/controllers/api-v2/pushNotifications.js @@ -1,3 +1,4 @@ +// TODO move to /api-v2 var api = module.exports; var _ = require('lodash'); var nconf = require('nconf'); diff --git a/website/src/controllers/api-v2/unsubscription.js b/website/server/controllers/api-v2/unsubscription.js similarity index 80% rename from website/src/controllers/api-v2/unsubscription.js rename to website/server/controllers/api-v2/unsubscription.js index 2fecbef03f..a91db19d86 100644 --- a/website/src/controllers/api-v2/unsubscription.js +++ b/website/server/controllers/api-v2/unsubscription.js @@ -1,6 +1,10 @@ -var User = require('../../models/user').model; -var EmailUnsubscription = require('../../models/emailUnsubscription').model; -var utils = require('../../libs/utils'); +import { + model as User, +} from '../../models/user'; +import { + model as EmailUnsubscription, +} from '../../models/emailUnsubscription'; +var utils = require('../../libs/api-v2/utils'); var i18n = require('../../../../common').i18n; var api = module.exports = {}; @@ -15,7 +19,7 @@ api.unsubscribe = function(req, res, next){ $set: {'preferences.emailNotifications.unsubscribeFromAll': true} }, {multi: false}, function(err, updateRes){ if(err) return next(err); - if(updateRes !== 1) return res.status(404).json({err: 'User not found'}); + if(updateRes.n !== 1) return res.json(404, {err: 'User not found'}); res.send('

' + i18n.t('unsubscribedSuccessfully', null, req.language) + '

' + i18n.t('unsubscribedTextUsers', null, req.language)); }); diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js new file mode 100644 index 0000000000..656a48c908 --- /dev/null +++ b/website/server/controllers/api-v2/user.js @@ -0,0 +1,1053 @@ +var url = require('url'); +var ipn = require('paypal-ipn'); +var _ = require('lodash'); +var validator = require('validator'); +var nconf = require('nconf'); +var asyncM = require('async'); +var shared = require('../../../../common'); +import { + model as User, +} from '../../models/user'; +import { + NotFound, +} from '../../libs/api-v3/errors'; +import { model as Tag } from '../../models/tag'; +import * as Tasks from '../../models/task'; +import Bluebird from 'bluebird'; +import {removeFromArray} from './../../libs/api-v3/collectionManipulators'; +var utils = require('./../../libs/api-v2/utils'); +var analytics = utils.analytics; +import { + basicFields as basicGroupFields, + model as Group, +} from '../../models/group'; +import { + model as Challenge, +} from '../../models/challenge'; +var moment = require('moment'); +var logging = require('./../../libs/api-v2/logging'); +var acceptablePUTPaths; +let restrictedPUTSubPaths; +import v3UserController from '../api-v3/user'; + +let i18n = shared.i18n; + +var api = module.exports; +var firebase = require('../../libs/api-v2/firebase'); +var webhook = require('../../libs/api-v2/webhook'); + +const partyMembersFields = 'profile.name stats achievements items.special'; + +// api.purchase // Shared.ops + +api.getContent = function(req, res, next) { + var language = 'en'; + + if (typeof req.query.language != 'undefined') + language = req.query.language.toString(); //|| 'en' in i18n + + var content = _.cloneDeep(shared.content); + var walk = function(obj, lang){ + _.each(obj, function(item, key, source){ + if (_.isPlainObject(item) || _.isArray(item)) return walk(item, lang); + if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); + }); + } + walk(content, language); + res.json(content); +} + +api.getModelPaths = function(req,res,next){ + res.json(_.reduce(User.schema.paths,function(m,v,k){ + m[k] = v.instance || 'Boolean'; + return m; + },{})); +} + +/* + ------------------------------------------------------------------------ + Tasks + ------------------------------------------------------------------------ +*/ + + +/* + Local Methods + --------------- +*/ + +var findTask = function(req, res) { + return res.locals.user.tasks[req.params.id]; +}; + +function findTaskByIdOrLegacyId (user, taskId, callback) { + asyncM.waterfall([ + function (cb) { + Tasks.Task.findOne({ + _id: taskId, + userId: user._id, + }, cb); + }, + function (task, cb) { + if (task) return cb(null, task); + + Tasks.Task.findOne({ + _legacyId: taskId, + userId: user._id, + }, cb); + }, + ], callback); +} + +/* + API Routes + --------------- +*/ + +api.score = function(req, res, next) { + var id = req.params.id, + direction = req.params.direction, + user = res.locals.user, + body = req.body || {}, + task; + + // Send error responses for improper API call + if (!id) return res.json(400, {err: ':id required'}); + if (direction !== 'up' && direction !== 'down') { + if (direction == 'unlink' || direction == 'sort') return next(); + return res.json(400, {err: ":direction must be 'up' or 'down'"}); + } + + findTaskByIdOrLegacyId(user, id, function (err, task) { + if (err) return next(err); + + // If exists already, score it + if (!task) { + // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it + // Defaults. Other defaults are handled in user.ops.addTask() + var taskOptions = { + type: body.type || 'habit', + text: body.text || id, + userId: user._id, + notes: body.notes || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." // TODO translate + } + + if (validator.isUUID(id)) { + taskOptions._id = id; // TODO this might easily lead to conflicts as ids are now unique db-wide + } else { + taskOptions._legacyId = id; + } + + task = new Tasks.Task(taskOptions); + + user.tasksOrder[task.type + 's'].unshift(task._id); + } + + // Set completed if type is daily or todo + if (task.type === 'daily' || task.type === 'todo') { + task.completed = direction === 'up'; + } + + var [delta] = shared.ops.scoreTask({ + user, + task, + direction, + }, req); + // Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results) + if (direction === 'up') user.fns.randomDrop({task, delta}, req); + + asyncM.parallel({ + task: task.save.bind(task), + user: user.save.bind(user) + }, function(err, results){ + if(err) return next(err); + + // TODO this is suuuper strange, sometimes results.user is an array, sometimes user directly + var saved = Array.isArray(results.user) ? results.user[0] : results.user; + var task = Array.isArray(results.task) ? results.task[0] : results.task; + + var userStats = saved.toJSON().stats; + var resJsonData = _.extend({ delta: delta, _tmp: user._tmp }, userStats); + res.json(200, resJsonData); + + var webhookData = _generateWebhookTaskData( + task, direction, delta, userStats, user + ); + webhook.sendTaskWebhook(user.preferences.webhooks, webhookData); + + if ( + (!task.challenge.id || task.challenge.broken) // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response and the user doesn't care what happens back there + || (task.type == 'reward') // we don't want to update the reward GP cost + ) return; + + // select name and shortName because they can be synced on syncToUser + Challenge.findById(task.challenge.id, 'name shortName', function(err, chal) { + if (err) return next(err); + if (!chal) { + task.challenge.broken = 'CHALLENGE_DELETED'; + task.save(); + return; + } + + Tasks.Task.findOne({ + '_id': task.challenge.taskId, + userId: {$exists: false} + }, function(err, chalTask){ + if(err) return; //TODO + // this task was removed from the challenge, notify user + if(!chalTask) { + // TODO finish + chal.getTasks(function(err, chalTasks){ + if(err) return; //TODO + chal.syncToUser(user, chalTasks); + }); + } else { + chalTask.value += delta; + if (chalTask.type == 'habit' || chalTask.type == 'daily') + chalTask.history.push({value: chalTask.value, date: +new Date}); + chalTask.save(); + } + }); + }); + }); + }); +}; + +/** + * Get all tasks + */ +api.getTasks = function(req, res, next) { + var user = res.locals.user; + + user.getTasks(req.query.type, function (err, tasks) { + if (err) return next(err); + res.status(200).json(tasks.map(task => task.toJSONV2())); + }); +}; + +/** + * Get Task + */ +api.getTask = function(req, res, next) { + var user = res.locals.user, + id = req.params.id; + + findTaskByIdOrLegacyId(user, id, function (err, task) { + if (err) return next(err); + if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); + res.status(200).json(task.toJSONV2()); + }); +}; + +/* + ------------------------------------------------------------------------ + Items + ------------------------------------------------------------------------ +*/ +// api.buy // handled in Shard.ops + +api.getBuyList = function (req, res, next) { + var list = shared.updateStore(res.locals.user); + return res.status(200).json(list); +}; + +/* + ------------------------------------------------------------------------ + User + ------------------------------------------------------------------------ +*/ + +/** + * Get User + */ +api.getUser = function(req, res, next) { + res.locals.user.getTransformedData(function(err, user){ + user.stats.toNextLevel = shared.tnl(user.stats.lvl); + user.stats.maxHealth = shared.maxHealth; + user.stats.maxMP = res.locals.user._statsComputed.maxMP; + delete user.apiToken; + if (user.auth && user.auth.local) { + delete user.auth.local.hashed_password; + delete user.auth.local.salt; + } + return res.status(200).json(user); + }); +}; + +/** + * Get anonymized User + */ +api.getUserAnonymized = function(req, res, next) { + res.locals.user.getTransformedData(function(err, user){ + user.stats.toNextLevel = shared.tnl(user.stats.lvl); + user.stats.maxHealth = shared.maxHealth; + user.stats.maxMP = res.locals.user._statsComputed.maxMP; + + delete user.apiToken; + + if (user.auth) { + delete user.auth.local; + delete user.auth.facebook; + } + + delete user.newMessages; + + delete user.profile; + delete user.purchased.plan; + delete user.contributor; + delete user.invitations; + + delete user.items.special.nyeReceived; + delete user.items.special.valentineReceived; + + delete user.webhooks; + delete user.achievements.challenges; + + _.forEach(user.inbox.messages, function(msg){ + msg.text = "inbox message text"; + }); + + _.forEach(user.tags, function(tag){ + tag.name = "tag"; + tag.challenge = "challenge"; + }); + + function cleanChecklist(task){ + var checklistIndex = 0; + + _.forEach(task.checklist, function(c){ + c.text = "item" + checklistIndex++; + }); + } + + _.forEach(user.habits, function(task){ + task.text = "task text"; + task.notes = "task notes"; + }); + + _.forEach(user.rewards, function(task){ + task.text = "task text"; + task.notes = "task notes"; + }); + + _.forEach(user.dailys, function(task){ + task.text = "task text"; + task.notes = "task notes"; + + cleanChecklist(task); + }); + + _.forEach(user.todos, function(task){ + task.text = "task text"; + task.notes = "task notes"; + + cleanChecklist(task); + }); + + return res.status(200).json(user); + }); +}; + +/** + * This tells us for which paths users can call `PUT /user` (or batch-update equiv, which use `User.set()` on our client). + * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) + * TODO - one-by-one we want to widdle down this list, instead replacing each needed set path with API operations + */ +acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (m, v, leaf) => { + let updatablePaths = 'achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '); + let found = _.find(updatablePaths, (rootPath) => { + return leaf.indexOf(rootPath) === 0; + }); + + if (found) m[leaf] = true; + + return m; +}, {}); + +restrictedPUTSubPaths = 'stats.class'.split(' '); + +_.each(restrictedPUTSubPaths, (removePath) => { + delete acceptablePUTPaths[removePath]; +}); + +let requiresPurchase = { + 'preferences.background': 'background', + 'preferences.shirt': 'shirt', + 'preferences.size': 'size', + 'preferences.skin': 'skin', + 'preferences.chair': 'chair', + 'preferences.hair.bangs': 'hair.bangs', + 'preferences.hair.base': 'hair.base', + 'preferences.hair.beard': 'hair.beard', + 'preferences.hair.color': 'hair.color', + 'preferences.hair.flower': 'hair.flower', + 'preferences.hair.mustache': 'hair.mustache', +}; + +let checkPreferencePurchase = (user, path, item) => { + let itemPath = `${path}.${item}`; + let appearance = _.get(shared.content.appearances, itemPath) + if (!appearance) return false; + if (appearance.price === 0) return true; + + return _.get(user.purchased, itemPath); +}; + +/** + * Update user + * Send up PUT /user as `req.body={path1:val, path2:val, etc}`. Example: + * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false} + * See acceptablePUTPaths for which user paths are supported +*/ +api.update = (req, res, next) => { + let user = res.locals.user; + let errors = []; + + if (_.isEmpty(req.body)) return res.status(200).json(user); + + _.each(req.body, (v, k) => { + let purchasable = requiresPurchase[k]; + + if (purchasable && !checkPreferencePurchase(user, purchasable, v)) { + return errors.push(`Must purchase ${v} to set it on ${k}`); + } + + if (acceptablePUTPaths[k]) { + user.fns.dotSet(k, v); + } else { + errors.push(shared.i18n.t('messageUserOperationProtected', { operation: k })); + } + return true; + }); + + user.save((err) => { + if (!_.isEmpty(errors)) return res.status(401).json({err: errors}); + if (err) { + if (err.name == 'ValidationError') { + let errorMessages = _.map(_.values(err.errors), (error) => { + return error.message; + }); + return res.status(400).json({err: errorMessages}); + } + return next(err); + } + + res.status(200).json(user); + user = errors = null; + }); +}; + +api.cron = require('../../middlewares/api-v3/cron'); + +// api.reroll // Shared.ops +// api.reset // Shared.ops + +api.delete = function(req, res, next) { + var user = res.locals.user; + var plan = user.purchased.plan; + + if (plan && plan.customerId && !plan.dateTerminated){ + return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."}); + } + + let types = ['party', 'guilds']; + let groupFields = basicGroupFields.concat(' leader memberCount'); + + Group.getGroups({user, types, groupFields}) + .then(groups => { + return Bluebird.all(groups.map((group) => { + return group.leave(user, 'remove-all'); + })); + }) + .then(() => { + return Tasks.Task.remove({ + userId: user._id, + }).exec(); + }) + .then(() => { + return user.remove(); + }) + .then(() => { + firebase.deleteUser(user._id); + res.sendStatus(200); + }) + .catch(next); +} + +/* + ------------------------------------------------------------------------ + Development Only Operations + ------------------------------------------------------------------------ + */ +if (nconf.get('NODE_ENV') === 'development') { + + api.addTenGems = function(req, res, next) { + var user = res.locals.user; + + user.balance += 2.5; + + user.save(function(err){ + if (err) return next(err); + res.sendStatus(204); + }); + }; + + api.addHourglass = function(req, res, next) { + var user = res.locals.user; + + user.purchased.plan.consecutive.trinkets += 1; + + user.save(function(err){ + if (err) return next(err); + res.sendStatus(204); + }); + }; +} + +/* + ------------------------------------------------------------------------ + Tags + ------------------------------------------------------------------------ + */ + +api.getTags = function (req, res, next) { + res.json(res.locals.user.tags.toObject().map(tag => { + return { + name: tag.name, + id: tag.id, + challenge: tag.challenge, + } + })); +}; + +api.getTag = function (req, res, next) { + let tag = _.find(res.locals.user.tags, {id: req.params.id}); + if (!tag) { + return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); + } + + res.json({ + name: tag.name, + id: tag.id, + challenge: tag.challenge, + }); +}; + +api.addTag = function (req, res, next) { + let user = res.locals.user; + + user.tags.push(Tag.sanitize(req.body)); + user.save(function (err, user) { + if (err) return next(err); + + res.json(user.tags.toObject().map(tag => { + return { + name: tag.name, + id: tag.id, + challenge: tag.challenge, + } + })); + }); +}; + +api.updateTag = function (req, res, next) { + let user = res.locals.user; + + let tag = _.find(res.locals.user.tags, {id: req.params.id}); + if (!tag) { + return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); + } + + tag.name = req.body.name; + user.save(function (err, user) { + if (err) return next(err); + + res.json({ + name: tag.name, + id: tag.id, + challenge: tag.challenge, + }); + }); +} + +api.sortTag = function (req, res, next) { + var ref = req.query; + var to = ref.to; + var from = ref.from; + let user = res.locals.user; + + if (!((to != null) && (from != null))) { + return res.statu(500).json('?to=__&from=__ are required'); + } + + user.tags.splice(to, 0, user.tags.splice(from, 1)[0]); + user.save(function (err, user) { + if (err) return next(err); + + res.json(user.tags.toObject().map(tag => { + return { + name: tag.name, + id: tag.id, + challenge: tag.challenge, + } + })); + }); +} + +api.deleteTag = function (req, res, next) { + let user = res.locals.user; + + let tag = removeFromArray(user.tags, { id: req.params.id }); + if (!tag) { + return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); + } + + + Tasks.Task.update({ + userId: user._id, + }, { + $pull: { + tags: tag.id, + }, + }, {multi: true}).exec(); + + user.save(function (err, user) { + if (err) return next(err); + + res.json(user.tags.toObject().map(tag => { + return { + name: tag.name, + id: tag.id, + challenge: tag.challenge, + } + })); + }); +} + +/* + ------------------------------------------------------------------------ + Spells + ------------------------------------------------------------------------ + */ +api.cast = async function(req, res, next) { + try { + let user = res.locals.user; + let spellId = req.params.spell; + let targetId = req.query.targetId; + + if (spellId === 'heallAll') { + spellId = 'healAll'; + } else if (spellId === 'spookDust') { + spellId = 'spookySparkles'; + } + + let klass = shared.content.spells.special[spellId] ? 'special' : user.stats.class; + let spell = shared.content.spells[klass][spellId]; + + if (!spell) return res.status(404).json({err: 'Spell "' + req.params.spell + '" not found.'}); + if (spell.mana > user.stats.mp) return res.status(400).json({err: 'Not enough mana to cast spell'}); + + let targetType = spell.target; + + if (targetType === 'task') { + let task = await Tasks.Task.findOne({ + _id: targetId, + userId: user._id, + }).exec(); + if (!task) { + return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); + } + + spell.cast(user, task, req); + await task.save(); + } else if (targetType === 'self') { + spell.cast(user, null, req); + await user.save(); + } else if (targetType === 'tasks') { // new target type when all the user's tasks are necessary + let tasks = await Tasks.Task.find({ + userId: user._id, + 'challenge.id': {$exists: false}, // exclude challenge tasks + $or: [ // Exclude completed todos + {type: 'todo', completed: false}, + {type: {$in: ['habit', 'daily', 'reward']}}, + ], + }).exec(); + + spell.cast(user, tasks, req); + + let toSave = tasks.filter(t => t.isModified()); + let isUserModified = user.isModified(); + toSave.unshift(user.save()); + let saved = await Bluebird.all(toSave); + } else if (targetType === 'party' || targetType === 'user') { + let party = await Group.getGroup({groupId: 'party', user}); + // arrays of users when targetType is 'party' otherwise single users + let partyMembers; + + if (targetType === 'party') { + if (!party) { + partyMembers = [user]; // Act as solo party + } else { + partyMembers = await User.find({'party._id': party._id}).select(partyMembersFields).exec(); + } + + spell.cast(user, partyMembers, req); + await Bluebird.all(partyMembers.map(m => m.save())); + } else { + if (!party && (!targetId || user._id === targetId)) { + partyMembers = user; + } else { + partyMembers = await User.findOne({_id: targetId, 'party._id': party._id}).select(partyMembersFields).exec(); + } + + if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); + spell.cast(user, partyMembers, req); + if (partyMembers === user) { + await partyMembers.save(); + } else { + await Bluebird.all([ + await partyMembers.save(), + await user.save(), + ]); + } + } + + if (party && !spell.silent) { + let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; + party.sendChat(message); + await party.save(); + } + } + + user.getTransformedData(function (err, transformedUser) { + if (err) next(err); + res.json(transformedUser); + }); + } catch (e) { + return res.status(500).json({err: 'An error happened'}); + } +} + +// It supports guild too now but we'll stick to partyInvite for backward compatibility +api.sessionPartyInvite = function(req,res,next){ + if (!req.session.partyInvite) return next(); + var inv = res.locals.user.invitations; + if (inv.party && inv.party.id) return next(); // already invited to a party + asyncM.waterfall([ + function(cb){ + Group.findOne({_id:req.session.partyInvite.id, members:{$in:[req.session.partyInvite.inviter]}}) + .select('invites members type').exec(cb); + }, + function(group, cb){ + if (!group){ + // Don't send error as it will prevent users from using the site + delete req.session.partyInvite; + return cb(); + } + + if (group.type == 'guild'){ + inv.guilds.push(req.session.partyInvite); + } else{ + //req.body.type in 'guild', 'party' + inv.party = req.session.partyInvite; + } + inv.party = req.session.partyInvite; + delete req.session.partyInvite; + if (!~group.invites.indexOf(res.locals.user._id)) + group.invites.push(res.locals.user._id); //$addToSt + group.save(cb); + }, + function(saved, cb){ + res.locals.user.save(cb); + } + ], next); +} + +api.clearCompleted = function(req, res, next) { + var user = res.locals.user; + + Tasks.Task.remove({ + userId: user._id, + type: 'todo', + completed: true, + 'challenge.id': {$exists: false}, + }, function (err) { + if (err) return next(err); + + Tasks.Task.find({ + userId: user._id, + type: 'todo', + completed: false, + }, function (err, uncompleted) { + if (err) return next(err); + res.json(uncompleted); + }); + }); +}; + +api.sortTask = async function (req, res, next) { + try { + let user = res.locals.user; + let to = Number(req.query.to); + + let task = await Tasks.Task.findOne({ + _id: req.params.id, + userId: user._id, + }).exec(); + + if (!task) return res.status(404).json(i18n.t('messageTaskNotFound', req.language)); + if (task.type !== 'todo' || !task.completed) { + let order = user.tasksOrder[`${task.type}s`]; + let currentIndex = order.indexOf(task._id); + + // If for some reason the task isn't ordered (should never happen), push it in the new position + // if the task is moved to a non existing position + // or if the task is moved to position -1 (push to bottom) + // -> push task at end of list + if (!order[to] && to !== -1) { + order.push(task._id); + } else { + if (currentIndex !== -1) order.splice(currentIndex, 1); + if (to === -1) { + order.push(task._id); + } else { + order.splice(to, 0, task._id); + } + } + await user.save(); + } + + user.getTasks(function (err, userTasks) { + if(err) return next(err); + res.json(userTasks); + }); + } catch (e) { + res.status(500).json({err: 'An error happened.'}); + } +} + +api.deleteTask = function(req, res, next) { + var user = res.locals.user; + if(!req.params || !req.params.id) return res.json(404, shared.i18n.t('messageTaskNotFound', req.language)); + + var id = req.params.id; + // Try removing from all orders since we don't know the task's type + var removeTaskFromOrder = function(array) { + removeFromArray(array, id); + }; + + ['habits', 'dailys', 'todos', 'rewards'].forEach(function (type){ + removeTaskFromOrder(user.tasksOrder[type]) + }); + + asyncM.parallel({ + user: user.save.bind(user), + task: function(cb) { + Tasks.Task.remove({_id: id, userId: user._id}, cb); + } + }, function(err, results) { + if(err) return next(err); + + if(results.task.result.n < 1){ + return res.status(404).json({err: shared.i18n.t('messageTaskNotFound', req.language)}) + } + + res.status(200).json({}); + }); +}; + +api.updateTask = function(req, res, next) { + var user = res.locals.user, + id = req.params.id; + + req.body = Tasks.Task.fromJSONV2(req.body); + + findTaskByIdOrLegacyId(user, id, function (err, task) { + if(err) return next(err); + if(!task) return res.status(404).json({err: 'Task not found.'}) + + try { + _.assign(task, shared.ops.updateTask(task.toObject(), req)[0]); + task.save(function(err, task){ + if(err) return next(err); + + return res.json(task.toJSONV2()); + }); + } catch (err) { + return res.status(err.code).json({err: err.message}); + } + }); +}; + +api.addTask = function(req, res, next) { + var user = res.locals.user; + req.body.type = req.body.type || 'habit'; + req.body.text = req.body.text || 'text'; + req.body = Tasks.Task.fromJSONV2(req.body); + + var task = new Tasks[req.body.type](Tasks.Task.sanitize(req.body)); + + task.userId = user._id; + user.tasksOrder[task.type + 's'].unshift(task._id); + + // Validate that the task is valid and throw if it isn't + // otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality + let validationErrors = task.validateSync(); + if (validationErrors) return next(validationErrors); + + Bluebird.all([ + user.save(), + task.save({validateBeforeSave: false}) // already done ^ + ]).then(results => { + res.status(200).json(results[1].toJSONV2()); + }).catch(next); +}; + +/** + * All other user.ops which can easily be mapped to common/script/index.js, not requiring custom API-wrapping + */ +_.each(shared.ops, function(op,k){ + var kv3; + + if (['rebirth', 'reroll', 'reset'].indexOf(k) !== -1) { // proxy ops that change tasks directly to v3 + if (k === 'rebirth') kv3 = 'userRebirth'; // the name is different in v3 + if (k === 'reroll') kv3 = 'userReroll'; + if (k === 'reset') kv3 = 'userReset'; + + api[k] = function (req, res, next) { + req.v2 = true; + v3UserController[kv3].handler(req, res, next).catch(next); + } + } else if (!api[k]) { + api[k] = function(req, res, next) { + var opResponse; + try { + req.v2 = true; // Used to indicate to the shared code that the old response data should be returned + opResponse = shared.ops[k](res.locals.user, req, analytics); + if (Array.isArray(opResponse) && opResponse.length < 3) { + opResponse = opResponse[0]; + } + } catch (err) { + if (!err.code) return next(err); + if (err.code >= 400) return res.status(err.code).json({err:err.message}); + } + + // If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"} + res.locals.user.save(function(err){ + if (err) return next(err); + if (opResponse === res.locals.user) { // add tasks + res.locals.user.getTransformedData(function (err, transformedUser) { + if (err) return next(err); + res.status(200).json(transformedUser); + }); + } else { + res.status(200).json(opResponse); + } + }); + } + } +}) + +/* + ------------------------------------------------------------------------ + Batch Update + Run a bunch of updates all at once + ------------------------------------------------------------------------ +*/ +api.batchUpdate = function(req, res, next) { + if (_.isEmpty(req.body)) req.body = []; // cases of {} or null + if (req.body[0] && req.body[0].data) + return res.status(501).json({err: "API has been updated, please refresh your browser or upgrade your mobile app."}) + + var user = res.locals.user; + var oldSend = res.send; + var oldJson = res.json; + + // Stash user.save, we'll queue the save op till the end (so we don't overload the server) + //var oldSave = user.save; + //user.save = function(cb){cb(null,user)} + + // Setup the array of functions we're going to call in parallel with async + res.locals.ops = []; + var ops = _.transform(req.body, function(m,_req){ + if (_.isEmpty(_req)) return; + _req.language = req.language; + + m.push(function() { + var cb = arguments[arguments.length-1]; + res.locals.ops.push(_req); + res.send = res.json = function(code, data) { + if (_.isNumber(code) && code >= 500) + return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data))); + return cb(); + }; + if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); } + + api[_req.op](_req, res, cb); + }); + }) + // Finally, save user at the end + .concat(/*function(){ + user.save = oldSave; + user.save(arguments[arguments.length-1]); + }*/); + + // call all the operations, then return the user object to the requester + asyncM.waterfall(ops, function(err) { + res.json = oldJson; + res.send = oldSend; + if (err) return next(err); + + var response; + + // return only drops & streaks + if (user._tmp && user._tmp.drop){ + response = user.toJSON(); + res.status(200).json({_tmp: {drop: response._tmp.drop}, _v: response._v}); + + // Fetch full user object + } else if (res.locals.wasModified){ + // Preen 3-day past-completed To-Dos from Angular & mobile app + user.getTransformedData(function(err, transformedData){ + if (err) next(err); + response = transformedData; + + response.todos = shared.preenTodos(response.todos); + response.wasModified = true; + res.status(200).json(response); + }); + // return only the version number + } else{ + response = user.toJSON(); + res.status(200).json({_v: response._v}); + } + + //user.fns.nullify(); + user = res.locals.user = oldSend = oldJson = null; + }); +}; + +function _generateWebhookTaskData(task, direction, delta, stats, user) { + var extendedStats = _.extend(stats, { + toNextLevel: shared.tnl(user.stats.lvl), + maxHealth: shared.maxHealth, + maxMP: user._statsComputed.maxMP + }); + + var userData = { + _id: user._id, + _tmp: user._tmp, + stats: extendedStats + }; + + var taskData = { + details: task, + direction: direction, + delta: delta + } + + return { + task: taskData, + user: userData + } +} diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js new file mode 100644 index 0000000000..f8eabf4c82 --- /dev/null +++ b/website/server/controllers/api-v3/auth.js @@ -0,0 +1,512 @@ +import validator from 'validator'; +import moment from 'moment'; +import passport from 'passport'; +import nconf from 'nconf'; +import { + authWithHeaders, +} from '../../middlewares/api-v3/auth'; +import { + NotAuthorized, + BadRequest, + NotFound, +} from '../../libs/api-v3/errors'; +import Bluebird from 'bluebird'; +import * as passwordUtils from '../../libs/api-v3/password'; +import logger from '../../libs/api-v3/logger'; +import { model as User } from '../../models/user'; +import { model as Group } from '../../models/group'; +import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; +import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; +import { decrypt } from '../../libs/api-v3/encryption'; +import FirebaseTokenGenerator from 'firebase-token-generator'; +import { send as sendEmail } from '../../libs/api-v3/email'; + +let api = {}; + +// When the user signed up after having been invited to a group, invite them automatically to the group +async function _handleGroupInvitation (user, invite) { + // wrapping the code in a try because we don't want it to prevent the user from signing up + // that's why errors are not translated + try { + let {sentAt, id: groupId, inviter} = JSON.parse(decrypt(invite)); + + // check that the invite has not expired (after 7 days) + if (sentAt && moment().subtract(7, 'days').isAfter(sentAt)) { + let err = new Error('Invite expired.'); + err.privateData = invite; + throw err; + } + + let group = await Group.getGroup({user, optionalMembership: true, groupId, fields: 'name type'}); + if (!group) throw new NotFound('Group not found.'); + + if (group.type === 'party') { + user.invitations.party = {id: group._id, name: group.name, inviter}; + } else { + user.invitations.guilds.push({id: group._id, name: group.name, inviter}); + } + } catch (err) { + logger.error(err); + } +} + +/** + * @api {post} /api/v3/user/auth/local/register Register + * @apiDescription Register a new user with email, username and password or attach local auth to a social user + * @apiVersion 3.0.0 + * @apiName UserRegisterLocal + * @apiGroup User + * + * @apiParam {String} username Body parameter - Username of the new user + * @apiParam {String} email Body parameter - Email address of the new user + * @apiParam {String} password Body parameter - Password for the new user + * @apiParam {String} confirmPassword Body parameter - Password confirmation + * + * @apiSuccess {Object} data The user object, if local auth was just attached to a social user then only user.auth.local + */ +api.registerLocal = { + method: 'POST', + middlewares: [authWithHeaders(true)], + url: '/user/auth/local/register', + async handler (req, res) { + let fbUser = res.locals.user; // If adding local auth to social user + + req.checkBody({ + email: { + notEmpty: {errorMessage: res.t('missingEmail')}, + isEmail: {errorMessage: res.t('notAnEmail')}, + }, + username: {notEmpty: {errorMessage: res.t('missingUsername')}}, + password: { + notEmpty: {errorMessage: res.t('missingPassword')}, + equals: {options: [req.body.confirmPassword], errorMessage: res.t('passwordConfirmationMatch')}, + }, + }); + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let { email, username, password } = req.body; + + // Get the lowercase version of username to check that we do not have duplicates + // So we can search for it in the database and then reject the choosen username if 1 or more results are found + email = email.toLowerCase(); + let lowerCaseUsername = username.toLowerCase(); + + // Search for duplicates using lowercase version of username + let user = await User.findOne({$or: [ + {'auth.local.email': email}, + {'auth.local.lowerCaseUsername': lowerCaseUsername}, + ]}, {'auth.local': 1}).exec(); + + if (user) { + if (email === user.auth.local.email) throw new NotAuthorized(res.t('emailTaken')); + // Check that the lowercase username isn't already used + if (lowerCaseUsername === user.auth.local.lowerCaseUsername) throw new NotAuthorized(res.t('usernameTaken')); + } + + let salt = passwordUtils.makeSalt(); + let hashed_password = passwordUtils.encrypt(password, salt); // eslint-disable-line camelcase + let newUser = { + auth: { + local: { + username, + lowerCaseUsername, + email, + salt, + hashed_password, // eslint-disable-line camelcase + }, + }, + preferences: { + language: req.language, + }, + }; + + if (fbUser) { + if (!fbUser.auth.facebook.id) throw new NotAuthorized(res.t('onlySocialAttachLocal')); + fbUser.auth.local = newUser.auth.local; + newUser = fbUser; + } else { + newUser = new User(newUser); + newUser.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used + } + + // we check for partyInvite for backward compatibility + if (req.query.groupInvite || req.query.partyInvite) { + await _handleGroupInvitation(newUser, req.query.groupInvite || req.query.partyInvite); + } + + let savedUser = await newUser.save(); + + if (savedUser.auth.facebook.id) { + res.respond(200, savedUser.toJSON().auth.local); // We convert to toJSON to hide private fields + } else { + res.respond(201, savedUser); + } + + // Clean previous email preferences and send welcome email + EmailUnsubscription + .remove({email: savedUser.auth.local.email}) + .then(() => sendTxnEmail(savedUser, 'welcome')); + + if (!savedUser.auth.facebook.id) { + res.analytics.track('register', { + category: 'acquisition', + type: 'local', + gaLabel: 'local', + uuid: savedUser._id, + }); + } + + return null; + }, +}; + +function _loginRes (user, req, res) { + if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id})); + return res.respond(200, {id: user._id, apiToken: user.apiToken}); +} + +/** + * @api {post} /api/v3/user/auth/local/login Login + * @apiDescription Login a user with email / username and password + * @apiVersion 3.0.0 + * @apiName UserLoginLocal + * @apiGroup User + * + * @apiParam {String} username Body parameter - Username or email of the user + * @apiParam {String} password Body parameter - The user's password + * + * @apiSuccess {String} data._id The user's unique identifier + * @apiSuccess {String} data.apiToken The user's api token that must be used to authenticate requests. + */ +api.loginLocal = { + method: 'POST', + url: '/user/auth/local/login', + middlewares: [], + async handler (req, res) { + req.checkBody({ + username: { + notEmpty: true, + errorMessage: res.t('missingUsernameEmail'), + }, + password: { + notEmpty: true, + errorMessage: res.t('missingPassword'), + }, + }); + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + req.sanitizeBody('username').trim(); + req.sanitizeBody('password').trim(); + + let login; + let username = req.body.username; + + if (validator.isEmail(username)) { + login = {'auth.local.email': username.toLowerCase()}; // Emails are stored lowercase + } else { + login = {'auth.local.username': username}; + } + + let user = await User.findOne(login, {auth: 1, apiToken: 1}).exec(); + let isValidPassword = user && user.auth.local.hashed_password === passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (!isValidPassword) throw new NotAuthorized(res.t('invalidLoginCredentialsLong')); + return _loginRes(user, ...arguments); + }, +}; + +function _passportFbProfile (accessToken) { + return new Bluebird((resolve, reject) => { + passport._strategies.facebook.userProfile(accessToken, (err, profile) => { + if (err) { + reject(err); + } else { + resolve(profile); + } + }); + }); +} + +// Called as a callback by Facebook (or other social providers). Internal route +api.loginSocial = { + method: 'POST', + url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2 + async handler (req, res) { + let accessToken = req.body.authResponse.access_token; + let network = req.body.network; + + if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported')); + + let profile = await _passportFbProfile(accessToken); + + let user = await User.findOne({ + [`auth.${network}.id`]: profile.id, + }, {_id: 1, apiToken: 1, auth: 1}).exec(); + + // User already signed up + if (user) { + _loginRes(user, ...arguments); + } else { // Create new user + user = new User({ + auth: { + [network]: profile, + }, + preferences: { + language: req.language, + }, + }); + user.registeredThrough = req.headers['x-client']; + + let savedUser = await user.save(); + + _loginRes(user, ...arguments); + + // Clean previous email preferences + if (savedUser.auth[network].emails && savedUser.auth.facebook.emails[0] && savedUser.auth[network].emails[0].value) { + EmailUnsubscription + .remove({email: savedUser.auth[network].emails[0].value.toLowerCase()}) + .exec() + .then(() => sendTxnEmail(savedUser, 'welcome')); // eslint-disable-line max-nested-callbacks + } + + res.analytics.track('register', { + category: 'acquisition', + type: network, + gaLabel: network, + uuid: savedUser._id, + }); + + return null; + } + }, +}; + +/** + * @api {put} /api/v3/user/auth/update-username Update username + * @apiDescription Update the username of a local user + * @apiVersion 3.0.0 + * @apiName UpdateUsername + * @apiGroup User + * + * @apiParam {string} password Body parameter - The current user password + * @apiParam {string} username Body parameter - The new username + + * @apiSuccess {String} data.username The new username + **/ +api.updateUsername = { + method: 'PUT', + middlewares: [authWithHeaders()], + url: '/user/auth/update-username', + async handler (req, res) { + let user = res.locals.user; + + req.checkBody({ + password: { + notEmpty: {errorMessage: res.t('missingPassword')}, + }, + username: { + notEmpty: { errorMessage: res.t('missingUsername') }, + }, + }); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + if (!user.auth.local.username) throw new BadRequest(res.t('userHasNoLocalRegistration')); + + let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); + + let count = await User.count({ 'auth.local.lowerCaseUsername': req.body.username.toLowerCase() }); + if (count > 0) throw new BadRequest(res.t('usernameTaken')); + + // save username + user.auth.local.lowerCaseUsername = req.body.username.toLowerCase(); + user.auth.local.username = req.body.username; + await user.save(); + + res.respond(200, { username: req.body.username }); + }, +}; + +/** + * @api {put} /api/v3/user/auth/update-password + * @apiDescription Update the password of a local user + * @apiVersion 3.0.0 + * @apiName UpdatePassword + * @apiGroup User + * + * @apiParam {string} password Body parameter - The old password + * @apiParam {string} newPassword Body parameter - The new password + * @apiParam {string} confirmPassword Body parameter - New password confirmation + * + * @apiSuccess {Object} data An empty object + **/ +api.updatePassword = { + method: 'PUT', + middlewares: [authWithHeaders()], + url: '/user/auth/update-password', + async handler (req, res) { + let user = res.locals.user; + + if (!user.auth.local.hashed_password) throw new BadRequest(res.t('userHasNoLocalRegistration')); + + let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); + + req.checkBody({ + password: { + notEmpty: {errorMessage: res.t('missingNewPassword')}, + }, + newPassword: { + notEmpty: {errorMessage: res.t('missingPassword')}, + }, + }); + + if (req.body.newPassword !== req.body.confirmPassword) throw new NotAuthorized(res.t('passwordConfirmationMatch')); + + user.auth.local.hashed_password = passwordUtils.encrypt(req.body.newPassword, user.auth.local.salt); // eslint-disable-line camelcase + await user.save(); + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/user/reset-password Reset password + * @apiDescription Reset the user password + * @apiVersion 3.0.0 + * @apiName ResetPassword + * @apiGroup User + * + * @apiParam {string} email Body parameter - The email address of the user + * + * @apiSuccess {string} message The localized success message + **/ +api.resetPassword = { + method: 'POST', + middlewares: [], + url: '/user/reset-password', + async handler (req, res) { + req.checkBody({ + email: { + notEmpty: {errorMessage: res.t('missingEmail')}, + }, + }); + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let email = req.body.email.toLowerCase(); + let salt = passwordUtils.makeSalt(); + let newPassword = passwordUtils.makeSalt(); // use a salt as the new password too (they'll change it later) + let hashedPassword = passwordUtils.encrypt(newPassword, salt); + + let user = await User.findOne({ 'auth.local.email': email }, { 'auth.local': 1 }); + + if (user) { + user.auth.local.salt = salt; + user.auth.local.hashed_password = hashedPassword; // eslint-disable-line camelcase + sendEmail({ + from: 'Habitica ', + to: email, + subject: res.t('passwordResetEmailSubject'), + text: res.t('passwordResetEmailText', { username: user.auth.local.username, + newPassword, + baseUrl: nconf.get('BASE_URL'), + }), + html: res.t('passwordResetEmailHtml', { username: user.auth.local.username, + newPassword, + baseUrl: nconf.get('BASE_URL'), + }), + }); + await user.save(); + } + res.respond(200, {}, res.t('passwordReset')); + }, +}; + +/** + * @api {put} /api/v3/user/auth/update-email Update email + * @apiDescription Change the user email address + * @apiVersion 3.0.0 + * @apiName UpdateEmail + * @apiGroup User + * + * @apiParam {string} Body parameter - newEmail The new email address. + * @apiParam {string} Body parameter - password The user password. + * + * @apiSuccess {string} data.email The updated email address + */ +api.updateEmail = { + method: 'PUT', + middlewares: [authWithHeaders()], + url: '/user/auth/update-email', + async handler (req, res) { + let user = res.locals.user; + + if (!user.auth.local.email) throw new BadRequest(res.t('userHasNoLocalRegistration')); + + req.checkBody('newEmail', res.t('newEmailRequired')).notEmpty().isEmail(); + req.checkBody('password', res.t('missingPassword')).notEmpty(); + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let candidatePassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (candidatePassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); + + user.auth.local.email = req.body.newEmail; + await user.save(); + + return res.respond(200, { email: user.auth.local.email }); + }, +}; + +const firebaseTokenGenerator = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); + +// Internal route +api.getFirebaseToken = { + method: 'POST', + url: '/user/auth/firebase', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + // Expires 24 hours from now (60*60*24*1000) (in milliseconds) + let expires = new Date(); + expires.setTime(expires.getTime() + 86400000); + + let token = firebaseTokenGenerator.createToken({ + uid: user._id, + isHabiticaUser: true, + }, { expires }); + + res.respond(200, {token, expires}); + }, +}; + +/** + * @api {delete} /api/v3/user/auth/social/:network Delete social authentication method + * @apiDescription Remove a social authentication method (only facebook supported) from a user profile. The user must have local authentication enabled + * @apiVersion 3.0.0 + * @apiName UserDeleteSocial + * @apiGroup User + * + * @apiSuccess {Object} data Empty object + */ +api.deleteSocial = { + method: 'DELETE', + url: '/user/auth/social/:network', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let network = req.params.network; + + if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported')); + if (!user.auth.local.username) throw new NotAuthorized(res.t('cantDetachFb')); + + await User.update({_id: user._id}, {$unset: {'auth.facebook': 1}}).exec(); + + res.respond(200, {}); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js new file mode 100644 index 0000000000..6d7bc56667 --- /dev/null +++ b/website/server/controllers/api-v3/challenges.js @@ -0,0 +1,518 @@ +import { authWithHeaders, authWithSession } from '../../middlewares/api-v3/auth'; +import _ from 'lodash'; +import { model as Challenge } from '../../models/challenge'; +import { + model as Group, + basicFields as basicGroupFields, + TAVERN_ID, +} from '../../models/group'; +import { + model as User, + nameFields, +} from '../../models/user'; +import { + NotFound, + NotAuthorized, +} from '../../libs/api-v3/errors'; +import * as Tasks from '../../models/task'; +import Bluebird from 'bluebird'; +import csvStringify from '../../libs/api-v3/csvStringify'; + +let api = {}; + +/** + * @api {post} /api/v3/challenges Create a new challenge + * @apiVersion 3.0.0 + * @apiName CreateChallenge + * @apiGroup Challenge + * + * @apiSuccess {object} data The newly created challenge + */ +api.createChallenge = { + method: 'POST', + url: '/challenges', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkBody('group', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let groupId = req.body.group; + let prize = req.body.prize; + + let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); + if (!group) throw new NotFound(res.t('groupNotFound')); + if (!group.isMember(user)) throw new NotAuthorized(res.t('mustBeGroupMember')); + + if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { + throw new NotAuthorized(res.t('onlyGroupLeaderChal')); + } + + if (group._id === TAVERN_ID && prize < 1) { + throw new NotAuthorized(res.t('tavChalsMinPrize')); + } + + if (prize > 0) { + let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; + let prizeCost = prize / 4; + + if (prizeCost > user.balance + groupBalance) { + throw new NotAuthorized(res.t('cantAfford')); + } + + if (groupBalance >= prizeCost) { + // Group pays for all of prize + group.balance -= prizeCost; + } else if (groupBalance > 0) { + // User pays remainder of prize cost after group + let remainder = prizeCost - group.balance; + group.balance = 0; + user.balance -= remainder; + } else { + // User pays for all of prize + user.balance -= prizeCost; + } + } + + group.challengeCount += 1; + + req.body.leader = user._id; + req.body.official = user.contributor.admin && req.body.official ? true : false; + let challenge = new Challenge(Challenge.sanitize(req.body)); + + // First validate challenge so we don't save group if it's invalid (only runs sync validators) + let challengeValidationErrors = challenge.validateSync(); + if (challengeValidationErrors) throw challengeValidationErrors; + + let results = await Bluebird.all([challenge.save({ + validateBeforeSave: false, // already validate + }), group.save()]); + let savedChal = results[0]; + + await savedChal.syncToUser(user); // (it also saves the user) + + let response = savedChal.toJSON(); + response.leader = { // the leader is the authenticated user + _id: user._id, + profile: {name: user.profile.name}, + }; + response.group = { // we already have the group data + _id: group._id, + name: group.name, + type: group.type, + privacy: group.privacy, + }; + + res.respond(201, response); + }, +}; + +/** + * @api {post} /api/v3/challenges/:challengeId/join Joins a challenge + * @apiVersion 3.0.0 + * @apiName JoinChallenge + * @apiGroup Challenge + * @apiParam {UUID} challengeId The challenge _id + * + * @apiSuccess {object} data The challenge the user joined + */ +api.joinChallenge = { + method: 'POST', + url: '/challenges/:challengeId/join', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let challenge = await Challenge.findOne({ _id: req.params.challengeId }); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge')); + + let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); + if (!group || !challenge.hasAccess(user, group)) throw new NotFound(res.t('challengeNotFound')); + + challenge.memberCount += 1; + + // Add all challenge's tasks to user's tasks and save the challenge + let results = await Bluebird.all([challenge.syncToUser(user), challenge.save()]); + + let response = results[1].toJSON(); + response.group = { // we already have the group data + _id: group._id, + name: group.name, + type: group.type, + privacy: group.privacy, + }; + let chalLeader = await User.findById(response.leader).select(nameFields).exec(); + response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; + + res.respond(200, response); + }, +}; + +/** + * @api {post} /api/v3/challenges/:challengeId/leave Leaves a challenge + * @apiVersion 3.0.0 + * @apiName LeaveChallenge + * @apiGroup Challenge + * @apiParam {UUID} challengeId The challenge _id + * + * @apiSuccess {object} data An empty object + */ +api.leaveChallenge = { + method: 'POST', + url: '/challenges/:challengeId/leave', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let keep = req.body.keep === 'remove-all' ? 'remove-all' : 'keep-all'; + + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let challenge = await Challenge.findOne({ _id: req.params.challengeId }); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + + let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + + if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound')); + + challenge.memberCount -= 1; + + // Unlink challenge's tasks from user's tasks and save the challenge + await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]); + res.respond(200, {}); + }, +}; + +/** + * @api {get} /api/v3/challenges/user Get challenges for a user + * @apiVersion 3.0.0 + * @apiName GetUserChallenges + * @apiGroup Challenge + * + * @apiSuccess {Array} data An array of challenges + */ +api.getUserChallenges = { + method: 'GET', + url: '/challenges/user', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + let challenges = await Challenge.find({ + $or: [ + {_id: {$in: user.challenges}}, // Challenges where the user is participating + {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member + {leader: user._id}, // Challenges where I'm the leader + ], + _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit + }) + .sort('-official -timestamp') + // see below why we're not using populate + // .populate('group', basicGroupFields) + // .populate('leader', nameFields) + .exec(); + + let resChals = challenges.map(challenge => challenge.toJSON()); + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + await Bluebird.all(resChals.map((chal, index) => { + return Bluebird.all([ + User.findById(chal.leader).select(nameFields).exec(), + Group.findById(chal.group).select(basicGroupFields).exec(), + ]).then(populatedData => { + resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null; + resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null; + }); + })); + + res.respond(200, resChals); + }, +}; + +/** + * @api {get} /api/v3/challenges/group/group:Id Get challenges for a group + * @apiDescription Get challenges that the user is a member, public challenges and the ones from the user's groups. + * @apiVersion 3.0.0 + * @apiName GetGroupChallenges + * @apiGroup Challenge + * + * @apiParam {groupId} groupId The group _id + * + * @apiSuccess {Array} data An array of challenges + */ +api.getGroupChallenges = { + method: 'GET', + url: '/challenges/groups/:groupId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + let challenges = await Challenge.find({group: groupId}) + .sort('-official -timestamp') + // .populate('leader', nameFields) // Only populate the leader as the group is implicit + .exec(); + + let resChals = challenges.map(challenge => challenge.toJSON()); + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + await Bluebird.all(resChals.map((chal, index) => { + return User.findById(chal.leader).select(nameFields).exec().then(populatedLeader => { + resChals[index].leader = populatedLeader ? populatedLeader.toJSON({minimize: true}) : null; + }); + })); + + res.respond(200, resChals); + }, +}; + +/** + * @api {get} /api/v3/challenges/:challengeId Get a challenge given its id + * @apiVersion 3.0.0 + * @apiName GetChallenge + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The challenge _id + * + * @apiSuccess {object} data The challenge object + */ +api.getChallenge = { + method: 'GET', + url: '/challenges/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + + let challenge = await Challenge.findById(challengeId) + // Don't populate the group as we'll fetch it manually later + // .populate('leader', nameFields) + .exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + + // Fetching basic group data + let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + + let chalRes = challenge.toJSON(); + chalRes.group = group.toJSON({minimize: true}); + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + let chalLeader = await User.findById(chalRes.leader).select(nameFields).exec(); + chalRes.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; + + res.respond(200, chalRes); + }, +}; + +/** + * @api {get} /api/v3/challenges/:challengeId/export/csv Export a challenge in CSV + * @apiVersion 3.0.0 + * @apiName ExportChallengeCsv + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The challenge _id + * + * @apiSuccess {string} challenge A csv file + */ +api.exportChallengeCsv = { + method: 'GET', + url: '/challenges/:challengeId/export/csv', + middlewares: [authWithSession], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + + let challenge = await Challenge.findById(challengeId).select('_id group leader tasksOrder').exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + + // In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all + // results on the server so the perf difference isn't that big (hopefully) + + let [members, tasks] = await Bluebird.all([ + User.find({challenges: challengeId}) + .select(nameFields) + .sort({_id: 1}) + .lean() // so we don't involve mongoose + .exec(), + + Tasks.Task.find({'challenge.id': challengeId, userId: {$exists: true}}) + .sort({userId: 1, text: 1}).select('userId type text value notes').lean().exec(), + ]); + + let resArray = members.map(member => [member._id, member.profile.name]); + + // We assume every user in the challenge as at least some data so we can say that members[0] tasks will be at tasks [0] + let lastUserId; + let index = -1; + tasks.forEach(task => { + if (task.userId !== lastUserId) { + lastUserId = task.userId; + index++; + } + + resArray[index].push(`${task.type}:${task.text}`, task.value, task.notes); + }); + + // The first row is going to be UUID name Task Value Notes repeated n times for the n challenge tasks + let challengeTasks = _.reduce(challenge.tasksOrder.toObject(), (result, array) => { + return result.concat(array); + }, []).sort(); + resArray.unshift(['UUID', 'name']); + _.times(challengeTasks.length, () => resArray[0].push('Task', 'Value', 'Notes')); + + res.set({ + 'Content-Type': 'text/csv', + 'Content-disposition': `attachment; filename=${challengeId}.csv`, + }); + + let csvRes = await csvStringify(resArray); + res.status(200).send(csvRes); + }, +}; + +/** + * @api {put} /api/v3/challenges/:challengeId Update a challenge + * @apiVersion 3.0.0 + * @apiName UpdateChallenge + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The challenge _id + * + * @apiSuccess {object} data The updated challenge + */ +api.updateChallenge = { + method: 'PUT', + url: '/challenges/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + + let challenge = await Challenge.findById(challengeId).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + + let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderUpdateChal')); + + _.merge(challenge, Challenge.sanitizeUpdate(req.body)); + + let savedChal = await challenge.save(); + let response = savedChal.toJSON(); + response.group = { // we already have the group data + _id: group._id, + name: group.name, + type: group.type, + privacy: group.privacy, + }; + let chalLeader = await User.findById(response.leader).select(nameFields).exec(); + response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; + res.respond(200, response); + }, +}; + +/** + * @api {delete} /api/v3/challenges/:challengeId Delete a challenge + * @apiVersion 3.0.0 + * @apiName DeleteChallenge + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The _id for the challenge to delete + * + * @apiSuccess {object} data An empty object + */ +api.deleteChallenge = { + method: 'DELETE', + url: '/challenges/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + + // Close channel in background, some ops are run in the background without `await`ing + await challenge.closeChal({broken: 'CHALLENGE_DELETED'}); + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/challenges/:challengeId/selectWinner/:winnerId Select winner for challenge + * @apiVersion 3.0.0 + * @apiName SelectChallengeWinner + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The _id for the challenge to close with a winner + * @apiParam {UUID} winnerId The _id of the winning user + * + * @apiSuccess {object} data An empty object + */ +api.selectChallengeWinner = { + method: 'POST', + url: '/challenges/:challengeId/selectWinner/:winnerId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + req.checkParams('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + + let winner = await User.findOne({_id: req.params.winnerId}).exec(); + if (!winner || winner.challenges.indexOf(challenge._id) === -1) throw new NotFound(res.t('winnerNotFound', {userId: req.params.winnerId})); + + // Close channel in background, some ops are run in the background without `await`ing + await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner}); + res.respond(200, {}); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js new file mode 100644 index 0000000000..7738189b6f --- /dev/null +++ b/website/server/controllers/api-v3/chat.js @@ -0,0 +1,398 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { + model as Group, + TAVERN_ID, +} from '../../models/group'; +import { model as User } from '../../models/user'; +import { + NotFound, + NotAuthorized, +} from '../../libs/api-v3/errors'; +import _ from 'lodash'; +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; +import { sendTxn } from '../../libs/api-v3/email'; +import nconf from 'nconf'; +import Bluebird from 'bluebird'; + +const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => { + return { email, canSend: true }; +}); + +let api = {}; + +/** + * @api {get} /api/v3/groups/:groupId/chat Get chat messages from a group + * @apiVersion 3.0.0 + * @apiName GetChat + * @apiGroup Chat + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Array} data An array of chat messages + */ +api.getChat = { + method: 'GET', + url: '/groups/:groupId/chat', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'chat'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + res.respond(200, Group.toJSONCleanChat(group, user).chat); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/chat Post chat message to a group + * @apiVersion 3.0.0 + * @apiName PostCat + * @apiGroup Chat + * + * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {message} Body parameter - message The message to post + * @apiParam {previousMsg} previousMsg Query parameter - The previous chat message which will force a return of the full group chat + * + * @apiSuccess data An array of chat messages if a new message was posted after previousMsg, otherwise the posted message + */ +api.postChat = { + method: 'POST', + url: '/groups/:groupId/chat', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + let chatUpdated; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkBody('message', res.t('messageGroupChatBlankMessage')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId}); + + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party' && user.flags.chatRevoked) { + throw new NotFound('Your chat privileges have been revoked.'); + } + + let lastClientMsg = req.query.previousMsg; + chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; + + group.sendChat(req.body.message, user); + + let toSave = [group.save()]; + + if (group.type === 'party') { + user.party.lastMessageSeen = group.chat[0].id; + toSave.push(user.save()); + } + + let [savedGroup] = await Bluebird.all(toSave); + if (chatUpdated) { + res.respond(200, {chat: Group.toJSONCleanChat(savedGroup, user).chat}); + } else { + res.respond(200, {message: savedGroup.chat[0]}); + } + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/chat/:chatId/like Like a group chat message + * @apiVersion 3.0.0 + * @apiName LikeChat + * @apiGroup Chat + * + * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {chatId} chatId The chat message _id + * + * @apiSuccess {Object} data The liked chat message + */ +api.likeChat = { + method: 'POST', + url: '/groups/:groupId/chat/:chatId/like', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + let message = _.find(group.chat, {id: req.params.chatId}); + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatLikeOwnMessage')); + + let update = {$set: {}}; + + if (!message.likes) message.likes = {}; + + message.likes[user._id] = !message.likes[user._id]; + update.$set[`chat.$.likes.${user._id}`] = message.likes[user._id]; + + await Group.update( + {_id: group._id, 'chat.id': message.id}, + update + ); + res.respond(200, message); // TODO what if the message is flagged and shouldn't be returned? + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/chat/:chatId/like Like a group chat message + * @apiVersion 3.0.0 + * @apiName LikeChat + * @apiGroup Chat + * + * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {chatId} chatId The chat message id + * + * @apiSuccess {object} data The flagged chat message + */ +api.flagChat = { + method: 'POST', + url: '/groups/:groupId/chat/:chatId/flag', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId}); + if (!group) throw new NotFound(res.t('groupNotFound')); + let message = _.find(group.chat, {id: req.params.chatId}); + + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + + if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatFlagOwnMessage')); + + let author = await User.findOne({_id: message.uuid}, {auth: 1}); + + let update = {$set: {}}; + + // Log user ids that have flagged the message + if (!message.flags) message.flags = {}; + if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); + message.flags[user._id] = true; + update.$set[`chat.$.flags.${user._id}`] = true; + + // Log total number of flags (publicly viewable) + if (!message.flagCount) message.flagCount = 0; + if (user.contributor.admin) { + // Arbitraty amount, higher than 2 + message.flagCount = 5; + } else { + message.flagCount++; + } + update.$set['chat.$.flagCount'] = message.flagCount; + + await Group.update( + {_id: group._id, 'chat.id': message.id}, + update + ); + + let reporterEmailContent; + if (user.auth.local) { + reporterEmailContent = user.auth.local.email; + } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) { + reporterEmailContent = user.auth.facebook.emails[0].value; + } + + let authorEmailContent; + if (author.auth.local) { + authorEmailContent = author.auth.local.email; + } else if (author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) { + authorEmailContent = author.auth.facebook.emails[0].value; + } + + let groupUrl; + if (group._id === TAVERN_ID) { + groupUrl = '/#/options/groups/tavern'; + } else if (group.type === 'guild') { + groupUrl = `/#/options/groups/guilds/${group._id}`; + } else { + groupUrl = 'party'; + } + + sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', [ + {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, + {name: 'MESSAGE_TEXT', content: message.text}, + + {name: 'REPORTER_USERNAME', content: user.profile.name}, + {name: 'REPORTER_UUID', content: user._id}, + {name: 'REPORTER_EMAIL', content: reporterEmailContent}, + {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId=${user._id}`}, + + {name: 'AUTHOR_USERNAME', content: message.user}, + {name: 'AUTHOR_UUID', content: message.uuid}, + {name: 'AUTHOR_EMAIL', content: authorEmailContent}, + {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId=${message.uuid}`}, + + {name: 'GROUP_NAME', content: group.name}, + {name: 'GROUP_TYPE', content: group.type}, + {name: 'GROUP_ID', content: group._id}, + {name: 'GROUP_URL', content: groupUrl}, + ]); + + res.respond(200, message); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/chat/:chatId/clear-flags Clear a group chat message's flags + * @apiDescription Admin-only + * @apiVersion 3.0.0 + * @apiName ClearFlags + * @apiGroup Chat + * + * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {chatId} chatId The chat message id + * + * @apiSuccess {Object} data An empty object + */ +api.clearChatFlags = { + method: 'Post', + url: '/groups/:groupId/chat/:chatId/clearflags', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + let chatId = req.params.chatId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + if (!user.contributor.admin) { + throw new NotAuthorized(res.t('messageGroupChatAdminClearFlagCount')); + } + + let group = await Group.getGroup({user, groupId}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + let message = _.find(group.chat, {id: chatId}); + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + + message.flagCount = 0; + + await Group.update( + {_id: group._id, 'chat.id': message.id}, + {$set: {'chat.$.flagCount': message.flagCount}} + ); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/chat/:chatId/seen Seen a group chat message + * @apiVersion 3.0.0 + * @apiName SeenChat + * @apiGroup Chat + * + * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Object} data An empty object + */ +api.seenChat = { + method: 'POST', + url: '/groups/:groupId/chat/seen', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + // Do not validate group existence, it doesn't really matter and make it works if the group gets deleted + // let group = await Group.getGroup({user, groupId}); + // if (!group) throw new NotFound(res.t('groupNotFound')); + + let update = {$unset: {}}; + update.$unset[`newMessages.${groupId}`] = true; + + await User.update({_id: user._id}, update).exec(); + res.respond(200, {}); + }, +}; + +/** + * @api {delete} /api/v3/groups/:groupId/chat/:chatId Delete chat message from a group + * @apiVersion 3.0.0 + * @apiName DeleteChat + * @apiGroup Chat + * + * @apiParam {string} previousMsg Query parameter - The last message fetched by the client so that the whole chat will be returned only if new messages have been posted in the meantime + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {string} chatId The chat message id + * + * @apiSuccess data The updated chat array or an empty object if no message was posted after previousMsg + * @apiSuccess {Object} data An empty object when the previous message was deleted + */ +api.deleteChat = { + method: 'DELETE', + url: '/groups/:groupId/chat/:chatId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + let chatId = req.params.chatId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId, fields: 'chat'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + let message = _.find(group.chat, {id: chatId}); + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + + if (user._id !== message.uuid && !user.contributor.admin) { + throw new NotAuthorized(res.t('onlyCreatorOrAdminCanDeleteChat')); + } + + let lastClientMsg = req.query.previousMsg; + let chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; + + await Group.update( + {_id: group._id}, + {$pull: {chat: {id: chatId}}} + ); + + if (chatUpdated) { + let chatRes = Group.toJSONCleanChat(group, user).chat; + removeFromArray(chatRes, {id: chatId}); + res.respond(200, chatRes); + } else { + res.respond(200, {}); + } + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js new file mode 100644 index 0000000000..0235f61645 --- /dev/null +++ b/website/server/controllers/api-v3/content.js @@ -0,0 +1,110 @@ +import common from '../../../../common'; +import _ from 'lodash'; +import { langCodes } from '../../libs/api-v3/i18n'; +import Bluebird from 'bluebird'; +import fsCallback from 'fs'; +import path from 'path'; +import logger from '../../libs/api-v3/logger'; + +// Transform fs methods that accept callbacks in ones that return promises +const fs = { + readFile: Bluebird.promisify(fsCallback.readFile, {context: fsCallback}), + writeFile: Bluebird.promisify(fsCallback.writeFile, {context: fsCallback}), + stat: Bluebird.promisify(fsCallback.stat, {context: fsCallback}), + mkdir: Bluebird.promisify(fsCallback.mkdir, {context: fsCallback}), +}; + +let api = {}; + +function walkContent (obj, lang) { + _.each(obj, (item, key, source) => { + if (_.isPlainObject(item) || _.isArray(item)) return walkContent(item, lang); + if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); + }); +} + +// After the getContent route is called the first time for a certain language +// the response is saved on disk and subsequentially served directly from there to reduce computation. +// Example: if `cachedContentResponses.en` is true it means that the response is cached +let cachedContentResponses = {}; + +// Language key set to true while the cache file is being written +let cacheBeingWritten = {}; + +_.each(langCodes, code => { + cachedContentResponses[code] = false; + cacheBeingWritten[code] = false; +}); + + +const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../build/content_cache/'); + +async function saveContentToDisk (language, content) { + try { + cacheBeingWritten[language] = true; + + await fs.stat(CONTENT_CACHE_PATH); // check if the directory exists, if it doesn't an error is thrown + await fs.writeFile(`${CONTENT_CACHE_PATH}${language}.json`, content, 'utf8'); + + cacheBeingWritten[language] = false; + cachedContentResponses[language] = true; + } catch (err) { + if (err.code === 'ENOENT' && err.syscall === 'stat') { // the directory doesn't exists, create it and retry + await fs.mkdir(CONTENT_CACHE_PATH); + return saveContentToDisk(language, content); + } else { + cacheBeingWritten[language] = false; + logger.error(err); + return; + } + } +} + +/** + * @api {get} /api/v3/content Get all available content objects + * @apiDescription Does not require authentication. + * @apiVersion 3.0.0 + * @apiName ContentGet + * @apiGroup Content + * + * @apiParam {string} language Query parameter, the language code used for the items' strings. Defaulting to english + * + * @apiSuccess {Object} data All the content available on Habitica + */ +api.getContent = { + method: 'GET', + url: '/content', + async handler (req, res) { + let language = 'en'; + let proposedLang = req.query.language && req.query.language.toString(); + + if (proposedLang in cachedContentResponses) { + language = proposedLang; + } + + let content; + + // is the content response for this language cached? + if (cachedContentResponses[language] === true) { + content = await fs.readFile(`${CONTENT_CACHE_PATH}${language}.json`, 'utf8'); + } else { // generate the response + content = _.cloneDeep(common.content); + walkContent(content, language); + content = JSON.stringify(content); + } + + res.set({ + 'Content-Type': 'application/json', + }); + + let jsonResString = `{"success": true, "data": ${content}}`; + res.status(200).send(jsonResString); + + // save the file in background unless it's already cached or being written right now + if (cachedContentResponses[language] !== true && cacheBeingWritten[language] !== true) { + saveContentToDisk(language, content); + } + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/coupon.js b/website/server/controllers/api-v3/coupon.js new file mode 100644 index 0000000000..4dc434aa8c --- /dev/null +++ b/website/server/controllers/api-v3/coupon.js @@ -0,0 +1,126 @@ +import csvStringify from '../../libs/api-v3/csvStringify'; +import { + authWithHeaders, + authWithSession, +} from '../../middlewares/api-v3/auth'; +import { ensureSudo } from '../../middlewares/api-v3/ensureAccessRight'; +import { model as Coupon } from '../../models/coupon'; +import _ from 'lodash'; +import couponCode from 'coupon-code'; + +let api = {}; + +/** + * @api {get} /api/v3/coupons Get coupons + * @apiDescription Sudo users only + * @apiVersion 3.0.0 + * @apiName GetCoupons + * @apiGroup Coupon + * + * @apiSuccess {string} Coupons in CSV format + */ +api.getCoupons = { + method: 'GET', + url: '/coupons', + middlewares: [authWithSession, ensureSudo], + async handler (req, res) { + let coupons = await Coupon.find().sort('createdAt').lean().exec(); + + let output = [['code', 'event', 'date', 'user']].concat(_.map(coupons, coupon => { + return [coupon._id, coupon.event, coupon.createdAt, coupon.user]; + })); + let csv = await csvStringify(output); + + res.set({ + 'Content-Type': 'text/csv', + 'Content-disposition': 'attachment; filename=habitica-coupons.csv', + }); + res.status(200).send(csv); + }, +}; + +/** + * @api {post} /api/v3/coupons/generate/:event Generate coupons for an event + * @apiDescription Sudo users only + * @apiVersion 3.0.0 + * @apiName GenerateCoupons + * @apiGroup Coupon + * + * @apiParam {string} event The event for which the coupon should be generated + * @apiParam {number} count Query parameter to specify the number of coupon codes to generate + * + * @apiSuccess {array} data Generated coupons + */ +api.generateCoupons = { + method: 'POST', + url: '/coupons/generate/:event', + middlewares: [authWithHeaders(), ensureSudo], + async handler (req, res) { + req.checkParams('event', res.t('eventRequired')).notEmpty(); + req.checkQuery('count', res.t('countRequired')).notEmpty().isNumeric(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let coupons = await Coupon.generate(req.params.event, req.query.count); + res.respond(200, coupons); + }, +}; + +/** + * @api {post} /api/v3/user/coupon/:code Enter coupon code + * @apiVersion 3.0.0 + * @apiName EnterCouponCode + * @apiGroup Coupon + * + * @apiParam {string} code The coupon code to apply + * + * @apiSuccess {object} data User object + */ +api.enterCouponCode = { + method: 'POST', + url: '/coupons/enter/:code', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('code', res.t('couponCodeRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + await Coupon.apply(user, req, req.params.code); + res.respond(200, user); + }, +}; + +/** + * @api {post} /api/v3/coupons/validate/:code Validate a coupon code + * @apiVersion 3.0.0 + * @apiName ValidateCoupon + * @apiGroup Coupon + * + * @apiSuccess {boolean} data.valid True or false + */ +api.validateCoupon = { + method: 'POST', + url: '/coupons/validate/:code', + middlewares: [authWithHeaders(true)], + async handler (req, res) { + req.checkParams('code', res.t('couponCodeRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let valid = false; + let code = couponCode.validate(req.params.code); + if (code) { + let coupon = await Coupon.findOne({_id: code}).exec(); + valid = coupon ? true : false; + } + + res.respond(200, {valid}); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js new file mode 100644 index 0000000000..9949421513 --- /dev/null +++ b/website/server/controllers/api-v3/debug.js @@ -0,0 +1,190 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import ensureDevelpmentMode from '../../middlewares/api-v3/ensureDevelpmentMode'; +import { BadRequest } from '../../libs/api-v3/errors'; +import { content } from '../../../../common'; +import _ from 'lodash'; + +let api = {}; + +/** + * @api {post} /api/v3/debug/add-ten-gems Add ten gems to the current user + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName AddTenGems + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +api.addTenGems = { + method: 'POST', + url: '/debug/add-ten-gems', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + user.balance += 2.5; + + await user.save(); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/debug/add-hourglass Add Hourglass to the current user + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName AddHourglass + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +api.addHourglass = { + method: 'POST', + url: '/debug/add-hourglass', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + user.purchased.plan.consecutive.trinkets += 1; + + await user.save(); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/debug/set-cron Sets lastCron for user + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName setCron + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +api.setCron = { + method: 'POST', + url: '/debug/set-cron', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let cron = req.body.lastCron; + + user.lastCron = cron; + + await user.save(); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/debug/make-admin Sets contributor.admin to true + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName setCron + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +// TODO: Re-enable after v3 prod testing is done +// api.makeAdmin = { +// method: 'POST', +// url: '/debug/make-admin', +// middlewares: [ensureDevelpmentMode, authWithHeaders()], +// async handler (req, res) { +// let user = res.locals.user; +// +// user.contributor.admin = true; +// +// await user.save(); +// +// res.respond(200, {}); +// }, +// }; + +/** + * @api {post} /api/v3/debug/modify-inventory Manipulate user's inventory + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName modifyInventory + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +api.modifyInventory = { + method: 'POST', + url: '/debug/modify-inventory', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let { gear } = req.body; + + if (gear) { + user.items.gear.owned = gear; + } + + [ + 'special', + 'pets', + 'mounts', + 'eggs', + 'hatchingPotions', + 'food', + 'quests', + ].forEach((type) => { + if (req.body[type]) { + user.items[type] = req.body[type]; + } + }); + + await user.save(); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/debug/quest-progress Artificially accelerate quest progress + * @apiDescription Only available in development mode. + * @apiVersion 3.0.0 + * @apiName questProgress + * @apiGroup Development + * + * @apiSuccess {Object} data An empty Object + */ +api.questProgress = { + method: 'POST', + url: '/debug/quest-progress', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let key = _.get(user, 'party.quest.key'); + let quest = content.quests[key]; + + if (!quest) { + throw new BadRequest('User is not on a valid quest.'); + } + + if (quest.boss) { + user.party.quest.progress.up += 1000; + } + + if (quest.collect) { + let collect = user.party.quest.progress.collect; + _.each(quest.collect, (details, item) => { + collect[item] = collect[item] || 0; + collect[item] += 300; + }); + } + + user.markModified('party.quest.progress'); + + await user.save(); + + res.respond(200, {}); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js new file mode 100644 index 0000000000..07e9b5be54 --- /dev/null +++ b/website/server/controllers/api-v3/groups.js @@ -0,0 +1,670 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import { + INVITES_LIMIT, + model as Group, + basicFields as basicGroupFields, +} from '../../models/group'; +import { + model as User, + nameFields, +} from '../../models/user'; +import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; +import { + NotFound, + BadRequest, + NotAuthorized, +} from '../../libs/api-v3/errors'; +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; +import * as firebase from '../../libs/api-v3/firebase'; +import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; +import { encrypt } from '../../libs/api-v3/encryption'; +import common from '../../../../common'; +import sendPushNotification from '../../libs/api-v3/pushNotifications'; +let api = {}; + +/** + * @api {post} /api/v3/groups Create group + * @apiVersion 3.0.0 + * @apiName CreateGroup + * @apiGroup Group + * + * @apiSuccess {Object} data The create group + */ +api.createGroup = { + method: 'POST', + url: '/groups', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let group = new Group(Group.sanitize(req.body)); + group.leader = user._id; + + if (group.type === 'guild') { + if (user.balance < 1) throw new NotAuthorized(res.t('messageInsufficientGems')); + + group.balance = 1; + + user.balance--; + user.guilds.push(group._id); + } else { + if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate')); + if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty')); + + user.party._id = group._id; + } + + let results = await Bluebird.all([user.save(), group.save()]); + let savedGroup = results[1]; + + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + // await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise + let response = savedGroup.toJSON(); + // the leader is the authenticated user + response.leader = { + _id: user._id, + profile: {name: user.profile.name}, + }; + res.respond(201, response); // do not remove chat flags data as we've just created the group + + firebase.updateGroupData(savedGroup); + firebase.addUserToGroup(savedGroup._id, user._id); + }, +}; + +/** + * @api {get} /api/v3/groups Get groups for a user + * @apiVersion 3.0.0 + * @apiName GetGroups + * @apiGroup Group + * + * @apiParam {string} type The type of groups to retrieve. Must be a query string representing a list of values like 'tavern,party'. Possible values are party, guilds, privateGuilds, publicGuilds, tavern + * + * @apiSuccess {Array} data An array of the requested groups + */ +api.getGroups = { + method: 'GET', + url: '/groups', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkQuery('type', res.t('groupTypesRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let types = req.query.type.split(','); + let groupFields = basicGroupFields.concat(' description memberCount balance'); + let sort = '-memberCount'; + + let results = await Group.getGroups({user, types, groupFields, sort}); + res.respond(200, results); + }, +}; + +/** + * @api {get} /api/v3/groups/:groupId Get group + * @apiVersion 3.0.0 + * @apiName GetGroup + * @apiGroup Group + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Object} data The group object + */ +api.getGroup = { + method: 'GET', + url: '/groups/:groupId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, populateLeader: false}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + group = Group.toJSONCleanChat(group, user); + // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 + let leader = await User.findById(group.leader).select(nameFields).exec(); + if (leader) group.leader = leader.toJSON({minimize: true}); + + res.respond(200, group); + }, +}; + +/** + * @api {put} /api/v3/groups/:groupId Update group + * @apiVersion 3.0.0 + * @apiName UpdateGroup + * @apiGroup Group + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Object} data The updated group + */ +api.updateGroup = { + method: 'PUT', + url: '/groups/:groupId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + if (group.leader !== user._id) throw new NotAuthorized(res.t('messageGroupOnlyLeaderCanUpdate')); + + _.assign(group, _.merge(group.toObject(), Group.sanitizeUpdate(req.body))); + + let savedGroup = await group.save(); + let response = Group.toJSONCleanChat(savedGroup, user); + // If the leader changed fetch new data, otherwise use authenticated user + if (response.leader !== user._id) { + response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true}); + } else { + response.leader = { + _id: user._id, + profile: {name: user.profile.name}, + }; + } + res.respond(200, response); + + firebase.updateGroupData(savedGroup); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/join Join a group + * @apiVersion 3.0.0 + * @apiName JoinGroup + * @apiGroup Group + * + * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Object} data The joined group + */ +api.joinGroup = { + method: 'POST', + url: '/groups/:groupId/join', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let inviter; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party' + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + // Works even if the user is not yet a member of the group + let group = await Group.getGroup({user, groupId: req.params.groupId, optionalMembership: true}); // Do not fetch chat and work even if the user is not yet a member of the group + if (!group) throw new NotFound(res.t('groupNotFound')); + + let isUserInvited = false; + + if (group.type === 'party' && group._id === user.invitations.party.id) { + inviter = user.invitations.party.inviter; + user.invitations.party = {}; // Clear invite + user.markModified('invitations.party'); + + // invite new user to pending quest + if (group.quest.key && !group.quest.active) { + user.party.quest.RSVPNeeded = true; + user.party.quest.key = group.quest.key; + group.quest.members[user._id] = null; + group.markModified('quest.members'); + } + + // If user was in a different party (when partying solo you can be invited to a new party) + // make him leave that party before doing anything + if (user.party._id) { + let userPreviousParty = await Group.getGroup({user, groupId: user.party._id}); + if (userPreviousParty) await userPreviousParty.leave(user); + } + + user.party._id = group._id; // Set group as user's party + + isUserInvited = true; + } else if (group.type === 'guild') { + let hasInvitation = removeFromArray(user.invitations.guilds, { id: group._id }); + + if (hasInvitation) { + isUserInvited = true; + } else { + isUserInvited = group.privacy === 'private' ? false : true; + } + } + + if (isUserInvited && group.type === 'guild') { + if (user.guilds.indexOf(group._id) !== -1) { // if user is already a member (party is checked previously) + throw new NotAuthorized(res.t('userAlreadyInGroup')); + } + user.guilds.push(group._id); // Add group to user's guilds + } + if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); + + if (group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader + + group.memberCount += 1; + + let promises = [group.save(), user.save()]; + + if (group.type === 'party' && inviter) { + promises.push(User.update({_id: inviter}, {$inc: {'items.quests.basilist': 1}}).exec()); // Reward inviter + if (group.memberCount > 1) { + promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyUp': {$ne: true}}, {$set: {'achievements.partyUp': true}}, {multi: true}).exec()); + } + if (group.memberCount > 3) { + promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyOn': {$ne: true}}, {$set: {'achievements.partyOn': true}}, {multi: true}).exec()); + } + } + + promises = await Bluebird.all(promises); + + let response = Group.toJSONCleanChat(promises[0], user); + let leader = await User.findById(response.leader).select(nameFields).exec(); + if (leader) { + response.leader = leader.toJSON({minimize: true}); + } + res.respond(200, response); + + firebase.addUserToGroup(group._id, user._id); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/reject Reject a group invitation + * @apiVersion 3.0.0 + * @apiName RejectGroupInvite + * @apiGroup Group + * + * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiSuccess {Object} data An empty object + */ +api.rejectGroupInvite = { + method: 'POST', + url: '/groups/:groupId/reject-invite', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party' + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let groupId = req.params.groupId; + let isUserInvited = false; + + if (groupId === user.invitations.party.id) { + user.invitations.party = {}; + user.markModified('invitations.party'); + isUserInvited = true; + } else { + let hasInvitation = removeFromArray(user.invitations.guilds, { id: groupId }); + + if (hasInvitation) { + isUserInvited = true; + } + } + + if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); + + await user.save(); + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/leave Leave a group + * @apiVersion 3.0.0 + * @apiName LeaveGroup + * @apiGroup Group + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {string="remove-all","keep-all"} keep Query parameter - Whether to keep or not challenges' tasks. Defaults to keep-all + * + * @apiSuccess {Object} data An empty object + */ +api.leaveGroup = { + method: 'POST', + url: '/groups/:groupId/leave', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + // When removing the user from challenges, should we keep the tasks? + req.checkQuery('keep', res.t('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat', requireMembership: true}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + // During quests, checke wheter user can leave + if (group.type === 'party') { + if (group.quest && group.quest.leader === user._id) { + throw new NotAuthorized(res.t('questLeaderCannotLeaveGroup')); + } + + if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { + throw new NotAuthorized(res.t('cannotLeaveWhileActiveQuest')); + } + } + + await group.leave(user, req.query.keep); + res.respond(200, {}); + }, +}; + +// Send an email to the removed user with an optional message from the leader +function _sendMessageToRemoved (group, removedUser, message) { + if (removedUser.preferences.emailNotifications.kickedGroup !== false) { + sendTxnEmail(removedUser, `kicked-from-${group.type}`, [ + {name: 'GROUP_NAME', content: group.name}, + {name: 'MESSAGE', content: message}, + {name: 'GUILDS_LINK', content: '/#/options/groups/guilds/public'}, + {name: 'PARTY_WANTED_GUILD', content: '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'}, + ]); + } +} + +/** + * @api {post} /api/v3/groups/:groupId/removeMember/:memberId Remove a member from a group + * @apiVersion 3.0.0 + * @apiName RemoveGroupMember + * @apiGroup Group + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * @apiParam {UUID} memberId The _id of the member to remove + * @apiParam {string} message Query parameter - The message to send to the removed members + * + * @apiSuccess {Object} data An empty object + */ +api.removeGroupMember = { + method: 'POST', + url: '/groups/:groupId/removeMember/:memberId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('memberId', res.t('userIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat'}); // Do not fetch chat + if (!group) throw new NotFound(res.t('groupNotFound')); + + let uuid = req.params.memberId; + + if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCanRemoveMember')); + if (user._id === uuid) throw new NotAuthorized(res.t('memberCannotRemoveYourself')); + + let member = await User.findOne({_id: uuid}).exec(); + + // We're removing the user from a guild or a party? is the user invited only? + let isInGroup; + if (member.party._id === group._id) { + isInGroup = 'party'; + } else if (member.guilds.indexOf(group._id) !== -1) { + isInGroup = 'guild'; + } + + let isInvited; + if (member.invitations.party && member.invitations.party.id === group._id) { + isInvited = 'party'; + } else if (_.findIndex(member.invitations.guilds, {id: group._id}) !== -1) { + isInvited = 'guild'; + } + + if (isInGroup) { + group.memberCount -= 1; + + if (group.quest && group.quest.leader === member._id) { + group.quest.key = undefined; + group.quest.leader = undefined; + } else if (group.quest && group.quest.members) { + // remove member from quest + group.quest.members[member._id] = undefined; + group.markModified('quest.members'); + } + + if (isInGroup === 'guild') { + removeFromArray(member.guilds, group._id); + } + if (isInGroup === 'party') member.party._id = undefined; // TODO remove quest information too? Use group.leave()? + + if (member.newMessages[group._id]) { + member.newMessages[group._id] = undefined; + member.markModified('newMessages'); + } + + if (group.quest && group.quest.active && group.quest.leader === member._id) { + member.items.quests[group.quest.key] += 1; + } + } else if (isInvited) { + if (isInvited === 'guild') { + removeFromArray(member.invitations.guilds, { id: group._id }); + } + if (isInvited === 'party') { + user.invitations.party = {}; + user.markModified('invitations.party'); + } + } else { + throw new NotFound(res.t('groupMemberNotFound')); + } + + let message = req.query.message; + if (message) _sendMessageToRemoved(group, member, message); + + await Bluebird.all([ + member.save(), + group.save(), + ]); + res.respond(200, {}); + }, +}; + +async function _inviteByUUID (uuid, group, inviter, req, res) { + let userToInvite = await User.findById(uuid).exec(); + + if (!userToInvite) { + throw new NotFound(res.t('userWithIDNotFound', {userId: uuid})); + } + + if (group.type === 'guild') { + if (_.contains(userToInvite.guilds, group._id)) { + throw new NotAuthorized(res.t('userAlreadyInGroup')); + } + if (_.find(userToInvite.invitations.guilds, {id: group._id})) { + throw new NotAuthorized(res.t('userAlreadyInvitedToGroup')); + } + userToInvite.invitations.guilds.push({id: group._id, name: group.name, inviter: inviter._id}); + } else if (group.type === 'party') { + if (userToInvite.invitations.party.id) { + throw new NotAuthorized(res.t('userAlreadyPendingInvitation')); + } + + if (userToInvite.party._id) { + let userParty = await Group.getGroup({user: userToInvite, groupId: 'party', fields: 'memberCount'}); + + // Allow user to be invited to a new party when they're partying solo + if (userParty.memberCount !== 1) throw new NotAuthorized(res.t('userAlreadyInAParty')); + } + + userToInvite.invitations.party = {id: group._id, name: group.name, inviter: inviter._id}; + } + + let groupLabel = group.type === 'guild' ? 'Guild' : 'Party'; + let groupTemplate = group.type === 'guild' ? 'guild' : 'party'; + if (userToInvite.preferences.emailNotifications[`invited${groupLabel}`] !== false) { + let emailVars = [ + {name: 'INVITER', content: inviter.profile.name}, + ]; + + if (group.type === 'guild') { + emailVars.push( + {name: 'GUILD_NAME', content: group.name}, + {name: 'GUILD_URL', content: '/#/options/groups/guilds/public'} + ); + } else { + emailVars.push( + {name: 'PARTY_NAME', content: group.name}, + {name: 'PARTY_URL', content: '/#/options/groups/party'} + ); + } + + sendTxnEmail(userToInvite, `invited-${groupTemplate}`, emailVars); + } + + sendPushNotification( + userToInvite, + common.i18n.t(group.type === 'guild' ? 'invitedGuild' : 'invitedParty'), + group.name + ); + + let userInvited = await userToInvite.save(); + if (group.type === 'guild') { + return userInvited.invitations.guilds[userToInvite.invitations.guilds.length - 1]; + } else if (group.type === 'party') { + return userInvited.invitations.party; + } +} + +async function _inviteByEmail (invite, group, inviter, req, res) { + let userReturnInfo; + + if (!invite.email) throw new BadRequest(res.t('inviteMissingEmail')); + + let userToContact = await User.findOne({$or: [ + {'auth.local.email': invite.email}, + {'auth.facebook.emails.value': invite.email}, + ]}) + .select({_id: true, 'preferences.emailNotifications': true}) + .exec(); + + if (userToContact) { + userReturnInfo = await _inviteByUUID(userToContact._id, group, inviter, req, res); + } else { + userReturnInfo = invite.email; + const groupQueryString = JSON.stringify({ + id: group._id, + inviter: inviter._id, + sentAt: Date.now(), // so we can let it expire + }); + let link = `/static/front?groupInvite=${encrypt(groupQueryString)}`; + + let variables = [ + {name: 'LINK', content: link}, + {name: 'INVITER', content: req.body.inviter || inviter.profile.name}, + ]; + + if (group.type === 'guild') { + variables.push({name: 'GUILD_NAME', content: group.name}); + } + + // Check for the email address not to be unsubscribed + let userIsUnsubscribed = await EmailUnsubscription.findOne({email: invite.email}).exec(); + let groupLabel = group.type === 'guild' ? '-guild' : ''; + if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables); + } + + return userReturnInfo; +} + +/** + * @api {post} /api/v3/groups/:groupId/invite Invite users to a group using their UUIDs or email addresses + * @apiVersion 3.0.0 + * @apiName InviteToGroup + * @apiGroup Group + * + * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) + * + * @apiParam {array} emails Body parameter - An array of emails addresses to invite (optional) + * @apiParam {array} uuids Body parameter - An array of uuids to invite (optional) + * @apiParam {string} inviter Body parameter - The inviters' name (optional) + * + * @apiSuccess {array} data The invites + */ +api.inviteToGroup = { + method: 'POST', + url: '/groups/:groupId/invite', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + + let uuids = req.body.uuids; + let emails = req.body.emails; + + let uuidsIsArray = Array.isArray(uuids); + let emailsIsArray = Array.isArray(emails); + + if (!uuids && !emails) { + throw new BadRequest(res.t('canOnlyInviteEmailUuid')); + } + + let results = []; + let totalInvites = 0; + + if (uuids) { + if (!uuidsIsArray) { + throw new BadRequest(res.t('uuidsMustBeAnArray')); + } else { + totalInvites += uuids.length; + } + } + + if (emails) { + if (!emailsIsArray) { + throw new BadRequest(res.t('emailsMustBeAnArray')); + } else { + totalInvites += emails.length; + } + } + + if (totalInvites > INVITES_LIMIT) { + throw new BadRequest(res.t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT})); + } + + if (uuids) { + let uuidInvites = uuids.map((uuid) => _inviteByUUID(uuid, group, user, req, res)); + let uuidResults = await Bluebird.all(uuidInvites); + results.push(...uuidResults); + } + + if (emails) { + let emailInvites = emails.map((invite) => _inviteByEmail(invite, group, user, req, res)); + let emailResults = await Bluebird.all(emailInvites); + results.push(...emailResults); + } + + res.respond(200, results); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/hall.js b/website/server/controllers/api-v3/hall.js new file mode 100644 index 0000000000..077b1ef74b --- /dev/null +++ b/website/server/controllers/api-v3/hall.js @@ -0,0 +1,183 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { ensureAdmin } from '../../middlewares/api-v3/ensureAccessRight'; +import { model as User } from '../../models/user'; +import { + NotFound, +} from '../../libs/api-v3/errors'; +import _ from 'lodash'; + +let api = {}; + +/** + * @api {get} /api/v3/hall/patrons Get all patrons + * @apiDescription Only the first 50 patrons are returned. More can be accessed passing ?page=n + * @apiVersion 3.0.0 + * @apiName GetPatrons + * @apiGroup Hall + * + * @apiParam {Number} page Query Parameter - The result page. Default is 0 + * + * @apiSuccess {Array} data An array of patrons + */ +api.getPatrons = { + method: 'GET', + url: '/hall/patrons', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkQuery('page', res.t('pageMustBeNumber')).optional().isNumeric(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let page = req.query.page ? Number(req.query.page) : 0; + const perPage = 50; + + let patrons = await User + .find({ + 'backer.tier': {$gt: 0}, + }) + .select('contributor backer profile.name') + .sort('-backer.tier') + .skip(page * perPage) + .limit(perPage) + .lean() + .exec(); + + res.respond(200, patrons); + }, +}; + +/** + * @api {get} /api/v3/hall/heroes Get all Heroes + * @apiVersion 3.0.0 + * @apiName GetHeroes + * @apiGroup Hall + * + * @apiSuccess {Array} data An array of heroes + */ +api.getHeroes = { + method: 'GET', + url: '/hall/heroes', + middlewares: [authWithHeaders()], + async handler (req, res) { + let heroes = await User + .find({ + 'contributor.level': {$gt: 0}, + }) + .select('contributor backer profile.name') + .sort('-contributor.level') + .lean() + .exec(); + + res.respond(200, heroes); + }, +}; + +// Note, while the following routes are called getHero / updateHero +// they can be used by admins to get/update any user + +const heroAdminFields = 'contributor balance profile.name purchased items auth'; + +/** + * @api {get} /api/v3/hall/heroes/:heroId Get any user ("hero") given the UUID + * @apiDescription Must be an admin to make this request. + * @apiVersion 3.0.0 + * @apiName GetHero + * @apiGroup Hall + * + * @apiSuccess {Object} data The user object + */ +api.getHero = { + method: 'GET', + url: '/hall/heroes/:heroId', + middlewares: [authWithHeaders(), ensureAdmin], + async handler (req, res) { + let heroId = req.params.heroId; + + req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let hero = await User + .findById(heroId) + .select(heroAdminFields) + .exec(); + + if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId})); + let heroRes = hero.toJSON({minimize: true}); + // supply to the possible absence of hero.contributor + // if we didn't pass minimize: true it would have returned all fields as empty + if (!heroRes.contributor) heroRes.contributor = {}; + res.respond(200, heroRes); + }, +}; + +// e.g., tier 5 gives 4 gems. Tier 8 = moderator. Tier 9 = staff +const gemsPerTier = {1: 3, 2: 3, 3: 3, 4: 4, 5: 4, 6: 4, 7: 4, 8: 0, 9: 0}; + +/** + * @api {put} /api/v3/hall/heroes/:heroId Update any user ("hero") + * @apiDescription Must be an admin to make this request. + * @apiVersion 3.0.0 + * @apiName UpdateHero + * @apiGroup Hall + * + * @apiSuccess {Object} data The updated user object + */ +api.updateHero = { + method: 'PUT', + url: '/hall/heroes/:heroId', + middlewares: [authWithHeaders(), ensureAdmin], + async handler (req, res) { + let heroId = req.params.heroId; + let updateData = req.body; + + req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let hero = await User.findById(heroId).exec(); + if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId})); + + if (updateData.balance) hero.balance = updateData.balance; + + // give them gems if they got an higher level + let newTier = updateData.contributor && updateData.contributor.level; // tier = level in this context + let oldTier = hero.contributor && hero.contributor.level || 0; + if (newTier > oldTier) { + hero.flags.contributor = true; + let tierDiff = newTier - oldTier; // can be 2+ tier increases at once + while (tierDiff) { + hero.balance += gemsPerTier[newTier] / 4; // balance is in $ + tierDiff--; + newTier--; // give them gems for the next tier down if they weren't aready that tier + } + } + + if (updateData.contributor) _.assign(hero.contributor, updateData.contributor); + if (updateData.purchased && updateData.purchased.ads) hero.purchased.ads = updateData.purchased.ads; + + // give them the Dragon Hydra pet if they're above level 6 + if (hero.contributor.level >= 6) hero.items.pets['Dragon-Hydra'] = 5; + if (updateData.itemPath && updateData.itemVal && + updateData.itemPath.indexOf('items.') === 0 && + User.schema.paths[updateData.itemPath]) { + _.set(hero, updateData.itemPath, updateData.itemVal); // Sanitization at 5c30944 (deemed unnecessary) + } + + if (updateData.auth && _.isBoolean(updateData.auth.blocked)) hero.auth.blocked = updateData.auth.blocked; + + let savedHero = await hero.save(); + let heroJSON = savedHero.toJSON(); + let responseHero = {_id: heroJSON._id}; // only respond with important fields + heroAdminFields.split(' ').forEach(field => { + _.set(responseHero, field, _.get(heroJSON, field)); + }); + + res.respond(200, responseHero); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/iap.js b/website/server/controllers/api-v3/iap.js new file mode 100644 index 0000000000..daaef0cef8 --- /dev/null +++ b/website/server/controllers/api-v3/iap.js @@ -0,0 +1,4 @@ +// NOTE: this file is only used because the mobile apps expect IAP routes +// to be found at /api/v3/iap instead of /iap. + +module.exports = require('../top-level/payments/iap'); \ No newline at end of file diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js new file mode 100644 index 0000000000..37bc3c5d27 --- /dev/null +++ b/website/server/controllers/api-v3/members.js @@ -0,0 +1,364 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { + model as User, + publicFields as memberFields, + nameFields, +} from '../../models/user'; +import { model as Group } from '../../models/group'; +import { model as Challenge } from '../../models/challenge'; +import { + NotFound, + NotAuthorized, +} from '../../libs/api-v3/errors'; +import * as Tasks from '../../models/task'; +import { + getUserInfo, + sendTxn as sendTxnEmail, +} from '../../libs/api-v3/email'; +import Bluebird from 'bluebird'; +import sendPushNotification from '../../libs/api-v3/pushNotifications'; + +let api = {}; + +/** + * @api {get} /api/v3/members/:memberId Get a member profile + * @apiVersion 3.0.0 + * @apiName GetMember + * @apiGroup Member + * + * @apiParam {UUID} memberId The member's id + * + * @apiSuccess {object} data The member object + */ +api.getMember = { + method: 'GET', + url: '/members/:memberId', + middlewares: [], + async handler (req, res) { + req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let memberId = req.params.memberId; + + let member = await User + .findById(memberId) + .select(memberFields) + .exec(); + + if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); + + // manually call toJSON with minimize: true so empty paths aren't returned + res.respond(200, member.toJSON({minimize: true})); + }, +}; + +// Return a request handler for getMembersForGroup / getInvitesForGroup / getMembersForChallenge +// type is `invites` or `members` +function _getMembersForItem (type) { + if (['group-members', 'group-invites', 'challenge-members'].indexOf(type) === -1) { + throw new Error('Type must be one of "group-members", "group-invites", "challenge-members"'); + } + + return async function handleGetMembersForItem (req, res) { + if (type === 'challenge-members') { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + } else { + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + } + req.checkQuery('lastId').optional().notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let groupId = req.params.groupId; + let challengeId = req.params.challengeId; + let lastId = req.query.lastId; + let user = res.locals.user; + let challenge; + let group; + + if (type === 'challenge-members') { + challenge = await Challenge.findById(challengeId).select('_id type leader group').exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + + // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge + // for example if you've been booted from it, are the leader or a site admin + group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + } else { + group = await Group.getGroup({user, groupId, fields: '_id type'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + } + + let query = {}; + let fields = nameFields; + + if (type === 'challenge-members') { + query.challenges = challenge._id; + } else if (type === 'group-members') { + if (group.type === 'guild') { + query.guilds = group._id; + } else { + query['party._id'] = group._id; // group._id and not groupId because groupId could be === 'party' + + if (req.query.includeAllPublicFields === 'true') { + fields = memberFields; + } + } + } else if (type === 'group-invites') { + if (group.type === 'guild') { // eslint-disable-line no-lonely-if + query['invitations.guilds.id'] = group._id; + } else { + query['invitations.party.id'] = group._id; // group._id and not groupId because groupId could be === 'party' + } + } + + if (lastId) query._id = {$gt: lastId}; + + let members = await User + .find(query) + .sort({_id: 1}) + .limit(30) + .select(fields) + .exec(); + + // manually call toJSON with minimize: true so empty paths aren't returned + res.respond(200, members.map(member => member.toJSON({minimize: true}))); + }; +} + +/** + * @api {get} /api/v3/groups/:groupId/members Get members for a group + * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * @apiVersion 3.0.0 + * @apiName GetMembersForGroup + * @apiGroup Member + * + * @apiParam {UUID} groupId The group id + * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results + * @apiParam {boolean} includeAllPublicFields Query parameter available only when fetching a party. If === `true` then all public fields for members will be returned (liek when making a request for a single member) + * + * @apiSuccess {array} data An array of members, sorted by _id + */ +api.getMembersForGroup = { + method: 'GET', + url: '/groups/:groupId/members', + middlewares: [authWithHeaders()], + handler: _getMembersForItem('group-members'), +}; + +/** + * @api {get} /api/v3/groups/:groupId/invites Get invites for a group + * @apiDescription With a limit of 30 member per request. To get all invites run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * @apiVersion 3.0.0 + * @apiName GetInvitesForGroup + * @apiGroup Member + * + * @apiParam {UUID} groupId The group id + * @apiParam {UUID} lastId Query parameter to specify the last invite returned in a previous request to this route and get the next batch of results + * + * @apiSuccess {array} data An array of invites, sorted by _id + */ +api.getInvitesForGroup = { + method: 'GET', + url: '/groups/:groupId/invites', + middlewares: [authWithHeaders()], + handler: _getMembersForItem('group-invites'), +}; + +/** + * @api {get} /api/v3/challenges/:challengeId/members Get members for a challenge + * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * @apiVersion 3.0.0 + * @apiName GetMembersForChallenge + * @apiGroup Member + * + * @apiParam {UUID} challengeId The challenge id + * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results + * + * @apiSuccess {array} data An array of members, sorted by _id + */ +api.getMembersForChallenge = { + method: 'GET', + url: '/challenges/:challengeId/members', + middlewares: [authWithHeaders()], + handler: _getMembersForItem('challenge-members'), +}; + +/** + * @api {get} /api/v3/challenges/:challengeId/members/:memberId Get a challenge member progress + * @apiVersion 3.0.0 + * @apiName GetChallenge + * @apiGroup Challenge + * + * @apiParam {UUID} challengeId The challenge _id + * @apiParam {UUID} member The member _id + * + * @apiSuccess {object} data Return an object with member _id, profile.name and a tasks object with the challenge tasks for the member + */ +api.getChallengeMemberProgress = { + method: 'GET', + url: '/challenges/:challengeId/members/:memberId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + let memberId = req.params.memberId; + + let member = await User.findById(memberId).select(`${nameFields} challenges`).exec(); + if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); + + let challenge = await Challenge.findById(challengeId).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + + // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge + // for example if you've been booted from it, are the leader or a site admin + let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + if (!challenge.isMember(member)) throw new NotFound(res.t('challengeMemberNotFound')); + + let chalTasks = await Tasks.Task.find({ + userId: memberId, + 'challenge.id': challengeId, + }) + .select('-tags') // We don't want to return the tags publicly TODO same for other data? + .exec(); + + // manually call toJSON with minimize: true so empty paths aren't returned + let response = member.toJSON({minimize: true}); + delete response.challenges; + response.tasks = chalTasks.map(chalTask => chalTask.toJSON({minimize: true})); + res.respond(200, response); + }, +}; + +/** + * @api {posts} /members/send-private-message Send a private message to a member + * @apiVersion 3.0.0 + * @apiName SendPrivateMessage + * @apiGroup Members + * + * @apiParam {String} message Body parameter - The message + * @apiParam {UUID} toUserId Body parameter - The user to contact + * + * @apiSuccess {Object} data An empty Object + */ +api.sendPrivateMessage = { + method: 'POST', + url: '/members/send-private-message', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkBody('message', res.t('messageRequired')).notEmpty(); + req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let sender = res.locals.user; + let message = req.body.message; + + let receiver = await User.findById(req.body.toUserId).exec(); + if (!receiver) throw new NotFound(res.t('userNotFound')); + + let userBlockedSender = receiver.inbox.blocks.indexOf(sender._id) !== -1; + let userIsBlockBySender = sender.inbox.blocks.indexOf(receiver._id) !== -1; + let userOptedOutOfMessaging = receiver.inbox.optOut; + + if (userBlockedSender || userIsBlockBySender || userOptedOutOfMessaging) { + throw new NotAuthorized(res.t('notAuthorizedToSendMessageToThisUser')); + } + + await sender.sendMessage(receiver, message); + + if (receiver.preferences.emailNotifications.newPM !== false) { + sendTxnEmail(receiver, 'new-pm', [ + {name: 'SENDER', content: getUserInfo(sender, ['name']).name}, + {name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'}, + ]); + } + + res.respond(200, {}); + }, +}; + +/** + * @api {posts} /members/transfer-gems Send a gem gift to a member + * @apiVersion 3.0.0 + * @apiName TransferGems + * @apiGroup Members + * + * @apiParam {String} message Body parameter The message + * @apiParam {UUID} toUserId Body parameter The toUser _id + * @apiParam {Integer} gemAmount Body parameter The number of gems to send + * + * @apiSuccess {Object} data An empty Object + */ +api.transferGems = { + method: 'POST', + url: '/members/transfer-gems', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID(); + req.checkBody('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let sender = res.locals.user; + + let receiver = await User.findById(req.body.toUserId).exec(); + if (!receiver) throw new NotFound(res.t('userNotFound')); + + if (receiver._id === sender._id) { + throw new NotAuthorized(res.t('cannotSendGemsToYourself')); + } + + let gemAmount = req.body.gemAmount; + let amount = gemAmount / 4; + + if (amount <= 0 || sender.balance < amount) { + throw new NotAuthorized(res.t('badAmountOfGemsToSend')); + } + + receiver.balance += amount; + sender.balance -= amount; + let promises = [receiver.save(), sender.save()]; + await Bluebird.all(promises); + + let message = res.t('privateMessageGiftIntro', { + receiverName: receiver.profile.name, + senderName: sender.profile.name, + }); + message += res.t('privateMessageGiftGemsMessage', {gemAmount}); + + if (req.body.message) { + message += req.body.message; + } + + await sender.sendMessage(receiver, message); + + let byUsername = getUserInfo(sender, ['name']).name; + + if (receiver.preferences.emailNotifications.giftedGems !== false) { + sendTxnEmail(receiver, 'gifted-gems', [ + {name: 'GIFTER', content: byUsername}, + {name: 'X_GEMS_GIFTED', content: gemAmount}, + ]); + } + + sendPushNotification(sender, res.t('giftedGems'), res.t('giftedGemsInfo', { amount: gemAmount, name: byUsername })); + + res.respond(200, {}); + }, +}; + + +module.exports = api; diff --git a/website/server/controllers/api-v3/modelsPaths.js b/website/server/controllers/api-v3/modelsPaths.js new file mode 100644 index 0000000000..927087df01 --- /dev/null +++ b/website/server/controllers/api-v3/modelsPaths.js @@ -0,0 +1,40 @@ +import mongoose from 'mongoose'; + +let api = {}; + +let tasksModels = ['habit', 'daily', 'todo', 'reward']; +let allModels = ['user', 'tag', 'challenge', 'group'].concat(tasksModels); + +/** + * @api {get} /api/v3/models/:model/paths Get all paths for the specified model + * @apiDescription Doesn't require authentication + * @apiVersion 3.0.0 + * @apiName GetUserModelPaths + * @apiGroup Meta + * + * @apiParam {string="user","group","challenge","tag","habit","daily","todo","reward"} model The name of the model + * + * @apiSuccess {object} data A key-value object made of fieldPath: fieldType (like {'field.nested': Boolean}) + */ +api.getModelPaths = { + method: 'GET', + url: '/models/:model/paths', + async handler (req, res) { + req.checkParams('model', res.t('modelNotFound')).notEmpty().isIn(allModels); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let model = req.params.model; + // tasks models are lowercase, the others have the first letter uppercase (User, Group) + if (tasksModels.indexOf(model) === -1) { + model = model.charAt(0).toUpperCase() + model.slice(1); + } + + model = mongoose.model(model); + + res.respond(200, model.getModelPaths()); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/quests.js b/website/server/controllers/api-v3/quests.js new file mode 100644 index 0000000000..9107fda8d6 --- /dev/null +++ b/website/server/controllers/api-v3/quests.js @@ -0,0 +1,451 @@ +import _ from 'lodash'; +import Bluebird from 'bluebird'; +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import analytics from '../../libs/api-v3/analyticsService'; +import { + model as Group, +} from '../../models/group'; +import { model as User } from '../../models/user'; +import { + NotFound, + NotAuthorized, + BadRequest, +} from '../../libs/api-v3/errors'; +import { + getUserInfo, + sendTxn as sendTxnEmail, +} from '../../libs/api-v3/email'; +import common from '../../../../common'; +import sendPushNotification from '../../libs/api-v3/pushNotifications'; + +const questScrolls = common.content.quests; + +function canStartQuestAutomatically (group) { + // If all members are either true (accepted) or false (rejected) return true + // If any member is null/undefined (undecided) return false + return _.every(group.quest.members, _.isBoolean); +} + +let api = {}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/invite Invite users to a quest + * @apiVersion 3.0.0 + * @apiName InviteToQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * @apiParam {string} questKey + * + * @apiSuccess {Object} data Quest object + */ +api.inviteToQuest = { + method: 'POST', + url: '/groups/:groupId/quests/invite/:questKey', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let questKey = req.params.questKey; + let quest = questScrolls[questKey]; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); + + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!quest) throw new NotFound(res.t('questNotFound', { key: questKey })); + if (!user.items.quests[questKey]) throw new NotAuthorized(res.t('questNotOwned')); + if (user.stats.lvl < quest.lvl) throw new NotAuthorized(res.t('questLevelTooHigh', { level: quest.lvl })); + if (group.quest.key) throw new NotAuthorized(res.t('questAlreadyUnderway')); + + let members = await User.find({ + 'party._id': group._id, + _id: {$ne: user._id}, + }).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') + .exec(); + + group.markModified('quest'); + group.quest.key = questKey; + group.quest.leader = user._id; + group.quest.members = {}; + group.quest.members[user._id] = true; + + user.party.quest.RSVPNeeded = false; + user.party.quest.key = questKey; + + await User.update({ + 'party._id': group._id, + _id: {$ne: user._id}, + }, { + $set: { + 'party.quest.RSVPNeeded': true, + 'party.quest.key': questKey, + }, + }, {multi: true}).exec(); + + _.each(members, (member) => { + group.quest.members[member._id] = null; + }); + + if (canStartQuestAutomatically(group)) { + await group.startQuest(user); + } + + let [savedGroup] = await Bluebird.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + + // send out invites + let inviterVars = getUserInfo(user, ['name', 'email']); + let membersToEmail = members.filter(member => { + // send push notifications while filtering members before sending emails + sendPushNotification( + member, + common.i18n.t('questInvitationTitle'), + common.i18n.t('questInvitationInfo', { quest: quest.text() }) + ); + + return member.preferences.emailNotifications.invitedQuest !== false; + }); + sendTxnEmail(membersToEmail, `invite-${quest.boss ? 'boss' : 'collection'}-quest`, [ + {name: 'QUEST_NAME', content: quest.text()}, + {name: 'INVITER', content: inviterVars.name}, + {name: 'PARTY_URL', content: '/#/options/groups/party'}, + ]); + + // track that the inviting user has accepted the quest + analytics.track('quest', { + category: 'behavior', + owner: true, + response: 'accept', + gaLabel: 'accept', + questName: questKey, + uuid: user._id, + }); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/accept Accept a pending quest + * @apiVersion 3.0.0 + * @apiName AcceptQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.acceptQuest = { + method: 'POST', + url: '/groups/:groupId/quests/accept', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); + + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.key) throw new NotFound(res.t('questInviteNotFound')); + if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); + if (group.quest.members[user._id]) throw new BadRequest(res.t('questAlreadyAccepted')); + + group.markModified('quest'); + group.quest.members[user._id] = true; + user.party.quest.RSVPNeeded = false; + + if (canStartQuestAutomatically(group)) { + await group.startQuest(user); + } + + let [savedGroup] = await Bluebird.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + + // track that a user has accepted the quest + analytics.track('quest', { + category: 'behavior', + owner: false, + response: 'accept', + gaLabel: 'accept', + questName: group.quest.key, + uuid: user._id, + }); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/reject Reject a quest + * @apiVersion 3.0.0 + * @apiName RejectQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.rejectQuest = { + method: 'POST', + url: '/groups/:groupId/quests/reject', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist')); + if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); + if (group.quest.members[user._id]) throw new BadRequest(res.t('questAlreadyAccepted')); + if (group.quest.members[user._id] === false) throw new BadRequest(res.t('questAlreadyRejected')); + + group.quest.members[user._id] = false; + group.markModified('quest.members'); + + user.party.quest = Group.cleanQuestProgress(); + user.markModified('party.quest'); + + if (canStartQuestAutomatically(group)) { + await group.startQuest(user); + } + + let [savedGroup] = await Bluebird.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + + analytics.track('quest', { + category: 'behavior', + owner: false, + response: 'reject', + gaLabel: 'reject', + questName: group.quest.key, + uuid: user._id, + }); + }, +}; + + +/** + * @api {post} /api/v3/groups/:groupId/quests/force-start Force-start a pending quest + * @apiVersion 3.0.0 + * @apiName ForceQuestStart + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.forceStart = { + method: 'POST', + url: '/groups/:groupId/quests/force-start', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest leader'}); + + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.key) throw new NotFound(res.t('questNotPending')); + if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); + if (!(user._id === group.quest.leader || user._id === group.leader)) throw new NotAuthorized(res.t('questOrGroupLeaderOnlyStartQuest')); + + group.markModified('quest'); + + await group.startQuest(user); + + let [savedGroup] = await Bluebird.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + + analytics.track('quest', { + category: 'behavior', + owner: user._id === group.quest.leader, + response: 'force-start', + gaLabel: 'force-start', + questName: group.quest.key, + uuid: user._id, + }); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/cancel Cancels a quest + * @apiVersion 3.0.0 + * @apiName CancelQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.cancelQuest = { + method: 'POST', + url: '/groups/:groupId/quests/cancel', + middlewares: [authWithHeaders()], + async handler (req, res) { + // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) + // Quest scroll has not yet left quest owner's inventory so no need to return it. + // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId, fields: 'type leader quest'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist')); + if (user._id !== group.leader && group.quest.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCancelQuest')); + if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest')); + + group.quest = Group.cleanGroupQuest(); + group.markModified('quest'); + + let [savedGroup] = await Bluebird.all([ + group.save(), + User.update( + {'party._id': groupId}, + {$set: {'party.quest': Group.cleanQuestProgress()}}, + {multi: true} + ), + ]); + + res.respond(200, savedGroup.quest); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/abort Abort the current quest + * @apiVersion 3.0.0 + * @apiName AbortQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.abortQuest = { + method: 'POST', + url: '/groups/:groupId/quests/abort', + middlewares: [authWithHeaders()], + async handler (req, res) { + // Abort a quest AFTER it has begun (see questCancel for BEFORE) + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId, fields: 'type quest leader'}); + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.active) throw new NotFound(res.t('noActiveQuestToAbort')); + if (user._id !== group.leader && user._id !== group.quest.leader) throw new NotAuthorized(res.t('onlyLeaderAbortQuest')); + + let memberUpdates = User.update({ + 'party._id': groupId, + }, { + $set: {'party.quest': Group.cleanQuestProgress()}, + }, {multi: true}).exec(); + + let questLeaderUpdate = User.update({ + _id: group.quest.leader, + }, { + $inc: { + [`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader + }, + }).exec(); + + group.quest = Group.cleanGroupQuest(); + group.markModified('quest'); + + let [groupSaved] = await Bluebird.all([group.save(), memberUpdates, questLeaderUpdate]); + + res.respond(200, groupSaved.quest); + }, +}; + +/** + * @api {post} /api/v3/groups/:groupId/quests/leave Leaves the active quest + * @apiVersion 3.0.0 + * @apiName LeaveQuest + * @apiGroup Group + * + * @apiParam {string} groupId The group _id (or 'party') + * + * @apiSuccess {Object} data Quest Object + */ +api.leaveQuest = { + method: 'POST', + url: '/groups/:groupId/quests/leave', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let groupId = req.params.groupId; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({user, groupId, fields: 'type quest'}); + + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); + if (!group.quest.active) throw new NotFound(res.t('noActiveQuestToLeave')); + if (group.quest.leader === user._id) throw new NotAuthorized(res.t('questLeaderCannotLeaveQuest')); + if (!group.quest.members[user._id]) throw new NotAuthorized(res.t('notPartOfQuest')); + + group.quest.members[user._id] = false; + group.markModified('quest.members'); + + user.party.quest = Group.cleanQuestProgress(); + user.markModified('party.quest'); + + let [savedGroup] = await Bluebird.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/status.js b/website/server/controllers/api-v3/status.js new file mode 100644 index 0000000000..68c232463f --- /dev/null +++ b/website/server/controllers/api-v3/status.js @@ -0,0 +1,21 @@ +let api = {}; + +/** + * @api {get} /api/v3/status Get Habitica's API status + * @apiVersion 3.0.0 + * @apiName GetStatus + * @apiGroup Status + * + * @apiSuccess {status} data.status 'up' if everything is ok + */ +api.getStatus = { + method: 'GET', + url: '/status', + async handler (req, res) { + res.respond(200, { + status: 'up', + }); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/tags.js b/website/server/controllers/api-v3/tags.js new file mode 100644 index 0000000000..250c343537 --- /dev/null +++ b/website/server/controllers/api-v3/tags.js @@ -0,0 +1,190 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { model as Tag } from '../../models/tag'; +import * as Tasks from '../../models/task'; +import { + NotFound, +} from '../../libs/api-v3/errors'; +import _ from 'lodash'; +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; + +let api = {}; + +/** + * @api {post} /api/v3/tags Create a new tag + * @apiVersion 3.0.0 + * @apiName CreateTag + * @apiGroup Tag + * + * @apiSuccess {Object} data The newly created tag + */ +api.createTag = { + method: 'POST', + url: '/tags', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + user.tags.push(Tag.sanitize(req.body)); + let savedUser = await user.save(); + + let l = savedUser.tags.length; + let tag = savedUser.tags[l - 1]; + res.respond(201, tag); + }, +}; + +/** + * @api {get} /api/v3/tag Get a user's tags + * @apiVersion 3.0.0 + * @apiName GetTags + * @apiGroup Tag + * + * @apiSuccess {Array} data An array of tags + */ +api.getTags = { + method: 'GET', + url: '/tags', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + res.respond(200, user.tags); + }, +}; + +/** + * @api {get} /api/v3/tags/:tagId Get a tag given its id + * @apiVersion 3.0.0 + * @apiName GetTag + * @apiGroup Tag + * + * @apiParam {UUID} tagId The tag _id + * + * @apiSuccess {object} data The tag object + */ +api.getTag = { + method: 'GET', + url: '/tags/:tagId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let tag = _.find(user.tags, {id: req.params.tagId}); + if (!tag) throw new NotFound(res.t('tagNotFound')); + res.respond(200, tag); + }, +}; + +/** + * @api {put} /api/v3/tag/:tagId Update a tag + * @apiVersion 3.0.0 + * @apiName UpdateTag + * @apiGroup Tag + * + * @apiParam {UUID} tagId The tag _id + * + * @apiSuccess {object} data The updated tag + */ +api.updateTag = { + method: 'PUT', + url: '/tags/:tagId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); + + let tagId = req.params.tagId; + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let tag = _.find(user.tags, {id: tagId}); + if (!tag) throw new NotFound(res.t('tagNotFound')); + + _.merge(tag, Tag.sanitize(req.body)); + + let savedUser = await user.save(); + res.respond(200, _.find(savedUser.tags, {id: tagId})); + }, +}; + +/** + * @api {post} /api/v3/reorder-tags Reorder a tag + * @apiVersion 3.0.0 + * @apiName ReorderTags + * @apiGroup Tag + * + * @apiParam {tagId} UUID Id of the tag to move + * @apiParam {to} number Position the tag is moving to + * + * @apiSuccess {object} data An empty object + */ +api.reorderTags = { + method: 'POST', + url: '/reorder-tags', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkBody('to', res.t('toRequired')).notEmpty(); + req.checkBody('tagId', res.t('tagIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let tagIndex = _.findIndex(user.tags, function findTag (tag) { + return tag.id === req.body.tagId; + }); + if (tagIndex === -1) throw new NotFound(res.t('tagNotFound')); + user.tags.splice(req.body.to, 0, user.tags.splice(tagIndex, 1)[0]); + + await user.save(); + res.respond(200, {}); + }, +}; + +/** + * @api {delete} /api/v3/tag/:tagId Delete a user tag given its id + * @apiVersion 3.0.0 + * @apiName DeleteTag + * @apiGroup Tag + * + * @apiParam {UUID} tagId The tag _id + * + * @apiSuccess {object} data An empty object + */ +api.deleteTag = { + method: 'DELETE', + url: '/tags/:tagId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let tag = removeFromArray(user.tags, { id: req.params.tagId }); + if (!tag) throw new NotFound(res.t('tagNotFound')); + + // Remove from all the tasks TODO test + await Tasks.Task.update({ + userId: user._id, + }, { + $pull: { + tags: tag.id, + }, + }, {multi: true}).exec(); + + await user.save(); + res.respond(200, {}); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js new file mode 100644 index 0000000000..919c2fb541 --- /dev/null +++ b/website/server/controllers/api-v3/tasks.js @@ -0,0 +1,971 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import { sendTaskWebhook } from '../../libs/api-v3/webhook'; +import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; +import * as Tasks from '../../models/task'; +import { model as Challenge } from '../../models/challenge'; +import { model as Group } from '../../models/group'; +import { + NotFound, + NotAuthorized, + BadRequest, +} from '../../libs/api-v3/errors'; +import common from '../../../../common'; +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import logger from '../../libs/api-v3/logger'; + +let api = {}; + +// challenge must be passed only when a challenge task is being created +async function _createTasks (req, res, user, challenge) { + let toSave = Array.isArray(req.body) ? req.body : [req.body]; + + toSave = toSave.map(taskData => { + // Validate that task.type is valid + if (!taskData || Tasks.tasksTypes.indexOf(taskData.type) === -1) throw new BadRequest(res.t('invalidTaskType')); + + let taskType = taskData.type; + let newTask = new Tasks[taskType](Tasks.Task.sanitize(taskData)); + + if (challenge) { + newTask.challenge.id = challenge.id; + } else { + newTask.userId = user._id; + } + + // Validate that the task is valid and throw if it isn't + // otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality + let validationErrors = newTask.validateSync(); + if (validationErrors) throw validationErrors; + + // Otherwise update the user/challenge + (challenge || user).tasksOrder[`${taskType}s`].unshift(newTask._id); + + return newTask; + }).map(task => task.save({ // If all tasks are valid (this is why it's not in the previous .map()), save everything, withough running validation again + validateBeforeSave: false, + })); + + toSave.unshift((challenge || user).save()); + + let tasks = await Bluebird.all(toSave); + tasks.splice(0, 1); // Remove user or challenge + return tasks; +} + +/** + * @api {post} /api/v3/tasks/user Create a new task belonging to the user + * @apiDescription Can be passed an object to create a single task or an array of objects to create multiple tasks. + * @apiVersion 3.0.0 + * @apiName CreateUserTasks + * @apiGroup Task + * + * @apiSuccess data An object if a single task was created, otherwise an array of tasks + */ +api.createUserTasks = { + method: 'POST', + url: '/tasks/user', + middlewares: [authWithHeaders()], + async handler (req, res) { + let tasks = await _createTasks(req, res, res.locals.user); + res.respond(201, tasks.length === 1 ? tasks[0] : tasks); + }, +}; + +/** + * @api {post} /api/v3/tasks/challenge/:challengeId Create a new task belonging to a challenge + * @apiDescription Can be passed an object to create a single task or an array of objects to create multiple tasks. + * @apiVersion 3.0.0 + * @apiName CreateChallengeTasks + * @apiGroup Task + * + * @apiParam {UUID} challengeId The id of the challenge the new task(s) will belong to + * + * @apiSuccess data An object if a single task was created, otherwise an array of tasks + */ +api.createChallengeTasks = { + method: 'POST', + url: '/tasks/challenge/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + + let reqValidationErrors = req.validationErrors(); + if (reqValidationErrors) throw reqValidationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + + let challenge = await Challenge.findOne({_id: challengeId}).exec(); + + // If the challenge does not exist, or if it exists but user is not the leader -> throw error + if (!challenge || user.challenges.indexOf(challengeId) === -1) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + + let tasks = await _createTasks(req, res, user, challenge); + + res.respond(201, tasks.length === 1 ? tasks[0] : tasks); + + // If adding tasks to a challenge -> sync users + if (challenge) challenge.addTasks(tasks); + + return null; + }, +}; + +// challenge must be passed only when a challenge task is being created +async function _getTasks (req, res, user, challenge) { + let query = challenge ? {'challenge.id': challenge.id, userId: {$exists: false}} : {userId: user._id}; + let type = req.query.type; + + if (type) { + if (type === 'todos') { + query.completed = false; // Exclude completed todos + query.type = 'todo'; + } else if (type === 'completedTodos') { + query = Tasks.Task.find({ + userId: user._id, + type: 'todo', + completed: true, + }).limit(30).sort({ // TODO add ability to pick more than 30 completed todos + dateCompleted: -1, + }); + } else { + query.type = type.slice(0, -1); // removing the final "s" + } + } else { + query.$or = [ // Exclude completed todos + {type: 'todo', completed: false}, + {type: {$in: ['habit', 'daily', 'reward']}}, + ]; + } + + let tasks = await Tasks.Task.find(query).exec(); + + // Order tasks based on tasksOrder + if (type && type !== 'completedTodos') { + let order = (challenge || user).tasksOrder[type]; + let orderedTasks = new Array(tasks.length); + let unorderedTasks = []; // what we want to add later + + tasks.forEach((task, index) => { + let taskId = task._id; + let i = order[index] === taskId ? index : order.indexOf(taskId); + if (i === -1) { + unorderedTasks.push(task); + } else { + orderedTasks[i] = task; + } + }); + + // Remove empty values from the array and add any unordered task + orderedTasks = _.compact(orderedTasks).concat(unorderedTasks); + res.respond(200, orderedTasks); + } else { + res.respond(200, tasks); + } +} + +/** + * @api {get} /api/v3/tasks/user Get a user's tasks + * @apiVersion 3.0.0 + * @apiName GetUserTasks + * @apiGroup Task + * + * @apiParam {string="habits","dailys","todos","rewards","completedTodos"} type Optional query parameter to return just a type of tasks. By default all types will be returned except completed todos that must be requested separately. + * + * @apiSuccess {Array} data An array of tasks + */ +api.getUserTasks = { + method: 'GET', + url: '/tasks/user', + middlewares: [authWithHeaders()], + async handler (req, res) { + let types = Tasks.tasksTypes.map(type => `${type}s`); + types.push('completedTodos'); + req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + return await _getTasks(req, res, res.locals.user); + }, +}; + +/** + * @api {get} /api/v3/tasks/challenge/:challengeId Get a challenge's tasks + * @apiVersion 3.0.0 + * @apiName GetChallengeTasks + * @apiGroup Task + * + * @apiParam {UUID} challengeId The id of the challenge from which to retrieve the tasks + * @apiParam {string="habits","dailys","todos","rewards"} type Optional query parameter to return just a type of tasks + * + * @apiSuccess {Array} data An array of tasks + */ +api.getChallengeTasks = { + method: 'GET', + url: '/tasks/challenge/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + let types = Tasks.tasksTypes.map(type => `${type}s`); + req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let challengeId = req.params.challengeId; + + let challenge = await Challenge.findOne({_id: challengeId}).select('group leader tasksOrder').exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); + + return await _getTasks(req, res, res.locals.user, challenge); + }, +}; + +/** + * @api {get} /api/v3/task/:taskId Get a task + * @apiVersion 3.0.0 + * @apiName GetTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * + * @apiSuccess {object} data The task object + */ +api.getTask = { + method: 'GET', + url: '/tasks/:taskId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + }).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + let challenge = await Challenge.find({_id: task.challenge.id}).select('leader').exec(); + if (!challenge || (user.challenges.indexOf(task.challenge.id) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens + throw new NotFound(res.t('taskNotFound')); + } + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } + + res.respond(200, task); + }, +}; + +/** + * @api {put} /api/v3/task/:taskId Update a task + * @apiVersion 3.0.0 + * @apiName UpdateTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * + * @apiSuccess {object} data The updated task + */ +api.updateTask = { + method: 'PUT', + url: '/tasks/:taskId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let challenge; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + }).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } + + // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? + let [updatedTaskObj] = common.ops.updateTask(task.toObject(), req); + + + // Sanitize differently user tasks linked to a challenge + let sanitizedObj; + + if (!challenge && task.userId && task.challenge && task.challenge.id) { + sanitizedObj = Tasks.Task.sanitizeUserChallengeTask(updatedTaskObj); + } else { + sanitizedObj = Tasks.Task.sanitize(updatedTaskObj); + } + + _.assign(task, sanitizedObj); + // console.log(task.modifiedPaths(), task.toObject().repeat === tep) + // repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject() + // see https://github.com/Automattic/mongoose/issues/2749 + + let savedTask = await task.save(); + res.respond(200, savedTask); + if (challenge) challenge.updateTask(savedTask); + + return null; + }, +}; + +function _generateWebhookTaskData (task, direction, delta, stats, user) { + let extendedStats = _.extend(stats, { + toNextLevel: common.tnl(user.stats.lvl), + maxHealth: common.maxHealth, + maxMP: common.statsComputed(user).maxMP, + }); + + let userData = { + _id: user._id, + _tmp: user._tmp, + stats: extendedStats, + }; + + let taskData = { + details: task, + direction, + delta, + }; + + return { + task: taskData, + user: userData, + }; +} + +/** + * @api {put} /api/v3/tasks/:taskId/score/:direction Score a task + * @apiVersion 3.0.0 + * @apiName ScoreTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {string="up","down"} direction The direction for scoring the task + * + * @apiSuccess {object} data._tmp If an item was dropped it'll be returned in te _tmp object + * @apiSuccess {number} data.delta + * @apiSuccess {object} data The user stats + */ +api.scoreTask = { + method: 'POST', + url: '/tasks/:taskId/score/:direction', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let direction = req.params.direction; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + + let wasCompleted = task.completed; + + let [delta] = common.ops.scoreTask({task, user, direction}, req); + // Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results) + if (direction === 'up') user.fns.randomDrop({task, delta}, req); + + // If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list + // TODO move to common code? + if (task.type === 'todo') { + if (!wasCompleted && task.completed) { + removeFromArray(user.tasksOrder.todos, task._id); + } else if (wasCompleted && !task.completed) { + let hasTask = removeFromArray(user.tasksOrder.todos, task._id); + if (!hasTask) { + user.tasksOrder.todos.push(task._id); + } // If for some reason it hadn't been removed previously don't do anything + } + } + + let results = await Bluebird.all([ + user.save(), + task.save(), + ]); + + let savedUser = results[0]; + + let userStats = savedUser.stats.toJSON(); + let resJsonData = _.extend({delta, _tmp: user._tmp}, userStats); + res.respond(200, resJsonData); + + sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user)); + + if (task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') { + // Wrapping everything in a try/catch block because if an error occurs using `await` it MUST NOT bubble up because the request has already been handled + try { + let chalTask = await Tasks.Task.findOne({ + _id: task.challenge.taskId, + }).exec(); + + await chalTask.scoreChallengeTask(delta); + } catch (e) { + logger.error(e); + } + } + + return null; + }, +}; + +/** + * @api {post} /api/v3/tasks/:taskId/move/to/:position Move a task to a new position + * @apiDescription Note: completed To-Dos are not sortable, do not appear in user.tasksOrder.todos, and are ordered by date of completion. + * @apiVersion 3.0.0 + * @apiName MoveTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {Number} position Query parameter - Where to move the task (-1 means push to bottom). First position is 0 + * + * @apiSuccess {array} data The new tasks order (user.tasksOrder.{task.type}s) + */ +api.moveTask = { + method: 'POST', + url: '/tasks/:taskId/move/to/:position', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let to = Number(req.params.position); + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type === 'todo' && task.completed) throw new BadRequest(res.t('cantMoveCompletedTodo')); + let order = user.tasksOrder[`${task.type}s`]; + let currentIndex = order.indexOf(task._id); + + // If for some reason the task isn't ordered (should never happen), push it in the new position + // if the task is moved to a non existing position + // or if the task is moved to position -1 (push to bottom) + // -> push task at end of list + if (!order[to] && to !== -1) { + order.push(task._id); + } else { + if (currentIndex !== -1) order.splice(currentIndex, 1); + if (to === -1) { + order.push(task._id); + } else { + order.splice(to, 0, task._id); + } + } + + await user.save(); + res.respond(200, order); + }, +}; + +/** + * @api {post} /api/v3/tasks/:taskId/checklist Add an item to the task's checklist + * @apiVersion 3.0.0 + * @apiName AddChecklistItem + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * + * @apiSuccess {object} data The updated task + */ +api.addChecklistItem = { + method: 'POST', + url: '/tasks/:taskId/checklist', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let challenge; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + }).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } + + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + + task.checklist.push(Tasks.Task.sanitizeChecklist(req.body)); + let savedTask = await task.save(); + + res.respond(200, savedTask); + if (challenge) challenge.updateTask(savedTask); + + return null; + }, +}; + +/** + * @api {post} /api/v3/tasks/:taskId/checklist/:itemId/score Score a checklist item + * @apiVersion 3.0.0 + * @apiName ScoreChecklistItem + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {UUID} itemId The checklist item _id + * + * @apiSuccess {object} data The updated task + */ +api.scoreCheckListItem = { + method: 'POST', + url: '/tasks/:taskId/checklist/:itemId/score', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + + let item = _.find(task.checklist, {id: req.params.itemId}); + + if (!item) throw new NotFound(res.t('checklistItemNotFound')); + item.completed = !item.completed; + let savedTask = await task.save(); + + res.respond(200, savedTask); + }, +}; + +/** + * @api {put} /api/v3/tasks/:taskId/checklist/:itemId Update a checklist item + * @apiVersion 3.0.0 + * @apiName UpdateChecklistItem + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {UUID} itemId The checklist item _id + * + * @apiSuccess {object} data The updated task + */ +api.updateChecklistItem = { + method: 'PUT', + url: '/tasks/:taskId/checklist/:itemId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let challenge; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + }).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + + let item = _.find(task.checklist, {id: req.params.itemId}); + if (!item) throw new NotFound(res.t('checklistItemNotFound')); + + _.merge(item, Tasks.Task.sanitizeChecklist(req.body)); + let savedTask = await task.save(); + + res.respond(200, savedTask); + if (challenge) challenge.updateTask(savedTask); + + return null; + }, +}; + +/** + * @api {delete} /api/v3/tasks/:taskId/checklist/:itemId Remove a checklist item + * @apiVersion 3.0.0 + * @apiName RemoveChecklistItem + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {UUID} itemId The checklist item _id + * + * @apiSuccess {object} data The updated task + */ +api.removeChecklistItem = { + method: 'DELETE', + url: '/tasks/:taskId/checklist/:itemId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let challenge; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + }).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + + let hasItem = removeFromArray(task.checklist, { id: req.params.itemId }); + if (!hasItem) throw new NotFound(res.t('checklistItemNotFound')); + + let savedTask = await task.save(); + res.respond(200, savedTask); + if (challenge) challenge.updateTask(savedTask); + + return null; + }, +}; + +/** + * @api {post} /api/v3/tasks/:taskId/tags/:tagId Add a tag to a task + * @apiVersion 3.0.0 + * @apiName AddTagToTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {UUID} tagId The tag id + * + * @apiSuccess {object} data The updated task + */ +api.addTagToTask = { + method: 'POST', + url: '/tasks/:taskId/tags/:tagId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + let userTags = user.tags.map(tag => tag.id); + req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + let tagId = req.params.tagId; + + let alreadyTagged = task.tags.indexOf(tagId) !== -1; + if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged')); + + task.tags.push(tagId); + + let savedTask = await task.save(); + res.respond(200, savedTask); + }, +}; + +/** + * @api {delete} /api/v3/tasks/:taskId/tags/:tagId Remove a tag from atask + * @apiVersion 3.0.0 + * @apiName RemoveTagFromTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {UUID} tagId The tag id + * + * @apiSuccess {object} data The updated task + */ +api.removeTagFromTask = { + method: 'DELETE', + url: '/tasks/:taskId/tags/:tagId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let task = await Tasks.Task.findOne({ + _id: req.params.taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + + let hasTag = removeFromArray(task.tags, req.params.tagId); + if (!hasTag) throw new NotFound(res.t('tagNotFound')); + + let savedTask = await task.save(); + res.respond(200, savedTask); + }, +}; + +/** + * @api {post} /api/v3/tasks/unlink-all/:challengeId Unlink all tasks from a challenge + * @apiVersion 3.0.0 + * @apiName UnlinkAllTasks + * @apiGroup Task + * + * @apiParam {UUID} challengeId The challenge _id + * @apiParam {string} keep Query parameter - keep-all or remove-all + * + * @apiSuccess {object} data An empty object + */ +api.unlinkAllTasks = { + method: 'POST', + url: '/tasks/unlink-all/:challengeId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); + req.checkQuery('keep', res.t('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let keep = req.query.keep; + let challengeId = req.params.challengeId; + + let tasks = await Tasks.Task.find({ + 'challenge.id': challengeId, + userId: user._id, + }).exec(); + + let validTasks = tasks.every(task => { + return task.challenge.broken; + }); + + if (!validTasks) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); + + if (keep === 'keep-all') { + await Bluebird.all(tasks.map(task => { + task.challenge = {}; + return task.save(); + })); + } else { // remove + let toSave = []; + + tasks.forEach(task => { + if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if + removeFromArray(user.tasksOrder[`${task.type}s`], task._id); + } + + toSave.push(task.remove()); + }); + + toSave.push(user.save()); + + await Bluebird.all(toSave); + } + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/tasks/unlink-one/:taskId Unlink a challenge task + * @apiVersion 3.0.0 + * @apiName UnlinkOneTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * @apiParam {string} keep Query parameter - keep or remove + * + * @apiSuccess {object} data An empty object + */ +api.unlinkOneTask = { + method: 'POST', + url: '/tasks/unlink-one/:taskId', + middlewares: [authWithHeaders()], + async handler (req, res) { + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + req.checkQuery('keep', res.t('keepOrRemove')).notEmpty().isIn(['keep', 'remove']); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let user = res.locals.user; + let keep = req.query.keep; + let taskId = req.params.taskId; + + let task = await Tasks.Task.findOne({ + _id: taskId, + userId: user._id, + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + if (!task.challenge.id) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); + if (!task.challenge.broken) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); + + if (keep === 'keep') { + task.challenge = {}; + await task.save(); + } else { // remove + if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if + removeFromArray(user.tasksOrder[`${task.type}s`], taskId); + await Bluebird.all([user.save(), task.remove()]); + } else { + await task.remove(); + } + } + + res.respond(200, {}); + }, +}; + +/** + * @api {post} /api/v3/tasks/clearCompletedTodos Delete user's completed todos + * @apiVersion 3.0.0 + * @apiName ClearCompletedTodos + * @apiGroup Task + * + * @apiSuccess {object} data An empty object + */ +api.clearCompletedTodos = { + method: 'POST', + url: '/tasks/clearCompletedTodos', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + // Clear completed todos + // Do not delete challenges completed todos unless the task is broken + await Tasks.Task.remove({ + userId: user._id, + type: 'todo', + completed: true, + $or: [ + {'challenge.id': {$exists: false}}, + {'challenge.broken': {$exists: true}}, + ], + }).exec(); + + res.respond(200, {}); + }, +}; + +/** + * @api {delete} /api/v3/tasks/:taskId Delete a task given its id + * @apiVersion 3.0.0 + * @apiName DeleteTask + * @apiGroup Task + * + * @apiParam {UUID} taskId The task _id + * + * @apiSuccess {object} data An empty object + */ +api.deleteTask = { + method: 'DELETE', + url: '/tasks/:taskId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + let challenge; + + req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let taskId = req.params.taskId; + let task = await Tasks.Task.findById(taskId).exec(); + + if (!task) { + throw new NotFound(res.t('taskNotFound')); + } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights + challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); + } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one + throw new NotFound(res.t('taskNotFound')); + } else if (task.userId && task.challenge.id && !task.challenge.broken) { + throw new NotAuthorized(res.t('cantDeleteChallengeTasks')); + } + + if (task.type !== 'todo' || !task.completed) { + removeFromArray((challenge || user).tasksOrder[`${task.type}s`], taskId); + await Bluebird.all([(challenge || user).save(), task.remove()]); + } else { + await task.remove(); + } + + res.respond(200, {}); + if (challenge) challenge.removeTask(task); + + return null; + }, +}; + +module.exports = api; diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js new file mode 100644 index 0000000000..c3b03f620d --- /dev/null +++ b/website/server/controllers/api-v3/user.js @@ -0,0 +1,1358 @@ +import { authWithHeaders } from '../../middlewares/api-v3/auth'; +import common from '../../../../common'; +import { + NotFound, + BadRequest, + NotAuthorized, +} from '../../libs/api-v3/errors'; +import * as Tasks from '../../models/task'; +import { + basicFields as basicGroupFields, + model as Group, +} from '../../models/group'; +import { model as User } from '../../models/user'; +import Bluebird from 'bluebird'; +import _ from 'lodash'; +import * as firebase from '../../libs/api-v3/firebase'; +import * as passwordUtils from '../../libs/api-v3/password'; + +let api = {}; + +/** + * @api {get} /api/v3/user Get the authenticated user's profile + * @apiVersion 3.0.0 + * @apiName UserGet + * @apiGroup User + * + * @apiSuccess {Object} data The user object + */ +api.getUser = { + method: 'GET', + middlewares: [authWithHeaders()], + url: '/user', + async handler (req, res) { + let user = res.locals.user.toJSON(); + + // Remove apiToken from response TODO make it private at the user level? returned in signup/login + delete user.apiToken; + + // TODO move to model? (maybe virtuals, maybe in toJSON) + // NOTE: if an item is manually added to user.stats common/fns/predictableRandom must be tweaked + // so it's not considered. Otherwise the client will have it while the server won't and the results will be different. + user.stats.toNextLevel = common.tnl(user.stats.lvl); + user.stats.maxHealth = common.maxHealth; + user.stats.maxMP = common.statsComputed(user).maxMP; + + return res.respond(200, user); + }, +}; + +/** + * @api {get} /api/v3/user/inventory/buy Get the gear items available for purchase for the current user + * @apiVersion 3.0.0 + * @apiName UserGetBuyList + * @apiGroup User + * + * @apiSuccess {Object} data The buy list + */ +api.getBuyList = { + method: 'GET', + middlewares: [authWithHeaders()], + url: '/user/inventory/buy', + async handler (req, res) { + let list = _.cloneDeep(common.updateStore(res.locals.user)); + + // return text and notes strings + _.each(list, item => { + _.each(item, (itemPropVal, itemPropKey) => { + if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language); + }); + }); + + res.respond(200, list); + }, +}; + +let updatablePaths = [ + 'flags.customizationsNotification', + 'flags.showTour', + 'flags.tour', + 'flags.tutorial', + 'flags.communityGuidelinesAccepted', + 'flags.welcomed', + 'flags.cardReceived', + 'flags.warnedLowHealth', + 'flags.newStuff', + + 'achievements', + + 'party.order', + 'party.orderAscending', + 'party.quest.completed', + 'party.quest.RSVPNeeded', + + 'preferences', + 'profile', + 'stats', + 'inbox.optOut', +]; + +// This tells us for which paths users can call `PUT /user`. +// The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) +let acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (accumulator, val, leaf) => { + let found = _.find(updatablePaths, (rootPath) => { + return leaf.indexOf(rootPath) === 0; + }); + + if (found) accumulator[leaf] = true; + + return accumulator; +}, {}); + +let restrictedPUTSubPaths = [ + 'stats.class', + + 'preferences.disableClasses', + 'preferences.sleep', + 'preferences.webhooks', +]; + +_.each(restrictedPUTSubPaths, (removePath) => { + delete acceptablePUTPaths[removePath]; +}); + +let requiresPurchase = { + 'preferences.background': 'background', + 'preferences.shirt': 'shirt', + 'preferences.size': 'size', + 'preferences.skin': 'skin', + 'preferences.hair.bangs': 'hair.bangs', + 'preferences.hair.base': 'hair.base', + 'preferences.hair.beard': 'hair.beard', + 'preferences.hair.color': 'hair.color', + 'preferences.hair.flower': 'hair.flower', + 'preferences.hair.mustache': 'hair.mustache', +}; + +let checkPreferencePurchase = (user, path, item) => { + let itemPath = `${path}.${item}`; + let appearance = _.get(common.content.appearances, itemPath); + if (!appearance) return false; + if (appearance.price === 0) return true; + + return _.get(user.purchased, itemPath); +}; + +/** + * @api {put} /api/v3/user Update the user + * @apiDescription Example body: {'stats.hp':50, 'preferences.background': 'beach'} + * @apiVersion 3.0.0 + * @apiName UserUpdate + * @apiGroup User + * + * @apiSuccess {object} data The updated user object + */ +api.updateUser = { + method: 'PUT', + middlewares: [authWithHeaders()], + url: '/user', + async handler (req, res) { + let user = res.locals.user; + + _.each(req.body, (val, key) => { + let purchasable = requiresPurchase[key]; + + if (purchasable && !checkPreferencePurchase(user, purchasable, val)) { + throw new NotAuthorized(res.t('mustPurchaseToSet', { val, key })); + } + + if (acceptablePUTPaths[key]) { + _.set(user, key, val); + } else { + throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key })); + } + }); + + await user.save(); + return res.respond(200, user); + }, +}; + +/** + * @api {delete} /api/v3/user Delete an authenticated user's account + * @apiVersion 3.0.0 + * @apiName UserDelete + * @apiGroup User + * + * @apiParam {string} password The user's password (unless it's a Facebook account) + * + * @apiSuccess {Object} data An empty Object + */ +api.deleteUser = { + method: 'DELETE', + middlewares: [authWithHeaders()], + url: '/user', + async handler (req, res) { + let user = res.locals.user; + let plan = user.purchased.plan; + + req.checkBody({ + password: { + notEmpty: {errorMessage: res.t('missingPassword')}, + }, + }); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); + + if (plan && plan.customerId && !plan.dateTerminated) { + throw new NotAuthorized(res.t('cannotDeleteActiveAccount')); + } + + let types = ['party', 'guilds']; + let groupFields = basicGroupFields.concat(' leader memberCount'); + + let groupsUserIsMemberOf = await Group.getGroups({user, types, groupFields}); + + let groupLeavePromises = groupsUserIsMemberOf.map((group) => { + return group.leave(user, 'remove-all'); + }); + + await Bluebird.all(groupLeavePromises); + + await Tasks.Task.remove({ + userId: user._id, + }).exec(); + + await user.remove(); + + res.respond(200, {}); + + firebase.deleteUser(user._id); + }, +}; + +function _cleanChecklist (task) { + _.forEach(task.checklist, (c, i) => { + c.text = `item ${i}`; + }); +} + +/** + * @api {get} /api/v3/user/anonymized Get anonymized user data + * @apiVersion 3.0.0 + * @apiName UserGetAnonymized + * @apiGroup User + * + * @apiSuccess {Object} data.user + * @apiSuccess {Array} data.tasks + **/ +api.getUserAnonymized = { + method: 'GET', + middlewares: [authWithHeaders()], + url: '/user/anonymized', + async handler (req, res) { + let user = res.locals.user.toJSON(); + user.stats.toNextLevel = common.tnl(user.stats.lvl); + user.stats.maxHealth = common.maxHealth; + user.stats.maxMP = res.locals.user._statsComputed.maxMP; + + delete user.apiToken; + if (user.auth) { + delete user.auth.local; + delete user.auth.facebook; + } + delete user.newMessages; + delete user.profile; + delete user.purchased.plan; + delete user.contributor; + delete user.invitations; + delete user.items.special.nyeReceived; + delete user.items.special.valentineReceived; + delete user.webhooks; + delete user.achievements.challenges; + + _.forEach(user.inbox.messages, (msg) => { + msg.text = 'inbox message text'; + }); + _.forEach(user.tags, (tag) => { + tag.name = 'tag'; + tag.challenge = 'challenge'; + }); + + let query = { + userId: user._id, + $or: [ + { type: 'todo', completed: false }, + { type: { $in: ['habit', 'daily', 'reward'] } }, + ], + }; + let tasks = await Tasks.Task.find(query).exec(); + + _.forEach(tasks, (task) => { + task.text = 'task text'; + task.notes = 'task notes'; + if (task.type === 'todo' || task.type === 'daily') { + _cleanChecklist(task); + } + }); + + return res.respond(200, { user, tasks }); + }, +}; + +const partyMembersFields = 'profile.name stats achievements items.special'; + +/** + * @api {post} /api/v3/user/class/cast/:spellId Cast a skill (spell) on a target + * @apiVersion 3.0.0 + * @apiName UserCast + * @apiGroup User + * + * @apiParam {string} spellId The skill to cast + * @apiParam {UUID} targetId Optional query parameter, the id of the target when casting a skill on a party member or a task + * + * @apiSuccess data Will return the modified targets. For party members only the necessary fields will be populated. The user is always returned. + */ +api.castSpell = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/class/cast/:spellId', + async handler (req, res) { + let user = res.locals.user; + let spellId = req.params.spellId; + let targetId = req.query.targetId; + + // optional because not required by all targetTypes, presence is checked later if necessary + req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID(); + + let reqValidationErrors = req.validationErrors(); + if (reqValidationErrors) throw reqValidationErrors; + + let klass = common.content.spells.special[spellId] ? 'special' : user.stats.class; + let spell = common.content.spells[klass][spellId]; + + if (!spell) throw new NotFound(res.t('spellNotFound', {spellId})); + if (spell.mana > user.stats.mp) throw new NotAuthorized(res.t('notEnoughMana')); + if (spell.value > user.stats.gp && !spell.previousPurchase) throw new NotAuthorized(res.t('messageNotEnoughGold')); + if (spell.lvl > user.stats.lvl) throw new NotAuthorized(res.t('spellLevelTooHigh', {level: spell.lvl})); + + let targetType = spell.target; + + if (targetType === 'task') { + if (!targetId) throw new BadRequest(res.t('targetIdUUID')); + + let task = await Tasks.Task.findOne({ + _id: targetId, + userId: user._id, + }).exec(); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.challenge.id) throw new BadRequest(res.t('challengeTasksNoCast')); + + spell.cast(user, task, req); + + let results = await Bluebird.all([ + user.save(), + task.save(), + ]); + + res.respond(200, { + user: results[0], + task: results[1], + }); + } else if (targetType === 'self') { + spell.cast(user, null, req); + await user.save(); + res.respond(200, { user }); + } else if (targetType === 'tasks') { // new target type in v3: when all the user's tasks are necessary + let tasks = await Tasks.Task.find({ + userId: user._id, + $or: [ // exclude challenge tasks + {'challenge.id': {$exists: false}}, + {'challenge.broken': {$exists: true}}, + ], + }).exec(); + + spell.cast(user, tasks, req); + + let toSave = tasks + .filter(t => t.isModified()) + .map(t => t.save()); + + toSave.unshift(user.save()); + let saved = await Bluebird.all(toSave); + + let response = { + tasks: saved, + user, + }; + + res.respond(200, response); + } else if (targetType === 'party' || targetType === 'user') { + let party = await Group.getGroup({groupId: 'party', user}); + // arrays of users when targetType is 'party' otherwise single users + let partyMembers; + + if (targetType === 'party') { + if (!party) { + partyMembers = [user]; // Act as solo party + } else { + partyMembers = await User + .find({ + 'party._id': party._id, + _id: { $ne: user._id }, // add separately + }) + // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save + // default values for non-selected fields and pre('save') will mess up thinking some values are missing + .exec(); + + partyMembers.unshift(user); + } + + spell.cast(user, partyMembers, req); + await Bluebird.all(partyMembers.map(m => m.save())); + } else { + if (!party && (!targetId || user._id === targetId)) { + partyMembers = user; + } else { + if (!targetId) throw new BadRequest(res.t('targetIdUUID')); + if (!party) throw new NotFound(res.t('partyNotFound')); + partyMembers = await User + .findOne({_id: targetId, 'party._id': party._id}) + // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save + // default values for non-selected fields and pre('save') will mess up thinking some values are missing + .exec(); + } + + if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); + + spell.cast(user, partyMembers, req); + + if (partyMembers !== user) { + await Bluebird.all([ + user.save(), + partyMembers.save(), + ]); + } else { + await partyMembers.save(); // partyMembers is user + } + } + + let partyMembersRes = Array.isArray(partyMembers) ? partyMembers : [partyMembers]; + // Only return some fields. + // See comment above on why we can't just select the necessary fields when querying + partyMembersRes = partyMembersRes.map(partyMember => { + return common.pickDeep(partyMember.toJSON(), common.$w(partyMembersFields)); + }); + + res.respond(200, { + partyMembers: partyMembersRes, + user, + }); + + if (party && !spell.silent) { + let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; + party.sendChat(message); + await party.save(); + } + } + }, +}; + +/** + * @api {post} /api/v3/user/sleep Make the user start / stop sleeping (resting in the Inn) + * @apiVersion 3.0.0 + * @apiName UserSleep + * @apiGroup User + * + * @apiSuccess {boolean} data user.preferences.sleep + */ +api.sleep = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/sleep', + async handler (req, res) { + let user = res.locals.user; + let sleepRes = common.ops.sleep(user); + await user.save(); + res.respond(200, ...sleepRes); + }, +}; + +/** + * @api {post} /api/v3/user/allocate Allocate an attribute point + * @apiVersion 3.0.0 + * @apiName UserAllocate + * @apiGroup User + * + * @apiParam {string} stat Query parameter - Defaults to 'str', mast be one of be of str, con, int or per + * + * @apiSuccess {Object} data user.stats + */ +api.allocate = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/allocate', + async handler (req, res) { + let user = res.locals.user; + let allocateRes = common.ops.allocate(user, req); + await user.save(); + res.respond(200, ...allocateRes); + }, +}; + +/** + * @api {post} /api/v3/user/allocate-now Allocate all attribute points + * @apiDescription Uses the user's chosen automatic allocation method, or if none, assigns all to STR. + * @apiVersion 3.0.0 + * @apiName UserAllocateNow + * @apiGroup User + * + * @apiSuccess {Object} data user.stats + */ +api.allocateNow = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/allocate-now', + async handler (req, res) { + let user = res.locals.user; + let allocateNowRes = common.ops.allocateNow(user, req); + await user.save(); + res.respond(200, ...allocateNowRes); + }, +}; + +/** + * @api {post} /user/buy/:key Buy gear, armoire or potion + * @apiDescription Under the hood uses UserBuyGear, UserBuyPotion and UserBuyArmoire + * @apiVersion 3.0.0 + * @apiName UserBuy + * @apiGroup User + * + * @apiParam {string} key The item to buy + */ +api.buy = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy/:key', + async handler (req, res) { + let user = res.locals.user; + let buyRes = common.ops.buy(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyRes); + }, +}; + +/** + * @api {post} /user/buy-gear/:key Buy a piece of gear + * @apiVersion 3.0.0 + * @apiName UserBuyGear + * @apiGroup User + * + * @apiParam {string} key The item to buy + * + * @apiSuccess {object} data.items user.items + * @apiSuccess {object} data.flags user.flags + * @apiSuccess {object} data.achievements user.achievements + * @apiSuccess {object} data.stats user.stats + * @apiSuccess {string} message Success message + */ +api.buyGear = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-gear/:key', + async handler (req, res) { + let user = res.locals.user; + let buyGearRes = common.ops.buyGear(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyGearRes); + }, +}; + +/** + * @api {post} /user/buy-armoire Buy an armoire item + * @apiVersion 3.0.0 + * @apiName UserBuyArmoire + * @apiGroup User + * + * @apiSuccess {object} data.items user.items + * @apiSuccess {object} data.flags user.flags + * @apiSuccess {object} data.armoire Extra item given by the armoire + * @apiSuccess {string} message Success message + */ +api.buyArmoire = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-armoire', + async handler (req, res) { + let user = res.locals.user; + let buyArmoireResponse = common.ops.buyArmoire(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyArmoireResponse); + }, +}; + +/** + * @api {post} /user/buy-health-potion Buy a health potion + * @apiVersion 3.0.0 + * @apiName UserBuyPotion + * @apiGroup User + * + * @apiSuccess {Object} data user.stats + * @apiSuccess {string} message Success message + */ +api.buyHealthPotion = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-health-potion', + async handler (req, res) { + let user = res.locals.user; + let buyHealthPotionResponse = common.ops.buyHealthPotion(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyHealthPotionResponse); + }, +}; + +/** + * @api {post} /user/buy-mystery-set/:key Buy a mystery set + * @apiVersion 3.0.0 + * @apiName UserBuyMysterySet + * @apiGroup User + * + * @apiParam {string} key The mystery set to buy + * + * @apiSuccess {Object} data.items user.items + * @apiSuccess {Object} data.purchasedPlanConsecutive user.purchased.plan.consecutive + * @apiSuccess {string} message Success message + */ +api.buyMysterySet = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-mystery-set/:key', + async handler (req, res) { + let user = res.locals.user; + let buyMysterySetRes = common.ops.buyMysterySet(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyMysterySetRes); + }, +}; + +/** + * @api {post} /api/v3/user/buy-quest/:key Buy a quest with gold + * @apiVersion 3.0.0 + * @apiName UserBuyQuest + * @apiGroup User + * + * @apiParam {string} key The quest scroll to buy + * + * @apiSuccess {Object} data `user.items.quests` + * @apiSuccess {string} message Success message + */ +api.buyQuest = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-quest/:key', + async handler (req, res) { + let user = res.locals.user; + let buyQuestRes = common.ops.buyQuest(user, req, res.analytics); + await user.save(); + res.respond(200, ...buyQuestRes); + }, +}; + +/** + * @api {post} /api/v3/user/buy-special-spell/:key Buy special "spell" item + * @apiDescription Includes gift cards (e.g., birthday card), and avatar Transformation Items and their antidotes (e.g., Snowball item and Salt reward). + * @apiVersion 3.0.0 + * @apiName UserBuySpecialSpell + * @apiGroup User + * + * @apiParam {string} key The special item to buy. Must be one of the keys from "content.special", such as birthday, snowball, salt. + * + * @apiSuccess {Object} data.stats user.stats + * @apiSuccess {Object} data.items user.items + * @apiSuccess {string} message Success message + */ +api.buySpecialSpell = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/buy-special-spell/:key', + async handler (req, res) { + let user = res.locals.user; + let buySpecialSpellRes = common.ops.buySpecialSpell(user, req); + await user.save(); + res.respond(200, ...buySpecialSpellRes); + }, +}; + +/** + * @api {post} /api/v3/user/hatch/:egg/:hatchingPotion Hatch a pet + * @apiVersion 3.0.0 + * @apiName UserHatch + * @apiGroup User + * + * @apiParam {string} egg The egg to use + * @apiParam {string} hatchingPotion The hatching potion to use + * + * @apiSuccess {Object} data user.items + * @apiSuccess {string} message + */ +api.hatch = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/hatch/:egg/:hatchingPotion', + async handler (req, res) { + let user = res.locals.user; + let hatchRes = common.ops.hatch(user, req); + await user.save(); + res.respond(200, ...hatchRes); + }, +}; + +/** + * @api {post} /api/v3/user/equip/:type/:key Equip an item + * @apiVersion 3.0.0 + * @apiName UserEquip + * @apiGroup User + * + * @apiParam {string} type The type of item to equip (mount, pet, costume or equipped) + * @apiParam {string} key The item to equip + * + * @apiSuccess {Object} data user.items + * @apiSuccess {string} message Optional success message + */ +api.equip = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/equip/:type/:key', + async handler (req, res) { + let user = res.locals.user; + let equipRes = common.ops.equip(user, req); + await user.save(); + res.respond(200, ...equipRes); + }, +}; + +/** + * @api {post} /api/v3/user/equip/:pet/:food Feed a pet + * @apiVersion 3.0.0 + * @apiName UserFeed + * @apiGroup User + * + * @apiParam {string} pet + * @apiParam {string} food + * + * @apiSuccess {number} data The pet value + * @apiSuccess {string} message Success message + */ +api.feed = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/feed/:pet/:food', + async handler (req, res) { + let user = res.locals.user; + let feedRes = common.ops.feed(user, req); + await user.save(); + res.respond(200, ...feedRes); + }, +}; + +/** +* @api {post} /api/v3/user/change-class Change class +* @apiDescription User must be at least level 10. If ?class is defined and user.flags.classSelected is false it'll change the class. If user.preferences.disableClasses it'll enable classes, otherwise it sets user.flags.classSelected to false (costs 3 gems) +* @apiVersion 3.0.0 +* @apiName UserChangeClass +* @apiGroup User +* +* @apiParam {string} class Query parameter - ?class={warrior|rogue|wizard|healer} +* +* @apiSuccess {object} data.flags user.flags +* @apiSuccess {object} data.stats user.stats +* @apiSuccess {object} data.preferences user.preferences +* @apiSuccess {object} data.items user.items +*/ +api.changeClass = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/change-class', + async handler (req, res) { + let user = res.locals.user; + let changeClassRes = common.ops.changeClass(user, req, res.analytics); + await user.save(); + res.respond(200, ...changeClassRes); + }, +}; + +/** +* @api {post} /api/v3/user/disable-classes Disable classes +* @apiVersion 3.0.0 +* @apiName UserDisableClasses +* @apiGroup User +* +* @apiSuccess {object} data.flags user.flags +* @apiSuccess {object} data.stats user.stats +* @apiSuccess {object} data.preferences user.preferences +*/ +api.disableClasses = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/disable-classes', + async handler (req, res) { + let user = res.locals.user; + let disableClassesRes = common.ops.disableClasses(user, req); + await user.save(); + res.respond(200, ...disableClassesRes); + }, +}; + +/** +* @api {post} /api/v3/user/purchase/:type/:key Purchase Gem or Gem-purchasable item +* @apiVersion 3.0.0 +* @apiName UserPurchase +* @apiGroup User +* +* @apiParam {string} type Type of item to purchase. Must be one of: gems, eggs, hatchingPotions, food, quests, or gear +* @apiParam {string} key Item's key (use "gem" for purchasing gems) +* +* @apiSuccess {object} data.items user.items +* @apiSuccess {number} data.balance user.balance +* @apiSuccess {string} message Success message +*/ +api.purchase = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/purchase/:type/:key', + async handler (req, res) { + let user = res.locals.user; + let purchaseRes = common.ops.purchase(user, req, res.analytics); + await user.save(); + res.respond(200, ...purchaseRes); + }, +}; + +/** +* @api {post} /api/v3/user/purchase-hourglass/:type/:key Purchase Hourglass-purchasable item +* @apiVersion 3.0.0 +* @apiName UserPurchaseHourglass +* @apiGroup User +* +* @apiParam {string} type The type of item to purchase (pets or mounts) +* @apiParam {string} key Ex: {MantisShrimp-Base}. The key for the mount/pet +* +* @apiSuccess {object} data.items user.items +* @apiSuccess {object} data.purchasedPlanConsecutive user.purchased.plan.consecutive +* @apiSuccess {string} message Success message +*/ +api.userPurchaseHourglass = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/purchase-hourglass/:type/:key', + async handler (req, res) { + let user = res.locals.user; + let purchaseHourglassRes = common.ops.purchaseHourglass(user, req, res.analytics); + await user.save(); + res.respond(200, ...purchaseHourglassRes); + }, +}; + +/** +* @api {post} /api/v3/user/read-card/:cardType Reads a card +* @apiVersion 3.0.0 +* @apiName UserReadCard +* @apiGroup User +* +* @apiParam {string} cardType Type of card to read +* +* @apiSuccess {object} data.specialItems user.items.special +* @apiSuccess {boolean} data.cardReceived user.flags.cardReceived +* @apiSuccess {string} message Success message +*/ +api.readCard = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/read-card/:cardType', + async handler (req, res) { + let user = res.locals.user; + let readCardRes = common.ops.readCard(user, req); + await user.save(); + res.respond(200, ...readCardRes); + }, +}; + +/** +* @api {post} /api/v3/user/open-mystery-item Open the Mystery Item box +* @apiVersion 3.0.0 +* @apiName UserOpenMysteryItem +* @apiGroup User +* +* @apiSuccess {Object} data user.items.gear.owned +* @apiSuccess {string} message Success message +*/ +api.userOpenMysteryItem = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/open-mystery-item', + async handler (req, res) { + let user = res.locals.user; + let openMysteryItemRes = common.ops.openMysteryItem(user, req, res.analytics); + await user.save(); + res.respond(200, ...openMysteryItemRes); + }, +}; + +/** +* @api {post} /api/v3/user/webhook Create a new webhook - BETA +* @apiVersion 3.0.0 +* @apiName UserAddWebhook +* @apiGroup User +* +* @apiParam {string} url Body parameter - The webhook's URL +* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled +* +* @apiSuccess {Object} data The created webhook +*/ +api.addWebhook = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/webhook', + async handler (req, res) { + let user = res.locals.user; + let addWebhookRes = common.ops.addWebhook(user, req); + await user.save(); + res.respond(200, ...addWebhookRes); + }, +}; + +/** +* @api {put} /api/v3/user/webhook/:id Edit a webhook - BETA +* @apiVersion 3.0.0 +* @apiName UserUpdateWebhook +* @apiGroup User +* +* @apiParam {UUID} id The id of the webhook to update +* @apiParam {string} url Body parameter - The webhook's URL +* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled +* +* @apiSuccess {Object} data The updated webhook +*/ +api.updateWebhook = { + method: 'PUT', + middlewares: [authWithHeaders()], + url: '/user/webhook/:id', + async handler (req, res) { + let user = res.locals.user; + let updateWebhookRes = common.ops.updateWebhook(user, req); + await user.save(); + res.respond(200, ...updateWebhookRes); + }, +}; + +/** +* @api {delete} /api/v3/user/webhook/:id Delete a webhook - BETA +* @apiVersion 3.0.0 +* @apiName UserDeleteWebhook +* @apiGroup User +* +* @apiParam {UUID} id The id of the webhook to delete +* +* @apiSuccess {Object} data The user webhooks +*/ +api.deleteWebhook = { + method: 'DELETE', + middlewares: [authWithHeaders()], + url: '/user/webhook/:id', + async handler (req, res) { + let user = res.locals.user; + let deleteWebhookRes = common.ops.deleteWebhook(user, req); + await user.save(); + res.respond(200, ...deleteWebhookRes); + }, +}; + + +/* @api {post} /api/v3/user/release-pets Release pets +* @apiVersion 3.0.0 +* @apiName UserReleasePets +* @apiGroup User +* +* @apiSuccess {Object} data.items `user.items.pets` +* @apiSuccess {string} message Success message +*/ +api.userReleasePets = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/release-pets', + async handler (req, res) { + let user = res.locals.user; + let releasePetsRes = common.ops.releasePets(user, req, res.analytics); + await user.save(); + res.respond(200, ...releasePetsRes); + }, +}; + +/** +* @api {post} /api/v3/user/release-both Release pets and mounts and grants Triad Bingo +* @apiVersion 3.0.0 +* @apiName UserReleaseBoth +* @apiGroup User + +* @apiSuccess {Object} data.achievements +* @apiSuccess {Object} data.items +* @apiSuccess {number} data.balance +* @apiSuccess {string} message Success message +*/ +api.userReleaseBoth = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/release-both', + async handler (req, res) { + let user = res.locals.user; + let releaseBothRes = common.ops.releaseBoth(user, req, res.analytics); + await user.save(); + res.respond(200, ...releaseBothRes); + }, +}; + +/** +* @api {post} /api/v3/user/release-mounts Release mounts +* @apiVersion 3.0.0 +* @apiName UserReleaseMounts +* @apiGroup User +* +* @apiSuccess {Object} data user.items.mounts +* @apiSuccess {string} message Success message +*/ +api.userReleaseMounts = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/release-mounts', + async handler (req, res) { + let user = res.locals.user; + let releaseMountsRes = common.ops.releaseMounts(user, req, res.analytics); + await user.save(); + res.respond(200, ...releaseMountsRes); + }, +}; + +/** +* @api {post} /api/v3/user/sell/:type/:key Sell a gold-sellable item owned by the user +* @apiVersion 3.0.0 +* @apiName UserSell +* @apiGroup User +* +* @apiParam {string} type The type of item to sell. Must be one of: eggs, hatchingPotions, or food +* @apiParam {string} key The key of the item +* +* @apiSuccess {Object} data.stats +* @apiSuccess {Object} data.items +* @apiSuccess {string} message Success message +*/ +api.userSell = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/sell/:type/:key', + async handler (req, res) { + let user = res.locals.user; + let sellRes = common.ops.sell(user, req); + await user.save(); + res.respond(200, ...sellRes); + }, +}; + +/** +* @api {post} /api/v3/user/unlock Unlock item or set of items by purchase +* @apiVersion 3.0.0 +* @apiName UserUnlock +* @apiGroup User +* +* @apiParam {string} path Query parameter. The path to unlock +* +* @apiSuccess {Object} data.purchased +* @apiSuccess {Object} data.items +* @apiSuccess {Object} data.preferences +* @apiSuccess {string} message +*/ +api.userUnlock = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/unlock', + async handler (req, res) { + let user = res.locals.user; + let unlockRes = common.ops.unlock(user, req); + await user.save(); + res.respond(200, ...unlockRes); + }, +}; + +/** +* @api {post} /api/v3/user/revive Revive user from death +* @apiVersion 3.0.0 +* @apiName UserRevive +* @apiGroup User +* +* @apiSuccess {Object} data user.items +* @apiSuccess {string} message Success message +*/ +api.userRevive = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/revive', + async handler (req, res) { + let user = res.locals.user; + let reviveRes = common.ops.revive(user, req, res.analytics); + await user.save(); + res.respond(200, ...reviveRes); + }, +}; + +/** +* @api {post} /api/v3/user/rebirth Use Orb of Rebirth on user +* @apiVersion 3.0.0 +* @apiName UserRebirth +* @apiGroup User +* +* @apiSuccess {Object} data.user +* @apiSuccess {array} data.tasks User's modified tasks (no rewards) +* @apiSuccess {string} message Success message +*/ +api.userRebirth = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/rebirth', + async handler (req, res) { + let user = res.locals.user; + let tasks = await Tasks.Task.find({ + userId: user._id, + type: {$in: ['daily', 'habit', 'todo']}, + $or: [ // exclude challenge tasks + {'challenge.id': {$exists: false}}, + {'challenge.broken': {$exists: true}}, + ], + }).exec(); + + let rebirthRes = common.ops.rebirth(user, tasks, req, res.analytics); + + let toSave = tasks.map(task => task.save()); + + toSave.push(user.save()); + + await Bluebird.all(toSave); + + res.respond(200, ...rebirthRes); + }, +}; + +/** + * @api {post} /api/v3/user/block/:uuid Block and unblock a user + * @apiDescription Must be an admin to make this request. + * @apiVersion 3.0.0 + * @apiName BlockUser + * @apiGroup User + * + * @apiParam {UUID} uuid The uuid of the user to block / unblock + * + * @apiSuccess {array} data user.inbox.blocks +**/ +api.blockUser = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/block/:uuid', + async handler (req, res) { + let user = res.locals.user; + let blockUserRes = common.ops.blockUser(user, req); + await user.save(); + res.respond(200, ...blockUserRes); + }, +}; + +/** + * @api {delete} /api/v3/user/messages/:id Delete a message + * @apiVersion 3.0.0 + * @apiName deleteMessage + * @apiGroup User + * + * @apiParam {UUID} id The id of the message to delete + * + * @apiSuccess {object} data user.inbox.messages +**/ +api.deleteMessage = { + method: 'DELETE', + middlewares: [authWithHeaders()], + url: '/user/messages/:id', + async handler (req, res) { + let user = res.locals.user; + let deletePMRes = common.ops.deletePM(user, req); + await user.save(); + res.respond(200, ...deletePMRes); + }, +}; + +/** + * @api {delete} /api/v3/user/messages Delete all messages + * @apiVersion 3.0.0 + * @apiName clearMessages + * @apiGroup User + * + * @apiSuccess {object} data user.inbox.messages +**/ +api.clearMessages = { + method: 'DELETE', + middlewares: [authWithHeaders()], + url: '/user/messages', + async handler (req, res) { + let user = res.locals.user; + let clearPMsRes = common.ops.clearPMs(user, req); + await user.save(); + res.respond(200, ...clearPMsRes); + }, +}; + +/** + * @api {post} /api/v3/user/mark-pms-read Marks Private Messages as read + * @apiVersion 3.0.0 + * @apiName markPmsRead + * @apiGroup User + * + * @apiSuccess {object} data user.inbox.messages +**/ +api.markPmsRead = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/mark-pms-read', + async handler (req, res) { + let user = res.locals.user; + let markPmsResponse = common.ops.markPmsRead(user, req); + await user.save(); + res.respond(200, markPmsResponse); + }, +}; + +/** +* @api {post} /api/v3/user/reroll Reroll a user using the Fortify Potion +* @apiVersion 3.0.0 +* @apiName UserReroll +* @apiGroup User +* +* @apiSuccess {Object} data.user +* @apiSuccess {Object} data.tasks User's modified tasks (no rewards) +* @apiSuccess {Object} message Success message +*/ +api.userReroll = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/reroll', + async handler (req, res) { + let user = res.locals.user; + let query = { + userId: user._id, + type: {$in: ['daily', 'habit', 'todo']}, + $or: [ // exclude challenge tasks + {'challenge.id': {$exists: false}}, + {'challenge.broken': {$exists: true}}, + ], + }; + let tasks = await Tasks.Task.find(query).exec(); + let rerollRes = common.ops.reroll(user, tasks, req, res.analytics); + + let promises = tasks.map(task => task.save()); + promises.push(user.save()); + + await Bluebird.all(promises); + + res.respond(200, ...rerollRes); + }, +}; + +/** +* @api {post} /api/v3/user/addPushDevice Add a push device to a user +* @apiVersion 3.0.0 +* @apiName UserAddPushDevice +* @apiGroup User +* +* @apiParam {string} regId The id of the push device +* @apiParam {string} uuid The type of push device +* +* @apiSuccess {Object} data List of push devices +* @apiSuccess {string} message Success message +*/ +api.userAddPushDevice = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/addPushDevice', + async handler (req, res) { + let user = res.locals.user; + + let addPushDeviceRes = common.ops.addPushDevice(user, req); + await user.save(); + + res.respond(200, ...addPushDeviceRes); + }, +}; + +/** +* @api {post} /api/v3/user/reset Reset user +* @apiVersion 3.0.0 +* @apiName UserReset +* @apiGroup User +* +* @apiSuccess {Object} data.user +* @apiSuccess {Object} data.tasksToRemove IDs of removed tasks +* @apiSuccess {string} message Success message +*/ +api.userReset = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/reset', + async handler (req, res) { + let user = res.locals.user; + + let tasks = await Tasks.Task.find({ + userId: user._id, + $or: [ // exclude challenge tasks + {'challenge.id': {$exists: false}}, + {'challenge.broken': {$exists: true}}, + ], + }).select('_id type challenge').exec(); + + let resetRes = common.ops.reset(user, tasks, req); + + await Bluebird.all([ + Tasks.Task.remove({_id: {$in: resetRes[0].tasksToRemove}, userId: user._id}), + user.save(), + ]); + + res.respond(200, ...resetRes); + }, +}; + +/** +* @api {post} /api/v3/user/custom-day-start Sets preferences.dayStart for user +* @apiVersion 3.0.0 +* @apiName setCustomDayStart +* @apiGroup User +* +* @apiSuccess {Object} data An empty Object +*/ +api.setCustomDayStart = { + method: 'POST', + middlewares: [authWithHeaders()], + url: '/user/custom-day-start', + async handler (req, res) { + let user = res.locals.user; + let dayStart = req.body.dayStart; + + user.preferences.dayStart = dayStart; + user.lastCron = new Date(); + + await user.save(); + + res.respond(200, { + message: res.t('customDayStartHasChanged'), + }); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/auth.js b/website/server/controllers/top-level/auth.js new file mode 100644 index 0000000000..90724ab1f4 --- /dev/null +++ b/website/server/controllers/top-level/auth.js @@ -0,0 +1,16 @@ +let api = {}; + +// Internal authentication routes + +// Logout the user from the website. +api.logout = { + method: 'GET', + url: '/logout', + async handler (req, res) { + if (req.logout) req.logout(); // passportjs method + req.session = null; + res.redirect('/'); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js new file mode 100644 index 0000000000..08bc4a1f8e --- /dev/null +++ b/website/server/controllers/top-level/dataexport.js @@ -0,0 +1,247 @@ +import { authWithSession } from '../../middlewares/api-v3/auth'; +import { model as User } from '../../models/user'; +import * as Tasks from '../../models/task'; +import { + NotFound, +} from '../../libs/api-v3/errors'; +import _ from 'lodash'; +import csvStringify from '../../libs/api-v3/csvStringify'; +import moment from 'moment'; +import js2xml from 'js2xmlparser'; +import Pageres from 'pageres'; +import AWS from 'aws-sdk'; +import nconf from 'nconf'; +import got from 'got'; +import Bluebird from 'bluebird'; +import locals from '../../middlewares/api-v3/locals'; + +let S3 = new AWS.S3({ + accessKeyId: nconf.get('S3:accessKeyId'), + secretAccessKey: nconf.get('S3:secretAccessKey'), +}); +const S3_BUCKET = nconf.get('S3:bucket'); + +const BASE_URL = nconf.get('BASE_URL'); + +let api = {}; + +/** + * @api {get} /export/history.csv Export user tasks history in CSV format + * @apiDescription History is only available for habits and dailys so todos and rewards won't be included. NOTE: Part of the private API that may change at any time. + * @apiVersion 3.0.0 + * @apiName ExportUserHistory + * @apiGroup DataExport + * + * @apiSuccess {string} A cvs file + */ +api.exportUserHistory = { + method: 'GET', + url: '/export/history.csv', + middlewares: [authWithSession], + async handler (req, res) { + let user = res.locals.user; + + let tasks = await Tasks.Task.find({ + userId: user._id, + type: {$in: ['habit', 'daily']}, + }).exec(); + + let output = [ + ['Task Name', 'Task ID', 'Task Type', 'Date', 'Value'], + ]; + + tasks.forEach(task => { + task.history.forEach(history => { + output.push([ + task.text, + task._id, + task.type, + moment(history.date).format('YYYY-MM-DD HH:mm:ss'), + history.value, + ]); + }); + }); + + res.set({ + 'Content-Type': 'text/csv', + 'Content-disposition': 'attachment; filename=habitica-tasks-history.csv', + }); + + let csvRes = await csvStringify(output); + res.status(200).send(csvRes); + }, +}; + +// Convert user to json and attach tasks divided by type +// at user.tasks[`${taskType}s`] (user.tasks.{dailys/habits/...}) +async function _getUserDataForExport (user) { + let userData = user.toJSON(); + userData.tasks = {}; + + let tasks = await Tasks.Task.find({ + userId: user._id, + }).exec(); + + tasks = _.chain(tasks) + .map(task => task.toJSON()) + .groupBy(task => task.type) + .each((tasksPerType, taskType) => { + userData.tasks[`${taskType}s`] = tasksPerType; + }) + .value(); + + return userData; +} + +/** + * @api {get} /export/userdata.json Export user data in JSON format + * @apiVersion 3.0.0 + * @apiName ExportUserDataJson + * @apiGroup DataExport + * @apiDescription NOTE: Part of the private API that may change at any time. + * + * @apiSuccess {string} A json file + */ +api.exportUserDataJson = { + method: 'GET', + url: '/export/userdata.json', + middlewares: [authWithSession], + async handler (req, res) { + let userData = await _getUserDataForExport(res.locals.user); + + res.set({ + 'Content-Type': 'application/json', + 'Content-disposition': 'attachment; filename=habitica-user-data.json', + }); + let jsonRes = JSON.stringify(userData); + + res.status(200).send(jsonRes); + }, +}; + +/** + * @api {get} /export/userdata.xml Export user data in XML format + * @apiVersion 3.0.0 + * @apiName ExportUserDataXml + * @apiGroup DataExport + * @apiDescription NOTE: Part of the private API that may change at any time. + * + * @apiSuccess {string} A xml file + */ +api.exportUserDataXml = { + method: 'GET', + url: '/export/userdata.xml', + middlewares: [authWithSession], + async handler (req, res) { + let userData = await _getUserDataForExport(res.locals.user); + + res.set({ + 'Content-Type': 'text/xml', + 'Content-disposition': 'attachment; filename=habitica-user-data.xml', + }); + res.status(200).send(js2xml('user', userData)); + }, +}; + +/** + * @api {get} /export/avatar-:uuid.html Render a user avatar as an HTML page + * @apiVersion 3.0.0 + * @apiName ExportUserAvatarHtml + * @apiGroup DataExport + * @apiDescription NOTE: Part of the private API that may change at any time. + * + * @apiSuccess {string} An html page + */ +api.exportUserAvatarHtml = { + method: 'GET', + url: '/export/avatar-:memberId.html', + middlewares: [locals], + async handler (req, res) { + req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let memberId = req.params.memberId; + let member = await User + .findById(memberId) + .select('stats profile items achievements preferences backer contributor') + .exec(); + + if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); + res.render('avatar-static', { + title: member.profile.name, + env: _.defaults({user: member}, res.locals.habitrpg), + }); + }, +}; + +/** + * @api {get} /export/avatar-:uuid.html Export a user avatar as a PNG file + * @apiVersion 3.0.0 + * @apiName ExportUserAvatarPng + * @apiGroup DataExport + * @apiDescription NOTE: Part of the private API that may change at any time. + * + * @apiSuccess {string} A png file + */ +api.exportUserAvatarPng = { + method: 'GET', + url: '/export/avatar-:memberId.png', + async handler (req, res) { + req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let memberId = req.params.memberId; + + let filename = `avatars/${memberId}.png`; + let s3url = `https://${S3_BUCKET}.s3.amazonaws.com/${filename}`; + + let response; + try { + response = await got.head(s3url); + } catch (gotError) { + if (gotError.code !== 'ENOTFOUND' && gotError.statusCode !== 404) { + throw gotError; + } + } + + // cache images for 30 minutes on aws, else upload a new one + if (response && response.statusCode === 200 && moment().diff(response.headers['last-modified'], 'minutes') < 30) { + return res.redirect(s3url); + } + + let [stream] = await new Pageres() + .src(`${BASE_URL}/export/avatar-${memberId}.html`, ['140x147'], { + crop: true, + filename: filename.replace('.png', ''), + }) + .run(); + + let s3upload = S3.upload({ + Bucket: S3_BUCKET, + Key: filename, + ACL: 'public-read', + StorageClass: 'REDUCED_REDUNDANCY', + ContentType: 'image/png', + Expires: moment().add({minutes: 5}).toDate(), + Body: stream, + }); + + let s3res = await new Bluebird((resolve, reject) => { + s3upload.send((err, s3uploadRes) => { + if (err) { + reject(err); + } else { + resolve(s3uploadRes); + } + }); + }); + + res.redirect(s3res.Location); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/email.js b/website/server/controllers/top-level/email.js new file mode 100644 index 0000000000..b84f54c6e1 --- /dev/null +++ b/website/server/controllers/top-level/email.js @@ -0,0 +1,54 @@ +import { model as User } from '../../models/user'; +import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; +import { decrypt } from '../../libs/api-v3/encryption'; +import { + NotFound, +} from '../../libs/api-v3/errors'; + +let api = {}; + +/** + * @api {get} /api/v3/email/unsubscribe Unsubscribe an email or user from email notifications + * @apiDescription Does not require authentication + * @apiVersion 3.0.0 + * @apiName UnsubscribeEmail + * @apiGroup Unsubscribe + * @apiDescription This is a GET method so that you can put the unsubscribe link in emails. + * + * @apiParam {String} code Query parameter - An unsubscription code + * + * @apiSuccess {String} An html success message + */ +api.unsubscribe = { + method: 'GET', + url: '/email/unsubscribe', + async handler (req, res) { + req.checkQuery({ + code: { + notEmpty: {errorMessage: res.t('missingUnsubscriptionCode')}, + }, + }); + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let data = JSON.parse(decrypt(req.query.code)); + + if (data._id) { + let userUpdated = await User.update( + {_id: data._id}, + { $set: {'preferences.emailNotifications.unsubscribeFromAll': true}} + ); + + if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound')); + + res.send(`

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextUsers')}`); + } else { + let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email.toLowerCase()}); + let okResponse = `

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextOthers')}`; + if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email.toLowerCase()}); + res.send(okResponse); + } + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/pages.js b/website/server/controllers/top-level/pages.js new file mode 100644 index 0000000000..614b1edc0a --- /dev/null +++ b/website/server/controllers/top-level/pages.js @@ -0,0 +1,79 @@ +import locals from '../../middlewares/api-v3/locals'; +import _ from 'lodash'; +import markdownIt from 'markdown-it'; + +const md = markdownIt({ + html: true, +}); + +let api = {}; + +const TOTAL_USER_COUNT = '1,100,000'; + +api.getFrontPage = { + method: 'GET', + url: '/', + middlewares: [locals], + runCron: false, + async handler (req, res) { + if (!req.header('x-api-user') && !req.header('x-api-key') && !(req.session && req.session.userId)) { + return res.redirect('/static/front'); + } + + return res.render('index.jade', { + title: 'Habitica | Your Life The Role Playing Game', + env: res.locals.habitrpg, + }); + }, +}; + +let staticPages = ['front', 'privacy', 'terms', 'api-v2', 'features', + 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', + 'old-news', 'press-kit', 'faq', 'overview', 'apps', + 'clear-browser-data', 'merch', 'maintenance-info']; + +_.each(staticPages, (name) => { + api[`get${name}Page`] = { + method: 'GET', + url: `/static/${name}`, + middlewares: [locals], + runCron: false, + async handler (req, res) { + return res.render(`static/${name}.jade`, { + env: res.locals.habitrpg, + md, + userCount: TOTAL_USER_COUNT, + }); + }, + }; +}); + +let shareables = ['level-up', 'hatch-pet', 'raise-pet', 'unlock-quest', 'won-challenge', 'achievement']; + +_.each(shareables, (name) => { + api[`get${name}ShareablePage`] = { + method: 'GET', + url: `/social/${name}`, + middlewares: [locals], + runCron: false, + async handler (req, res) { + return res.render(`social/${name}`, { + env: res.locals.habitrpg, + md, + userCount: TOTAL_USER_COUNT, + }); + }, + }; +}); + +api.redirectExtensionsPage = { + method: 'GET', + url: '/static/extensions', + runCron: false, + async handler (req, res) { + return res.redirect('http://habitica.wikia.com/wiki/App_and_Extension_Integrations'); + }, +}; + + +module.exports = api; diff --git a/website/server/controllers/top-level/payments/amazon.js b/website/server/controllers/top-level/payments/amazon.js new file mode 100644 index 0000000000..a22618fa23 --- /dev/null +++ b/website/server/controllers/top-level/payments/amazon.js @@ -0,0 +1,256 @@ +import { + BadRequest, + NotAuthorized, +} from '../../../libs/api-v3/errors'; +import amzLib from '../../../libs/api-v3/amazonPayments'; +import { + authWithHeaders, + authWithUrl, +} from '../../../middlewares/api-v3/auth'; +import shared from '../../../../../common'; +import payments from '../../../libs/api-v3/payments'; +import moment from 'moment'; +import { model as Coupon } from '../../../models/coupon'; +import { model as User } from '../../../models/user'; +import cc from 'coupon-code'; + +let api = {}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /amazon/verifyAccessToken Amazon Payments: verify access token + * @apiVersion 3.0.0 + * @apiName AmazonVerifyAccessToken + * @apiGroup Payments + * + * @apiSuccess {Object} data Empty object + **/ +api.verifyAccessToken = { + method: 'POST', + url: '/amazon/verifyAccessToken', + middlewares: [authWithHeaders()], + async handler (req, res) { + let accessToken = req.body.access_token; + + if (!accessToken) throw new BadRequest('Missing req.body.access_token'); + + await amzLib.getTokenInfo(accessToken); + res.respond(200, {}); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /amazon/createOrderReferenceId Amazon Payments: create order reference id + * @apiVersion 3.0.0 + * @apiName AmazonCreateOrderReferenceId + * @apiGroup Payments + * + * @apiSuccess {string} data.orderReferenceId The order reference id. + **/ +api.createOrderReferenceId = { + method: 'POST', + url: '/amazon/createOrderReferenceId', + middlewares: [authWithHeaders()], + async handler (req, res) { + let billingAgreementId = req.body.billingAgreementId; + + if (!billingAgreementId) throw new BadRequest('Missing req.body.billingAgreementId'); + + let response = await amzLib.createOrderReferenceId({ + Id: billingAgreementId, + IdType: 'BillingAgreement', + ConfirmNow: false, + }); + + res.respond(200, { + orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId, + }); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /amazon/checkout Amazon Payments: checkout + * @apiVersion 3.0.0 + * @apiName AmazonCheckout + * @apiGroup Payments + * + * @apiSuccess {object} data Empty object + **/ +api.checkout = { + method: 'POST', + url: '/amazon/checkout', + middlewares: [authWithHeaders()], + async handler (req, res) { + let gift = req.body.gift; + let user = res.locals.user; + let orderReferenceId = req.body.orderReferenceId; + let amount = 5; + + if (!orderReferenceId) throw new BadRequest('Missing req.body.orderReferenceId'); + + if (gift) { + if (gift.type === 'gems') { + amount = gift.gems.amount / 4; + } else if (gift.type === 'subscription') { + amount = shared.content.subscriptionBlocks[gift.subscription.key].price; + } + } + + await amzLib.setOrderReferenceDetails({ + AmazonOrderReferenceId: orderReferenceId, + OrderReferenceAttributes: { + OrderTotal: { + CurrencyCode: 'USD', + Amount: amount, + }, + SellerNote: 'HabitRPG Payment', + SellerOrderAttributes: { + SellerOrderId: shared.uuid(), + StoreName: 'HabitRPG', + }, + }, + }); + + await amzLib.confirmOrderReference({ AmazonOrderReferenceId: orderReferenceId }); + + await amzLib.authorize({ + AmazonOrderReferenceId: orderReferenceId, + AuthorizationReferenceId: shared.uuid().substring(0, 32), + AuthorizationAmount: { + CurrencyCode: 'USD', + Amount: amount, + }, + SellerAuthorizationNote: 'HabitRPG Payment', + TransactionTimeout: 0, + CaptureNow: true, + }); + + await amzLib.closeOrderReference({ AmazonOrderReferenceId: orderReferenceId }); + + // execute payment + let method = 'buyGems'; + let data = { user, paymentMethod: 'Amazon Payments' }; + + if (gift) { + if (gift.type === 'subscription') method = 'createSubscription'; + gift.member = await User.findById(gift ? gift.uuid : undefined); + data.gift = gift; + data.paymentMethod = 'Gift'; + } + + await payments[method](data); + + res.respond(200); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /amazon/subscribe Amazon Payments: subscribe + * @apiVersion 3.0.0 + * @apiName AmazonSubscribe + * @apiGroup Payments + * + * @apiSuccess {object} data Empty object + **/ +api.subscribe = { + method: 'POST', + url: '/amazon/subscribe', + middlewares: [authWithHeaders()], + async handler (req, res) { + let billingAgreementId = req.body.billingAgreementId; + let sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false; + let coupon = req.body.coupon; + let user = res.locals.user; + + if (!sub) throw new BadRequest(res.t('missingSubscriptionCode')); + if (!billingAgreementId) throw new BadRequest('Missing req.body.billingAgreementId'); + + if (sub.discount) { // apply discount + if (!coupon) throw new BadRequest(res.t('couponCodeRequired')); + let result = await Coupon.findOne({_id: cc.validate(coupon), event: sub.key}); + if (!result) throw new NotAuthorized(res.t('invalidCoupon')); + } + + await amzLib.setBillingAgreementDetails({ + AmazonBillingAgreementId: billingAgreementId, + BillingAgreementAttributes: { + SellerNote: 'HabitRPG Subscription', + SellerBillingAgreementAttributes: { + SellerBillingAgreementId: shared.uuid(), + StoreName: 'HabitRPG', + CustomInformation: 'HabitRPG Subscription', + }, + }, + }); + + await amzLib.confirmBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId, + }); + + await amzLib.authorizeOnBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId, + AuthorizationReferenceId: shared.uuid().substring(0, 32), + AuthorizationAmount: { + CurrencyCode: 'USD', + Amount: sub.price, + }, + SellerAuthorizationNote: 'HabitRPG Subscription Payment', + TransactionTimeout: 0, + CaptureNow: true, + SellerNote: 'HabitRPG Subscription Payment', + SellerOrderAttributes: { + SellerOrderId: shared.uuid(), + StoreName: 'HabitRPG', + }, + }); + + await payments.createSubscription({ + user, + customerId: billingAgreementId, + paymentMethod: 'Amazon Payments', + sub, + }); + + res.respond(200); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /amazon/subscribe/cancel Amazon Payments: subscribe cancel + * @apiVersion 3.0.0 + * @apiName AmazonSubscribe + * @apiGroup Payments + **/ +api.subscribeCancel = { + method: 'GET', + url: '/amazon/subscribe/cancel', + middlewares: [authWithUrl], + async handler (req, res) { + let user = res.locals.user; + let billingAgreementId = user.purchased.plan.customerId; + + if (!billingAgreementId) throw new NotAuthorized(res.t('missingSubscription')); + + await amzLib.closeBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId, + }); + + await payments.cancelSubscription({ + user, + nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: 30 }), + paymentMethod: 'Amazon Payments', + }); + + if (req.query.noRedirect) { + res.respond(200); + } else { + res.redirect('/'); + } + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js new file mode 100644 index 0000000000..e99590fa71 --- /dev/null +++ b/website/server/controllers/top-level/payments/iap.js @@ -0,0 +1,191 @@ +import iap from 'in-app-purchase'; +import nconf from 'nconf'; +import { + authWithHeaders, + authWithUrl, +} from '../../../middlewares/api-v3/auth'; +import payments from '../../../libs/api-v3/payments'; + +// NOT PORTED TO v3 + +iap.config({ + // this is the path to the directory containing iap-sanbox/iap-live files + googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR'), +}); + +// Validation ERROR Codes +const INVALID_PAYLOAD = 6778001; +// const CONNECTION_FAILED = 6778002; +// const PURCHASE_EXPIRED = 6778003; + +let api = {}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /iap/android/verify Android Verify IAP + * @apiVersion 3.0.0 + * @apiName IapAndroidVerify + * @apiGroup Payments + **/ +api.iapAndroidVerify = { + method: 'POST', + url: '/iap/android/verify', + middlewares: [authWithUrl], + async handler (req, res) { + let user = res.locals.user; + let iapBody = req.body; + + iap.setup((error) => { + if (error) { + let resObj = { + ok: false, + data: 'IAP Error', + }; + + return res.json(resObj); + } + + // google receipt must be provided as an object + // { + // "data": "{stringified data object}", + // "signature": "signature from google" + // } + let testObj = { + data: iapBody.transaction.receipt, + signature: iapBody.transaction.signature, + }; + + // iap is ready + iap.validate(iap.GOOGLE, testObj, (err, googleRes) => { + if (err) { + let resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString(), + }, + }; + + return res.json(resObj); + } + + if (iap.isValidated(googleRes)) { + let resObj = { + ok: true, + data: googleRes, + }; + + payments.buyGems({ + user, + paymentMethod: 'IAP GooglePlay', + amount: 5.25, + }).then(() => res.json(resObj)); + } + }); + }); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /iap/ios/verify iOS Verify IAP + * @apiVersion 3.0.0 + * @apiName IapiOSVerify + * @apiGroup Payments + **/ +api.iapiOSVerify = { + method: 'POST', + url: '/iap/ios/verify', + middlewares: [authWithHeaders()], + async handler (req, res) { + let iapBody = req.body; + let user = res.locals.user; + + iap.setup(function iosSetupResult (error) { + if (error) { + let resObj = { + ok: false, + data: 'IAP Error', + }; + + return res.json(resObj); + } + + // iap is ready + iap.validate(iap.APPLE, iapBody.transaction.receipt, (err, appleRes) => { + if (err) { + let resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString(), + }, + }; + + return res.json(resObj); + } + + if (iap.isValidated(appleRes)) { + let purchaseDataList = iap.getPurchaseData(appleRes); + if (purchaseDataList.length > 0) { + let correctReceipt = true; + + for (let index in purchaseDataList) { + switch (purchaseDataList[index].productId) { + case 'com.habitrpg.ios.Habitica.4gems': + payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1}); + break; + case 'com.habitrpg.ios.Habitica.8gems': + payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2}); + break; + case 'com.habitrpg.ios.Habitica.20gems': + case 'com.habitrpg.ios.Habitica.21gems': + payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25}); + break; + case 'com.habitrpg.ios.Habitica.42gems': + payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5}); + break; + default: + correctReceipt = false; + } + } + + if (correctReceipt) { + let resObj = { + ok: true, + data: appleRes, + }; + + // yay good! + return res.json(resObj); + } + } + + // wrong receipt content + let resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: 'Incorrect receipt content', + }, + }; + + return res.json(resObj); + } + + // invalid receipt + let resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: 'Invalid receipt', + }, + }; + + return res.json(resObj); + }); + }); + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js new file mode 100644 index 0000000000..4bcd2d0735 --- /dev/null +++ b/website/server/controllers/top-level/payments/paypal.js @@ -0,0 +1,278 @@ +/* eslint-disable camelcase */ + +import nconf from 'nconf'; +import moment from 'moment'; +import _ from 'lodash'; +import payments from '../../../libs/api-v3/payments'; +import ipn from 'paypal-ipn'; +import paypal from 'paypal-rest-sdk'; +import shared from '../../../../../common'; +import cc from 'coupon-code'; +import Bluebird from 'bluebird'; +import { model as Coupon } from '../../../models/coupon'; +import { model as User } from '../../../models/user'; +import { + authWithUrl, + authWithSession, +} from '../../../middlewares/api-v3/auth'; +import { + BadRequest, + NotAuthorized, +} from '../../../libs/api-v3/errors'; + +const BASE_URL = nconf.get('BASE_URL'); + +// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have +// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created +// there, get it's plan.id and store it in config.json +_.each(shared.content.subscriptionBlocks, (block) => { + block.paypalKey = nconf.get(`PAYPAL:billing_plans:${block.key}`); +}); + +paypal.configure({ + mode: nconf.get('PAYPAL:mode'), // sandbox or live + client_id: nconf.get('PAYPAL:client_id'), + client_secret: nconf.get('PAYPAL:client_secret'), +}); + +// TODO better handling of errors +const paypalPaymentCreate = Bluebird.promisify(paypal.payment.create, {context: paypal.payment}); +const paypalPaymentExecute = Bluebird.promisify(paypal.payment.execute, {context: paypal.payment}); +const paypalBillingAgreementCreate = Bluebird.promisify(paypal.billingAgreement.create, {context: paypal.billingAgreement}); +const paypalBillingAgreementExecute = Bluebird.promisify(paypal.billingAgreement.execute, {context: paypal.billingAgreement}); +const paypalBillingAgreementGet = Bluebird.promisify(paypal.billingAgreement.get, {context: paypal.billingAgreement}); +const paypalBillingAgreementCancel = Bluebird.promisify(paypal.billingAgreement.cancel, {context: paypal.billingAgreement}); + +const ipnVerifyAsync = Bluebird.promisify(ipn.verify, {context: ipn}); + +let api = {}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /paypal/checkout Paypal: checkout + * @apiVersion 3.0.0 + * @apiName PaypalCheckout + * @apiGroup Payments + **/ +api.checkout = { + method: 'GET', + url: '/paypal/checkout', + middlewares: [authWithUrl], + async handler (req, res) { + let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; + req.session.gift = req.query.gift; + + let amount = 5.00; + let description = 'HabitRPG gems'; + if (gift) { + if (gift.type === 'gems') { + amount = Number(gift.gems.amount / 4).toFixed(2); + description = `${description} (Gift)`; + } else { + amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2); + description = 'mo. HabitRPG Subscription (Gift)'; + } + } + + let createPayment = { + intent: 'sale', + payer: { payment_method: 'Paypal' }, + redirect_urls: { + return_url: `${BASE_URL}/paypal/checkout/success`, + cancel_url: `${BASE_URL}`, + }, + transactions: [{ + item_list: { + items: [{ + name: description, + // sku: 1, + price: amount, + currency: 'USD', + quantity: 1, + }], + }, + amount: { + currency: 'USD', + total: amount, + }, + description, + }], + }; + + let result = await paypalPaymentCreate(createPayment); + let link = _.find(result.links, { rel: 'approval_url' }).href; + res.redirect(link); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /paypal/checkout/success Paypal: checkout success + * @apiVersion 3.0.0 + * @apiName PaypalCheckoutSuccess + * @apiGroup Payments + **/ +api.checkoutSuccess = { + method: 'GET', + url: '/paypal/checkout/success', + middlewares: [authWithSession], + async handler (req, res) { + let paymentId = req.query.paymentId; + let customerId = req.query.payerID; + + let method = 'buyGems'; + let data = { + user: res.locals.user, + customerId, + paymentMethod: 'Paypal', + }; + + let gift = req.session.gift ? JSON.parse(req.session.gift) : undefined; + delete req.session.gift; + + if (gift) { + gift.member = await User.findById(gift.uuid); + if (gift.type === 'subscription') { + method = 'createSubscription'; + } + + data.paymentMethod = 'Gift'; + data.gift = gift; + } + + await paypalPaymentExecute(paymentId, { payer_id: customerId }); + await payments[method](data); + res.redirect('/'); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /paypal/subscribe Paypal: subscribe + * @apiVersion 3.0.0 + * @apiName PaypalSubscribe + * @apiGroup Payments + **/ +api.subscribe = { + method: 'GET', + url: '/paypal/subscribe', + middlewares: [authWithUrl], + async handler (req, res) { + let sub = shared.content.subscriptionBlocks[req.query.sub]; + + if (sub.discount) { + if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); + let coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); + if (!coupon) throw new NotAuthorized(res.t('invalidCoupon')); + } + + let billingPlanTitle = `HabitRPG Subscription ($${sub.price} every ${sub.months} months, recurring)`; + let billingAgreementAttributes = { + name: billingPlanTitle, + description: billingPlanTitle, + start_date: moment().add({ minutes: 5 }).format(), + plan: { + id: sub.paypalKey, + }, + payer: { + payment_method: 'Paypal', + }, + }; + let billingAgreement = await paypalBillingAgreementCreate(billingAgreementAttributes); + + req.session.paypalBlock = req.query.sub; + let link = _.find(billingAgreement.links, { rel: 'approval_url' }).href; + res.redirect(link); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /paypal/subscribe/success Paypal: subscribe success + * @apiVersion 3.0.0 + * @apiName PaypalSubscribeSuccess + * @apiGroup Payments + **/ +api.subscribeSuccess = { + method: 'GET', + url: '/paypal/subscribe/success', + middlewares: [authWithSession], + async handler (req, res) { + let user = res.locals.user; + let block = shared.content.subscriptionBlocks[req.session.paypalBlock]; + delete req.session.paypalBlock; + + let result = await paypalBillingAgreementExecute(req.query.token, {}); + await payments.createSubscription({ + user, + customerId: result.id, + paymentMethod: 'Paypal', + sub: block, + }); + + res.redirect('/'); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /paypal/subscribe/cancel Paypal: subscribe cancel + * @apiVersion 3.0.0 + * @apiName PaypalSubscribeCancel + * @apiGroup Payments + **/ +api.subscribeCancel = { + method: 'GET', + url: '/paypal/subscribe/cancel', + middlewares: [authWithUrl], + async handler (req, res) { + let user = res.locals.user; + let customerId = user.purchased.plan.customerId; + if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription')); + + let customer = await paypalBillingAgreementGet(customerId); + + let nextBillingDate = customer.agreement_details.next_billing_date; + if (customer.agreement_details.cycles_completed === '0') { // hasn't billed yet + throw new BadRequest(res.t('planNotActive', { nextBillingDate })); + } + + await paypalBillingAgreementCancel(customerId, { note: res.t('cancelingSubscription') }); + await payments.cancelSubscription({ + user, + paymentMethod: 'Paypal', + nextBill: nextBillingDate, + }); + + res.redirect('/'); + }, +}; + +// General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their +// recurring paypal payments in their paypal dashboard. TODO ? Remove this when we can move to webhooks or some other solution + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /paypal/ipn Paypal IPN + * @apiVersion 3.0.0 + * @apiName PaypalIpn + * @apiGroup Payments + **/ +api.ipn = { + method: 'POST', + url: '/paypal/ipn', + async handler (req, res) { + res.sendStatus(200); + + await ipnVerifyAsync(req.body); + + if (req.body.txn_type === 'recurring_payment_profile_cancel' || req.body.txn_type === 'subscr_cancel') { + let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }); + if (user) { + await payments.cancelSubscription({ user, paymentMethod: 'Paypal' }); + } + } + }, +}; + +module.exports = api; diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js new file mode 100644 index 0000000000..2ac8c863f7 --- /dev/null +++ b/website/server/controllers/top-level/payments/stripe.js @@ -0,0 +1,169 @@ +import stripeModule from 'stripe'; +import shared from '../../../../../common'; +import { + BadRequest, + NotAuthorized, +} from '../../../libs/api-v3/errors'; +import { model as Coupon } from '../../../models/coupon'; +import payments from '../../../libs/api-v3/payments'; +import nconf from 'nconf'; +import { model as User } from '../../../models/user'; +import cc from 'coupon-code'; +import { + authWithHeaders, + authWithUrl, +} from '../../../middlewares/api-v3/auth'; + +const stripe = stripeModule(nconf.get('STRIPE_API_KEY')); + +let api = {}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /stripe/checkout Stripe checkout + * @apiVersion 3.0.0 + * @apiName StripeCheckout + * @apiGroup Payments + * + * @apiParam {string} id Body parameter - The token + * @apiParam {string} email Body parameter - the customer email + * @apiParam {string} gift Query parameter - stringified json object, gift + * @apiParam {string} sub Query parameter - subscription, possible values are: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo + * @apiParam {string} coupon Query parameter - coupon for the matching subscription, required only for certain subscriptions + * + * @apiSuccess {Object} data Empty object + **/ +api.checkout = { + method: 'POST', + url: '/stripe/checkout', + middlewares: [authWithHeaders()], + async handler (req, res) { + let token = req.body.id; + let user = res.locals.user; + let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; + let sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false; + let coupon; + let response; + + if (!token) throw new BadRequest('Missing req.body.id'); + + if (sub) { + if (sub.discount) { + if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); + coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); + if (!coupon) throw new BadRequest(res.t('invalidCoupon')); + } + + response = await stripe.customers.create({ + email: req.body.email, + metadata: { uuid: user._id }, + card: token, + plan: sub.key, + }); + } else { + let amount = 500; // $5 + + if (gift) { + if (gift.type === 'subscription') { + amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`; + } else { + amount = `${gift.gems.amount / 4 * 100}`; + } + } + + response = await stripe.charges.create({ + amount, + currency: 'usd', + card: token, + }); + } + + if (sub) { + await payments.createSubscription({ + user, + customerId: response.id, + paymentMethod: 'Stripe', + sub, + }); + } else { + let method = 'buyGems'; + let data = { + user, + customerId: response.id, + paymentMethod: 'Stripe', + gift, + }; + + if (gift) { + let member = await User.findById(gift.uuid); + gift.member = member; + if (gift.type === 'subscription') method = 'createSubscription'; + data.paymentMethod = 'Gift'; + } + + await payments[method](data); + } + + res.respond(200, {}); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {post} /stripe/subscribe/edit Edit Stripe subscription + * @apiVersion 3.0.0 + * @apiName StripeSubscribeEdit + * @apiGroup Payments + * + * @apiParam {string} id Body parameter - The token + * + * @apiSuccess {Object} data Empty object + **/ +api.subscribeEdit = { + method: 'POST', + url: '/stripe/subscribe/edit', + middlewares: [authWithHeaders()], + async handler (req, res) { + let token = req.body.id; + let user = res.locals.user; + let customerId = user.purchased.plan.customerId; + + if (!customerId) throw new NotAuthorized(res.t('missingSubscription')); + if (!token) throw new BadRequest('Missing req.body.id'); + + let subscriptions = await stripe.customers.listSubscriptions(customerId); + let subscriptionId = subscriptions.data[0].id; + await stripe.customers.updateSubscription(customerId, subscriptionId, { card: token }); + + res.respond(200, {}); + }, +}; + +/** + * @apiIgnore Payments are considered part of the private API + * @api {get} /stripe/subscribe/cancel Cancel Stripe subscription + * @apiVersion 3.0.0 + * @apiName StripeSubscribeCancel + * @apiGroup Payments + **/ +api.subscribeCancel = { + method: 'GET', + url: '/stripe/subscribe/cancel', + middlewares: [authWithUrl], + async handler (req, res) { + let user = res.locals.user; + if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription')); + + let customer = await stripe.customers.retrieve(user.purchased.plan.customeerId); + await stripe.customers.del(user.purchased.plan.customerId); + await payments.cancelSubscriptoin({ + user, + nextBill: customer.subscription.current_period_end * 1000, // timestamp in seconds + paymentMethod: 'Stripe', + }); + + res.redirect('/'); + }, +}; + +module.exports = api; diff --git a/website/server/index.js b/website/server/index.js new file mode 100644 index 0000000000..ec8e882c99 --- /dev/null +++ b/website/server/index.js @@ -0,0 +1,46 @@ +'use strict'; +/* eslint-disable global-require, no-process-env */ + +// Register babel hook so we can write the real entry file (server.js) in ES6 +// In production, the es6 code is pre-transpiled so it doesn't need it +if (process.env.NODE_ENV !== 'production') { + require('babel-register'); +} + +// The BabelJS polyfill is needed in production too +require('babel-polyfill'); + +// Setup Bluebird as the global promise library +global.Promise = require('bluebird'); + +// Initialize configuration BEFORE anything +const setupNconf = require('./libs/api-v3/setupNconf'); +setupNconf(); + +const nconf = require('nconf'); + +const cluster = require('cluster'); +const logger = require('./libs/api-v3/logger'); + +const IS_PROD = nconf.get('IS_PROD'); +const IS_DEV = nconf.get('IS_DEV'); +const CORES = Number(nconf.get('WEB_CONCURRENCY')) || 0; + +// Initialize New Relic +if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') require('newrelic'); + +// Setup the cluster module +if (CORES !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) { + // Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production) + for (let i = 0; i < CORES; i += 1) { + cluster.fork(); + } + + cluster.on('disconnect', function onWorkerDisconnect (worker) { + let w = cluster.fork(); // replace the dead worker + + logger.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); + }); +} else { + module.exports = require('./server.js'); +} diff --git a/website/src/libs/analytics.js b/website/server/libs/api-v2/analytics.js similarity index 98% rename from website/src/libs/analytics.js rename to website/server/libs/api-v2/analytics.js index dffa7c2a9b..6c1ccd3020 100644 --- a/website/src/libs/analytics.js +++ b/website/server/libs/api-v2/analytics.js @@ -1,7 +1,7 @@ require('./i18n'); var _ = require('lodash'); -var Content = require('../../../common').content; +var Content = require('../../../../common').content; var Amplitude = require('amplitude'); var googleAnalytics = require('universal-analytics'); diff --git a/website/src/libs/buildManifest.js b/website/server/libs/api-v2/buildManifest.js similarity index 91% rename from website/src/libs/buildManifest.js rename to website/server/libs/api-v2/buildManifest.js index e2b337860f..bfbab1421a 100644 --- a/website/src/libs/buildManifest.js +++ b/website/server/libs/api-v2/buildManifest.js @@ -2,7 +2,7 @@ var fs = require('fs'); var path = require('path'); var nconf = require('nconf'); var _ = require('lodash'); -var manifestFiles = require("../../public/manifest.json"); +var manifestFiles = require("../../../client/manifest.json"); var IS_PROD = nconf.get('NODE_ENV') === 'production'; var buildFiles = []; @@ -15,7 +15,7 @@ var walk = function(folder){ if(fs.statSync(file).isDirectory()){ walk(file); }else{ - var relFolder = path.relative(path.join(__dirname, "/../../build"), folder); + var relFolder = path.relative(path.join(__dirname, "/../../../build"), folder); var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); if(relFolder){ @@ -28,7 +28,7 @@ var walk = function(folder){ }); }; -walk(path.join(__dirname, "/../../build")); +walk(path.join(__dirname, "/../../../build")); var getBuildUrl = module.exports.getBuildUrl = function(url){ if(buildFiles[url]) return '/' + buildFiles[url]; @@ -56,4 +56,4 @@ module.exports.getManifestFiles = function(page){ } return code; -}; \ No newline at end of file +}; diff --git a/website/src/libs/firebase.js b/website/server/libs/api-v2/firebase.js similarity index 87% rename from website/src/libs/firebase.js rename to website/server/libs/api-v2/firebase.js index 84a90b22d4..8a8d9f002c 100644 --- a/website/src/libs/firebase.js +++ b/website/server/libs/api-v2/firebase.js @@ -6,6 +6,8 @@ var firebaseConfig = nconf.get('FIREBASE'); var firebaseRef; var isFirebaseEnabled = (nconf.get('NODE_ENV') === 'production') && (firebaseConfig.ENABLED === 'true'); +import { TAVERN_ID } from '../../models/group'; + // Setup if(isFirebaseEnabled){ firebaseRef = new Firebase('https://' + firebaseConfig.APP + '.firebaseio.com'); @@ -24,7 +26,7 @@ api.updateGroupData = function(group){ // TODO is throw ok? we don't have callbacks if(!group) throw new Error('group is required.'); // Return in case of tavern (comparison working because we use string for _id) - if(group._id === 'habitrpg') return; + if(group._id === TAVERN_ID) return; firebaseRef.child('rooms/' + group._id) .set({ @@ -35,7 +37,7 @@ api.updateGroupData = function(group){ api.addUserToGroup = function(groupId, userId){ if(!isFirebaseEnabled) return; if(!userId || !groupId) throw new Error('groupId, userId are required.'); - if(groupId === 'habitrpg') return; + if(groupId === TAVERN_ID) return; firebaseRef.child('members/' + groupId + '/' + userId) .set(true); @@ -47,7 +49,7 @@ api.addUserToGroup = function(groupId, userId){ api.removeUserFromGroup = function(groupId, userId){ if(!isFirebaseEnabled) return; if(!userId || !groupId) throw new Error('groupId, userId are required.'); - if(groupId === 'habitrpg') return; + if(groupId === TAVERN_ID) return; firebaseRef.child('members/' + groupId + '/' + userId) .remove(); @@ -59,18 +61,18 @@ api.removeUserFromGroup = function(groupId, userId){ api.deleteGroup = function(groupId){ if(!isFirebaseEnabled) return; if(!groupId) throw new Error('groupId is required.'); - if(groupId === 'habitrpg') return; + if(groupId === TAVERN_ID) return; firebaseRef.child('rooms/' + groupId) .remove(); - // FIXME not really necessary as long as we only store room data, + // TODO not really necessary as long as we only store room data, // as empty objects are automatically deleted (/members/... in future...) firebaseRef.child('members/' + groupId) .remove(); }; -// FIXME not really necessary as long as we only store room data, +// TODO not really necessary as long as we only store room data, // as empty objects are automatically deleted api.deleteUser = function(userId){ if(!isFirebaseEnabled) return; @@ -78,4 +80,4 @@ api.deleteUser = function(userId){ firebaseRef.child('users/' + userId) .remove(); -}; \ No newline at end of file +}; diff --git a/website/src/libs/i18n.js b/website/server/libs/api-v2/i18n.js similarity index 90% rename from website/src/libs/i18n.js rename to website/server/libs/api-v2/i18n.js index 3edf901279..e8295deb03 100644 --- a/website/src/libs/i18n.js +++ b/website/server/libs/api-v2/i18n.js @@ -1,11 +1,12 @@ var fs = require('fs'), path = require('path'), _ = require('lodash'), - User = require('../models/user').model, - shared = require('../../../common'), + User = require('../../models/user').model, + accepts = require('accepts'), + shared = require('../../../../common'), translations = {}; -var localePath = path.join(__dirname, "/../../../common/locales/") +var localePath = path.join(__dirname, "/../../../../common/locales/") var loadTranslations = function(locale){ var files = fs.readdirSync(path.join(localePath, locale)); @@ -54,7 +55,7 @@ _.each(langCodes, function(code){ lang.momentLangCode = (momentLangsMapping[code] || code); try{ // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files - var f = fs.readFileSync(path.join(__dirname, '/../../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); + var f = fs.readFileSync(path.join(__dirname, '/../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); momentLangs[code] = f; }catch (e){} }); @@ -94,7 +95,9 @@ var chineseVersions = { var getUserLanguage = function(req, res, next){ var getFromBrowser = function(){ - var acceptable = _(req.acceptedLanguages).map(function(lang){ + var acceptedLanguages = accepts(req).languages(); + + var acceptable = _(acceptedLanguages).map(function(lang){ return lang.slice(0, 2); }).uniq().value(); @@ -103,7 +106,7 @@ var getUserLanguage = function(req, res, next){ var iAcceptedCompleteLang = (matches.length > 0) ? multipleVersionsLanguages.indexOf(matches[0].toLowerCase()) : -1; if(iAcceptedCompleteLang !== -1){ - var acceptedCompleteLang = _.find(req.acceptedLanguages, function(accepted){ + var acceptedCompleteLang = _.find(acceptedLanguages, function(accepted){ return accepted.slice(0, 2) == multipleVersionsLanguages[iAcceptedCompleteLang]; }); diff --git a/website/src/libs/logging.js b/website/server/libs/api-v2/logging.js similarity index 94% rename from website/src/libs/logging.js rename to website/server/libs/api-v2/logging.js index f832adb6d5..9737159f43 100644 --- a/website/src/libs/logging.js +++ b/website/server/libs/api-v2/logging.js @@ -22,9 +22,9 @@ if (nconf.get('LOGGLY:enabled')){ if (!logger) { logger = new (winston.Logger)({}); + logger.add(winston.transports.Console, {colorize:true}); // TODO remove if (nconf.get('NODE_ENV') !== 'production') { - logger.add(winston.transports.Console, {colorize:true}); logger.add(winston.transports.File, {filename: 'habitrpg.log'}); } } diff --git a/website/src/libs/utils.js b/website/server/libs/api-v2/utils.js similarity index 90% rename from website/src/libs/utils.js rename to website/server/libs/api-v2/utils.js index 6266843edf..8657bf6513 100644 --- a/website/src/libs/utils.js +++ b/website/server/libs/api-v2/utils.js @@ -4,8 +4,8 @@ var crypto = require('crypto'); var path = require("path"); var request = require('request'); -// Set when utils.setupConfig is run -var isProd, baseUrl; +const IS_PROD = nconf.get('IS_PROD'); +const BASE_URL = nconf.get('BASE_URL'); module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport({ @@ -17,7 +17,7 @@ module.exports.sendEmail = function(mailData) { }); smtpTransport.sendMail(mailData, function(error, response){ - var logging = require('./logging'); + var logging = require('./api-v2/logging'); if(error) logging.error(error); else logging.info("Message sent: " + response.message); smtpTransport.close(); // shut down the connection pool, no more messages @@ -60,7 +60,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; var variables = [ - {name: 'BASE_URL', content: baseUrl} + {name: 'BASE_URL', content: BASE_URL} ].concat(variables || []); // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed @@ -121,7 +121,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso }); } - if(isProd && mailingInfoArray.length > 0){ + if(IS_PROD && mailingInfoArray.length > 0){ request({ url: nconf.get('EMAIL_SERVER:url') + '/job', method: 'POST', @@ -168,20 +168,12 @@ module.exports.analytics = { track: function() { }, trackPurchase: function() { * Load nconf and define default configuration values if config.json or ENV vars are not found */ module.exports.setupConfig = function(){ - nconf.argv() - .env() - //.file('defaults', path.join(path.resolve(__dirname, '../config.json.example'))) - .file('user', path.join(path.resolve(__dirname, './../../../config.json'))); - - if (nconf.get('NODE_ENV') === "development") + if (nconf.get('IS_DEV')) Error.stackTraceLimit = Infinity; - if (nconf.get('NODE_ENV') === 'production' && nconf.get('NEW_RELIC_ENABLED') === 'true') + if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') require('newrelic'); - isProd = nconf.get('NODE_ENV') === 'production'; - baseUrl = nconf.get('BASE_URL'); - - var analytics = isProd && require('./analytics'); + var analytics = IS_PROD && require('./api-v2/analytics'); var analyticsTokens = { amplitudeToken: nconf.get('AMPLITUDE_KEY'), googleAnalytics: nconf.get('GA_ID') diff --git a/website/src/libs/webhook.js b/website/server/libs/api-v2/webhook.js similarity index 100% rename from website/src/libs/webhook.js rename to website/server/libs/api-v2/webhook.js diff --git a/website/server/libs/api-v3/amazonPayments.js b/website/server/libs/api-v3/amazonPayments.js new file mode 100644 index 0000000000..4d3a3756b8 --- /dev/null +++ b/website/server/libs/api-v3/amazonPayments.js @@ -0,0 +1,62 @@ +import amazonPayments from 'amazon-payments'; +import nconf from 'nconf'; +import common from '../../../../common'; +import Bluebird from 'bluebird'; +import { + BadRequest, +} from './errors'; + +// TODO better handling of errors + +const i18n = common.i18n; +const IS_PROD = nconf.get('NODE_ENV') === 'production'; + +let amzPayment = amazonPayments.connect({ + environment: amazonPayments.Environment[IS_PROD ? 'Production' : 'Sandbox'], + sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), + mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'), + mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'), + clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'), +}); + +let getTokenInfo = Bluebird.promisify(amzPayment.api.getTokenInfo, {context: amzPayment.api}); +let createOrderReferenceId = Bluebird.promisify(amzPayment.offAmazonPayments.createOrderReferenceForId, {context: amzPayment.offAmazonPayments}); +let setOrderReferenceDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setOrderReferenceDetails, {context: amzPayment.offAmazonPayments}); +let confirmOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.confirmOrderReference, {context: amzPayment.offAmazonPayments}); +let closeOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.closeOrderReference, {context: amzPayment.offAmazonPayments}); +let setBillingAgreementDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setBillingAgreementDetails, {context: amzPayment.offAmazonPayments}); +let confirmBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.confirmBillingAgreement, {context: amzPayment.offAmazonPayments}); +let closeBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.closeBillingAgreement, {context: amzPayment.offAmazonPayments}); + +let authorizeOnBillingAgreement = (inputSet) => { + return new Promise((resolve, reject) => { + amzPayment.offAmazonPayments.authorizeOnBillingAgreement(inputSet, (err, response) => { + if (err) return reject(err); + if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful'))); + return resolve(response); + }); + }); +}; + +let authorize = (inputSet) => { + return new Promise((resolve, reject) => { + amzPayment.offAmazonPayments.authorize(inputSet, (err, response) => { + if (err) return reject(err); + if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful'))); + return resolve(response); + }); + }); +}; + +module.exports = { + getTokenInfo, + createOrderReferenceId, + setOrderReferenceDetails, + confirmOrderReference, + closeOrderReference, + confirmBillingAgreement, + setBillingAgreementDetails, + closeBillingAgreement, + authorizeOnBillingAgreement, + authorize, +}; diff --git a/website/server/libs/api-v3/analyticsService.js b/website/server/libs/api-v3/analyticsService.js new file mode 100644 index 0000000000..b810ac1858 --- /dev/null +++ b/website/server/libs/api-v3/analyticsService.js @@ -0,0 +1,237 @@ +/* eslint-disable camelcase */ +import nconf from 'nconf'; +import Amplitude from 'amplitude'; +import Bluebird from 'bluebird'; +import googleAnalytics from 'universal-analytics'; +import { + each, + omit, +} from 'lodash'; +import { content as Content } from '../../../../common'; + +const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY'); +const GA_TOKEN = nconf.get('GA_ID'); +const GA_POSSIBLE_LABELS = ['gaLabel', 'itemKey']; +const GA_POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost']; +const AMPLITUDE_PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue']; + +let amplitude = new Amplitude(AMPLIUDE_TOKEN); +let ga = googleAnalytics(GA_TOKEN); + +let _lookUpItemName = (itemKey) => { + if (!itemKey) return; + + let gear = Content.gear.flat[itemKey]; + let egg = Content.eggs[itemKey]; + let food = Content.food[itemKey]; + let hatchingPotion = Content.hatchingPotions[itemKey]; + let quest = Content.quests[itemKey]; + let spell = Content.special[itemKey]; + + let itemName; + + if (gear) { + itemName = gear.text(); + } else if (egg) { + itemName = `${egg.text()} Egg`; + } else if (food) { + itemName = food.text(); + } else if (hatchingPotion) { + itemName = `${hatchingPotion.text()} Hatching Potion`; + } else if (quest) { + itemName = quest.text(); + } else if (spell) { + itemName = spell.text(); + } + + return itemName; +}; + +let _formatUserData = (user) => { + let properties = {}; + + if (user.stats) { + properties.Class = user.stats.class; + properties.Experience = Math.floor(user.stats.exp); + properties.Gold = Math.floor(user.stats.gp); + properties.Health = Math.ceil(user.stats.hp); + properties.Level = user.stats.lvl; + properties.Mana = Math.floor(user.stats.mp); + } + + properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; + + if (user.habits && user.dailys && user.todos && user.rewards) { + properties['Number Of Tasks'] = { + habits: user.habits.length, + dailys: user.dailys.length, + todos: user.todos.length, + rewards: user.rewards.length, + }; + } + + if (user.contributor && user.contributor.level) { + properties.contributorLevel = user.contributor.level; + } + + if (user.purchased && user.purchased.plan.planId) { + properties.subscription = user.purchased.plan.planId; + } + + return properties; +}; + + +let _formatDataForAmplitude = (data) => { + let event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB); + + let ampData = { + user_id: data.uuid || 'no-user-id-was-provided', + platform: 'server', + event_properties, + }; + + if (data.user) { + ampData.user_properties = _formatUserData(data.user); + } + + let itemName = _lookUpItemName(data.itemKey); + + if (itemName) { + event_properties.itemName = itemName; + } + + return ampData; +}; + +let _sendDataToAmplitude = (eventType, data) => { + let amplitudeData = _formatDataForAmplitude(data); + + amplitudeData.event_type = eventType; + + return new Bluebird((resolve, reject) => { + amplitude.track(amplitudeData) + .then(resolve) + .catch(reject); + }); +}; + +let _generateLabelForGoogleAnalytics = (data) => { + let label; + + each(GA_POSSIBLE_LABELS, (key) => { + if (data[key]) { + label = data[key]; + return false; // exit each early + } + }); + + return label; +}; + +let _generateValueForGoogleAnalytics = (data) => { + let value; + + each(GA_POSSIBLE_VALUES, (key) => { + if (data[key]) { + value = data[key]; + return false; // exit each early + } + }); + + return value; +}; + +let _sendDataToGoogle = (eventType, data) => { + let eventData = { + ec: data.category, + ea: eventType, + }; + + let label = _generateLabelForGoogleAnalytics(data); + + if (label) { + eventData.el = label; + } + + let value = _generateValueForGoogleAnalytics(data); + + if (value) { + eventData.ev = value; + } + + return new Bluebird((resolve, reject) => { + ga.event(eventData, (err) => { + if (err) return reject(err); + resolve(); + }); + }); +}; + +let _sendPurchaseDataToAmplitude = (data) => { + let amplitudeData = _formatDataForAmplitude(data); + + amplitudeData.event_type = 'purchase'; + amplitudeData.revenue = data.purchaseValue; + + return new Bluebird((resolve, reject) => { + amplitude.track(amplitudeData) + .then(resolve) + .catch(reject); + }); +}; + +let _sendPurchaseDataToGoogle = (data) => { + let label = data.paymentMethod; + let type = data.purchaseType; + let price = data.purchaseValue; + let qty = data.quantity; + let sku = data.sku; + let itemKey = data.itemPurchased; + let variation = type; + + if (data.gift) variation += ' - Gift'; + + let eventData = { + ec: 'commerce', + ea: type, + el: label, + ev: price, + }; + + return new Bluebird((resolve) => { + ga.event(eventData).send(); + + ga.transaction(data.uuid, price) + .item(price, qty, sku, itemKey, variation) + .send(); + + resolve(); + }); +}; + +function track (eventType, data) { + return Bluebird.all([ + _sendDataToAmplitude(eventType, data), + _sendDataToGoogle(eventType, data), + ]); +} + +function trackPurchase (data) { + return Bluebird.all([ + _sendPurchaseDataToAmplitude(data), + _sendPurchaseDataToGoogle(data), + ]); +} + +// Stub for non-prod environments +let mockAnalyticsService = { + track: () => { }, + trackPurchase: () => { }, +}; + +module.exports = { + track, + trackPurchase, + mockAnalyticsService, +}; diff --git a/website/server/libs/api-v3/baseModel.js b/website/server/libs/api-v3/baseModel.js new file mode 100644 index 0000000000..009b735fa6 --- /dev/null +++ b/website/server/libs/api-v3/baseModel.js @@ -0,0 +1,79 @@ +import { v4 as uuid } from 'uuid'; +import validator from 'validator'; +import objectPath from 'object-path'; // TODO use lodash's unset once v4 is out +import _ from 'lodash'; + +module.exports = function baseModel (schema, options = {}) { + if (options._id !== false) { + schema.add({ + _id: { + type: String, + default: uuid, + validate: [validator.isUUID, 'Invalid uuid.'], + }, + }); + } + + if (options.timestamps) { + schema.add({ + createdAt: { + type: Date, + default: Date.now, + }, + updatedAt: { + type: Date, + default: Date.now, + }, + }); + } + + if (options.timestamps) { + schema.pre('save', function updateUpdatedAt (next) { + if (!this.isNew) this.updatedAt = Date.now(); + next(); + }); + + schema.pre('update', function preUpdateModel () { + this.update({}, { $set: { updatedAt: new Date() } }); + }); + } + + let noSetFields = ['createdAt', 'updatedAt']; + let privateFields = ['__v']; + + if (Array.isArray(options.noSet)) noSetFields.push(...options.noSet); + // This method accepts an additional array of fields to be sanitized that can be passed at runtime + schema.statics.sanitize = function sanitize (objToSanitize = {}, additionalFields = []) { + noSetFields.concat(additionalFields).forEach((fieldPath) => { + objectPath.del(objToSanitize, fieldPath); + }); + + // Allow a sanitize transform function to be used + return options.sanitizeTransform ? options.sanitizeTransform(objToSanitize) : objToSanitize; + }; + + if (Array.isArray(options.private)) privateFields.push(...options.private); + + if (!schema.options.toJSON) schema.options.toJSON = {}; + schema.options.toJSON.transform = function transformToObject (doc, plainObj) { + privateFields.forEach((fieldPath) => { + objectPath.del(plainObj, fieldPath); + }); + + // Always return `id` + if (!plainObj.id && plainObj._id) plainObj.id = plainObj._id; + + // Allow an additional toJSON transform function to be used + return options.toJSONTransform ? options.toJSONTransform(plainObj, doc) : plainObj; + }; + + schema.statics.getModelPaths = function getModelPaths () { + return _.reduce(this.schema.paths, (result, field, path) => { + if (privateFields.indexOf(path) === -1) { + result[path] = field.instance || 'Boolean'; + } + + return result; + }, {}); + }; +}; diff --git a/website/server/libs/api-v3/buildManifest.js b/website/server/libs/api-v3/buildManifest.js new file mode 100644 index 0000000000..55db474354 --- /dev/null +++ b/website/server/libs/api-v3/buildManifest.js @@ -0,0 +1,62 @@ +import fs from 'fs'; +import path from 'path'; +import nconf from 'nconf'; + +const MANIFEST_FILE_PATH = path.join(__dirname, '/../../../client/manifest.json'); +const BUILD_FOLDER_PATH = path.join(__dirname, '/../../../build'); +let manifestFiles = require(MANIFEST_FILE_PATH); + +const IS_PROD = nconf.get('IS_PROD'); +let buildFiles = []; + +function _walk (folder) { + let files = fs.readdirSync(folder); + + files.forEach((fileName) => { + let file = `${folder}/${fileName}`; + + if (fs.statSync(file).isDirectory()) { + _walk(file); + } else { + let relFolder = path.relative(BUILD_FOLDER_PATH, folder); + let original = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); // Match the hash part of the filename + + if (relFolder) { + original = `${relFolder}/${original}`; + fileName = `${relFolder}/${fileName}`; + } + + buildFiles[original] = fileName; + } + }); +} + +// Walks through all the files in the build directory +// and creates a map of original files names and hashed files names +_walk(BUILD_FOLDER_PATH); + +export function getBuildUrl (url) { + return `/${buildFiles[url] || url}`; +} + +export function getManifestFiles (page) { + let files = manifestFiles[page]; + + if (!files) throw new Error(`Page "${page}" not found!`); + + let htmlCode = ''; + + if (IS_PROD) { + htmlCode += ``; // eslint-disable-line prefer-template + htmlCode += ``; // eslint-disable-line prefer-template + } else { + files.css.forEach((file) => { + htmlCode += ``; + }); + files.js.forEach((file) => { + htmlCode += ``; + }); + } + + return htmlCode; +} diff --git a/website/server/libs/api-v3/collectionManipulators.js b/website/server/libs/api-v3/collectionManipulators.js new file mode 100644 index 0000000000..95d3981601 --- /dev/null +++ b/website/server/libs/api-v3/collectionManipulators.js @@ -0,0 +1,22 @@ +import { + findIndex, + isPlainObject, +} from 'lodash'; + +export function removeFromArray (array, element) { + let elementIndex; + + if (isPlainObject(element)) { + elementIndex = findIndex(array, element); + } else { + elementIndex = array.indexOf(element); + } + + if (elementIndex !== -1) { + let removedElement = array[elementIndex]; + array.splice(elementIndex, 1); + return removedElement; + } + + return false; +} diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js new file mode 100644 index 0000000000..da076b54ce --- /dev/null +++ b/website/server/libs/api-v3/cron.js @@ -0,0 +1,280 @@ +import moment from 'moment'; +import common from '../../../../common/'; +import { preenUserHistory } from '../../libs/api-v3/preening'; +import _ from 'lodash'; +import nconf from 'nconf'; + +const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; +const shouldDo = common.shouldDo; +const scoreTask = common.ops.scoreTask; +// const maxPMs = 200; + +let CLEAR_BUFFS = { + str: 0, + int: 0, + per: 0, + con: 0, + stealth: 0, + streaks: false, +}; + +function grantEndOfTheMonthPerks (user, now) { + let plan = user.purchased.plan; + + if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) { + plan.gemsBought = 0; // reset gem-cap + plan.dateUpdated = now; + // For every month, inc their "consecutive months" counter. Give perks based on consecutive blocks + // If they already got perks for those blocks (eg, 6mo subscription, subscription gifts, etc) - then dec the offset until it hits 0 + // TODO use month diff instead of ++ / --? see https://github.com/HabitRPG/habitrpg/issues/4317 + _.defaults(plan.consecutive, {count: 0, offset: 0, trinkets: 0, gemCapExtra: 0}); + + plan.consecutive.count++; + + if (plan.consecutive.offset > 0) { + plan.consecutive.offset--; + } else if (plan.consecutive.count % 3 === 0) { // every 3 months + plan.consecutive.trinkets++; + plan.consecutive.gemCapExtra += 5; + if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25; // cap it at 50 (hard 25 limit + extra 25) + } + } +} + +function removeTerminatedSubscription (user) { + // If subscription's termination date has arrived + let plan = user.purchased.plan; + + if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(new Date())) { + _.merge(plan, { + planId: null, + customerId: null, + paymentMethod: null, + }); + + _.merge(plan.consecutive, { + count: 0, + offset: 0, + gemCapExtra: 0, + }); + + user.markModified('purchased.plan'); + } +} + +function performSleepTasks (user, tasksByType, now) { + user.stats.buffs = _.cloneDeep(CLEAR_BUFFS); + + tasksByType.dailys.forEach((daily) => { + let completed = daily.completed; + let thatDay = moment(now).subtract({days: 1}); + + if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { + // TODO also untick checklists if the Daily was due on previous missed days, if two or more days were missed at once -- https://github.com/HabitRPG/habitrpg/pull/7218#issuecomment-219256016 + daily.checklist.forEach(box => box.completed = false); + } + + daily.completed = false; + }); +} + +// Perform various beginning-of-day reset actions. +export function cron (options = {}) { + let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options; + + user.auth.timestamps.loggedin = now; + user.lastCron = now; + user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; + // User is only allowed a certain number of drops a day. This resets the count. + if (user.items.lastDrop.count > 0) user.items.lastDrop.count = 0; + + // "Perfect Day" achievement for perfect-days + let perfect = true; + + if (user.isSubscribed()) { + grantEndOfTheMonthPerks(user, now); + if (!CRON_SAFE_MODE) removeTerminatedSubscription(user); + } + + // User is resting at the inn. + // On cron, buffs are cleared and all dailies are reset without performing damage + if (user.preferences.sleep === true) { + performSleepTasks(user, tasksByType, now); + return; + } + + let multiDaysCountAsOneDay = true; + // If the user does not log in for two or more days, cron (mostly) acts as if it were only one day. + // When site-wide difficulty settings are introduced, this can be a user preference option. + + // Tally each task + let todoTally = 0; + + tasksByType.todos.forEach(task => { // make uncompleted To-Dos redder (further incentive to complete them) + scoreTask({ + task, + user, + direction: 'down', + cron: true, + times: multiDaysCountAsOneDay ? 1 : daysMissed, + }); + + todoTally += task.value; + }); + + // For incomplete Dailys, add value (further incentive), deduct health, keep records for later decreasing the nightly mana gain + let dailyChecked = 0; // how many dailies were checked? + let dailyDueUnchecked = 0; // how many dailies were un-checked? + if (!user.party.quest.progress.down) user.party.quest.progress.down = 0; + + tasksByType.dailys.forEach((task) => { + let completed = task.completed; + // Deduct points for missed Daily tasks + let EvadeTask = 0; + let scheduleMisses = daysMissed; + + if (completed) { + dailyChecked += 1; + } else { + // dailys repeat, so need to calculate how many they've missed according to their own schedule + scheduleMisses = 0; + + for (let i = 0; i < daysMissed; i++) { + let thatDay = moment(now).subtract({days: i + 1}); + + if (shouldDo(thatDay.toDate(), task, user.preferences)) { + scheduleMisses++; + if (user.stats.buffs.stealth) { + user.stats.buffs.stealth--; + EvadeTask++; + } + if (multiDaysCountAsOneDay) break; + } + } + + if (scheduleMisses > EvadeTask) { + // The user did not complete this due Daily (but no penalty if cron is running in safe mode). + if (CRON_SAFE_MODE) { + dailyChecked += 1; // allows full allotment of mp to be gained + } else { + perfect = false; + + if (task.checklist && task.checklist.length > 0) { // Partially completed checklists dock fewer mana points + let fractionChecked = _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length; + dailyDueUnchecked += 1 - fractionChecked; + dailyChecked += fractionChecked; + } else { + dailyDueUnchecked += 1; + } + + let delta = scoreTask({ + user, + task, + direction: 'down', + times: multiDaysCountAsOneDay ? 1 : scheduleMisses - EvadeTask, + cron: true, + }); + + // Apply damage from a boss, less damage for Trivial priority (difficulty) + user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); + // NB: Medium and Hard priorities do not increase damage from boss. This was by accident + // initially, and when we realised, we could not fix it because users are used to + // their Medium and Hard Dailies doing an Easy amount of damage from boss. + // Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future + // setting between Trivial and Easy. + } + } + } + + task.history.push({ + date: Number(new Date()), + value: task.value, + }); + task.completed = false; + + if (completed || scheduleMisses > 0) { + task.checklist.forEach(i => i.completed = false); + } + }); + + // move singleton Habits towards yellow. + tasksByType.habits.forEach((task) => { // slowly reset 'onlies' value to 0 + if (task.up === false || task.down === false) { + task.value = Math.abs(task.value) < 0.1 ? 0 : task.value = task.value / 2; + } + }); + + // Finished tallying + user.history.todos.push({date: now, value: todoTally}); + + // tally experience + let expTally = user.stats.exp; + let lvl = 0; // iterator + while (lvl < user.stats.lvl - 1) { + lvl++; + expTally += common.tnl(lvl); + } + + user.history.exp.push({date: now, value: expTally}); + + // preen user history so that it doesn't become a performance problem + // also for subscribed users but differently + // TODO also do while resting in the inn. Note that later we'll be allowing the value/color of tasks to change while sleeping (https://github.com/HabitRPG/habitrpg/issues/5232), so the code in performSleepTasks() might be best merged back into here for that. Perhaps wait until then to do preen history for sleeping users. + preenUserHistory(user, tasksByType, user.preferences.timezoneOffset); + + if (perfect) { + user.achievements.perfect++; + let lvlDiv2 = Math.ceil(common.capByLevel(user.stats.lvl) / 2); + user.stats.buffs = { + str: lvlDiv2, + int: lvlDiv2, + per: lvlDiv2, + con: lvlDiv2, + stealth: 0, + streaks: false, + }; + } else { + user.stats.buffs = _.cloneDeep(CLEAR_BUFFS); + } + + // Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit + // Adjust for fraction of dailies completed + if (dailyDueUnchecked === 0 && dailyChecked === 0) dailyChecked = 1; + user.stats.mp += _.max([10, 0.1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked); + if (user.stats.mp > user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP; + + // After all is said and done, progress up user's effect on quest, return those values & reset the user's + let progress = user.party.quest.progress; + let _progress = _.cloneDeep(progress); + _.merge(progress, {down: 0, up: 0}); + progress.collect = _.transform(progress.collect, (m, v, k) => m[k] = 0); + + // TODO: Clean PMs - keep 200 for subscribers and 50 for free users. Should also be done while resting in the inn + // let numberOfPMs = Object.keys(user.inbox.messages).length; + // if (numberOfPMs > maxPMs) { + // _(user.inbox.messages) + // .sortBy('timestamp') + // .takeRight(numberOfPMs - maxPMs) + // .each(pm => { + // delete user.inbox.messages[pm.id]; + // }).value(); + // + // user.markModified('inbox.messages'); + // } + + // Analytics + user.flags.cronCount++; + analytics.track('Cron', { // TODO also do while resting in the inn. https://github.com/HabitRPG/habitrpg/issues/7161#issuecomment-218214191 + category: 'behavior', + gaLabel: 'Cron Count', + gaValue: user.flags.cronCount, + uuid: user._id, + user, + resting: user.preferences.sleep, + cronCount: user.flags.cronCount, + progressUp: _.min([_progress.up, 900]), + progressDown: _progress.down, + }); + + return _progress; +} diff --git a/website/server/libs/api-v3/csvStringify.js b/website/server/libs/api-v3/csvStringify.js new file mode 100644 index 0000000000..39fb7c16c8 --- /dev/null +++ b/website/server/libs/api-v3/csvStringify.js @@ -0,0 +1,11 @@ +import csvStringify from 'csv-stringify'; +import Bluebird from 'bluebird'; + +module.exports = (input) => { + return new Bluebird((resolve, reject) => { + csvStringify(input, (err, output) => { + if (err) return reject(err); + return resolve(output); + }); + }); +}; diff --git a/website/server/libs/api-v3/email.js b/website/server/libs/api-v3/email.js new file mode 100644 index 0000000000..fd2e166098 --- /dev/null +++ b/website/server/libs/api-v3/email.js @@ -0,0 +1,156 @@ +import { createTransport } from 'nodemailer'; +import nconf from 'nconf'; +import { encrypt } from './encryption'; +import request from 'request'; +import logger from './logger'; + +const IS_PROD = nconf.get('IS_PROD'); +const EMAIL_SERVER = { + url: nconf.get('EMAIL_SERVER:url'), + auth: { + user: nconf.get('EMAIL_SERVER:authUser'), + password: nconf.get('EMAIL_SERVER:authPassword'), + }, +}; +const BASE_URL = nconf.get('BASE_URL'); + +let smtpTransporter = createTransport({ + service: nconf.get('SMTP_SERVICE'), + auth: { + user: nconf.get('SMTP_USER'), + pass: nconf.get('SMTP_PASS'), + }, +}); + +// Send email directly from the server using the smtpTransporter, +// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them +export function send (mailData) { + return smtpTransporter.sendMail(mailData); // promise +} + +export function getUserInfo (user, fields = []) { + let info = {}; + + if (fields.indexOf('name') !== -1) { + info.name = user.profile && user.profile.name; + + if (!info.name) { + if (user.auth.local && user.auth.local.username) { + info.name = user.auth.local.username; + } else if (user.auth.facebook) { + info.name = user.auth.facebook.displayName || user.auth.facebook.username; + } + } + } + + if (fields.indexOf('email') !== -1) { + if (user.auth.local && user.auth.local.email) { + info.email = user.auth.local.email; + } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) { + info.email = user.auth.facebook.emails[0].value; + } + } + + if (fields.indexOf('_id') !== -1) { + info._id = user._id; + } + + if (fields.indexOf('canSend') !== -1) { + if (user.preferences && user.preferences.emailNotifications) { + info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true; + } + } + + return info; +} + +// Send a transactional email using Mandrill through the external email server +export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) { + mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; + + variables = [ + {name: 'BASE_URL', content: BASE_URL}, + ].concat(variables || []); + + // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed + mailingInfoArray = mailingInfoArray.map((mailingInfo) => { + return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo; + }).filter((mailingInfo) => { + // Always send reset-password emails + // Don't check canSend for non registered users as already checked before + return mailingInfo.email && (!mailingInfo._id || mailingInfo.canSend || emailType === 'reset-password'); + }); + + // Personal variables are personal to each email recipient, if they are missing + // we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL + // otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables + if (!personalVariables || personalVariables.length === 0) { + personalVariables = mailingInfoArray.map((mailingInfo) => { + return { + rcpt: mailingInfo.email, + vars: [ + { + name: 'RECIPIENT_NAME', + content: mailingInfo.name, + }, + { + name: 'RECIPIENT_UNSUB_URL', + content: `/email/unsubscribe?code=${encrypt(JSON.stringify({ + _id: mailingInfo._id, + email: mailingInfo.email, + }))}`, + }, + ], + }; + }); + } else { + let temporaryPersonalVariables = {}; + + mailingInfoArray.forEach((mailingInfo) => { + temporaryPersonalVariables[mailingInfo.email] = { + name: mailingInfo.name, + _id: mailingInfo._id, + }; + }); + + personalVariables.forEach((singlePersonalVariables) => { + singlePersonalVariables.vars.push( + { + name: 'RECIPIENT_NAME', + content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name, + }, + { + name: 'RECIPIENT_UNSUB_URL', + content: `/email/unsubscribe?code=${encrypt(JSON.stringify({ + _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id, + email: singlePersonalVariables.rcpt, + }))}`, + } + ); + }); + } + + if (IS_PROD && mailingInfoArray.length > 0) { + request.post({ + url: `${EMAIL_SERVER.url}/job`, + auth: { + user: EMAIL_SERVER.auth.user, + pass: EMAIL_SERVER.auth.password, + }, + json: { + type: 'email', + data: { + emailType, + to: mailingInfoArray, + variables, + personalVariables, + }, + options: { + priority: 'high', + attempts: 5, + backoff: {delay: 10 * 60 * 1000, type: 'fixed'}, + }, + }, + }, (err) => logger.error(err)); + } +} diff --git a/website/server/libs/api-v3/encryption.js b/website/server/libs/api-v3/encryption.js new file mode 100644 index 0000000000..390c59234e --- /dev/null +++ b/website/server/libs/api-v3/encryption.js @@ -0,0 +1,24 @@ +import { + createCipher, + createDecipher, +} from 'crypto'; +import nconf from 'nconf'; + +const algorithm = 'aes-256-ctr'; +const SESSION_SECRET = nconf.get('SESSION_SECRET'); + +export function encrypt (text) { + let cipher = createCipher(algorithm, SESSION_SECRET); + let crypted = cipher.update(text, 'utf8', 'hex'); + + crypted += cipher.final('hex'); + return crypted; +} + +export function decrypt (text) { + let decipher = createDecipher(algorithm, SESSION_SECRET); + let dec = decipher.update(text, 'hex', 'utf8'); + + dec += decipher.final('utf8'); + return dec; +} diff --git a/website/server/libs/api-v3/errors.js b/website/server/libs/api-v3/errors.js new file mode 100644 index 0000000000..2b6d52bbe3 --- /dev/null +++ b/website/server/libs/api-v3/errors.js @@ -0,0 +1,62 @@ +import common from '../../../../common'; + +export const CustomError = common.errors.CustomError; + +/** + * @apiDefine NotAuthorized + * @apiError NotAuthorized The client is not authorized to make this request. + * + * @apiErrorExample Error-Response: + * HTTP/1.1 401 Unauthorized + * { + * "error": "NotAuthorized", + * "message": "Not authorized." + * } + */ +export const NotAuthorized = common.errors.NotAuthorized; + +/** + * @apiDefine BadRequest + * @apiError BadRequest The request wasn't formatted correctly. + * + * @apiErrorExample Error-Response: + * HTTP/1.1 400 Bad Request + * { + * "error": "BadRequest", + * "message": "Bad request." + * } + */ +export const BadRequest = common.errors.BadRequest; + +/** + * @apiDefine NotFound + * @apiError NotFound The requested resource was not found. + * + * @apiErrorExample Error-Response: + * HTTP/1.1 404 Not Found + * { + * "error": "NotFound", + * "message": "Not found." + * } + */ +export const NotFound = common.errors.NotFound; + +/** + * @apiDefine InternalServerError + * @apiError InternalServerError An unexpected error occurred. + * + * @apiErrorExample Error-Response: + * HTTP/1.1 500 Internal Server Error + * { + * "error": "InternalServerError", + * "message": "An unexpected error occurred." + * } + */ +export class InternalServerError extends CustomError { + constructor (customMessage) { + super(); + this.name = this.constructor.name; + this.httpCode = 500; + this.message = customMessage || 'An unexpected error occurred.'; + } +} diff --git a/website/server/libs/api-v3/firebase.js b/website/server/libs/api-v3/firebase.js new file mode 100644 index 0000000000..324183e85f --- /dev/null +++ b/website/server/libs/api-v3/firebase.js @@ -0,0 +1,69 @@ +import Firebase from 'firebase'; +import nconf from 'nconf'; +import { TAVERN_ID } from '../../models/group'; + +const FIREBASE_CONFIG = nconf.get('FIREBASE'); +const FIREBASE_ENABLED = FIREBASE_CONFIG.ENABLED === 'true'; + +let firebaseRef; + +if (FIREBASE_ENABLED) { + firebaseRef = new Firebase(`https://${FIREBASE_CONFIG.APP}.firebaseio.com`); + + // TODO what happens if an op is sent before client is authenticated? + firebaseRef.authWithCustomToken(FIREBASE_CONFIG.SECRET, (err) => { + // TODO it's ok to kill the server here? what if FB is offline? + if (err) throw new Error('Impossible to authenticate Firebase'); + }); +} + +export function updateGroupData (group) { + if (!FIREBASE_ENABLED) return; + // TODO is throw ok? we don't have callbacks + if (!group) throw new Error('group obj is required.'); + // Return in case of tavern (comparison working because we use string for _id) + if (group._id === TAVERN_ID) return; + + firebaseRef.child(`rooms/${group._id}`) + .set({ + name: group.name, + }); +} + +export function addUserToGroup (groupId, userId) { + if (!FIREBASE_ENABLED) return; + if (!userId || !groupId) throw new Error('groupId, userId are required.'); + if (groupId === TAVERN_ID) return; + + firebaseRef.child(`members/${groupId}/${userId}`).set(true); + firebaseRef.child(`users/${userId}/rooms/${groupId}`).set(true); +} + +export function removeUserFromGroup (groupId, userId) { + if (!FIREBASE_ENABLED) return; + if (!userId || !groupId) throw new Error('groupId, userId are required.'); + if (groupId === TAVERN_ID) return; + + firebaseRef.child(`members/${groupId}/${userId}`).remove(); + firebaseRef.child(`users/${userId}/rooms/${groupId}`).remove(); +} + +export function deleteGroup (groupId) { + if (!FIREBASE_ENABLED) return; + if (!groupId) throw new Error('groupId is required.'); + if (groupId === TAVERN_ID) return; + + firebaseRef.child(`members/${groupId}`).remove(); + // TODO not really necessary as long as we only store room data, + // as empty objects are automatically deleted (/members/... in future...) + firebaseRef.child(`rooms/${groupId}`).remove(); +} + +// TODO not really necessary as long as we only store room data, +// as empty objects are automatically deleted +export function deleteUser (userId) { + if (!FIREBASE_ENABLED) return; + if (!userId) throw new Error('userId is required.'); + + firebaseRef.child(`users/${userId}`).remove(); +} diff --git a/website/server/libs/api-v3/i18n.js b/website/server/libs/api-v3/i18n.js new file mode 100644 index 0000000000..4c424ace39 --- /dev/null +++ b/website/server/libs/api-v3/i18n.js @@ -0,0 +1,105 @@ +import fs from 'fs'; +import path from 'path'; +import _ from 'lodash'; +import shared from '../../../../common'; + +export const localePath = path.join(__dirname, '/../../../../common/locales/'); + +// Store translations +export let translations = {}; +// Store MomentJS localization files +export let momentLangs = {}; + +// Handle differencies in language codes between MomentJS and /locales +let momentLangsMapping = { + en: 'en-gb', + en_GB: 'en-gb', // eslint-disable-line camelcase + no: 'nn', + zh: 'zh-cn', + es_419: 'es', // eslint-disable-line camelcase +}; + +function _loadTranslations (locale) { + let files = fs.readdirSync(path.join(localePath, locale)); + + translations[locale] = {}; + + files.forEach((file) => { + if (path.extname(file) !== '.json') return; + + // We use require to load and parse a JSON file + _.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require + }); +} + +// First fetch English strings so we can merge them with missing strings in other languages +_loadTranslations('en'); + +// Then load all other languages +fs.readdirSync(localePath).forEach((file) => { + if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return; + _loadTranslations(file); + + // Merge missing strings from english + _.defaults(translations[file], translations.en); +}); + +// Add translations to shared +shared.i18n.translations = translations; + +export let langCodes = Object.keys(translations); + +export let availableLanguages = langCodes.map((langCode) => { + return { + code: langCode, + name: translations[langCode].languageName, + }; +}); + +langCodes.forEach((code) => { + let lang = _.find(availableLanguages, {code}); + + lang.momentLangCode = momentLangsMapping[code] || code; + + try { + // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files + // We wrap everything in a try catch because the file might not exist + let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8'); + + momentLangs[code] = f; + } catch (e) { // eslint-disable-lint no-empty + // The catch block is mandatory so it won't crash the server + } +}); + +// Remove en_GB from langCodes checked by browser to avoid it being +// used in place of plain original 'en' (it's an optional language that can be enabled only in setting) +export let defaultLangCodes = _.without(langCodes, 'en_GB'); + +// A map of languages that have different versions and the relative versions +export let multipleVersionsLanguages = { + es: { + 'es-419': 'es_419', + 'es-mx': 'es_419', + 'es-gt': 'es_419', + 'es-cr': 'es_419', + 'es-pa': 'es_419', + 'es-do': 'es_419', + 'es-ve': 'es_419', + 'es-co': 'es_419', + 'es-pe': 'es_419', + 'es-ar': 'es_419', + 'es-ec': 'es_419', + 'es-cl': 'es_419', + 'es-uy': 'es_419', + 'es-py': 'es_419', + 'es-bo': 'es_419', + 'es-sv': 'es_419', + 'es-hn': 'es_419', + 'es-ni': 'es_419', + 'es-pr': 'es_419', + }, + zh: { + 'zh-tw': 'zh_TW', + }, +}; diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js new file mode 100644 index 0000000000..ed1353e8ad --- /dev/null +++ b/website/server/libs/api-v3/logger.js @@ -0,0 +1,60 @@ +// Logger utility +import winston from 'winston'; +import nconf from 'nconf'; +import _ from 'lodash'; + +const IS_PROD = nconf.get('IS_PROD'); +const IS_TEST = nconf.get('IS_TEST'); +const ENABLE_CONSOLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') === 'true'; + +const logger = new winston.Logger(); + +if (IS_PROD) { + if (ENABLE_CONSOLE_LOGS_IN_PROD) { + logger.add(winston.transports.Console, { + colorize: true, + prettyPrint: true, + }); + } +} else if (IS_TEST) { + // Do not log anything when testing +} else { + logger + .add(winston.transports.Console, { + colorize: true, + prettyPrint: true, + }); +} + +// exports a public interface insteaf of accessing directly the logger module +let loggerInterface = { + info (...args) { + logger.info(...args); + }, + + // Accepts two argument, + // an Error object (required) + // and an object of additional data to log alongside the error + // If the first argument isn't an Error, it'll call logger.error with all the arguments supplied + error (...args) { + let [err, errorData = {}, ...otherArgs] = args; + + if (err instanceof Error) { + // pass the error stack as the first parameter to logger.error + let stack = err.stack || err.message || err; + + if (_.isPlainObject(errorData) && !errorData.fullError) errorData.fullError = err; + logger.error(stack, errorData, ...otherArgs); + } else { + logger.error(...args); + } + }, +}; + +// Logs unhandled promises errors +// when no catch is attached to a promise a unhandledRejection event will be triggered +process.on('unhandledRejection', function handlePromiseRejection (reason) { + loggerInterface.error(reason); +}); + +module.exports = loggerInterface; diff --git a/website/server/libs/api-v3/password.js b/website/server/libs/api-v3/password.js new file mode 100644 index 0000000000..c825083936 --- /dev/null +++ b/website/server/libs/api-v3/password.js @@ -0,0 +1,18 @@ +// Utilities for working with passwords +import crypto from 'crypto'; + +// Return the encrypted version of a password (using sha1) given a salt +export function encrypt (password, salt) { + return crypto + .createHmac('sha1', salt) + .update(password) + .digest('hex'); +} + +// Create a salt, default length is 10 +export function makeSalt (len = 10) { + return crypto + .randomBytes(Math.ceil(len / 2)) + .toString('hex') + .substring(0, len); +} \ No newline at end of file diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js new file mode 100644 index 0000000000..5a9c888972 --- /dev/null +++ b/website/server/libs/api-v3/payments.js @@ -0,0 +1,185 @@ +import _ from 'lodash' ; +import analytics from './analyticsService'; +import { + getUserInfo, + sendTxn as txnEmail, +} from './email'; +import members from '../../controllers/api-v3/members'; +import moment from 'moment'; +import nconf from 'nconf'; +import pushNotify from './pushNotifications'; +import shared from '../../../../common' ; + +const IS_PROD = nconf.get('IS_PROD'); + +let api = {}; + +function revealMysteryItems (user) { + _.each(shared.content.gear.flat, function findMysteryItems (item) { + if ( + item.klass === 'mystery' && + moment().isAfter(shared.content.mystery[item.mystery].start) && + moment().isBefore(shared.content.mystery[item.mystery].end) && + !user.items.gear.owned[item.key] && + user.purchased.plan.mysteryItems.indexOf(item.key) !== -1 + ) { + user.purchased.plan.mysteryItems.push(item.key); + } + }); +} + +api.createSubscription = async function createSubscription (data) { + let recipient = data.gift ? data.gift.member : data.user; + let plan = recipient.purchased.plan; + let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key]; + let months = Number(block.months); + + if (data.gift) { + if (plan.customerId && !plan.dateTerminated) { // User has active plan + plan.extraMonths += months; + } else { + plan.dateTerminated = moment(plan.dateTerminated).add({months}).toDate(); + if (!plan.dateUpdated) plan.dateUpdated = new Date(); + } + + if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId + } else { + _(plan).merge({ // override with these values + planId: block.key, + customerId: data.customerId, + dateUpdated: new Date(), + gemsBought: 0, + paymentMethod: data.paymentMethod, + extraMonths: Number(plan.extraMonths) + + Number(plan.dateTerminated ? moment(plan.dateTerminated).diff(new Date(), 'months', true) : 0), + dateTerminated: null, + // Specify a lastBillingDate just for Amazon Payments + // Resetted every time the subscription restarts + lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined, + }).defaults({ // allow non-override if a plan was previously used + dateCreated: new Date(), + mysteryItems: [], + }).value(); + } + + // Block sub perks + let perks = Math.floor(months / 3); + if (perks) { + plan.consecutive.offset += months; + plan.consecutive.gemCapExtra += perks * 5; + if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25; + plan.consecutive.trinkets += perks; + } + + revealMysteryItems(recipient); + + if (IS_PROD) { + if (!data.gift) txnEmail(data.user, 'subscription-begins'); + + analytics.trackPurchase({ + uuid: data.user._id, + itemPurchased: 'Subscription', + sku: `${data.paymentMethod.toLowerCase()}-subscription`, + purchaseType: 'subscribe', + paymentMethod: data.paymentMethod, + quantity: 1, + gift: Boolean(data.gift), + purchaseValue: block.price, + }); + } + + data.user.purchased.txnCount++; + + if (data.gift) { + members.sendMessage(data.user, data.gift.member, data.gift); + + let byUserName = getUserInfo(data.user, ['name']).name; + + if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) { + txnEmail(data.gift.member, 'gifted-subscription', [ + {name: 'GIFTER', content: byUserName}, + {name: 'X_MONTHS_SUBSCRIPTION', content: months}, + ]); + } + + if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself + pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`); + } + } + + await data.user.save(); + if (data.gift) await data.gift.member.save(); +}; + +// Sets their subscription to be cancelled later +api.cancelSubscription = async function cancelSubscription (data) { + let plan = data.user.purchased.plan; + let now = moment(); + let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30; + let nowStr = `${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`; + let nowStrFormat = 'MM/DD/YYYY'; + + plan.dateTerminated = + moment(nowStr, nowStrFormat) + .add({days: remaining}) // end their subscription 1mo from their last payment + .add({days: Math.ceil(30 * plan.extraMonths)}) // plus any extra time (carry-over, gifted subscription, etc) they have. + .toDate(); + plan.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated + + await data.user.save(); + + txnEmail(data.user, 'cancel-subscription'); + + analytics.track('unsubscribe', { + uuid: data.user._id, + gaCategory: 'commerce', + gaLabel: data.paymentMethod, + paymentMethod: data.paymentMethod, + }); +}; + +api.buyGems = async function buyGems (data) { + let amt = data.amount || 5; + amt = data.gift ? data.gift.gems.amount / 4 : amt; + + (data.gift ? data.gift.member : data.user).balance += amt; + data.user.purchased.txnCount++; + + if (IS_PROD) { + if (!data.gift) txnEmail(data.user, 'donation'); + + analytics.trackPurchase({ + uuid: data.user._id, + itemPurchased: 'Gems', + sku: `${data.paymentMethod.toLowerCase()}-checkout`, + purchaseType: 'checkout', + paymentMethod: data.paymentMethod, + quantity: 1, + gift: Boolean(data.gift), + purchaseValue: amt, + }); + } + + if (data.gift) { + let byUsername = getUserInfo(data.user, ['name']).name; + let gemAmount = data.gift.gems.amount || 20; + + members.sendMessage(data.user, data.gift.member, data.gift); + if (data.gift.member.preferences.emailNotifications.giftedGems !== false) { + txnEmail(data.gift.member, 'gifted-gems', [ + {name: 'GIFTER', content: byUsername}, + {name: 'X_GEMS_GIFTED', content: gemAmount}, + ]); + } + + if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself + pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`); + } + + await data.gift.member.save(); + } + + await data.user.save(); +}; + +module.exports = api; diff --git a/website/server/libs/api-v3/preening.js b/website/server/libs/api-v3/preening.js new file mode 100644 index 0000000000..00be142299 --- /dev/null +++ b/website/server/libs/api-v3/preening.js @@ -0,0 +1,82 @@ +import _ from 'lodash'; +import moment from 'moment'; + +// Aggregate entries +function _aggregate (history, aggregateBy) { + return _.chain(history) + .groupBy(entry => { // group entries by aggregateBy + return moment(entry.date).format(aggregateBy); + }) + .sortBy((entry, key) => key) // sort by date + .map(entries => { + return { + date: Number(entries[0].date), + value: _.reduce(entries, (previousValue, entry) => { + return previousValue + entry.value; + }, 0) / entries.length, + }; + }) + .value(); +} + +/* Preen an array of history entries +Free users: +- 1 value for each day of the past 60 days (no compression) +- 1 value each month for the previous 10 months +- 1 value each year for the previous years +Subscribers and challenges: +- 1 value for each day of the past 365 days (no compression) +- 1 value each month for the previous 12 months +- 1 value each year for the previous years + */ +export function preenHistory (history, isSubscribed, timezoneOffset) { + // history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries + let now = timezoneOffset ? moment().zone(timezoneOffset) : moment(); + // Date after which to begin compressing data + let cutOff = now.subtract(isSubscribed ? 365 : 60, 'days').startOf('day'); + + // Keep uncompressed entries (modifies history and returns removed items) + let newHistory = _.remove(history, entry => { + let date = moment(entry.date); + return date.isSame(cutOff) || date.isAfter(cutOff); + }); + + // Date after which to begin compressing data by year + let monthsCutOff = cutOff.subtract(isSubscribed ? 12 : 10, 'months').startOf('day'); + let aggregateByMonth = _.remove(history, entry => { + let date = moment(entry.date); + return date.isSame(monthsCutOff) || date.isAfter(monthsCutOff); + }); + // Aggregate remaining entries by month and year + if (aggregateByMonth.length > 0) newHistory.unshift(..._aggregate(aggregateByMonth, 'YYYYMM')); + if (history.length > 0) newHistory.unshift(..._aggregate(history, 'YYYY')); + + return newHistory; +} + +// Preen history for users and tasks. +export function preenUserHistory (user, tasksByType) { + let isSubscribed = user.isSubscribed(); + let timezoneOffset = user.preferences.timezoneOffset; + let minHistoryLength = isSubscribed ? 365 : 60; + + function _processTask (task) { + if (task.history && task.history.length > minHistoryLength) { + task.history = preenHistory(task.history, isSubscribed, timezoneOffset); + task.markModified('history'); + } + } + + tasksByType.habits.forEach(_processTask); + tasksByType.dailys.forEach(_processTask); + + if (user.history.exp.length > minHistoryLength) { + user.history.exp = preenHistory(user.history.exp, isSubscribed, timezoneOffset); + user.markModified('history.exp'); + } + + if (user.history.todos.length > minHistoryLength) { + user.history.todos = preenHistory(user.history.todos, isSubscribed, timezoneOffset); + user.markModified('history.todos'); + } +} diff --git a/website/server/libs/api-v3/pushNotifications.js b/website/server/libs/api-v3/pushNotifications.js new file mode 100644 index 0000000000..8354de04e4 --- /dev/null +++ b/website/server/libs/api-v3/pushNotifications.js @@ -0,0 +1,53 @@ +import _ from 'lodash'; +import nconf from 'nconf'; +import pushNotify from 'push-notify'; + +const GCM_API_KEY = nconf.get('PUSH_CONFIGS:GCM_SERVER_API_KEY'); + +let gcm = GCM_API_KEY ? pushNotify.gcm({ + apiKey: GCM_API_KEY, + retries: 3, +}) : undefined; + +// TODO review and test this file when push notifications are added back + +if (gcm) { + gcm.on('transmitted', (/* result, message, registrationId */) => { + // console.info("transmitted", result, message, registrationId); + }); + + gcm.on('transmissionError', (/* error, message, registrationId */) => { + // console.info("transmissionError", error, message, registrationId); + }); + + gcm.on('updated', (/* result, registrationId */) => { + // console.info("updated", result, registrationId); + }); +} + +module.exports = function sendNotification (user, title, message, timeToLive = 15) { + if (!user) return; + + _.each(user.pushDevices, pushDevice => { + switch (pushDevice.type) { + case 'android': + if (gcm) { + gcm.send({ + registrationId: pushDevice.regId, + // collapseKey: 'COLLAPSE_KEY', + delayWhileIdle: true, + timeToLive, + data: { + title, + message, + }, + }); + } + + break; + + case 'ios': + break; + } + }); +}; diff --git a/website/server/libs/api-v3/routes.js b/website/server/libs/api-v3/routes.js new file mode 100644 index 0000000000..c0399fb73c --- /dev/null +++ b/website/server/libs/api-v3/routes.js @@ -0,0 +1,61 @@ +import fs from 'fs'; +import _ from 'lodash'; +import { + getUserLanguage, +} from '../../middlewares/api-v3/language'; +import cron from '../../middlewares/api-v3/cron'; + +// Wrapper function to handler `async` route handlers that return promises +// It takes the async function, execute it and pass any error to next (args[2]) +let _wrapAsyncFn = fn => (...args) => fn(...args).catch(args[2]); +let noop = (req, res, next) => next(); + +module.exports.readController = function readController (router, controller) { + _.each(controller, (action) => { + let {method, url, middlewares = [], handler, runCron} = action; + + // If an authentication middleware is used run getUserLanguage after it, otherwise before + // for cron instead use it only if an authentication middleware is present + let authMiddlewareIndex = _.findIndex(middlewares, middleware => { + if (middleware.name.indexOf('authWith') === 0) { // authWith{Headers|Session|Url|...} + return true; + } else { + return false; + } + }); + + let middlewaresToAdd = [getUserLanguage]; + + if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage and cron after authentication + if (!(runCron === false)) { // eslint-disable-line no-extra-parens + middlewaresToAdd.push(cron); + } + + if (authMiddlewareIndex === middlewares.length - 1) { + middlewares.push(...middlewaresToAdd); + } else { + middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd); + } + } else { // no auth, getUserLanguage as the first middleware + middlewares.unshift(...middlewaresToAdd); + } + + method = method.toLowerCase(); + let fn = handler ? _wrapAsyncFn(handler) : noop; + + router[method](url, ...middlewares, fn); + }); +}; + +module.exports.walkControllers = function walkControllers (router, filePath) { + fs + .readdirSync(filePath) + .forEach(fileName => { + if (!fs.statSync(filePath + fileName).isFile()) { + walkControllers(router, `${filePath}${fileName}/`); + } else if (fileName.match(/\.js$/)) { + let controller = require(filePath + fileName); // eslint-disable-line global-require + module.exports.readController(router, controller); + } + }); +}; diff --git a/website/server/libs/api-v3/setupMongoose.js b/website/server/libs/api-v3/setupMongoose.js new file mode 100644 index 0000000000..41ba9e5358 --- /dev/null +++ b/website/server/libs/api-v3/setupMongoose.js @@ -0,0 +1,28 @@ +import nconf from 'nconf'; +import logger from './logger'; +import autoinc from 'mongoose-id-autoinc'; +import mongoose from 'mongoose'; +import Bluebird from 'bluebird'; + +const IS_PROD = nconf.get('IS_PROD'); +const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE'); + +// Use Q promises instead of mpromise in mongoose +mongoose.Promise = Bluebird; + +// Do not connect to MongoDB when in maintenance mode +if (MAINTENANCE_MODE !== 'true') { + let mongooseOptions = !IS_PROD ? {} : { + replset: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } }, + server: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } }, + }; + + const NODE_DB_URI = nconf.get('IS_TEST') ? nconf.get('TEST_DB_URI') : nconf.get('NODE_DB_URI'); + + let db = mongoose.connect(NODE_DB_URI, mongooseOptions, (err) => { + if (err) throw err; + logger.info('Connected with Mongoose.'); + }); + + autoinc.init(db); +} \ No newline at end of file diff --git a/website/server/libs/api-v3/setupNconf.js b/website/server/libs/api-v3/setupNconf.js new file mode 100644 index 0000000000..d88b04014e --- /dev/null +++ b/website/server/libs/api-v3/setupNconf.js @@ -0,0 +1,17 @@ +import nconf from 'nconf'; +import { join, resolve } from 'path'; + +const PATH_TO_CONFIG = join(resolve(__dirname, '../../../../config.json')); + +module.exports = function setupNconf (file) { + let configFile = file || PATH_TO_CONFIG; + + nconf + .argv() + .env() + .file('user', configFile); + + nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production'); + nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development'); + nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test'); +}; diff --git a/website/server/libs/api-v3/setupPassport.js b/website/server/libs/api-v3/setupPassport.js new file mode 100644 index 0000000000..dd9fdeaaa2 --- /dev/null +++ b/website/server/libs/api-v3/setupPassport.js @@ -0,0 +1,24 @@ +import passport from 'passport'; +import nconf from 'nconf'; +import passportFacebook from 'passport-facebook'; + +const FacebookStrategy = passportFacebook.Strategy; + +// Passport session setup. +// To support persistent login sessions, Passport needs to be able to +// serialize users into and deserialize users out of the session. Typically, +// this will be as simple as storing the user ID when serializing, and finding +// the user by ID when deserializing. However, since this example does not +// have a database of user records, the complete Facebook profile is serialized +// and deserialized. +passport.serializeUser((user, done) => done(null, user)); +passport.deserializeUser((obj, done) => done(null, obj)); + +// TODO remove? +// This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile) +// The proper fix would be to move to a general OAuth module simply to verify accessTokens +passport.use(new FacebookStrategy({ + clientID: nconf.get('FACEBOOK_KEY'), + clientSecret: nconf.get('FACEBOOK_SECRET'), + // callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" +}, (accessToken, refreshToken, profile, done) => done(null, profile))); diff --git a/website/server/libs/api-v3/webhook.js b/website/server/libs/api-v3/webhook.js new file mode 100644 index 0000000000..e53eebf541 --- /dev/null +++ b/website/server/libs/api-v3/webhook.js @@ -0,0 +1,31 @@ +import { each } from 'lodash'; +import { post } from 'request'; +import { isURL } from 'validator'; +import logger from './logger'; + +let _sendWebhook = (url, body) => { + post({ + url, + body, + json: true, + }, (err) => logger.error(err)); +}; + +let _isInvalidWebhook = (hook) => { + return !hook.enabled || !isURL(hook.url); +}; + +export function sendTaskWebhook (webhooks, data) { + each(webhooks, (hook) => { + if (_isInvalidWebhook(hook)) return; + + let body = { + direction: data.task.direction, + task: data.task.details, + delta: data.task.delta, + user: data.user, + }; + + _sendWebhook(hook.url, body); + }); +} diff --git a/website/src/middlewares/domain.js b/website/server/middlewares/api-v2/domain.js similarity index 100% rename from website/src/middlewares/domain.js rename to website/server/middlewares/api-v2/domain.js diff --git a/website/src/middlewares/errorHandler.js b/website/server/middlewares/api-v2/errorHandler.js similarity index 95% rename from website/src/middlewares/errorHandler.js rename to website/server/middlewares/api-v2/errorHandler.js index 2b06824dce..e62753f5f9 100644 --- a/website/src/middlewares/errorHandler.js +++ b/website/server/middlewares/api-v2/errorHandler.js @@ -1,4 +1,4 @@ -var logging = require('../libs/logging'); +var logging = require('../../libs/api-v2/logging'); module.exports = function(err, req, res, next) { //res.locals.domain.emit('error', err); diff --git a/website/src/middlewares/locals.js b/website/server/middlewares/api-v2/locals.js similarity index 94% rename from website/src/middlewares/locals.js rename to website/server/middlewares/api-v2/locals.js index 2f238a6a99..223186a81a 100644 --- a/website/src/middlewares/locals.js +++ b/website/server/middlewares/api-v2/locals.js @@ -1,9 +1,9 @@ var nconf = require('nconf'); var _ = require('lodash'); -var utils = require('../libs/utils'); +var utils = require('../libs/api-v2/utils'); var shared = require('../../../common'); -var i18n = require('../libs/i18n'); -var buildManifest = require('../libs/buildManifest'); +var i18n = require('../libs/api-v2/i18n'); +var buildManifest = require('../libs/api-v2/buildManifest'); var shared = require('../../../common'); var forceRefresh = require('./forceRefresh'); var tavernQuest = require('../models/group').tavernQuest; diff --git a/website/server/middlewares/api-v3/analytics.js b/website/server/middlewares/api-v3/analytics.js new file mode 100644 index 0000000000..8512d16011 --- /dev/null +++ b/website/server/middlewares/api-v3/analytics.js @@ -0,0 +1,23 @@ +import nconf from 'nconf'; +import { + track, + trackPurchase, + mockAnalyticsService, +} from '../../libs/api-v3/analyticsService'; + +let service; + +if (nconf.get('IS_PROD')) { + service = { + track, + trackPurchase, + }; +} else { + service = mockAnalyticsService; +} + +module.exports = function attachAnalytics (req, res, next) { + res.analytics = service; + + next(); +}; diff --git a/website/server/middlewares/api-v3/auth.js b/website/server/middlewares/api-v3/auth.js new file mode 100644 index 0000000000..0feb73a98d --- /dev/null +++ b/website/server/middlewares/api-v3/auth.js @@ -0,0 +1,87 @@ +import { + NotAuthorized, +} from '../../libs/api-v3/errors'; +import { + model as User, +} from '../../models/user'; + +// Strins won't be translated here because getUserLanguage has not run yet + +// Authenticate a request through the x-api-user and x-api key header +// If optional is true, don't error on missing authentication +export function authWithHeaders (optional = false) { + return function authWithHeadersHandler (req, res, next) { + let userId = req.header('x-api-user'); + let apiToken = req.header('x-api-key'); + + if (!userId || !apiToken) { + if (optional) return next(); + return next(new NotAuthorized(res.t('missingAuthHeaders'))); + } + + return User.findOne({ + _id: userId, + apiToken, + }) + .exec() + .then((user) => { + if (!user) throw new NotAuthorized(res.t('invalidCredentials')); + if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id})); + + res.locals.user = user; + + req.session.userId = user._id; + return next(); + }) + .catch(next); + }; +} + +// Authenticate a request through a valid session +export function authWithSession (req, res, next) { + let userId = req.session.userId; + + // Always allow authentication with headers + if (!userId) { + if (!req.header('x-api-user') || !req.header('x-api-key')) { + return next(new NotAuthorized(res.t('invalidCredentials'))); + } else { + return authWithHeaders()(req, res, next); + } + } + + return User.findOne({ + _id: userId, + }) + .exec() + .then((user) => { + if (!user) throw new NotAuthorized(res.t('invalidCredentials')); + + res.locals.user = user; + return next(); + }) + .catch(next); +} + +export function authWithUrl (req, res, next) { + let userId = req.query._id; + let apiToken = req.query.apiToken; + + // Always allow authentication with headers + if (!userId || !apiToken) { + if (!req.header('x-api-user') || !req.header('x-api-key')) { + return next(new NotAuthorized(res.t('missingAuthParams'))); + } else { + return authWithHeaders()(req, res, next); + } + } + + return User.findOne({ _id: userId, apiToken }).exec() + .then((user) => { + if (!user) throw new NotAuthorized(res.t('invalidCredentials')); + + res.locals.user = user; + return next(); + }) + .catch(next); +} diff --git a/website/server/middlewares/api-v3/cors.js b/website/server/middlewares/api-v3/cors.js new file mode 100644 index 0000000000..c249c183c6 --- /dev/null +++ b/website/server/middlewares/api-v3/cors.js @@ -0,0 +1,9 @@ +module.exports = function corsMiddleware (req, res, next) { + res.set({ + 'Access-Control-Allow-Origin': req.header('origin') || '*', + 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', + 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', + }); + if (req.method === 'OPTIONS') return res.sendStatus(200); + return next(); +}; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js new file mode 100644 index 0000000000..780f400e8f --- /dev/null +++ b/website/server/middlewares/api-v3/cron.js @@ -0,0 +1,160 @@ +import _ from 'lodash'; +import moment from 'moment'; +import common from '../../../../common'; +import * as Tasks from '../../models/task'; +import Bluebird from 'bluebird'; +import { model as Group } from '../../models/group'; +import { model as User } from '../../models/user'; +import { cron } from '../../libs/api-v3/cron'; + +const daysSince = common.daysSince; + +module.exports = function cronMiddleware (req, res, next) { + let user = res.locals.user; + + if (!user) return next(); // User might not be available when authentication is not mandatory + + let analytics = res.analytics; + + let now = new Date(); + + // If the user's timezone has changed (due to travel or daylight savings), + // cron can be triggered twice in one day, so we check for that and use + // both timezones to work out if cron should run. + // CDS = Custom Day Start time. + let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; + let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; + let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset')); + timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs; + // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults + + if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { + // The user's browser has just told Habitica that the user's timezone has + // changed so store and use the new zone. + user.preferences.timezoneOffset = timezoneOffsetFromBrowser; + timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; + } + + // How many days have we missed using the user's current timezone: + let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences)); + + if (timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs) { + // Since cron last ran, the user's timezone has changed. + // How many days have we missed using the old timezone: + let daysMissedNewZone = daysMissed; + let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ + now, + timezoneOffsetOverride: timezoneOffsetAtLastCron, + }, user.preferences)); + + if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { + // The timezone change was in the unsafe direction. + // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). + // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). + // Local time changed from, for example, 03:00 to 02:00. + + if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { + // Both old and new timezones indicate that we SHOULD run cron, so + // it is safe to do so immediately. + daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); + // use minimum value to be nice to user + } else if (daysMissedOldZone > 0) { + // The old timezone says that cron should run; the new timezone does not. + // This should be impossible for this direction of timezone change, but + // just in case I'm wrong... + // TODO + // console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens + } else if (daysMissedNewZone > 0) { + // The old timezone says that cron should NOT run -- i.e., cron has + // already run today, from the old timezone's point of view. + // The new timezone says that cron SHOULD run, but this is almost + // certainly incorrect. + // This happens when cron occurred at a time soon after the CDS. When + // you reinterpret that time in the new timezone, it looks like it + // was before the CDS, because local time has stepped backwards. + // To fix this, rewrite the cron time to a time that the new + // timezone interprets as being in today. + + daysMissed = 0; // prevent cron running now + let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; + // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 + + user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); + // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. + // From now on we can ignore the old timezone: + user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; + } else { + // Both old and new timezones indicate that cron should + // NOT run. + daysMissed = 0; // prevent cron running now + } + } else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { + daysMissed = daysMissedNewZone; + // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. + // There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; + // if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). + } + } + + if (daysMissed <= 0) return next(); + + // Fetch active tasks (no completed todos) + Tasks.Task.find({ + userId: user._id, + $or: [ // Exclude completed todos + {type: 'todo', completed: false}, + {type: {$in: ['habit', 'daily', 'reward']}}, + ], + }).exec() + .then(tasks => { + let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; + tasks.forEach(task => tasksByType[`${task.type}s`].push(task)); + + // Run cron + let progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); + + // Clear old completed todos - 30 days for free users, 90 for subscribers + // Do not delete challenges completed todos TODO unless the task is broken? + Tasks.Task.remove({ + userId: user._id, + type: 'todo', + completed: true, + dateCompleted: { + $lt: moment(now).subtract(user.isSubscribed() ? 90 : 30, 'days').toDate(), + }, + 'challenge.id': {$exists: false}, + }).exec(); + + let ranCron = user.isModified(); + let quest = common.content.quests[user.party.quest.key]; + + if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired + if (!ranCron) return next(); + + // Group.tavernBoss(user, progress); + + // Save user and tasks + let toSave = [user.save()]; + tasks.forEach(task => { + if (task.isModified()) toSave.push(task.save()); + }); + + return Bluebird.all(toSave) + .then(saved => { + user = res.locals.user = saved[0]; + if (!quest) return; + // If user is on a quest, roll for boss & player, or handle collections + let questType = quest.boss ? 'boss' : 'collect'; + // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? + return Group[`${questType}Quest`](user, progress) + .then(() => User.findById(user._id).exec()) // fetch the updated user... + .then(updatedUser => { + res.locals.user = updatedUser; + + return null; + }); + }) + .then(() => next()) + .catch(next); + }); +}; diff --git a/website/server/middlewares/api-v3/domain.js b/website/server/middlewares/api-v3/domain.js new file mode 100644 index 0000000000..fb5e28d352 --- /dev/null +++ b/website/server/middlewares/api-v3/domain.js @@ -0,0 +1,13 @@ +import domainMiddleware from 'domain-middleware'; + +module.exports = function implementDomainMiddleware (server, mongoose) { + return domainMiddleware({ + server: { + close () { + server.close(); + mongoose.connection.close(); + }, + }, + killTimeout: 10000, + }); +}; diff --git a/website/server/middlewares/api-v3/ensureAccessRight.js b/website/server/middlewares/api-v3/ensureAccessRight.js new file mode 100644 index 0000000000..2fb64ed8af --- /dev/null +++ b/website/server/middlewares/api-v3/ensureAccessRight.js @@ -0,0 +1,23 @@ +import { + NotAuthorized, +} from '../../libs/api-v3/errors'; + +export function ensureAdmin (req, res, next) { + let user = res.locals.user; + + if (!user.contributor.admin) { + return next(new NotAuthorized(res.t('noAdminAccess'))); + } + + next(); +} + +export function ensureSudo (req, res, next) { + let user = res.locals.user; + + if (!user.contributor.sudo) { + return next(new NotAuthorized(res.t('noSudoAccess'))); + } + + next(); +} diff --git a/website/server/middlewares/api-v3/ensureDevelpmentMode.js b/website/server/middlewares/api-v3/ensureDevelpmentMode.js new file mode 100644 index 0000000000..98f70d33f5 --- /dev/null +++ b/website/server/middlewares/api-v3/ensureDevelpmentMode.js @@ -0,0 +1,12 @@ +import nconf from 'nconf'; +import { + NotFound, +} from '../../libs/api-v3/errors'; + +module.exports = function ensureDevelpmentMode (req, res, next) { + if (nconf.get('IS_PROD')) { + next(new NotFound()); + } else { + next(); + } +}; diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js new file mode 100644 index 0000000000..11d75042e3 --- /dev/null +++ b/website/server/middlewares/api-v3/errorHandler.js @@ -0,0 +1,86 @@ +// The error handler middleware that handles all errors +// and respond to the client +import logger from '../../libs/api-v3/logger'; +import { + CustomError, + BadRequest, + InternalServerError, +} from '../../libs/api-v3/errors'; +import { + map, + omit, +} from 'lodash'; + +module.exports = function errorHandler (err, req, res, next) { // eslint-disable-line no-unused-vars + logger.error(err, { + originalUrl: req.originalUrl, + headers: omit(req.headers, ['x-api-key']), + body: req.body, + }); + + // In case of a CustomError class, use it's data + // Otherwise try to identify the type of error (mongoose validation, mongodb unique, ...) + // If we can't identify it, respond with a generic 500 error + let responseErr = err instanceof CustomError ? err : null; + + // Handle errors created with 'http-errors' or similar that have a status/statusCode property + if (err.statusCode && typeof err.statusCode === 'number') { + responseErr = new CustomError(); + responseErr.httpCode = err.statusCode; + responseErr.name = err.name; + responseErr.message = err.message; + } + + // Handle errors by express-validator + if (Array.isArray(err) && err[0].param && err[0].msg) { + responseErr = new BadRequest(res.t('invalidReqParams')); + responseErr.errors = err.map((paramErr) => { + return { + message: paramErr.msg, + param: paramErr.param, + value: paramErr.value, + }; + }); + } + + // Handle mongoose validation errors + if (err.name === 'ValidationError') { + responseErr = new BadRequest(err.message); // TODO standard message? translate? + responseErr.errors = map(err.errors, (mongooseErr) => { + return { + message: mongooseErr.message, + path: mongooseErr.path, + value: mongooseErr.value, + }; + }); + } + + // Handle Stripe Card errors errors (can be safely shown to the users) + // https://stripe.com/docs/api/node#errors + if (err.type === 'StripeCardError') { + responseErr = new BadRequest(err.message); + } + + if (!responseErr || responseErr.httpCode >= 500) { + // Try to identify the error... + // ... + // Otherwise create an InternalServerError and use it + // we don't want to leak anything, just a generic error message + // Use it also in case of identified errors but with httpCode === 500 + responseErr = new InternalServerError(); + } + + let jsonRes = { + success: false, + error: responseErr.name, + message: responseErr.message, + }; + + if (responseErr.errors) { + jsonRes.errors = responseErr.errors; + } + + // In some occasions like when invalid JSON is supplied `res.respond` might be not yet avalaible, + // in this case we use the standard res.status(...).json(...) + return res.status(responseErr.httpCode).json(jsonRes); +}; diff --git a/website/server/middlewares/api-v3/index.js b/website/server/middlewares/api-v3/index.js new file mode 100644 index 0000000000..694fdb6ea5 --- /dev/null +++ b/website/server/middlewares/api-v3/index.js @@ -0,0 +1,87 @@ +// This module is only used to attach middlewares to the express app +import errorHandler from './errorHandler'; +import bodyParser from 'body-parser'; +import notFoundHandler from './notFound'; +import nconf from 'nconf'; +import morgan from 'morgan'; +import cookieSession from 'cookie-session'; +import cors from './cors'; +import staticMiddleware from './static'; +import domainMiddleware from './domain'; +import mongoose from 'mongoose'; +import compression from 'compression'; +import favicon from 'serve-favicon'; +import methodOverride from 'method-override'; +import passport from 'passport'; +import path from 'path'; +import maintenanceMode from './maintenanceMode'; +import { + forceSSL, + forceHabitica, +} from './redirects'; +import v1 from './v1'; +import v2 from './v2'; +import v3 from './v3'; +import responseHandler from './response'; +import { + attachTranslateFunction, +} from './language'; + +const IS_PROD = nconf.get('IS_PROD'); +const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); +const PUBLIC_DIR = path.join(__dirname, '/../../../client'); + +const SESSION_SECRET = nconf.get('SESSION_SECRET'); +const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; + +module.exports = function attachMiddlewares (app, server) { + app.set('view engine', 'jade'); + app.set('views', `${__dirname}/../views`); + + app.use(domainMiddleware(server, mongoose)); + + if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev')); + + // add res.respond and res.t + app.use(responseHandler); + app.use(attachTranslateFunction); + + app.use(compression()); + app.use(favicon(`${PUBLIC_DIR}/favicon.ico`)); + + app.use(maintenanceMode); + + app.use(cors); + app.use(forceSSL); + app.use(forceHabitica); + + app.use(bodyParser.urlencoded({ + extended: true, // Uses 'qs' library as old connect middleware + })); + app.use(bodyParser.json()); + app.use(methodOverride()); + + app.use(cookieSession({ + name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies + secret: SESSION_SECRET, + httpOnly: true, // so cookies are not accessible with browser JS + // TODO what about https only (secure) ? + maxAge: TWO_WEEKS, + })); + + // Initialize Passport! Also use passport.session() middleware, to support + // persistent login sessions (recommended). + app.use(passport.initialize()); + app.use(passport.session()); + + app.use('/api/v2', v2); + app.use('/api/v1', v1); + app.use(v3); // the main app, also setup top-level routes + staticMiddleware(app); + + app.use(notFoundHandler); + + // Error handler middleware, define as the last one. + // Used for v3 and v1, v2 will keep using its own error handler + app.use(errorHandler); +}; diff --git a/website/server/middlewares/api-v3/language.js b/website/server/middlewares/api-v3/language.js new file mode 100644 index 0000000000..b83c9d5208 --- /dev/null +++ b/website/server/middlewares/api-v3/language.js @@ -0,0 +1,91 @@ +import { model as User } from '../../models/user'; +import accepts from 'accepts'; +import common from '../../../../common'; +import _ from 'lodash'; +import { + translations, + defaultLangCodes, + multipleVersionsLanguages, +} from '../../libs/api-v3/i18n'; + +const i18n = common.i18n; + +function _getUniqueListOfLanguages (languages) { + let acceptableLanguages = _(languages).map((lang) => { + return lang.slice(0, 2); + }).uniq().value(); + + let uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes); + + return uniqueListOfLanguages; +} + +function _checkForApplicableLanguageVariant (originalLanguageOptions) { + let languageVariant = _.find(originalLanguageOptions, (accepted) => { + let trimmedAccepted = accepted.slice(0, 2); + + return multipleVersionsLanguages[trimmedAccepted]; + }); + + return languageVariant; +} + +function _getFromBrowser (req) { + let originalLanguageOptions = accepts(req).languages(); + let uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions); + let baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase(); + let languageMapping = multipleVersionsLanguages[baseLanguage]; + + if (languageMapping) { + let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions); + + if (languageVariant) { + languageVariant = languageVariant.toLowerCase(); + } else { + return 'en'; + } + + return languageMapping[languageVariant] || baseLanguage; + } else { + return baseLanguage || 'en'; + } +} + +function _getFromUser (user, req) { + let preferredLang = user && user.preferences && user.preferences.language; + let lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req); + + return lang; +} + +export function attachTranslateFunction (req, res, next) { + res.t = function reqTranslation () { + return i18n.t(...arguments, req.language); + }; + + next(); +} + +export function getUserLanguage (req, res, next) { + if (req.query.lang) { // In case the language is specified in the request url, use it + req.language = translations[req.query.lang] ? req.query.lang : 'en'; + return next(); + } else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language + req.language = _getFromUser(req.locals.user, req); + return next(); + } else if (req.session && req.session.userId) { // Same thing if the user has a valid session + return User.findOne({ + _id: req.session.userId, + }, 'preferences.language') + .lean() + .exec() + .then((user) => { + req.language = _getFromUser(user, req); + return next(); + }) + .catch(next); + } else { // Otherwise get from browser + req.language = _getFromUser(null, req); + return next(); + } +} diff --git a/website/server/middlewares/api-v3/locals.js b/website/server/middlewares/api-v3/locals.js new file mode 100644 index 0000000000..e6e06e3b16 --- /dev/null +++ b/website/server/middlewares/api-v3/locals.js @@ -0,0 +1,61 @@ +import nconf from 'nconf'; +import _ from 'lodash'; +import shared from '../../../../common'; +import * as i18n from '../../libs/api-v3/i18n'; +import { + getBuildUrl, + getManifestFiles, +} from '../../libs/api-v3/buildManifest'; +import forceRefresh from './../forceRefresh'; +import { tavernQuest } from '../../models/group'; +import { mods } from '../../models/user'; + +// To avoid stringifying more data then we need, +// items from `env` used on the client will have to be specified in this array +const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations', + 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID', + 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY', + 'worldDmg', 'mods', 'IS_MOBILE']; + +let env = { + getManifestFiles, + getBuildUrl, + _, + clientVars: CLIENT_VARS, + mods, + Content: shared.content, + siteVersion: forceRefresh.siteVersion, + availableLanguages: i18n.availableLanguages, + AMAZON_PAYMENTS: { + SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), + CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'), + }, +}; + +'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(key => { + env[key] = nconf.get(key); +}); + +module.exports = function locals (req, res, next) { + let language = _.find(i18n.availableLanguages, {code: req.language}); + let isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' + + // Load moment.js language file only when not on static pages + language.momentLang = !isStaticPage && i18n.momentLangs[language.code] || undefined; + + res.locals.habitrpg = _.assign(env, { + IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), + language, + isStaticPage, + translations: i18n.translations[language.code], + t (...args) { // stringName and vars are the allowed parameters + args.push(language.code); + return shared.i18n.t(...args); + }, + // Defined here and not outside of the middleware because tavernQuest might be an + // empty object until the query to fetch it finishes + worldDmg: tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg || {}, + }); + + next(); +}; diff --git a/website/server/middlewares/api-v3/maintenanceMode.js b/website/server/middlewares/api-v3/maintenanceMode.js new file mode 100644 index 0000000000..331663125a --- /dev/null +++ b/website/server/middlewares/api-v3/maintenanceMode.js @@ -0,0 +1,31 @@ +import { getUserLanguage } from './language'; +import nconf from 'nconf'; + +const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE'); + +module.exports = function maintenanceMode (req, res, next) { + if (MAINTENANCE_MODE !== 'true') return next(); + + getUserLanguage(req, res, (err) => { + if (err) return next(err); + + let pageVariables = { + maintenanceStart: nconf.get('MAINTENANCE_START'), + maintenanceEnd: nconf.get('MAINTENANCE_END'), + translation: res.t, + }; + + if (req.headers && req.headers.accept && req.headers.accept.indexOf('text/html') !== -1) { + if (req.path === '/views/static/maintenance-info') { + return res.status(503).render('../../../views/static/maintenance-info', pageVariables); + } else { + return res.status(503).render('../../../views/static/maintenance', pageVariables); + } + } else { + return res.status(503).send({ + error: 'Maintenance', + message: 'Server offline for maintenance.', + }); + } + }); +}; diff --git a/website/server/middlewares/api-v3/notFound.js b/website/server/middlewares/api-v3/notFound.js new file mode 100644 index 0000000000..6a71b5def4 --- /dev/null +++ b/website/server/middlewares/api-v3/notFound.js @@ -0,0 +1,7 @@ +import { + NotFound, +} from '../../libs/api-v3/errors'; + +module.exports = function NotFoundMiddleware (req, res, next) { + next(new NotFound()); +}; diff --git a/website/server/middlewares/api-v3/redirects.js b/website/server/middlewares/api-v3/redirects.js new file mode 100644 index 0000000000..a907a89b14 --- /dev/null +++ b/website/server/middlewares/api-v3/redirects.js @@ -0,0 +1,43 @@ +import nconf from 'nconf'; + +const IS_PROD = nconf.get('IS_PROD'); +const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT'); +const BASE_URL = nconf.get('BASE_URL'); + +function isHTTP (req) { + return ( // eslint-disable-line no-extra-parens + req.header('x-forwarded-proto') && + req.header('x-forwarded-proto') !== 'https' && + IS_PROD && + BASE_URL.indexOf('https') === 0 + ); +} + +function isProxied (req) { + return ( // eslint-disable-line no-extra-parens + req.header('x-habitica-lb') && + req.header('x-habitica-lb') === 'Yes' + ); +} + +export function forceSSL (req, res, next) { + if (isHTTP(req) && !isProxied(req)) { + return res.redirect(BASE_URL + req.originalUrl); + } + + next(); +} + +// Redirect to habitica for non-api urls + +function nonApiUrl (req) { + return req.originalUrl.search(/\/api\//) === -1; +} + +export function forceHabitica (req, res, next) { + if (IS_PROD && !IGNORE_REDIRECT && !isProxied(req) && nonApiUrl(req)) { + return res.redirect(301, BASE_URL + req.url); + } + + next(); +} diff --git a/website/server/middlewares/api-v3/response.js b/website/server/middlewares/api-v3/response.js new file mode 100644 index 0000000000..e00d691fb2 --- /dev/null +++ b/website/server/middlewares/api-v3/response.js @@ -0,0 +1,25 @@ +module.exports = function responseHandler (req, res, next) { + // Only used for successful responses + res.respond = function respond (status = 200, data = {}, message) { + let user = res.locals && res.locals.user; + + let response = { + success: status < 400, + data, + }; + + if (message) response.message = message; + + // When userV=Number (user version) query parameter is passed and a user is logged in, + // sends back the current user._v in the response so that the client + // can verify if it's the most up to date data. + // Considered part of the private API for now and not officially supported + if (user && req.query.userV) { + response.userV = user._v; + } + + res.status(status).json(response); + }; + + next(); +}; diff --git a/website/server/middlewares/api-v3/setupBody.js b/website/server/middlewares/api-v3/setupBody.js new file mode 100644 index 0000000000..b3309fb2da --- /dev/null +++ b/website/server/middlewares/api-v3/setupBody.js @@ -0,0 +1,5 @@ +// TODO test this middleware +module.exports = function setupBodyMiddleware (req, res, next) { + req.body = req.body || {}; + next(); +}; diff --git a/website/server/middlewares/api-v3/static.js b/website/server/middlewares/api-v3/static.js new file mode 100644 index 0000000000..19c1c9025c --- /dev/null +++ b/website/server/middlewares/api-v3/static.js @@ -0,0 +1,18 @@ +import express from 'express'; +import nconf from 'nconf'; +import path from 'path'; + +const IS_PROD = nconf.get('IS_PROD'); +const MAX_AGE = IS_PROD ? 31536000000 : 0; +const PUBLIC_DIR = path.join(__dirname, '/../../../client'); +const BUILD_DIR = path.join(__dirname, '/../../../build'); + +module.exports = function staticMiddleware (expressApp) { + // TODO move all static files to a single location (one for public and one for build) + expressApp.use(express.static(BUILD_DIR, { maxAge: MAX_AGE })); + expressApp.use('/common/dist', express.static(`${PUBLIC_DIR}/../../common/dist`, { maxAge: MAX_AGE })); + expressApp.use('/common/audio', express.static(`${PUBLIC_DIR}/../../common/audio`, { maxAge: MAX_AGE })); + expressApp.use('/common/script/public', express.static(`${PUBLIC_DIR}/../../common/script/public`, { maxAge: MAX_AGE })); + expressApp.use('/common/img', express.static(`${PUBLIC_DIR}/../../common/img`, { maxAge: MAX_AGE })); + expressApp.use(express.static(PUBLIC_DIR)); +}; diff --git a/website/server/middlewares/api-v3/v1.js b/website/server/middlewares/api-v3/v1.js new file mode 100644 index 0000000000..abe26f42e6 --- /dev/null +++ b/website/server/middlewares/api-v3/v1.js @@ -0,0 +1,19 @@ +// API v1 middlewares and routes +// DEPRECATED AND INACTIVE + +import express from 'express'; +import nconf from 'nconf'; +import { + NotFound, +} from '../../libs/api-v3/errors'; + +const router = express.Router(); // eslint-disable-line babel/new-cap + +const BASE_URL = nconf.get('BASE_URL'); + +router.all('*', function deprecatedV1 (req, res, next) { + let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/static/api).`); + return next(error); +}); + +module.exports = router; diff --git a/website/server/middlewares/api-v3/v2.js b/website/server/middlewares/api-v3/v2.js new file mode 100644 index 0000000000..ec49e326a4 --- /dev/null +++ b/website/server/middlewares/api-v3/v2.js @@ -0,0 +1,27 @@ +// DEPRECATED BUT STILL ACTIVE + +// import path from 'path'; +import swagger from 'swagger-node-express'; +// import shared from '../../../../common'; +import express from 'express'; +import analytics from './analytics'; +import responseHandler from './response'; + +const v2app = express(); + +// re-set the view options because they are not inherited from the top level app +v2app.set('view engine', 'jade'); +v2app.set('views', `${__dirname}/../../../views`); + +v2app.use(analytics); +v2app.use(responseHandler); + + +// Custom Directives +v2app.use('/', require('../../routes/api-v2/auth')); + +require('../../routes/api-v2/swagger')(swagger, v2app); + +v2app.use(require('../api-v2/errorHandler')); + +module.exports = v2app; diff --git a/website/server/middlewares/api-v3/v3.js b/website/server/middlewares/api-v3/v3.js new file mode 100644 index 0000000000..f9aa637fa0 --- /dev/null +++ b/website/server/middlewares/api-v3/v3.js @@ -0,0 +1,30 @@ +import express from 'express'; +import expressValidator from 'express-validator'; +import analytics from './analytics'; +import setupBody from './setupBody'; +import routes from '../../libs/api-v3/routes'; +import path from 'path'; + +const API_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/'); +const TOP_LEVEL_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/top-level/'); + +const v3app = express(); + +// re-set the view options because they are not inherited from the top level app +v3app.set('view engine', 'jade'); +v3app.set('views', `${__dirname}/../../../views`); + +v3app.use(expressValidator()); +v3app.use(analytics); +v3app.use(setupBody); + +const topLevelRouter = express.Router(); // eslint-disable-line babel/new-cap + +routes.walkControllers(topLevelRouter, TOP_LEVEL_CONTROLLERS_PATH); +v3app.use('/', topLevelRouter); + +const v3Router = express.Router(); // eslint-disable-line babel/new-cap +routes.walkControllers(v3Router, API_CONTROLLERS_PATH); +v3app.use('/api/v3', v3Router); + +module.exports = v3app; diff --git a/website/src/middlewares/apiThrottle.js b/website/server/middlewares/apiThrottle.js similarity index 74% rename from website/src/middlewares/apiThrottle.js rename to website/server/middlewares/apiThrottle.js index 8de298106c..b392cc777e 100644 --- a/website/src/middlewares/apiThrottle.js +++ b/website/server/middlewares/apiThrottle.js @@ -3,8 +3,10 @@ var limiter = require('connect-ratelimit'); var IS_PROD = nconf.get('NODE_ENV') === 'production'; +// TODO since Habitica runs on many different servers this module is pretty useless +// as it will only block requests that go to the same server but anyway we should probably have a rate limiter in place + module.exports = function(app) { - // TODO review later // disable the rate limiter middleware if (/*!IS_PROD || */true) return; app.use(limiter({ diff --git a/website/src/middlewares/forceRefresh.js b/website/server/middlewares/forceRefresh.js similarity index 83% rename from website/src/middlewares/forceRefresh.js rename to website/server/middlewares/forceRefresh.js index 6d5b4fa8c9..f843694790 100644 --- a/website/src/middlewares/forceRefresh.js +++ b/website/server/middlewares/forceRefresh.js @@ -1,3 +1,5 @@ +// TODO do we need this module anymore in v3? No + module.exports.siteVersion = 1; module.exports.middleware = function(req, res, next){ diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js new file mode 100644 index 0000000000..9901af0e79 --- /dev/null +++ b/website/server/models/challenge.js @@ -0,0 +1,426 @@ +import mongoose from 'mongoose'; +import Bluebird from 'bluebird'; +import validator from 'validator'; +import baseModel from '../libs/api-v3/baseModel'; +import _ from 'lodash'; +import * as Tasks from './task'; +import { model as User } from './user'; +import { + model as Group, + TAVERN_ID, +} from './group'; +import { removeFromArray } from '../libs/api-v3/collectionManipulators'; +import shared from '../../../common'; +import { sendTxn as txnEmail } from '../libs/api-v3/email'; +import sendPushNotification from '../libs/api-v3/pushNotifications'; +import cwait from 'cwait'; + +let Schema = mongoose.Schema; + +let schema = new Schema({ + name: {type: String, required: true}, + shortName: {type: String, required: true, minlength: 3}, + description: String, + official: {type: Boolean, default: false}, + tasksOrder: { + habits: [{type: String, ref: 'Task'}], + dailys: [{type: String, ref: 'Task'}], + todos: [{type: String, ref: 'Task'}], + rewards: [{type: String, ref: 'Task'}], + }, + leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, + group: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, + memberCount: {type: Number, default: 1}, + prize: {type: Number, default: 0, min: 0}, +}, { + strict: true, + minimize: false, // So empty objects are returned +}); + +schema.plugin(baseModel, { + noSet: ['_id', 'memberCount', 'tasksOrder'], + timestamps: true, +}); + +// A list of additional fields that cannot be updated (but can be set on creation) +let noUpdate = ['group', 'official', 'shortName', 'prize']; +schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) { + return this.sanitize(updateObj, noUpdate); +}; + +// Returns true if user is a member of the challenge +schema.methods.isMember = function isChallengeMember (user) { + return user.challenges.indexOf(this._id) !== -1; +}; + +// Returns true if the user can modify (close, selectWinner, ...) the challenge +schema.methods.canModify = function canModifyChallenge (user) { + return user.contributor.admin || this.leader === user._id; +}; + +// Returns true if user has access to the challenge (can join) +schema.methods.hasAccess = function hasAccessToChallenge (user, group) { + if (group.type === 'guild' && group.privacy === 'public') return true; + return user.getGroups().indexOf(this.group) !== -1; +}; + +// Returns true if user can view the challenge +// Different from hasAccess because you can see challenges of groups you've been removed from if you're partecipating in them +schema.methods.canView = function canViewChallenge (user, group) { + if (this.isMember(user)) return true; + return this.hasAccess(user, group); +}; + +// Takes a Task document and return a plain object of attributes that can be synced to the user +function _syncableAttrs (task) { + let t = task.toObject(); // lodash doesn't seem to like _.omit on Document + // only sync/compare important attrs + let omitAttrs = ['_id', 'userId', 'challenge', 'history', 'tags', 'completed', 'streak', 'notes', 'updatedAt']; + if (t.type !== 'reward') omitAttrs.push('value'); + return _.omit(t, omitAttrs); +} + +// Sync challenge to user, including tasks and tags. +// Used when user joins the challenge or to force sync. +schema.methods.syncToUser = async function syncChallengeToUser (user) { + let challenge = this; + challenge.shortName = challenge.shortName || challenge.name; + + // Add challenge to user.challenges + if (!_.contains(user.challenges, challenge._id)) user.challenges.push(challenge._id); + + // Sync tags + let userTags = user.tags; + let i = _.findIndex(userTags, {id: challenge._id}); + + if (i !== -1) { + if (userTags[i].name !== challenge.shortName) { + // update the name - it's been changed since + userTags[i].name = challenge.shortName; + } + } else { + userTags.push({ + id: challenge._id, + name: challenge.shortName, + challenge: true, + }); + } + + let [challengeTasks, userTasks] = await Bluebird.all([ + // Find original challenge tasks + Tasks.Task.find({ + userId: {$exists: false}, + 'challenge.id': challenge._id, + }).exec(), + // Find user's tasks linked to this challenge + Tasks.Task.find({ + userId: user._id, + 'challenge.id': challenge._id, + }).exec(), + ]); + + let toSave = []; // An array of things to save + + challengeTasks.forEach(chalTask => { + let matchingTask = _.find(userTasks, userTask => userTask.challenge.taskId === chalTask._id); + + if (!matchingTask) { // If the task is new, create it + matchingTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask))); + matchingTask.challenge = {taskId: chalTask._id, id: challenge._id}; + matchingTask.userId = user._id; + user.tasksOrder[`${chalTask.type}s`].push(matchingTask._id); + } else { + _.merge(matchingTask, _syncableAttrs(chalTask)); + // Make sure the task is in user.tasksOrder + let orderList = user.tasksOrder[`${chalTask.type}s`]; + if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id); + } + + if (!matchingTask.notes) matchingTask.notes = chalTask.notes; // don't override the notes, but provide it if not provided + if (matchingTask.tags.indexOf(challenge._id) === -1) matchingTask.tags.push(challenge._id); // add tag if missing + toSave.push(matchingTask.save()); + }); + + // Flag deleted tasks as "broken" + userTasks.forEach(userTask => { + if (!_.find(challengeTasks, chalTask => chalTask._id === userTask.challenge.taskId)) { + userTask.challenge.broken = 'TASK_DELETED'; + toSave.push(userTask.save()); + } + }); + + toSave.push(user.save()); + return Bluebird.all(toSave); +}; + +async function _fetchMembersIds (challengeId) { + return (await User.find({challenges: {$in: [challengeId]}}).select('_id').lean().exec()).map(member => member._id); +} + +async function _addTaskFn (challenge, tasks, memberId) { + let updateTasksOrderQ = {$push: {}}; + let toSave = []; + + tasks.forEach(chalTask => { + let userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask))); + userTask.challenge = {taskId: chalTask._id, id: challenge._id}; + userTask.userId = memberId; + + let tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`]; + if (!tasksOrderList) { + updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`] = { + $position: 0, // unshift + $each: [userTask._id], + }; + } else { + tasksOrderList.$each.unshift(userTask._id); + } + + toSave.push(userTask.save({ + validateBeforeSave: false, // no user data supplied + })); + }); + + // Update the user + toSave.unshift(User.update({_id: memberId}, updateTasksOrderQ).exec()); + return await Bluebird.all(toSave); +} + +// Add a new task to challenge members +schema.methods.addTasks = async function challengeAddTasks (tasks) { + let challenge = this; + let membersIds = await _fetchMembersIds(challenge._id); + + let queue = new cwait.TaskQueue(Bluebird, 5); // process only 5 users concurrently + + await Bluebird.map(membersIds, queue.wrap((memberId) => { + return _addTaskFn(challenge, tasks, memberId); + })); +}; + +// Sync updated task to challenge members +schema.methods.updateTask = async function challengeUpdateTask (task) { + let challenge = this; + + let updateCmd = {$set: {}}; + + let syncableAttrs = _syncableAttrs(task); + for (let key in syncableAttrs) { + updateCmd.$set[key] = syncableAttrs[key]; + } + + // Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks + await Tasks.Task.update({ + userId: {$exists: true}, + 'challenge.id': challenge.id, + 'challenge.taskId': task._id, + }, updateCmd, {multi: true}).exec(); +}; + +// Remove a task from challenge members +schema.methods.removeTask = async function challengeRemoveTask (task) { + let challenge = this; + + // Set the task as broken + await Tasks.Task.update({ + userId: {$exists: true}, + 'challenge.id': challenge.id, + 'challenge.taskId': task._id, + }, { + $set: {'challenge.broken': 'TASK_DELETED'}, + }, {multi: true}).exec(); +}; + +// Unlink challenges tasks (and the challenge itself) from user +schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) { + let challengeId = this._id; + let findQuery = { + userId: user._id, + 'challenge.id': challengeId, + }; + + removeFromArray(user.challenges, challengeId); + + if (keep === 'keep-all') { + await Tasks.Task.update(findQuery, { + $set: {challenge: {}}, + }, {multi: true}).exec(); + + await user.save(); + } else { // keep = 'remove-all' + let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec(); + let taskPromises = tasks.map(task => { + // Remove task from user.tasksOrder and delete them + if (task.type !== 'todo' || !task.completed) { + removeFromArray(user.tasksOrder[`${task.type}s`], task._id); + } + + return task.remove(); + }); + user.markModified('tasksOrder'); + taskPromises.push(user.save()); + return Bluebird.all(taskPromises); + } +}; + +// TODO everything here should be moved to a worker +// actually even for a worker it's probably just too big and will kill mongo, figure out something else +schema.methods.closeChal = async function closeChal (broken = {}) { + let challenge = this; + + let winner = broken.winner; + let brokenReason = broken.broken; + + // Delete the challenge + await this.model('Challenge').remove({_id: challenge._id}).exec(); + + // Refund the leader if the challenge is closed and the group not the tavern + if (challenge.group !== TAVERN_ID && brokenReason === 'CHALLENGE_DELETED') { + await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec(); + } + + // Update the challengeCount on the group + await Group.update({_id: challenge.group}, {$inc: {challengeCount: -1}}).exec(); + + // Award prize to winner and notify + if (winner) { + winner.achievements.challenges.push(challenge.name); + winner.balance += challenge.prize / 4; + let savedWinner = await winner.save(); + if (savedWinner.preferences.emailNotifications.wonChallenge !== false) { + txnEmail(savedWinner, 'won-challenge', [ + {name: 'CHALLENGE_NAME', content: challenge.name}, + ]); + } + + sendPushNotification(savedWinner, shared.i18n.t('wonChallenge'), challenge.name); + } + + // Run some operations in the background withouth blocking the thread + let backgroundTasks = [ + // And it's tasks + Tasks.Task.remove({'challenge.id': challenge._id, userId: {$exists: false}}).exec(), + // Set the challenge tag to non-challenge status and remove the challenge from the user's challenges + User.update({ + challenges: challenge._id, + 'tags._id': challenge._id, + }, { + $set: {'tags.$.challenge': false}, + $pull: {challenges: challenge._id}, + }, {multi: true}).exec(), + // Break users' tasks + Tasks.Task.update({ + 'challenge.id': challenge._id, + }, { + $set: { + 'challenge.broken': brokenReason, + 'challenge.winner': winner && winner.profile.name, + }, + }, {multi: true}).exec(), + ]; + + Bluebird.all(backgroundTasks); +}; + +// Methods to adapt the new schema to API v2 responses (mostly tasks inside the challenge model) +// These will be removed once API v2 is discontinued + +// Get all the tasks belonging to a challenge, +schema.methods.getTasks = function getChallengeTasks () { + let args = Array.from(arguments); + let cb; + let type; + + if (args.length === 1) { + cb = args[0]; + } else if (args.length > 1) { + type = args[0]; + cb = args[1]; + } else { + cb = function noop () {}; + } + + let query = { + userId: { + $exists: false, + }, + + 'challenge.id': this._id, + }; + + if (type) query.type = type; + + return Tasks.Task.find(query, cb); // so we can use it as a promise +}; + +// Given challenge and an array of tasks and one of members return an API compatible challenge + tasks obj + members +schema.methods.addToChallenge = function addToChallenge (tasks, members) { + let obj = this.toJSON(); + obj.members = members; + + let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it + + obj.habits = []; + obj.dailys = []; + obj.todos = []; + obj.rewards = []; + + obj.tasksOrder = undefined; + let unordered = []; + + tasks.forEach((task) => { + // We want to push the task at the same position where it's stored in tasksOrder + let pos = tasksOrder[`${task.type}s`].indexOf(task._id); + if (pos === -1) { // Should never happen, it means the lists got out of sync + unordered.push(task.toJSONV2()); + } else { + obj[`${task.type}s`][pos] = task.toJSONV2(); + } + }); + + // Reconcile unordered items + unordered.forEach((task) => { + obj[`${task.type}s`].push(task); + }); + + // Remove null values that can be created when inserting tasks at an index > length + ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { + obj[type] = _.compact(obj[type]); + }); + + return obj; +}; + +// Return the data maintaining backward compatibility +schema.methods.getTransformedData = function getTransformedData (options) { + let self = this; + + let cb = options.cb; + let populateMembers = options.populateMembers; + + let queryMembers = { + challenges: self._id, + }; + + let selectDataMembers = '_id'; + + if (populateMembers) { + selectDataMembers += ` ${populateMembers}`; + } + + let membersQuery = User.find(queryMembers).select(selectDataMembers); + if (options.limitPopulation) membersQuery.limit(15); + + Bluebird.all([ + membersQuery.exec(), + self.getTasks(), + ]) + .then((results) => { + cb(null, self.addToChallenge(results[1], results[0])); + }) + .catch(cb); +}; + +// END of API v2 methods + +export let model = mongoose.model('Challenge', schema); diff --git a/website/server/models/coupon.js b/website/server/models/coupon.js new file mode 100644 index 0000000000..af9953aba0 --- /dev/null +++ b/website/server/models/coupon.js @@ -0,0 +1,57 @@ +/* eslint-disable camelcase */ + +import mongoose from 'mongoose'; +import _ from 'lodash'; +import shared from '../../../common'; +import couponCode from 'coupon-code'; +import baseModel from '../libs/api-v3/baseModel'; +import { + BadRequest, + NotAuthorized, +} from '../libs/api-v3/errors'; + +export let schema = new mongoose.Schema({ + _id: {type: String, default: couponCode.generate}, + event: {type: String, enum: ['wondercon', 'google_6mo']}, + user: {type: String, ref: 'User'}, +}, { + strict: true, + minimize: false, // So empty objects are returned +}); + +schema.plugin(baseModel, { + timestamps: true, + _id: false, +}); + +schema.statics.generate = async function generateCoupons (event, count = 1) { + let coupons = _.times(count, () => { + return {event}; + }); + + return await this.create(coupons); +}; + +schema.statics.apply = async function applyCoupon (user, req, code) { + let coupon = await this.findById(couponCode.validate(code)).exec(); + if (!coupon) throw new BadRequest(shared.i18n.t('invalidCoupon', req.language)); + if (coupon.user) throw new NotAuthorized(shared.i18n.t('couponUsed', req.language)); + + if (coupon.event === 'wondercon') { + user.items.gear.owned.eyewear_special_wondercon_red = true; + user.items.gear.owned.eyewear_special_wondercon_black = true; + user.items.gear.owned.back_special_wondercon_black = true; + user.items.gear.owned.back_special_wondercon_red = true; + user.items.gear.owned.body_special_wondercon_red = true; + user.items.gear.owned.body_special_wondercon_black = true; + user.items.gear.owned.body_special_wondercon_gold = true; + user.extra = {signupEvent: 'wondercon'}; + } + + await user.save(); + coupon.user = user._id; + await coupon.save(); +}; + +module.exports.schema = schema; +export let model = mongoose.model('Coupon', schema); diff --git a/website/server/models/emailUnsubscription.js b/website/server/models/emailUnsubscription.js new file mode 100644 index 0000000000..fe30e5d608 --- /dev/null +++ b/website/server/models/emailUnsubscription.js @@ -0,0 +1,24 @@ +import mongoose from 'mongoose'; +import validator from 'validator'; +import baseModel from '../libs/api-v3/baseModel'; + +// A collection used to store mailing list unsubscription for non registered email addresses +export let schema = new mongoose.Schema({ + email: { + type: String, + required: true, + trim: true, + lowercase: true, + validator: [validator.isEmail, 'Invalid email.'], + }, +}, { + strict: true, + minimize: false, // So empty objects are returned +}); + +schema.plugin(baseModel, { + noSet: ['_id'], + timestamps: true, +}); + +export let model = mongoose.model('EmailUnsubscription', schema); diff --git a/website/server/models/group.js b/website/server/models/group.js new file mode 100644 index 0000000000..53305d94b9 --- /dev/null +++ b/website/server/models/group.js @@ -0,0 +1,760 @@ +import mongoose from 'mongoose'; +import { + model as User, + nameFields, +} from './user'; +import shared from '../../../common'; +import _ from 'lodash'; +import { model as Challenge} from './challenge'; +import validator from 'validator'; +import { removeFromArray } from '../libs/api-v3/collectionManipulators'; +import { + InternalServerError, + BadRequest, +} from '../libs/api-v3/errors'; +import * as firebase from '../libs/api-v2/firebase'; +import baseModel from '../libs/api-v3/baseModel'; +import { sendTxn as sendTxnEmail } from '../libs/api-v3/email'; +import Bluebird from 'bluebird'; +import nconf from 'nconf'; +import sendPushNotification from '../libs/api-v3/pushNotifications'; + +const questScrolls = shared.content.quests; +const Schema = mongoose.Schema; + +export const INVITES_LIMIT = 100; +export const TAVERN_ID = shared.TAVERN_ID; + +// NOTE once Firebase is enabled any change to groups' members in MongoDB will have to be run through the API +// changes made directly to the db will cause Firebase to get out of sync +export let schema = new Schema({ + name: {type: String, required: true}, + description: String, + leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, + type: {type: String, enum: ['guild', 'party'], required: true}, + privacy: {type: String, enum: ['private', 'public'], default: 'private', required: true}, + chat: Array, + /* + # [{ + # timestamp: Date + # user: String + # text: String + # contributor: String + # uuid: String + # id: String + # }] + */ + leaderOnly: { // restrict group actions to leader (members can't do them) + challenges: {type: Boolean, default: false, required: true}, + // invites: {type: Boolean, default: false, required: true}, + }, + memberCount: {type: Number, default: 1}, + challengeCount: {type: Number, default: 0}, + balance: {type: Number, default: 0}, + logo: String, + leaderMessage: String, + quest: { + key: String, + active: {type: Boolean, default: false}, + leader: {type: String, ref: 'User'}, + progress: { + hp: Number, + collect: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, // {feather: 5, ingot: 3} + rage: Number, // limit break / "energy stored in shell", for explosion-attacks + }, + + // Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click + // 'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them. + // TODO when booting user, remove from .joined and check again if we can now start the quest + members: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + extra: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + }, +}, { + strict: true, + minimize: false, // So empty objects are returned +}); + +schema.plugin(baseModel, { + noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount'], +}); + +// A list of additional fields that cannot be updated (but can be set on creation) +let noUpdate = ['privacy', 'type']; +schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) { + return this.sanitize(updateObj, noUpdate); +}; + +// Basic fields to fetch for populating a group info +export let basicFields = 'name type privacy'; + +schema.pre('remove', true, async function preRemoveGroup (next, done) { + next(); + try { + await this.removeGroupInvitations(); + done(); + } catch (err) { + done(err); + } +}); + +schema.post('remove', function postRemoveGroup (group) { + firebase.deleteGroup(group._id); +}); + +schema.statics.getGroup = async function getGroup (options = {}) { + let {user, groupId, fields, optionalMembership = false, populateLeader = false, requireMembership = false} = options; + let query; + + let isUserParty = groupId === 'party' || user.party._id === groupId; + let isUserGuild = user.guilds.indexOf(groupId) !== -1; + let isTavern = ['habitrpg', TAVERN_ID].indexOf(groupId) !== -1; + + // When requireMembership is true check that user is member even in public guild + if (requireMembership && !isUserParty && !isUserGuild && !isTavern) { + return null; + } + + // When optionalMembership is true it's not required for the user to be a member of the group + if (isUserParty) { + query = {type: 'party', _id: user.party._id}; + } else if (isTavern) { + query = {_id: TAVERN_ID}; + } else if (optionalMembership === true) { + query = {_id: groupId}; + } else if (isUserGuild) { + query = {type: 'guild', _id: groupId}; + } else { + query = {type: 'guild', privacy: 'public', _id: groupId}; + } + + let mQuery = this.findOne(query); + if (fields) mQuery.select(fields); + if (populateLeader === true) mQuery.populate('leader', nameFields); + let group = await mQuery.exec(); + return group; +}; + +export const VALID_QUERY_TYPES = ['party', 'guilds', 'privateGuilds', 'publicGuilds', 'tavern']; + +schema.statics.getGroups = async function getGroups (options = {}) { + let {user, types, groupFields = basicFields, sort = '-memberCount', populateLeader = false} = options; + let queries = []; + + // Throw error if an invalid type is supplied + let areValidTypes = types.every(type => VALID_QUERY_TYPES.indexOf(type) !== -1); + if (!areValidTypes) throw new BadRequest(shared.i18n.t('groupTypesRequired')); + + types.forEach(type => { + switch (type) { + case 'party': { + queries.push(this.getGroup({user, groupId: 'party', fields: groupFields, populateLeader})); + break; + } + case 'guilds': { + let userGuildsQuery = this.find({ + type: 'guild', + _id: {$in: user.guilds}, + }).select(groupFields); + if (populateLeader === true) userGuildsQuery.populate('leader', nameFields); + userGuildsQuery.sort(sort).exec(); + queries.push(userGuildsQuery); + break; + } + case 'privateGuilds': { + let privateGuildsQuery = this.find({ + type: 'guild', + privacy: 'private', + _id: {$in: user.guilds}, + }).select(groupFields); + if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields); + privateGuildsQuery.sort(sort).exec(); + queries.push(privateGuildsQuery); + break; + } + // NOTE: when returning publicGuilds we use `.lean()` so all mongoose methods won't be available. + // Docs are going to be plain javascript objects + case 'publicGuilds': { + let publicGuildsQuery = this.find({ + type: 'guild', + privacy: 'public', + }).select(groupFields); + if (populateLeader === true) publicGuildsQuery.populate('leader', nameFields); + publicGuildsQuery.sort(sort).lean().exec(); + queries.push(publicGuildsQuery); + break; + } + case 'tavern': { + if (types.indexOf('publicGuilds') === -1) { + queries.push(this.getGroup({user, groupId: TAVERN_ID, fields: groupFields})); + } + break; + } + } + }); + + let groupsArray = _.reduce(await Bluebird.all(queries), (previousValue, currentValue) => { + if (_.isEmpty(currentValue)) return previousValue; // don't add anything to the results if the query returned null or an empty array + return previousValue.concat(Array.isArray(currentValue) ? currentValue : [currentValue]); // otherwise concat the new results to the previousValue + }, []); + + return groupsArray; +}; + +// When converting to json remove chat messages with more than 1 flag and remove all flags info +// unless the user is an admin +// Not putting into toJSON because there we can't access user +schema.statics.toJSONCleanChat = function groupToJSONCleanChat (group, user) { + let toJSON = group.toJSON(); + if (!user.contributor.admin) { + _.remove(toJSON.chat, chatMsg => { + chatMsg.flags = {}; + return chatMsg.flagCount >= 2; + }); + } + return toJSON; +}; + +schema.methods.removeGroupInvitations = async function removeGroupInvitations () { + let group = this; + + let usersToRemoveInvitationsFrom = await User.find({ + [`invitations.${group.type}${group.type === 'guild' ? 's' : ''}.id`]: group._id, + }).exec(); + + let userUpdates = usersToRemoveInvitationsFrom.map(user => { + if (group.type === 'party') { + user.invitations.party = {}; + this.markModified('invitations.party'); + } else { + removeFromArray(user.invitations.guilds, { id: group._id }); + } + return user.save(); + }); + + return Bluebird.all(userUpdates); +}; + +// Return true if user is a member of the group +schema.methods.isMember = function isGroupMember (user) { + if (this._id === TAVERN_ID) { + return true; // everyone is considered part of the tavern + } else if (this.type === 'party') { + return user.party._id === this._id ? true : false; + } else { // guilds + return user.guilds.indexOf(this._id) !== -1; + } +}; + +export function chatDefaults (msg, user) { + let message = { + id: shared.uuid(), + text: msg, + timestamp: Number(new Date()), + likes: {}, + flags: {}, + flagCount: 0, + }; + + if (user) { + _.defaults(message, { + uuid: user._id, + contributor: user.contributor && user.contributor.toObject(), + backer: user.backer && user.backer.toObject(), + user: user.profile.name, + }); + } else { + message.uuid = 'system'; + } + + return message; +} + +const NO_CHAT_NOTIFICATIONS = [TAVERN_ID]; +schema.methods.sendChat = function sendChat (message, user) { + this.chat.unshift(chatDefaults(message, user)); + this.chat.splice(200); + + // Kick off chat notifications in the background. + let lastSeenUpdate = {$set: {}, $inc: {_v: 1}}; + lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true}; + + // do not send notifications for guilds with more than 5000 users and for the tavern + if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > 5000) { + // TODO For Tavern, only notify them if their name was mentioned + // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names? + // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec(); + } else { + let query = {}; + + if (this.type === 'party') { + query['party._id'] = this._id; + } else { + query.guilds = this._id; + } + + query._id = { $ne: user ? user._id : ''}; + + User.update(query, lastSeenUpdate, {multi: true}).exec(); + } +}; + +schema.methods.startQuest = async function startQuest (user) { + // not using i18n strings because these errors are meant for devs who forgot to pass some parameters + if (this.type !== 'party') throw new InternalServerError('Must be a party to use this method'); + if (!this.quest.key) throw new InternalServerError('Party does not have a pending quest'); + if (this.quest.active) throw new InternalServerError('Quest is already active'); + + let userIsParticipating = this.quest.members[user._id]; + let quest = questScrolls[this.quest.key]; + let collected = {}; + if (quest.collect) { + collected = _.transform(quest.collect, (result, n, itemToCollect) => { + result[itemToCollect] = 0; + }); + } + + this.markModified('quest'); + this.quest.active = true; + if (quest.boss) { + this.quest.progress.hp = quest.boss.hp; + if (quest.boss.rage) this.quest.progress.rage = 0; + } else if (quest.collect) { + this.quest.progress.collect = collected; + } + + // Changes quest.members to only include participating members + // TODO: is that important? What does it matter if the non-participating members + // are still on the object? + // TODO: is it important to run clean quest progress on non-members like we did in v2? + this.quest.members = _.pick(this.quest.members, _.identity); + let nonUserQuestMembers = _.keys(this.quest.members); + removeFromArray(nonUserQuestMembers, user._id); + + if (userIsParticipating) { + user.party.quest.key = this.quest.key; + user.party.quest.progress.down = 0; + user.party.quest.progress.collect = collected; + user.party.quest.completed = null; + user.markModified('party.quest'); + } + + // Remove the quest from the quest leader items (if they are the current user) + if (this.quest.leader === user._id) { + user.items.quests[this.quest.key] -= 1; + user.markModified('items.quests'); + } else { // another user is starting the quest, update the leader separately + await User.update({_id: this.quest.leader}, { + $inc: { + [`items.quests.${this.quest.key}`]: -1, + }, + }).exec(); + } + + // update the remaining users + await User.update({ + _id: { $in: nonUserQuestMembers }, + }, { + $set: { + 'party.quest.key': this.quest.key, + 'party.quest.progress.down': 0, + 'party.quest.progress.collect': collected, + 'party.quest.completed': null, + }, + }, { multi: true }).exec(); + + // send notifications in the background without blocking + User.find( + { _id: { $in: nonUserQuestMembers } }, + 'party.quest items.quests auth.facebook auth.local preferences.emailNotifications pushDevices profile.name' + ).exec().then((membersToNotify) => { + let membersToEmail = _.filter(membersToNotify, (member) => { + // send push notifications and filter users that disabled emails + sendPushNotification(member, 'HabitRPG', `${shared.i18n.t('questStarted')}: ${quest.text()}`); + + return member.preferences.emailNotifications.questStarted !== false && + member._id !== user._id; + }); + sendTxnEmail(membersToEmail, 'quest-started', [ + { name: 'PARTY_URL', content: '/#/options/groups/party' }, + ]); + }); +}; + +// return a clean object for user.quest +function _cleanQuestProgress (merge) { + let clean = { + key: null, + progress: { + up: 0, + down: 0, + collect: {}, + }, + completed: null, + RSVPNeeded: false, + }; + + if (merge) { + _.merge(clean, _.omit(merge, 'progress')); + if (merge.progress) _.merge(clean.progress, merge.progress); + } + + return clean; +} + +schema.statics.cleanQuestProgress = _cleanQuestProgress; + +// returns a clean object for group.quest +schema.statics.cleanGroupQuest = function cleanGroupQuest () { + return { + key: null, + active: false, + leader: null, + progress: { + collect: {}, + }, + members: {}, + }; +}; + +// Participants: Grant rewards & achievements, finish quest +// Returns the promise from update().exec() +schema.methods.finishQuest = function finishQuest (quest) { + let questK = quest.key; + let updates = {$inc: {}, $set: {}}; + + updates.$inc[`achievements.quests.${questK}`] = 1; + updates.$inc['stats.gp'] = Number(quest.drop.gp); + updates.$inc['stats.exp'] = Number(quest.drop.exp); + updates.$inc._v = 1; + + if (this._id === TAVERN_ID) { + updates.$set['party.quest.completed'] = questK; // Just show the notif + } else { + updates.$set['party.quest'] = _cleanQuestProgress({completed: questK}); // clear quest progress + } + + _.each(quest.drop.items, (item) => { + let dropK = item.key; + + switch (item.type) { + case 'gear': { + // TODO This means they can lose their new gear on death, is that what we want? + updates.$set[`items.gear.owned.${dropK}`] = true; + break; + } + case 'eggs': + case 'food': + case 'hatchingPotions': + case 'quests': { + updates.$inc[`items.${item.type}.${dropK}`] = _.where(quest.drop.items, {type: item.type, key: item.key}).length; + break; + } + case 'pets': { + updates.$set[`items.pets.${dropK}`] = 5; + break; + } + case 'mounts': { + updates.$set[`items.mounts.${dropK}`] = true; + break; + } + } + }); + + let q = this._id === TAVERN_ID ? {} : {_id: {$in: _.keys(this.quest.members)}}; + this.quest = {}; + this.markModified('quest'); + return User.update(q, updates, {multi: true}).exec(); +}; + +function _isOnQuest (user, progress, group) { + return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true; +} + +// Returns a promise +schema.statics.collectQuest = async function collectQuest (user, progress) { + let group = await this.getGroup({user, groupId: 'party'}); + if (!_isOnQuest(user, progress, group)) return; + let quest = shared.content.quests[group.quest.key]; + + _.each(progress.collect, (v, k) => { + group.quest.progress.collect[k] += v; + }); + + let foundText = _.reduce(progress.collect, (m, v, k) => { + m.push(`${v} ${quest.collect[k].text('en')}`); + return m; + }, []); + + foundText = foundText ? foundText.join(', ') : 'nothing'; + group.sendChat(`\`${user.profile.name} found ${foundText}.\``); + group.markModified('quest.progress.collect'); + + // Still needs completing + if (_.find(shared.content.quests[group.quest.key].collect, (v, k) => { + return group.quest.progress.collect[k] < v.count; + })) return group.save(); + + await group.finishQuest(quest); + group.sendChat('`All items found! Party has received their rewards.`'); + return group.save(); +}; + +schema.statics.bossQuest = async function bossQuest (user, progress) { + let group = await this.getGroup({user, groupId: 'party'}); + if (!_isOnQuest(user, progress, group)) return; + + let quest = shared.content.quests[group.quest.key]; + if (!progress || !quest) return; // TODO why is this ever happening, progress should be defined at this point, log? + + let down = progress.down * quest.boss.str; // multiply by boss strength + + group.quest.progress.hp -= progress.up; + // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests! + let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`; + let bossAttack = nconf.get('CRON_SAFE_MODE') === 'true' ? `${quest.boss.name('en')} did not attack the party because it was asleep while maintenance was happening.` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; + group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``); + + // If boss has Rage, increment Rage as well + if (quest.boss.rage) { + group.quest.progress.rage += Math.abs(down); + if (group.quest.progress.rage >= quest.boss.rage.value) { + group.sendChat(quest.boss.rage.effect('en')); + group.quest.progress.rage = 0; + + // TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage + if (quest.boss.rage.healing) group.quest.progress.hp += group.quest.progress.hp * quest.boss.rage.healing; + if (group.quest.progress.hp > quest.boss.hp) group.quest.progress.hp = quest.boss.hp; + } + } + + // Everyone takes damage + await User.update({ + _id: {$in: _.keys(group.quest.members)}, + }, { + $inc: {'stats.hp': down, _v: 1}, + }, {multi: true}).exec(); + // Apply changes the currently cronning user locally so we don't have to reload it to get the updated state + // TODO how to mark not modified? https://github.com/Automattic/mongoose/pull/1167 + // must be notModified or otherwise could overwrite future changes: if the user is saved it'll save + // the modified user.stats.hp but that must not happen as the hp value has already been updated by the User.update above + // if (down) user.stats.hp += down; + + // Boss slain, finish quest + if (group.quest.progress.hp <= 0) { + group.sendChat(`\`You defeated ${quest.boss.name('en')}! Questing party members receive the rewards of victory.\``); + + // Participants: Grant rewards & achievements, finish quest + await group.finishQuest(shared.content.quests[group.quest.key]); + return group.save(); + } + + return group.save(); +}; + +// to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` +// we export an empty object that is then populated with the query-returned data +export let tavernQuest = {}; +let tavernQ = {_id: TAVERN_ID, 'quest.key': {$ne: null}}; + +// we use process.nextTick because at this point the model is not yet available +process.nextTick(() => { + model // eslint-disable-line no-use-before-define + .findOne(tavernQ).exec() + .then(tavern => { + if (!tavern) return; // No tavern quest + + // Using _assign so we don't lose the reference to the exported tavernQuest + _.assign(tavernQuest, tavern.quest.toObject()); + }) + .catch(err => { + throw err; + }); +}); + +// returns a promise +schema.statics.tavernBoss = async function tavernBoss (user, progress) { + if (!progress) return; + + // hack: prevent crazy damage to world boss + let dmg = Math.min(900, Math.abs(progress.up || 0)); + let rage = -Math.min(900, Math.abs(progress.down || 0)); + + let tavern = await this.findOne(tavernQ).exec(); + if (!(tavern && tavern.quest && tavern.quest.key)) return; + + let quest = shared.content.quests[tavern.quest.key]; + + if (tavern.quest.progress.hp <= 0) { + tavern.sendChat(quest.completionChat('en')); + await tavern.finishQuest(quest); + _.assign(tavernQuest, {extra: null}); + return tavern.save(); + } else { + // Deal damage. Note a couple things here, str & def are calculated. If str/def are defined in the database, + // use those first - which allows us to update the boss on the go if things are too easy/hard. + if (!tavern.quest.extra) tavern.quest.extra = {}; + tavern.quest.progress.hp -= dmg / (tavern.quest.extra.def || quest.boss.def); + tavern.quest.progress.rage -= rage * (tavern.quest.extra.str || quest.boss.str); + + if (tavern.quest.progress.rage >= quest.boss.rage.value) { + if (!tavern.quest.extra.worldDmg) tavern.quest.extra.worldDmg = {}; + + let wd = tavern.quest.extra.worldDmg; + // Burnout attacks Ian, Seasonal Sorceress, tavern + // Be-Wilder attacks Alex, Matt, Bailey + let scene = wd.market ? wd.stables ? wd.bailey ? false : 'bailey' : 'stables' : 'market'; // eslint-disable-line no-nested-ternary + + if (!scene) { + tavern.sendChat(`\`${quest.boss.name('en')} tries to unleash ${quest.boss.rage.title('en')} but is too tired.\``); + tavern.quest.progress.rage = 0; // quest.boss.rage.value; + } else { + tavern.sendChat(quest.boss.rage[scene]('en')); + tavern.quest.extra.worldDmg[scene] = true; + tavern.quest.extra.worldDmg.recent = scene; + tavern.markModified('quest.extra.worldDmg'); + tavern.quest.progress.rage = 0; + if (quest.boss.rage.healing) { + tavern.quest.progress.hp += quest.boss.rage.healing * tavern.quest.progress.hp; + } + } + } + + if (quest.boss.desperation && tavern.quest.progress.hp < quest.boss.desperation.threshold && !tavern.quest.extra.desperate) { + tavern.sendChat(quest.boss.desperation.text('en')); + tavern.quest.extra.desperate = true; + tavern.quest.extra.def = quest.boss.desperation.def; + tavern.quest.extra.str = quest.boss.desperation.str; + tavern.markModified('quest.extra'); + } + + _.assign(tavernQuest, tavern.quest.toObject()); + return tavern.save(); + } +}; + +schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') { + let group = this; + + let challenges = await Challenge.find({ + _id: {$in: user.challenges}, + group: group._id, + }); + + let challengesToRemoveUserFrom = challenges.map(chal => { + return chal.unlinkTasks(user, keep); + }); + await Bluebird.all(challengesToRemoveUserFrom); + + let promises = []; + + // remove the group from the user's groups + if (group.type === 'guild') { + promises.push(User.update({_id: user._id}, {$pull: {guilds: group._id}}).exec()); + } else { + promises.push(User.update({_id: user._id}, {$set: {party: {}}}).exec()); + } + + // If user is the last one in group and group is private, delete it + if (group.memberCount <= 1 && group.privacy === 'private') { + promises.push(group.remove()); + } else { // otherwise If the leader is leaving (or if the leader previously left, and this wasn't accounted for) + let update = { + $inc: {memberCount: -1}, + }; + + if (group.leader === user._id) { + let query = group.type === 'party' ? {'party._id': group._id} : {guilds: group._id}; + query._id = {$ne: user._id}; + let seniorMember = await User.findOne(query).select('_id').exec(); + + // could be missing in case of public guild (that can have 0 members) with 1 member who is leaving + if (seniorMember) update.$set = {leader: seniorMember._id}; + } + promises.push(group.update(update).exec()); + } + + firebase.removeUserFromGroup(group._id, user._id); + + return await Bluebird.all(promises); +}; + +// API v2 compatibility methods +schema.methods.getTransformedData = function getTransformedData (options) { + let cb = options.cb; + let populateMembers = options.populateMembers; + let populateInvites = options.populateInvites; + let populateChallenges = options.populateChallenges; + + let obj = this.toJSON(); + + let queryMembers = {}; + let queryInvites = {}; + + if (this.type === 'guild') { + queryInvites['invitations.guilds.id'] = this._id; + } else { + queryInvites['invitations.party.id'] = this._id; + } + + if (this.type === 'guild') { + queryMembers.guilds = this._id; + } else { + queryMembers['party._id'] = this._id; + } + + let selectDataMembers = '_id'; + let selectDataInvites = '_id'; + let selectDataChallenges = '_id'; + + if (populateMembers) { + selectDataMembers += ` ${populateMembers}`; + } + if (populateInvites) { + selectDataInvites += ` ${populateInvites}`; + } + if (populateChallenges) { + selectDataChallenges += ` ${populateChallenges}`; + } + + let membersQuery = User.find(queryMembers).select(selectDataMembers); + if (options.limitPopulation) membersQuery.limit(15); + + Bluebird.all([ + membersQuery.exec(), + User.find(queryInvites).select(populateInvites).exec(), + Challenge.find({group: obj._id}).select(populateMembers).exec(), + ]) + .then((results) => { + obj.members = results[0]; + obj.invites = results[1]; + obj.challenges = results[2]; + + cb(null, obj); + }) + .catch(cb); +}; +// END API v2 compatibility methods + +export let model = mongoose.model('Group', schema); + +// initialize tavern if !exists (fresh installs) +// do not run when testing as it's handled by the tests and can easily cause a race condition +if (!nconf.get('IS_TEST')) { + model.count({_id: TAVERN_ID}, (err, ct) => { + if (err) throw err; + if (ct > 0) return; + new model({ // eslint-disable-line babel/new-cap + _id: TAVERN_ID, + leader: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', // Siena Leslie + name: 'Tavern', + type: 'guild', + privacy: 'public', + }).save(); + }); +} diff --git a/website/server/models/tag.js b/website/server/models/tag.js new file mode 100644 index 0000000000..f201541540 --- /dev/null +++ b/website/server/models/tag.js @@ -0,0 +1,27 @@ +import mongoose from 'mongoose'; +import baseModel from '../libs/api-v3/baseModel'; +import { v4 as uuid } from 'uuid'; +import validator from 'validator'; + +let Schema = mongoose.Schema; + +export let schema = new Schema({ + id: { + type: String, + default: uuid, + validate: [validator.isUUID, 'Invalid uuid.'], + }, + name: {type: String, required: true}, + challenge: {type: String}, +}, { + strict: true, + minimize: false, // So empty objects are returned + _id: false, // use id instead of _id +}); + +schema.plugin(baseModel, { + noSet: ['_id', 'id', 'challenge'], + _id: false, // use id instead of _id +}); + +export let model = mongoose.model('Tag', schema); diff --git a/website/server/models/task.js b/website/server/models/task.js new file mode 100644 index 0000000000..536d95ec0c --- /dev/null +++ b/website/server/models/task.js @@ -0,0 +1,222 @@ +import mongoose from 'mongoose'; +import shared from '../../../common'; +import validator from 'validator'; +import moment from 'moment'; +import baseModel from '../libs/api-v3/baseModel'; +import _ from 'lodash'; +import { preenHistory } from '../libs/api-v3/preening'; + +let Schema = mongoose.Schema; +let discriminatorOptions = { + discriminatorKey: 'type', // the key that distinguishes task types +}; +let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), {_id: false}); + +export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; + +// Important +// When something changes here remember to update the client side model at common/script/libs/taskDefaults +export let TaskSchema = new Schema({ + _legacyId: String, // TODO Remove when v2 is deprecated + type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]}, + text: {type: String, required: true}, + notes: {type: String, default: ''}, + tags: [{ + type: String, + validate: [validator.isUUID, 'Invalid uuid.'], + }], + value: {type: Number, default: 0, required: true}, // redness or cost for rewards Required because it must be settable (for rewards) + priority: { + type: Number, + default: 1, + required: true, + validate: [ + (val) => [0.1, 1, 1.5, 2].indexOf(val) !== -1, + 'Valid priority values are 0.1, 1, 1.5, 2.', + ], + }, + attribute: {type: String, default: 'str', enum: ['str', 'con', 'int', 'per']}, + userId: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set it belongs to a challenge + + challenge: { + id: {type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}, // When set (and userId not set) it's the original task + taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set but challenge.id defined it's the original task + broken: {type: String, enum: ['CHALLENGE_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED', 'CHALLENGE_CLOSED', 'CHALLENGE_TASK_NOT_FOUND']}, // CHALLENGE_TASK_NOT_FOUND comes from v3 migration + winner: String, // user.profile.name of the winner + }, + + reminders: [{ + _id: false, + id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], default: shared.uuid, required: true}, + startDate: {type: Date}, + time: {type: Date, required: true}, + }], +}, _.defaults({ + minimize: true, // So empty objects are returned + strict: true, +}, discriminatorOptions)); + +TaskSchema.plugin(baseModel, { + noSet: ['challenge', 'userId', 'completed', 'history', 'dateCompleted', '_legacyId'], + sanitizeTransform (taskObj) { + if (taskObj.type && taskObj.type !== 'reward') { // value should be settable directly only for rewards + delete taskObj.value; + } + + return taskObj; + }, + private: [], + timestamps: true, +}); + +// Sanitize user tasks linked to a challenge +// See http://habitica.wikia.com/wiki/Challenges#Challenge_Participant.27s_Permissions for more info +TaskSchema.statics.sanitizeUserChallengeTask = function sanitizeUserChallengeTask (taskObj) { + let initialSanitization = this.sanitize(taskObj); + + return _.pick(initialSanitization, ['streak', 'checklist', 'attribute', 'reminders', 'tags', 'notes']); +}; + +// Sanitize checklist objects (disallowing id) +TaskSchema.statics.sanitizeChecklist = function sanitizeChecklist (checklistObj) { + delete checklistObj.id; + return checklistObj; +}; + +// Sanitize reminder objects (disallowing id) +TaskSchema.statics.sanitizeReminder = function sanitizeReminder (reminderObj) { + delete reminderObj.id; + return reminderObj; +}; + +TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta) { + let chalTask = this; + + chalTask.value += delta; + + if (chalTask.type === 'habit' || chalTask.type === 'daily') { + // Add only one history entry per day + let lastChallengHistoryIndex = chalTask.history.length - 1; + + if (chalTask.history[lastChallengHistoryIndex] && + moment(chalTask.history[lastChallengHistoryIndex].date).isSame(new Date(), 'day')) { + chalTask.history[lastChallengHistoryIndex] = { + date: Number(new Date()), + value: chalTask.value, + }; + chalTask.markModified(`history.${lastChallengHistoryIndex}`); + } else { + chalTask.history.push({ + date: Number(new Date()), + value: chalTask.value, + }); + + // Only preen task history once a day when the task is scored first + if (chalTask.history.length > 365) { + chalTask.history = preenHistory(chalTask.history, true); // true means the challenge will retain as much entries as a subscribed user + } + } + } + + await chalTask.save(); +}; + + +// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) +// These will be removed once API v2 is discontinued + +// toJSON for API v2 +TaskSchema.methods.toJSONV2 = function toJSONV2 () { + let toJSON = this.toJSON(); + if (toJSON._legacyId) { + toJSON.id = toJSON._legacyId; + } else { + toJSON.id = toJSON._id; + } + + if (!toJSON.challenge) toJSON.challenge = {}; + + let v3Tags = this.tags; + + toJSON.tags = {}; + v3Tags.forEach(tag => { + toJSON.tags[tag] = true; + }); + + toJSON.dateCreated = this.createdAt; + + return toJSON; +}; + +TaskSchema.statics.fromJSONV2 = function fromJSONV2 (taskObj) { + if (taskObj.id) taskObj._id = taskObj.id; + + let v2Tags = taskObj.tags || {}; + + taskObj.tags = []; + taskObj.tags = _.map(v2Tags, (tag, key) => key); + + return taskObj; +}; + +// END of API v2 methods + +export let Task = mongoose.model('Task', TaskSchema); + +// habits and dailies shared fields +let habitDailySchema = () => { + return {history: Array}; // [{date:Date, value:Number}], // this causes major performance problems +}; + +// dailys and todos shared fields +let dailyTodoSchema = () => { + return { + completed: {type: Boolean, default: false}, + // Checklist fields (dailies and todos) + collapseChecklist: {type: Boolean, default: false}, + checklist: [{ + completed: {type: Boolean, default: false}, + text: {type: String, required: false, default: ''}, // required:false because it can be empty on creation + _id: false, + id: {type: String, default: shared.uuid, validate: [validator.isUUID, 'Invalid uuid.']}, + }], + }; +}; + +export let HabitSchema = new Schema(_.defaults({ + up: {type: Boolean, default: true}, + down: {type: Boolean, default: true}, +}, habitDailySchema()), subDiscriminatorOptions); +export let habit = Task.discriminator('habit', HabitSchema); + +export let DailySchema = new Schema(_.defaults({ + frequency: {type: String, default: 'weekly', enum: ['daily', 'weekly']}, + everyX: {type: Number, default: 1}, // e.g. once every X weeks + startDate: { + type: Date, + default () { + return moment().startOf('day').toDate(); + }, + }, + repeat: { // used only for 'weekly' frequency, + m: {type: Boolean, default: true}, + t: {type: Boolean, default: true}, + w: {type: Boolean, default: true}, + th: {type: Boolean, default: true}, + f: {type: Boolean, default: true}, + s: {type: Boolean, default: true}, + su: {type: Boolean, default: true}, + }, + streak: {type: Number, default: 0}, +}, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions); +export let daily = Task.discriminator('daily', DailySchema); + +export let TodoSchema = new Schema(_.defaults({ + dateCompleted: Date, + // TODO we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript + date: String, // due date for todos +}, dailyTodoSchema()), subDiscriminatorOptions); +export let todo = Task.discriminator('todo', TodoSchema); + +export let RewardSchema = new Schema({}, subDiscriminatorOptions); +export let reward = Task.discriminator('reward', RewardSchema); diff --git a/website/server/models/user.js b/website/server/models/user.js new file mode 100644 index 0000000000..2d0e561a66 --- /dev/null +++ b/website/server/models/user.js @@ -0,0 +1,824 @@ +import mongoose from 'mongoose'; +import shared from '../../../common'; +import _ from 'lodash'; +import validator from 'validator'; +import moment from 'moment'; +import * as Tasks from './task'; +import Bluebird from 'bluebird'; +import { schema as TagSchema } from './tag'; +import baseModel from '../libs/api-v3/baseModel'; +import { + chatDefaults, + TAVERN_ID, +} from './group'; +import { defaults } from 'lodash'; + +let Schema = mongoose.Schema; + +// User schema definition +export let schema = new Schema({ + apiToken: { + type: String, + default: shared.uuid, + }, + + auth: { + blocked: Boolean, + facebook: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + local: { + email: { + type: String, + validate: [validator.isEmail, shared.i18n.t('invalidEmail')], + }, + username: { + type: String, + }, + // Store a lowercase version of username to check for duplicates + lowerCaseUsername: String, + hashed_password: String, // eslint-disable-line camelcase + salt: String, + }, + timestamps: { + created: {type: Date, default: Date.now}, + loggedin: {type: Date, default: Date.now}, + }, + }, + // We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which + // have been updated (http://goo.gl/gQLz41), but we want *every* update + _v: { type: Number, default: 0 }, + migration: String, + achievements: { + originalUser: Boolean, + habitSurveys: Number, + ultimateGearSets: { + healer: {type: Boolean, default: false}, + wizard: {type: Boolean, default: false}, + rogue: {type: Boolean, default: false}, + warrior: {type: Boolean, default: false}, + }, + beastMaster: Boolean, + beastMasterCount: Number, + mountMaster: Boolean, + mountMasterCount: Number, + triadBingo: Boolean, + triadBingoCount: Number, + veteran: Boolean, + snowball: Number, + spookySparkles: Number, + shinySeed: Number, + seafoam: Number, + streak: Number, + challenges: Array, + quests: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + rebirths: Number, + rebirthLevel: Number, + perfect: {type: Number, default: 0}, + habitBirthdays: Number, + valentine: Number, + costumeContest: Boolean, // Superseded by costumeContests + nye: Number, + habiticaDays: Number, + greeting: Number, + thankyou: Number, + costumeContests: Number, + birthday: Number, + partyUp: Boolean, + partyOn: Boolean, + }, + + backer: { + tier: Number, + npc: String, + tokensApplied: Boolean, + }, + + contributor: { + // 1-9, see https://trello.com/c/wkFzONhE/277-contributor-gear https://github.com/HabitRPG/habitrpg/issues/3801 + level: { + type: Number, + min: 0, + max: 9, + }, + admin: Boolean, + sudo: Boolean, + // Artisan, Friend, Blacksmith, etc + text: String, + // a markdown textarea to list their contributions + links + contributions: String, + critical: String, + }, + + balance: {type: Number, default: 0}, + // Not saved on the user right now + filters: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + + purchased: { + ads: {type: Boolean, default: false}, + // eg, {skeleton: true, pumpkin: true, eb052b: true} + skin: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + hair: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + shirt: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + background: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + txnCount: {type: Number, default: 0}, + mobileChat: Boolean, + plan: { + planId: String, + paymentMethod: String, // enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']} + customerId: String, // Billing Agreement Id in case of Amazon Payments + dateCreated: Date, + dateTerminated: Date, + dateUpdated: Date, + extraMonths: {type: Number, default: 0}, + gemsBought: {type: Number, default: 0}, + mysteryItems: {type: Array, default: () => []}, + lastBillingDate: Date, // Used only for Amazon Payments to keep track of billing date + consecutive: { + count: {type: Number, default: 0}, + offset: {type: Number, default: 0}, // when gifted subs, offset++ for each month. offset-- each new-month (cron). count doesn't ++ until offset==0 + gemCapExtra: {type: Number, default: 0}, + trinkets: {type: Number, default: 0}, + }, + }, + }, + + flags: { + customizationsNotification: {type: Boolean, default: false}, + showTour: {type: Boolean, default: true}, + tour: { + // -1 indicates "uninitiated", -2 means "complete", any other number is the current tour step (0-index) + intro: {type: Number, default: -1}, + classes: {type: Number, default: -1}, + stats: {type: Number, default: -1}, + tavern: {type: Number, default: -1}, + party: {type: Number, default: -1}, + guilds: {type: Number, default: -1}, + challenges: {type: Number, default: -1}, + market: {type: Number, default: -1}, + pets: {type: Number, default: -1}, + mounts: {type: Number, default: -1}, + hall: {type: Number, default: -1}, + equipment: {type: Number, default: -1}, + }, + tutorial: { + common: { + habits: {type: Boolean, default: false}, + dailies: {type: Boolean, default: false}, + todos: {type: Boolean, default: false}, + rewards: {type: Boolean, default: false}, + party: {type: Boolean, default: false}, + pets: {type: Boolean, default: false}, + gems: {type: Boolean, default: false}, + skills: {type: Boolean, default: false}, + classes: {type: Boolean, default: false}, + tavern: {type: Boolean, default: false}, + equipment: {type: Boolean, default: false}, + items: {type: Boolean, default: false}, + }, + ios: { + addTask: {type: Boolean, default: false}, + editTask: {type: Boolean, default: false}, + deleteTask: {type: Boolean, default: false}, + filterTask: {type: Boolean, default: false}, + groupPets: {type: Boolean, default: false}, + inviteParty: {type: Boolean, default: false}, + }, + }, + dropsEnabled: {type: Boolean, default: false}, + itemsEnabled: {type: Boolean, default: false}, + newStuff: {type: Boolean, default: false}, + rewrite: {type: Boolean, default: true}, + contributor: Boolean, + classSelected: {type: Boolean, default: false}, + mathUpdates: Boolean, + rebirthEnabled: {type: Boolean, default: false}, + levelDrops: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + chatRevoked: Boolean, + // Used to track the status of recapture emails sent to each user, + // can be 0 - no email sent - 1, 2, 3 or 4 - 4 means no more email will be sent to the user + recaptureEmailsPhase: {type: Number, default: 0}, + // Needed to track the tip to send inside the email + weeklyRecapEmailsPhase: {type: Number, default: 0}, + // Used to track when the next weekly recap should be sent + lastWeeklyRecap: {type: Date, default: Date.now}, + // Used to enable weekly recap emails as users login + lastWeeklyRecapDiscriminator: Boolean, + communityGuidelinesAccepted: {type: Boolean, default: false}, + cronCount: {type: Number, default: 0}, + welcomed: {type: Boolean, default: false}, + armoireEnabled: {type: Boolean, default: false}, + armoireOpened: {type: Boolean, default: false}, + armoireEmpty: {type: Boolean, default: false}, + cardReceived: {type: Boolean, default: false}, + warnedLowHealth: {type: Boolean, default: false}, + }, + + history: { + exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined + todos: Array, // [{data: Date, value: Number}] // big peformance issues if these are defined + }, + + items: { + gear: { + owned: _.transform(shared.content.gear.flat, (m, v) => { + m[v.key] = {type: Boolean}; + if (v.key.match(/[armor|head|shield]_warrior_0/) || v.gearSet === 'glasses') { + m[v.key].default = true; + } + }), + + equipped: { + weapon: String, + armor: {type: String, default: 'armor_base_0'}, + head: {type: String, default: 'head_base_0'}, + shield: {type: String, default: 'shield_base_0'}, + back: String, + headAccessory: String, + eyewear: String, + body: String, + }, + costume: { + weapon: String, + armor: {type: String, default: 'armor_base_0'}, + head: {type: String, default: 'head_base_0'}, + shield: {type: String, default: 'shield_base_0'}, + back: String, + headAccessory: String, + eyewear: String, + body: String, + }, + }, + + special: { + snowball: {type: Number, default: 0}, + spookySparkles: {type: Number, default: 0}, + shinySeed: {type: Number, default: 0}, + seafoam: {type: Number, default: 0}, + valentine: {type: Number, default: 0}, + valentineReceived: Array, // array of strings, by sender name + nye: {type: Number, default: 0}, + nyeReceived: Array, + greeting: {type: Number, default: 0}, + greetingReceived: Array, + thankyou: {type: Number, default: 0}, + thankyouReceived: Array, + birthday: {type: Number, default: 0}, + birthdayReceived: Array, + }, + + // -------------- Animals ------------------- + // Complex bit here. The result looks like: + // pets: { + // 'Wolf-Desert': 0, // 0 means does not own + // 'PandaCub-Red': 10, // Number represents "Growth Points" + // etc... + // } + pets: _.defaults( + // First transform to a 1D eggs/potions mapping + _.transform(shared.content.pets, (m, v, k) => m[k] = Number), + // Then add additional pets (quest, backer, contributor, premium) + _.transform(shared.content.questPets, (m, v, k) => m[k] = Number), + _.transform(shared.content.specialPets, (m, v, k) => m[k] = Number), + _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Number) + ), + currentPet: String, // Cactus-Desert + + // eggs: { + // 'PandaCub': 0, // 0 indicates "doesn't own" + // 'Wolf': 5 // Number indicates "stacking" + // } + eggs: _.transform(shared.content.eggs, (m, v, k) => m[k] = Number), + + // hatchingPotions: { + // 'Desert': 0, // 0 indicates "doesn't own" + // 'CottonCandyBlue': 5 // Number indicates "stacking" + // } + hatchingPotions: _.transform(shared.content.hatchingPotions, (m, v, k) => m[k] = Number), + + // Food: { + // 'Watermelon': 0, // 0 indicates "doesn't own" + // 'RottenMeat': 5 // Number indicates "stacking" + // } + food: _.transform(shared.content.food, (m, v, k) => m[k] = Number), + + // mounts: { + // 'Wolf-Desert': true, + // 'PandaCub-Red': false, + // etc... + // } + mounts: _.defaults( + // First transform to a 1D eggs/potions mapping + _.transform(shared.content.pets, (m, v, k) => m[k] = Boolean), + // Then add quest and premium pets + _.transform(shared.content.questPets, (m, v, k) => m[k] = Boolean), + _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Boolean), + // Then add additional mounts (backer, contributor) + _.transform(shared.content.specialMounts, (m, v, k) => m[k] = Boolean) + ), + currentMount: String, + + // Quests: { + // 'boss_0': 0, // 0 indicates "doesn't own" + // 'collection_honey': 5 // Number indicates "stacking" + // } + quests: _.transform(shared.content.quests, (m, v, k) => m[k] = Number), + + lastDrop: { + date: {type: Date, default: Date.now}, + count: {type: Number, default: 0}, + }, + }, + + lastCron: {type: Date, default: Date.now}, + + // {GROUP_ID: Boolean}, represents whether they have unseen chat messages + newMessages: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + + challenges: [{type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}], + + invitations: { + // Using an array without validation because otherwise mongoose treat this as a subdocument and applies _id by default + // Schema is (id, name, inviter) + // TODO one way to fix is http://mongoosejs.com/docs/guide.html#_id + guilds: {type: Array, default: () => []}, + // Using a Mixed type because otherwise user.invitations.party = {} // to reset invitation, causes validation to fail TODO + // schema is the same as for guild invitations (id, name, inviter) + party: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + }, + + guilds: [{type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.']}], + + party: { + _id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], ref: 'Group'}, + order: {type: String, default: 'level'}, + orderAscending: {type: String, default: 'ascending'}, + quest: { + key: String, + progress: { + up: {type: Number, default: 0}, + down: {type: Number, default: 0}, + collect: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, // {feather:1, ingot:2} + }, + completed: String, // When quest is done, we move it from key => completed, and it's a one-time flag (for modal) that they unset by clicking "ok" in browser + RSVPNeeded: {type: Boolean, default: false}, // Set to true when invite is pending, set to false when quest invite is accepted or rejected, quest starts, or quest is cancelled + }, + }, + preferences: { + dayStart: {type: Number, default: 0, min: 0, max: 23}, + size: {type: String, enum: ['broad', 'slim'], default: 'slim'}, + hair: { + color: {type: String, default: 'red'}, + base: {type: Number, default: 3}, + bangs: {type: Number, default: 1}, + beard: {type: Number, default: 0}, + mustache: {type: Number, default: 0}, + flower: {type: Number, default: 1}, + }, + hideHeader: {type: Boolean, default: false}, + skin: {type: String, default: '915533'}, + shirt: {type: String, default: 'blue'}, + timezoneOffset: {type: Number, default: 0}, + sound: {type: String, default: 'off', enum: ['off', 'danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme']}, + chair: {type: String, default: 'none'}, + timezoneOffsetAtLastCron: Number, + language: String, + automaticAllocation: Boolean, + allocationMode: {type: String, enum: ['flat', 'classbased', 'taskbased'], default: 'flat'}, + autoEquip: {type: Boolean, default: true}, + costume: Boolean, + dateFormat: {type: String, enum: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], default: 'MM/dd/yyyy'}, + sleep: {type: Boolean, default: false}, + stickyHeader: {type: Boolean, default: true}, + disableClasses: {type: Boolean, default: false}, + newTaskEdit: {type: Boolean, default: false}, + dailyDueDefaultView: {type: Boolean, default: false}, + tagsCollapsed: {type: Boolean, default: false}, + advancedCollapsed: {type: Boolean, default: false}, + toolbarCollapsed: {type: Boolean, default: false}, + reverseChatOrder: {type: Boolean, default: false}, + background: String, + displayInviteToPartyWhenPartyIs1: {type: Boolean, default: true}, + webhooks: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + // For the following fields make sure to use strict comparison when searching for falsey values (=== false) + // As users who didn't login after these were introduced may have them undefined/null + emailNotifications: { + unsubscribeFromAll: {type: Boolean, default: false}, + newPM: {type: Boolean, default: true}, + kickedGroup: {type: Boolean, default: true}, + wonChallenge: {type: Boolean, default: true}, + giftedGems: {type: Boolean, default: true}, + giftedSubscription: {type: Boolean, default: true}, + invitedParty: {type: Boolean, default: true}, + invitedGuild: {type: Boolean, default: true}, + questStarted: {type: Boolean, default: true}, + invitedQuest: {type: Boolean, default: true}, + // remindersToLogin: {type: Boolean, default: true}, + // importantAnnouncements are in fact the recapture emails + importantAnnouncements: {type: Boolean, default: true}, + weeklyRecaps: {type: Boolean, default: true}, + }, + suppressModals: { + levelUp: {type: Boolean, default: false}, + hatchPet: {type: Boolean, default: false}, + raisePet: {type: Boolean, default: false}, + streak: {type: Boolean, default: false}, + }, + improvementCategories: { + type: Array, + validate: (categories) => { + const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity']; + let isValidCategory = categories.every(category => validCategories.indexOf(category) !== -1); + return isValidCategory; + }, + }, + }, + profile: { + blurb: String, + imageUrl: String, + name: String, + }, + stats: { + hp: {type: Number, default: shared.maxHealth}, + mp: {type: Number, default: 10}, + exp: {type: Number, default: 0}, + gp: {type: Number, default: 0}, + lvl: {type: Number, default: 1}, + + // Class System + class: {type: String, enum: ['warrior', 'rogue', 'wizard', 'healer'], default: 'warrior', required: true}, + points: {type: Number, default: 0}, + str: {type: Number, default: 0}, + con: {type: Number, default: 0}, + int: {type: Number, default: 0}, + per: {type: Number, default: 0}, + buffs: { + str: {type: Number, default: 0}, + int: {type: Number, default: 0}, + per: {type: Number, default: 0}, + con: {type: Number, default: 0}, + stealth: {type: Number, default: 0}, + streaks: {type: Boolean, default: false}, + snowball: {type: Boolean, default: false}, + spookySparkles: {type: Boolean, default: false}, + shinySeed: {type: Boolean, default: false}, + seafoam: {type: Boolean, default: false}, + }, + training: { + int: {type: Number, default: 0}, + per: {type: Number, default: 0}, + str: {type: Number, default: 0}, + con: {type: Number, default: 0}, + }, + }, + + tags: [TagSchema], + + inbox: { + newMessages: {type: Number, default: 0}, + blocks: {type: Array, default: () => []}, + messages: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + optOut: {type: Boolean, default: false}, + }, + tasksOrder: { + habits: [{type: String, ref: 'Task'}], + dailys: [{type: String, ref: 'Task'}], + todos: [{type: String, ref: 'Task'}], + rewards: [{type: String, ref: 'Task'}], + }, + extra: {type: Schema.Types.Mixed, default: () => { + return {}; + }}, + pushDevices: { + type: [{ + regId: {type: String}, + type: {type: String}, + }], + default: () => [], + }, +}, { + strict: true, + minimize: false, // So empty objects are returned +}); + +schema.plugin(baseModel, { + // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...) + noSet: [], + private: ['auth.local.hashed_password', 'auth.local.salt'], + toJSONTransform: function userToJSON (plainObj, originalDoc) { + // plainObj.filters = {}; // TODO Not saved, remove? + plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs + + return plainObj; + }, +}); + +// A list of publicly accessible fields (not everything from preferences because there are also a lot of settings tha should remain private) +export let publicFields = `preferences.size preferences.hair preferences.skin preferences.shirt + preferences.costume preferences.sleep preferences.background profile stats achievements party + backer contributor auth.timestamps items`; + +// The minimum amount of data needed when populating multiple users +export let nameFields = 'profile.name'; + +schema.post('init', function postInitUser (doc) { + shared.wrap(doc); +}); + +function _populateDefaultTasks (user, taskTypes) { + let tagsI = taskTypes.indexOf('tag'); + + if (tagsI !== -1) { + user.tags = _.map(shared.content.userDefaults.tags, (tag) => { + let newTag = _.cloneDeep(tag); + + // tasks automatically get _id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here + newTag.id = shared.uuid(); + // Render tag's name in user's language + newTag.name = newTag.name(user.preferences.language); + return newTag; + }); + } + + let tasksToCreate = []; + + if (tagsI !== -1) { + taskTypes = _.clone(taskTypes); + taskTypes.splice(tagsI, 1); + } + + _.each(taskTypes, (taskType) => { + let tasksOfType = _.map(shared.content.userDefaults[`${taskType}s`], (taskDefaults) => { + let newTask = new Tasks[taskType](taskDefaults); + + newTask.userId = user._id; + newTask.text = taskDefaults.text(user.preferences.language); + if (newTask.notes) newTask.notes = taskDefaults.notes(user.preferences.language); + if (taskDefaults.checklist) { + newTask.checklist = _.map(taskDefaults.checklist, (checklistItem) => { + checklistItem.text = checklistItem.text(user.preferences.language); + return checklistItem; + }); + } + + return newTask.save(); + }); + + tasksToCreate.push(...tasksOfType); + }); + + return Bluebird.all(tasksToCreate) + .then((tasksCreated) => { + _.each(tasksCreated, (task) => { + user.tasksOrder[`${task.type}s`].push(task._id); + }); + }); +} + +function _populateDefaultsForNewUser (user) { + let taskTypes; + let iterableFlags = user.flags.toObject(); + + if (user.registeredThrough === 'habitica-web' || user.registeredThrough === 'habitica-android') { + taskTypes = ['habit', 'daily', 'todo', 'reward', 'tag']; + + _.each(iterableFlags.tutorial.common, (val, section) => { + user.flags.tutorial.common[section] = true; + }); + } else { + taskTypes = ['todo', 'tag']; + user.flags.showTour = false; + + _.each(iterableFlags.tour, (val, section) => { + user.flags.tour[section] = -2; + }); + } + + return _populateDefaultTasks(user, taskTypes); +} + +function _setProfileName (user) { + let fb = user.auth.facebook; + + let localUsername = user.auth.local && user.auth.local.username; + let facebookUsername = fb && (fb.displayName || fb.name || fb.username || `${fb.first_name && fb.first_name} ${fb.last_name}`); + let anonymous = 'Anonymous'; + + return localUsername || facebookUsername || anonymous; +} + +schema.pre('save', true, function preSaveUser (next, done) { + next(); + + if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) { + this.preferences.dayStart = 0; + } + + if (!this.profile.name) { + this.profile.name = _setProfileName(this); + } + + // Determines if Beast Master should be awarded + let beastMasterProgress = shared.count.beastMasterProgress(this.items.pets); + + if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) { + this.achievements.beastMaster = true; + } + + // Determines if Mount Master should be awarded + let mountMasterProgress = shared.count.mountMasterProgress(this.items.mounts); + + if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) { + this.achievements.mountMaster = true; + } + + // Determines if Triad Bingo should be awarded + + let dropPetCount = shared.count.dropPetsCurrentlyOwned(this.items.pets); + let qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90; + + if (qualifiesForTriad || this.achievements.triadBingoCount > 0) { + this.achievements.triadBingo = true; + } + + // Enable weekly recap emails for old users who sign in + if (this.flags.lastWeeklyRecapDiscriminator) { + // Enable weekly recap emails in 24 hours + this.flags.lastWeeklyRecap = moment().subtract(6, 'days').toDate(); + // Unset the field so this is run only once + this.flags.lastWeeklyRecapDiscriminator = undefined; + } + + // EXAMPLE CODE for allowing all existing and new players to be + // automatically granted an item during a certain time period: + // if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01')) + // this.items.pets['JackOLantern-Base'] = 5; + + // our own version incrementer + if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0; + this._v++; + + // Populate new users with default content + if (this.isNew) { + _populateDefaultsForNewUser(this) + .then(() => done()) + .catch(done); + } else { + done(); + } +}); + +schema.pre('update', function preUpdateUser () { + this.update({}, {$inc: {_v: 1}}); +}); + +schema.methods.isSubscribed = function isSubscribed () { + return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion +}; + +// Get an array of groups ids the user is member of +schema.methods.getGroups = function getUserGroups () { + let userGroups = this.guilds.slice(0); // clone user.guilds so we don't modify the original + if (this.party._id) userGroups.push(this.party._id); + userGroups.push(TAVERN_ID); + return userGroups; +}; + +schema.methods.sendMessage = async function sendMessage (userToReceiveMessage, message) { + let sender = this; + + shared.refPush(userToReceiveMessage.inbox.messages, chatDefaults(message, sender)); + userToReceiveMessage.inbox.newMessages++; + userToReceiveMessage._v++; + userToReceiveMessage.markModified('inbox.messages'); + + shared.refPush(sender.inbox.messages, defaults({sent: true}, chatDefaults(message, userToReceiveMessage))); + sender.markModified('inbox.messages'); + + let promises = [userToReceiveMessage.save(), sender.save()]; + await Bluebird.all(promises); +}; + +// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) +// These will be removed once API v2 is discontinued + +// Get all the tasks belonging to a user, +schema.methods.getTasks = function getUserTasks () { + let args = Array.from(arguments); + let cb; + let type; + + if (args.length === 1) { + cb = args[0]; + } else { + type = args[0]; + cb = args[1]; + } + + let query = { + userId: this._id, + }; + + if (type) query.type = type; + + Tasks.Task.find(query, cb); +}; + +// Given user and an array of tasks, return an API compatible user + tasks obj +schema.methods.addTasksToUser = function addTasksToUser (tasks) { + let obj = this.toJSON(); + + obj.id = obj._id; + obj.filters = {}; + + obj.tags = obj.tags.map(tag => { + return { + id: tag.id, + name: tag.name, + challenge: tag.challenge, + }; + }); + + let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it + + obj.habits = []; + obj.dailys = []; + obj.todos = []; + obj.rewards = []; + + obj.tasksOrder = undefined; + let unordered = []; + + tasks.forEach((task) => { + // We want to push the task at the same position where it's stored in tasksOrder + let pos = tasksOrder[`${task.type}s`].indexOf(task._id); + if (pos === -1) { // Should never happen, it means the lists got out of sync + unordered.push(task.toJSONV2()); + } else { + obj[`${task.type}s`][pos] = task.toJSONV2(); + } + }); + + // Reconcile unordered items + unordered.forEach((task) => { + obj[`${task.type}s`].push(task); + }); + + // Remove null values that can be created when inserting tasks at an index > length + ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { + obj[type] = _.compact(obj[type]); + }); + + return obj; +}; + +// Return the data maintaining backward compatibility +schema.methods.getTransformedData = function getTransformedData (cb) { + let self = this; + this.getTasks((err, tasks) => { + if (err) return cb(err); + cb(null, self.addTasksToUser(tasks)); + }); +}; + +// END of API v2 methods +export let model = mongoose.model('User', schema); + +// Initially export an empty object so external requires will get +// the right object by reference when it's defined later +// Otherwise it would remain undefined if requested before the query executes +export let mods = []; + +mongoose.model('User') + .find({'contributor.admin': true}) + .sort('-contributor.level -backer.npc profile.name') + .select('profile contributor backer') + .exec() + .then((foundMods) => { + // Using push to maintain the reference to mods + mods.push(...foundMods); + }); // In case of failure we don't want this to crash the whole server diff --git a/website/server/routes/api-v2/auth.js b/website/server/routes/api-v2/auth.js new file mode 100644 index 0000000000..f8af1b4338 --- /dev/null +++ b/website/server/routes/api-v2/auth.js @@ -0,0 +1,21 @@ +var auth = require('../../controllers/api-v2/auth'); +var express = require('express'); +var i18n = require('../../libs/api-v2/i18n'); +var router = express.Router(); +import { + getUserLanguage +} from '../../middlewares/api-v3/language'; + +/* auth.auth*/ +// auth.setupPassport(router); //TODO make this consistent with the others +router.post('/register', getUserLanguage, auth.registerUser); +router.post('/user/auth/local', getUserLanguage, auth.loginLocal); +router.post('/user/auth/social', getUserLanguage, auth.loginSocial); +router.delete('/user/auth/social', getUserLanguage, auth.auth, auth.deleteSocial); +router.post('/user/reset-password', getUserLanguage, auth.resetPassword); +router.post('/user/change-password', getUserLanguage, auth.auth, auth.changePassword); +router.post('/user/change-username', getUserLanguage, auth.auth, auth.changeUsername); +router.post('/user/change-email', getUserLanguage, auth.auth, auth.changeEmail); +// router.post('/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken); + +module.exports = router; diff --git a/website/server/routes/api-v2/coupon.js b/website/server/routes/api-v2/coupon.js new file mode 100644 index 0000000000..7caee3f815 --- /dev/null +++ b/website/server/routes/api-v2/coupon.js @@ -0,0 +1,15 @@ +var nconf = require('nconf'); +var express = require('express'); +var router = express.Router(); +var auth = require('../../controllers/api-v2/auth'); +var coupon = require('../../controllers/api-v2/coupon'); +var i18n = require('../../libs/api-v2/i18n'); +import { + getUserLanguage +} from '../../middlewares/api-v3/language'; + +router.get('/coupons', auth.authWithUrl, getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); +router.post('/coupons/generate/:event', auth.auth, getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); +router.post('/user/coupon/:code', auth.auth, getUserLanguage, coupon.enterCode); + +module.exports = router; diff --git a/website/src/routes/api-v2/swagger.js b/website/server/routes/api-v2/swagger.js similarity index 91% rename from website/src/routes/api-v2/swagger.js rename to website/server/routes/api-v2/swagger.js index a1500ac39a..c60d8364b6 100644 --- a/website/src/routes/api-v2/swagger.js +++ b/website/server/routes/api-v2/swagger.js @@ -13,12 +13,15 @@ var members = require("../../controllers/api-v2/members"); var auth = require("../../controllers/api-v2/auth"); var hall = require("../../controllers/api-v2/hall"); var challenges = require("../../controllers/api-v2/challenges"); -var dataexport = require("../../controllers/dataexport"); +var dataexport = require("../../controllers/api-v2/dataexport"); var nconf = require("nconf"); var cron = user.cron; var _ = require('lodash'); var content = require('../../../../common').content; -var i18n = require('../../libs/i18n'); +var i18n = require('../../libs/api-v2/i18n'); +import { + getUserLanguage +} from '../../middlewares/api-v3/language'; var forceRefresh = require('../../middlewares/forceRefresh').middleware; module.exports = function(swagger, v2) { @@ -60,7 +63,7 @@ module.exports = function(swagger, v2) { description: "Export user history", method: 'GET' }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: dataexport.history }, "/user/tasks/{id}/{direction}": { @@ -134,7 +137,7 @@ module.exports = function(swagger, v2) { description: 'Unlink a task from its challenge', parameters: [path("id", "Task ID", "string"), query('keep', "When unlinking a challenge task, how to handle the orphans?", 'string', ['keep', 'keep-all', 'remove', 'remove-all'])] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.unlink }, "/user/inventory/buy": { @@ -235,7 +238,7 @@ module.exports = function(swagger, v2) { method: 'DELETE', description: "Delete a user object entirely, USE WITH CAUTION!" }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: user["delete"] }, "/user/revive": { @@ -311,7 +314,7 @@ module.exports = function(swagger, v2) { description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',parameters:{},body:{},query:{}}", parameters: [body('', 'The array of batch-operations to perform', 'object')] }, - middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite], + middleware: [forceRefresh, auth.auth, getUserLanguage, cron, user.sessionPartyInvite], action: user.batchUpdate }, "/user/tags/{id}:GET": { @@ -406,7 +409,7 @@ module.exports = function(swagger, v2) { description: "Get a list of groups", parameters: [query('type', "Comma-separated types of groups to return, eg 'party,guilds,public,tavern'", 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: groups.list }, "/groups:POST": { @@ -416,7 +419,7 @@ module.exports = function(swagger, v2) { description: 'Create a group', parameters: [body('', 'Group object (see GroupSchema)', 'object')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: groups.create }, "/groups/{gid}:GET": { @@ -425,7 +428,7 @@ module.exports = function(swagger, v2) { description: "Get a group. The party the user currently is in can be accessed with the gid 'party'.", parameters: [path('gid', 'Group ID', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: groups.get }, "/groups/{gid}:POST": { @@ -435,7 +438,7 @@ module.exports = function(swagger, v2) { description: "Edit a group", parameters: [body('', 'Group object (see GroupSchema)', 'object')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.update }, "/groups/{gid}/join": { @@ -444,7 +447,7 @@ module.exports = function(swagger, v2) { description: 'Join a group', parameters: [path('gid', 'Id of the group to join', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.join }, "/groups/{gid}/leave": { @@ -453,7 +456,7 @@ module.exports = function(swagger, v2) { description: 'Leave a group', parameters: [path('gid', 'ID of the group to leave', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.leave }, "/groups/{gid}/invite": { @@ -462,7 +465,7 @@ module.exports = function(swagger, v2) { description: "Invite a user to a group", parameters: [path('gid', 'Group id', 'string'), body('', 'a payload of invites either under body.uuids or body.emails, only one of them!', 'object')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.invite }, "/groups/{gid}/removeMember": { @@ -471,7 +474,7 @@ module.exports = function(swagger, v2) { description: "Remove / boot a member from a group", parameters: [path('gid', 'Group id', 'string'), query('uuid', 'User id to boot', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.removeMember }, "/groups/{gid}/questAccept": { @@ -480,7 +483,7 @@ module.exports = function(swagger, v2) { description: "Accept a quest invitation", parameters: [path('gid', "Group id", 'string'), query('key', "optional. if provided, trigger new invite, if not, accept existing invite", 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.questAccept }, "/groups/{gid}/questReject": { @@ -489,7 +492,7 @@ module.exports = function(swagger, v2) { description: 'Reject quest invitation', parameters: [path('gid', 'Group id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.questReject }, "/groups/{gid}/questCancel": { @@ -498,7 +501,7 @@ module.exports = function(swagger, v2) { description: 'Cancel quest before it starts (in invitation stage)', parameters: [path('gid', 'Group to cancel quest in', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.questCancel }, "/groups/{gid}/questAbort": { @@ -507,7 +510,7 @@ module.exports = function(swagger, v2) { description: 'Abort quest after it has started (all progress will be lost)', parameters: [path('gid', 'Group to abort quest in', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.questAbort }, "/groups/{gid}/questLeave": { @@ -516,7 +519,7 @@ module.exports = function(swagger, v2) { description: 'Leave an active quest (Quest leaders cannot leave active quests. They must abort the quest to leave)', parameters: [path('gid', 'Group to leave quest in', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.questLeave }, "/groups/{gid}/chat:GET": { @@ -525,7 +528,7 @@ module.exports = function(swagger, v2) { description: "Get all chat messages", parameters: [path('gid', 'Group to return the chat from ', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.getChat }, "/groups/{gid}/chat:POST": { @@ -535,7 +538,7 @@ module.exports = function(swagger, v2) { description: "Send a chat message", parameters: [query('message', 'Chat message', 'string'), path('gid', 'Group id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.postChat }, "/groups/{gid}/chat/seen": { @@ -552,7 +555,7 @@ module.exports = function(swagger, v2) { description: 'Delete a chat message in a given group', parameters: [path('gid', 'ID of the group containing the message to be deleted', 'string'), path('messageId', 'ID of message to be deleted', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.deleteChatMessage }, "/groups/{gid}/chat/{mid}/like": { @@ -561,7 +564,7 @@ module.exports = function(swagger, v2) { description: "Like a chat message", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.likeChatMessage }, "/groups/{gid}/chat/{mid}/flag": { @@ -570,7 +573,7 @@ module.exports = function(swagger, v2) { description: "Flag a chat message", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.flagChatMessage }, "/groups/{gid}/chat/{mid}/clearflags": { @@ -579,7 +582,7 @@ module.exports = function(swagger, v2) { description: "Clear flag count from message and unhide it", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], + middleware: [auth.auth, getUserLanguage, groups.attachGroup], action: groups.clearFlagCount }, "/members/{uuid}:GET": { @@ -588,7 +591,7 @@ module.exports = function(swagger, v2) { description: "Get a member.", parameters: [path('uuid', 'Member ID', 'string')] }, - middleware: [i18n.getUserLanguage], + middleware: [getUserLanguage], action: members.getMember }, "/members/{uuid}/message": { @@ -620,14 +623,14 @@ module.exports = function(swagger, v2) { }, "/hall/heroes": { spec: {}, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: hall.getHeroes }, "/hall/heroes/{uid}:GET": { spec: { path: "/hall/heroes/{uid}" }, - middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], + middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], action: hall.getHero }, "/hall/heroes/{uid}:POST": { @@ -635,14 +638,14 @@ module.exports = function(swagger, v2) { method: 'POST', path: "/hall/heroes/{uid}" }, - middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], + middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], action: hall.updateHero }, "/hall/patrons": { spec: { parameters: [query('page', 'Page number to fetch (this list is long)', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: hall.getPatrons }, "/challenges:GET": { @@ -650,7 +653,7 @@ module.exports = function(swagger, v2) { path: '/challenges', description: "Get a list of challenges" }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.list }, "/challenges:POST": { @@ -660,7 +663,7 @@ module.exports = function(swagger, v2) { description: "Create a challenge", parameters: [body('', 'Challenge object (see ChallengeSchema)', 'object')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.create }, "/challenges/{cid}:GET": { @@ -669,7 +672,7 @@ module.exports = function(swagger, v2) { description: 'Get a challenge', parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.get }, "/challenges/{cid}/csv": { @@ -686,7 +689,7 @@ module.exports = function(swagger, v2) { description: "Update a challenge", parameters: [path('cid', 'Challenge id', 'string'), body('', 'Challenge object (see ChallengeSchema)', 'object')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.update }, "/challenges/{cid}:DELETE": { @@ -696,7 +699,7 @@ module.exports = function(swagger, v2) { description: "Delete a challenge", parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges["delete"] }, "/challenges/{cid}/close": { @@ -705,7 +708,7 @@ module.exports = function(swagger, v2) { description: 'Close a challenge', parameters: [path('cid', 'Challenge id', 'string'), query('uid', 'User ID of the winner', 'string', true)] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.selectWinner }, "/challenges/{cid}/join": { @@ -714,7 +717,7 @@ module.exports = function(swagger, v2) { description: "Join a challenge", parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.join }, "/challenges/{cid}/leave": { @@ -723,7 +726,7 @@ module.exports = function(swagger, v2) { description: 'Leave a challenge', parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.leave }, "/challenges/{cid}/member/{uid}": { @@ -731,7 +734,7 @@ module.exports = function(swagger, v2) { description: "Get a member's progress in a particular challenge", parameters: [path('cid', 'Challenge id', 'string'), path('uid', 'User id', 'string')] }, - middleware: [auth.auth, i18n.getUserLanguage], + middleware: [auth.auth, getUserLanguage], action: challenges.getMember } }; @@ -765,7 +768,7 @@ module.exports = function(swagger, v2) { method: 'GET' }); if (route.middleware == null) { - route.middleware = path.indexOf('/user') === 0 ? [auth.auth, i18n.getUserLanguage, cron] : [i18n.getUserLanguage]; + route.middleware = path.indexOf('/user') === 0 ? [auth.auth, getUserLanguage, cron] : [i18n.getUserLanguage]; } swagger["add" + route.spec.method](route); return true; diff --git a/website/server/routes/api-v2/unsubscription.js b/website/server/routes/api-v2/unsubscription.js new file mode 100644 index 0000000000..cbd2e16554 --- /dev/null +++ b/website/server/routes/api-v2/unsubscription.js @@ -0,0 +1,11 @@ +var express = require('express'); +var router = express.Router(); +var i18n = require('../../libs/api-v2/i18n'); +var unsubscription = require('../../controllers/api-v2/unsubscription'); +import { + getUserLanguage +} from '../../middlewares/api-v3/language'; + +router.get('/unsubscribe', getUserLanguage, unsubscription.unsubscribe); + +module.exports = router; diff --git a/website/src/routes/pages.js b/website/server/routes/pages.js similarity index 94% rename from website/src/routes/pages.js rename to website/server/routes/pages.js index 04766d9551..17818285ed 100644 --- a/website/src/routes/pages.js +++ b/website/server/routes/pages.js @@ -2,8 +2,8 @@ var nconf = require('nconf'); var express = require('express'); var router = express.Router(); var _ = require('lodash'); -var locals = require('../middlewares/locals'); -var i18n = require('../libs/i18n'); +var locals = require('../middlewares/api-v2/locals'); +var i18n = require('../libs/api-v2/i18n'); var md = require('markdown-it')({ html: true, }); diff --git a/website/server/routes/payments.js b/website/server/routes/payments.js new file mode 100644 index 0000000000..13e9ec9163 --- /dev/null +++ b/website/server/routes/payments.js @@ -0,0 +1,34 @@ +var nconf = require('nconf'); +var express = require('express'); +var router = express.Router(); +var auth = require('../controllers/api-v2/auth'); +var payments = require('../controllers/payments'); +var i18n = require('../libs/api-v2/i18n'); +import { + getUserLanguage +} from '../../middlewares/api-v3/language'; + +router.get('/paypal/checkout', auth.authWithUrl, getUserLanguage, payments.paypalCheckout); +router.get('/paypal/checkout/success', getUserLanguage, payments.paypalCheckoutSuccess); +router.get('/paypal/subscribe', auth.authWithUrl, getUserLanguage, payments.paypalSubscribe); +router.get('/paypal/subscribe/success', getUserLanguage, payments.paypalSubscribeSuccess); +router.get('/paypal/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.paypalSubscribeCancel); +router.post('/paypal/ipn', getUserLanguage, payments.paypalIPN); // misc ipn handling + +router.post('/stripe/checkout', auth.auth, getUserLanguage, payments.stripeCheckout); +router.post('/stripe/subscribe/edit', auth.auth, getUserLanguage, payments.stripeSubscribeEdit); +//router.get('/stripe/subscribe', auth.authWithUrl, getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead +router.get('/stripe/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.stripeSubscribeCancel); + +router.post('/amazon/verifyAccessToken', auth.auth, getUserLanguage, payments.amazonVerifyAccessToken); +router.post('/amazon/createOrderReferenceId', auth.auth, getUserLanguage, payments.amazonCreateOrderReferenceId); +router.post('/amazon/checkout', auth.auth, getUserLanguage, payments.amazonCheckout); +router.post('/amazon/subscribe', auth.auth, getUserLanguage, payments.amazonSubscribe); +router.get('/amazon/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.amazonSubscribeCancel); + +router.post('/iap/android/verify', auth.authWithUrl, /*getUserLanguage, */payments.iapAndroidVerify); +router.post('/iap/ios/verify', auth.auth, /*getUserLanguage, */ payments.iapIosVerify); + +router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, getUserLanguage, */ payments.validCoupon); + +module.exports = router; diff --git a/website/server/server.js b/website/server/server.js new file mode 100644 index 0000000000..f86e6c63b7 --- /dev/null +++ b/website/server/server.js @@ -0,0 +1,35 @@ +import nconf from 'nconf'; +import logger from './libs/api-v3/logger'; +import express from 'express'; +import http from 'http'; +import attachMiddlewares from './middlewares/api-v3/index'; +import Bluebird from 'bluebird'; + +global.Promise = Bluebird; + +const server = http.createServer(); +const app = express(); + +app.set('port', nconf.get('PORT')); + +// Setup translations +import './libs/api-v3/i18n'; + +// Load config files +import './libs/api-v3/setupMongoose'; +import './libs/api-v3/firebase'; +import './libs/api-v3/setupPassport'; + +// Load some schemas & models +import './models/challenge'; +import './models/group'; +import './models/user'; + +attachMiddlewares(app, server); + +server.on('request', app); +server.listen(app.get('port'), () => { + logger.info(`Express server listening on port ${app.get('port')}`); +}); + +module.exports = server; diff --git a/website/src/controllers/api-v2/challenges.js b/website/src/controllers/api-v2/challenges.js deleted file mode 100644 index 967f175213..0000000000 --- a/website/src/controllers/api-v2/challenges.js +++ /dev/null @@ -1,453 +0,0 @@ -// @see ../routes for routing - -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var shared = require('../../../../common'); -var User = require('./../../models/user').model; -var Group = require('./../../models/group').model; -var Challenge = require('./../../models/challenge').model; -var logging = require('./../../libs/logging'); -var csvStringify = require('csv-stringify'); -var utils = require('../../libs/utils'); -var api = module.exports; -var pushNotify = require('./../pushNotifications'); - -/* - ------------------------------------------------------------------------ - Challenges - ------------------------------------------------------------------------ -*/ - -api.list = function(req, res, next) { - var user = res.locals.user; - async.waterfall([ - function(cb){ - // Get all available groups I belong to - Group.find({members: {$in: [user._id]}}).select('_id').exec(cb); - }, - function(gids, cb){ - // and their challenges - Challenge.find({ - $or:[ - {leader: user._id}, - {members:{$in:[user._id]}}, // all challenges I belong to (is this necessary? thought is a left a group, but not its challenge) - {group:{$in:gids}}, // all challenges in my groups - {group: 'habitrpg'} // public group - ], - _id:{$ne:'95533e05-1ff9-4e46-970b-d77219f199e9'} // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug - }) - .select('name leader description group memberCount prize official') - .select({members:{$elemMatch:{$in:[user._id]}}}) - .sort('-official -timestamp') - .populate('group', '_id name type') - .populate('leader', 'profile.name') - .exec(cb); - } - ], function(err, challenges){ - if (err) return next(err); - _.each(challenges, function(c){ - c._isMember = c.members.length > 0; - }) - res.json(challenges); - user = null; - }); -} - -// GET -api.get = function(req, res, next) { - var user = res.locals.user; - // TODO use mapReduce() or aggregate() here to - // 1) Find the sum of users.tasks.values within the challnege (eg, {'profile.name':'tyler', 'sum': 100}) - // 2) Sort by the sum - // 3) Limit 30 (only show the 30 users currently in the lead) - Challenge.findById(req.params.cid) - .populate('members', 'profile.name _id') - .populate('group', '_id name type') - .populate('leader', 'profile.name') - .exec(function(err, challenge){ - if(err) return next(err); - if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); - challenge._isMember = !!(_.find(challenge.members, function(member) { - return member._id === user._id; - })); - res.json(challenge); - }); -} - -api.csv = function(req, res, next) { - var cid = req.params.cid; - var challenge; - async.waterfall([ - function(cb){ - Challenge.findById(cid,cb) - }, - function(_challenge,cb) { - challenge = _challenge; - if (!challenge) return cb('Challenge ' + cid + ' not found'); - User.aggregate([ - {$match:{'_id':{ '$in': challenge.members}}}, //yes, we want members - {$project:{'profile.name':1,tasks:{$setUnion:["$habits","$dailys","$todos","$rewards"]}}}, - {$unwind:"$tasks"}, - {$match:{"tasks.challenge.id":cid}}, - {$sort:{'tasks.type':1,'tasks.id':1}}, - {$group:{_id:"$_id", "tasks":{$push:"$tasks"},"name":{$first:"$profile.name"}}} - ], cb); - } - ],function(err,users){ - if(err) return next(err); - var output = ['UUID','name']; - _.each(challenge.tasks,function(t){ - //output.push(t.type+':'+t.text); - //not the right order yet - output.push('Task'); - output.push('Value'); - output.push('Notes'); - }) - output = [output]; - _.each(users, function(u){ - var uData = [u._id,u.name]; - _.each(u.tasks,function(t){ - uData = uData.concat([t.type+':'+t.text, t.value, t.notes]); - }) - output.push(uData); - }); - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': `attachment; filename=${cid}.csv`, - }); - - csvStringify(output, (err, csv) => { - if (err) return next(err); - res.status(200).send(csv); - challenge = cid = null; - }); - }) -} - -api.getMember = function(req, res, next) { - var cid = req.params.cid; - var uid = req.params.uid; - - // We need to start using the aggregation framework instead of in-app filtering, see http://docs.mongodb.org/manual/aggregation/ - // See code at 32c0e75 for unwind/group example - - //http://stackoverflow.com/questions/24027213/how-to-match-multiple-array-elements-without-using-unwind - var proj = {'profile.name':'$profile.name'}; - _.each(['habits','dailys','todos','rewards'], function(type){ - proj[type] = { - $setDifference: [{ - $map: { - input: '$'+type, - as: "el", - in: { - $cond: [{$eq: ["$$el.challenge.id", cid]}, '$$el', false] - } - } - }, [false]] - } - }); - User.aggregate() - .match({_id: uid}) - .project(proj) - .exec(function(err, member){ - if (err) return next(err); - if (!member) return res.status(404).json({err: 'Member '+uid+' for challenge '+cid+' not found'}); - res.json(member[0]); - uid = cid = null; - }); -} - -// CREATE -api.create = function(req, res, next){ - var user = res.locals.user; - - async.auto({ - get_group: function(cb){ - var q = {_id:req.body.group}; - if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group - Group.findOne(q, cb); - }, - save_chal: ['get_group', function(cb, results){ - var group = results.get_group, - prize = +req.body.prize; - if (!group) - return cb({code:404, err:"Group." + req.body.group + " not found"}); - if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) - return cb({code:401, err: "Only the group leader can create challenges"}); - // If they're adding a prize, do some validation - if (prize < 0) - return cb({code:401, err: 'Challenge prize must be >= 0'}); - if (req.body.group=='habitrpg' && prize < 1) - return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'}); - if (prize > 0) { - var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0); - var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid... - if (prizeCost > user.balance + groupBalance) - return cb("You can't afford this prize. Purchase more gems or lower the prize amount.") - - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - var remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - } - req.body.leader = user._id; - req.body.official = user.contributor.admin && req.body.official; - var chal = new Challenge(req.body); // FIXME sanitize - chal.members.push(user._id); - chal.save(cb); - }], - save_group: ['save_chal', function(cb, results){ - results.get_group.challenges.push(results.save_chal[0]._id); - results.get_group.save(cb); - }], - sync_user: ['save_group', function(cb, results){ - // Auto-join creator to challenge (see members.push above) - results.save_chal[0].syncToUser(user, cb); - }] - }, function(err, results){ - if (err) return err.code? res.status(err.code).json(err) : next(err); - return res.json(results.save_chal[0]); - user = null; - }) -} - -// UPDATE -api.update = function(req, res, next){ - var cid = req.params.cid; - var user = res.locals.user; - var before; - async.waterfall([ - function(cb){ - // We first need the original challenge data, since we're going to compare against new & decide to sync users - Challenge.findById(cid, cb); - }, - function(_before, cb) { - if (!_before) return cb('Challenge ' + cid + ' not found'); - if (_before.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionEditChallenge', req.language)); - // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some - // before-save / after-save comparison to determine if we need to sync to users - before = _before; - var attrs = _.pick(req.body, 'name shortName description habits dailys todos rewards date'.split(' ')); - Challenge.findByIdAndUpdate(cid, {$set:attrs}, {new: true}, cb); - }, - function(saved, cb) { - - // Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers - if (before.isOutdated(req.body)) { - User.find({_id: {$in: saved.members}}, function(err, users){ - logging.info('Challenge updated, sync to subscribers'); - if (err) throw err; - _.each(users, function(user){ - saved.syncToUser(user); - }) - }) - } - - // after saving, we're done as far as the client's concerned. We kick off syncing (heavy task) in the background - cb(null, saved); - } - ], function(err, saved){ - if(err) next(err); - res.json(saved); - cid = user = before = null; - }) -} - -/** - * Called by either delete() or selectWinner(). Will delete the challenge and set the "broken" property on all users' subscribed tasks - * @param {cid} the challenge id - * @param {broken} the object representing the broken status of the challenge. Eg: - * {broken: 'CHALLENGE_DELETED', id: CHALLENGE_ID} - * {broken: 'CHALLENGE_CLOSED', id: CHALLENGE_ID, winner: USER_NAME} - */ -function closeChal(cid, broken, cb) { - var removed; - async.waterfall([ - function(cb2){ - Challenge.findOneAndRemove({_id:cid}, cb2) - }, - function(_removed, cb2) { - removed = _removed; - var pull = {'$pull':{}}; pull['$pull'][_removed._id] = 1; - Group.findByIdAndUpdate(_removed.group, {new: true}, pull); - User.find({_id:{$in: removed.members}}, cb2); - }, - function(users, cb2) { - var parallel = []; - _.each(users, function(user){ - var tag = _.find(user.tags, {id:cid}); - if (tag) tag.challenge = undefined; - _.each(user.tasks, function(task){ - if (task.challenge && task.challenge.id == removed._id) { - _.merge(task.challenge, broken); - } - }) - parallel.push(function(cb3){ - user.save(cb3); - }) - }) - async.parallel(parallel, cb2); - removed = null; - } - ], cb); -} - -/** - * Delete & close - */ -api.delete = function(req, res, next){ - var user = res.locals.user; - var cid = req.params.cid; - - async.waterfall([ - function(cb){ - Challenge.findById(cid, cb); - }, - function(chal, cb){ - if (!chal) return cb('Challenge ' + cid + ' not found'); - if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionDeleteChallenge', req.language)); - if (chal.group != 'habitrpg') user.balance += chal.prize/4; // Refund gems to user if a non-tavern challenge - user.save(cb); - }, - function(save, num, cb){ - closeChal(req.params.cid, {broken: 'CHALLENGE_DELETED'}, cb); - } - ], function(err){ - if (err) return next(err); - res.sendStatus(200); - user = cid = null; - }); -} - -/** - * Select Winner & Close - */ -api.selectWinner = function(req, res, next) { - if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'}); - var user = res.locals.user; - var cid = req.params.cid; - var chal; - async.waterfall([ - function(cb){ - Challenge.findById(cid, cb); - }, - function(_chal, cb){ - chal = _chal; - if (!chal) return cb('Challenge ' + cid + ' not found'); - if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionCloseChallenge', req.language)); - User.findById(req.query.uid, cb) - }, - function(winner, cb){ - if (!winner) return cb('Winner ' + req.query.uid + ' not found.'); - _.defaults(winner.achievements, {challenges: []}); - winner.achievements.challenges.push(chal.name); - winner.balance += chal.prize/4; - winner.save(cb); - }, - function(saved, num, cb) { - if(saved.preferences.emailNotifications.wonChallenge !== false){ - utils.txnEmail(saved, 'won-challenge', [ - {name: 'CHALLENGE_NAME', content: chal.name} - ]); - } - - pushNotify.sendNotify(saved, shared.i18n.t('wonChallenge'), chal.name); - - closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb); - } - ], function(err){ - if (err) return next(err); - res.sendStatus(200); - user = cid = chal = null; - }) -} - -api.join = function(req, res, next){ - var user = res.locals.user; - var cid = req.params.cid; - - async.waterfall([ - function(cb) { - Challenge.findByIdAndUpdate(cid, {$addToSet:{members:user._id}}, {new: true}, cb); - }, - function(chal, cb) { - - // Trigger updating challenge member count in the background. We can't do it above because we don't have - // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above. - Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec(); - - if (!~user.challenges.indexOf(cid)) - user.challenges.unshift(cid); - // Add all challenge's tasks to user's tasks - chal.syncToUser(user, function(err){ - if (err) return cb(err); - cb(null, chal); // we want the saved challenge in the return results, due to ng-resource - }); - } - ], function(err, chal){ - if(err) return next(err); - chal._isMember = true; - res.json(chal); - user = cid = null; - }); -} - - -api.leave = function(req, res, next){ - var user = res.locals.user; - var cid = req.params.cid; - // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided - var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - - async.waterfall([ - function(cb){ - Challenge.findByIdAndUpdate(cid, {$pull:{members:user._id}}, {new: true}, cb); - }, - function(chal, cb){ - - // Trigger updating challenge member count in the background. We can't do it above because we don't have - // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above. - if (chal) - Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec(); - - var i = user.challenges.indexOf(cid) - if (~i) user.challenges.splice(i,1); - user.unlink({cid:cid, keep:keep}, function(err){ - if (err) return cb(err); - cb(null, chal); - }) - } - ], function(err, chal){ - if(err) return next(err); - if (chal) chal._isMember = false; - res.json(chal); - user = cid = keep = null; - }); -} - -api.unlink = function(req, res, next) { - // they're scoring the task - commented out, we probably don't need it due to route ordering in api.js - //var urlParts = req.originalUrl.split('/'); - //if (_.contains(['up','down'], urlParts[urlParts.length -1])) return next(); - - var user = res.locals.user; - var tid = req.params.id; - var cid = user.tasks[tid].challenge.id; - if (!req.query.keep) - return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'}); - user.unlink({cid:cid, keep:req.query.keep, tid:tid}, function(err, saved){ - if (err) return next(err); - res.sendStatus(200); - user = tid = cid = null; - }); -} diff --git a/website/src/controllers/api-v2/user.js b/website/src/controllers/api-v2/user.js deleted file mode 100644 index da57347d15..0000000000 --- a/website/src/controllers/api-v2/user.js +++ /dev/null @@ -1,707 +0,0 @@ -var url = require('url'); -var ipn = require('paypal-ipn'); -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var shared = require('../../../../common'); -var User = require('./../../models/user').model; -var utils = require('./../../libs/utils'); -var analytics = utils.analytics; -var Group = require('./../../models/group').model; -var Challenge = require('./../../models/challenge').model; -var moment = require('moment'); -var logging = require('./../../libs/logging'); -let acceptablePUTPaths; -let restrictedPUTSubPaths; - -var api = module.exports; -var firebase = require('../../libs/firebase'); -var webhook = require('../../libs/webhook'); - -// api.purchase // Shared.ops - -api.getContent = function(req, res, next) { - var language = 'en'; - - if (typeof req.query.language != 'undefined') - language = req.query.language.toString(); //|| 'en' in i18n - - var content = _.cloneDeep(shared.content); - var walk = function(obj, lang){ - _.each(obj, function(item, key, source){ - if (_.isPlainObject(item) || _.isArray(item)) return walk(item, lang); - if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); - }); - } - walk(content, language); - res.json(content); -} - -api.getModelPaths = function(req,res,next){ - res.json(_.reduce(User.schema.paths,function(m,v,k){ - m[k] = v.instance || 'Boolean'; - return m; - },{})); -} - -/* - ------------------------------------------------------------------------ - Tasks - ------------------------------------------------------------------------ -*/ - - -/* - Local Methods - --------------- -*/ - -var findTask = function(req, res) { - return res.locals.user.tasks[req.params.id]; -}; - -/* - API Routes - --------------- -*/ - -api.score = function(req, res, next) { - var id = req.params.id, - direction = req.params.direction, - user = res.locals.user, - task; - - var clearMemory = function(){user = task = id = direction = null;} - - // Send error responses for improper API call - if (!id) return res.status(400).json({err: ':id required'}); - if (direction !== 'up' && direction !== 'down') { - if (direction == 'unlink' || direction == 'sort') return next(); - return res.status(400).json({err: ":direction must be 'up' or 'down'"}); - } - // If exists already, score it - if (task = user.tasks[id]) { - // Set completed if type is daily or todo and task exists - if (task.type === 'daily' || task.type === 'todo') { - task.completed = direction === 'up'; - } - } else { - // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it - // Defaults. Other defaults are handled in user.ops.addTask() - task = { - id: id, - type: req.body && req.body.type, - text: req.body && req.body.text, - notes: (req.body && req.body.notes) || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." - }; - - if (task.type === 'daily' || task.type === 'todo') - task.completed = direction === 'up'; - - task = user.ops.addTask({body:task}); - } - var delta = user.ops.score({params:{id:task.id, direction:direction}, language: req.language}); - - user.save(function(err, saved){ - if (err) return next(err); - - var userStats = saved.toJSON().stats; - var resJsonData = _.extend({ delta: delta, _tmp: user._tmp }, userStats); - res.status(200).json(resJsonData); - - var webhookData = _generateWebhookTaskData( - task, direction, delta, userStats, user - ); - webhook.sendTaskWebhook(user.preferences.webhooks, webhookData); - - if ( - (!task.challenge || !task.challenge.id || task.challenge.broken) // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response and the user doesn't care what happens back there - || (task.type == 'reward') // we don't want to update the reward GP cost - ) return clearMemory(); - - Challenge.findById(task.challenge.id, 'habits dailys todos rewards', function(err, chal) { - if (err) return next(err); - if (!chal) { - task.challenge.broken = 'CHALLENGE_DELETED'; - user.save(); - return clearMemory(); - } - var t = chal.tasks[task.id]; - // this task was removed from the challenge, notify user - if (!t) { - chal.syncToUser(user); - return clearMemory(); - } - - t.value += delta; - if (t.type == 'habit' || t.type == 'daily') { - t.history.push({value: t.value, date: +new Date}); - } - chal.save(); - clearMemory(); - }); - }); -}; - -/** - * Get all tasks - */ -api.getTasks = function(req, res, next) { - var user = res.locals.user; - if (req.query.type) { - return res.json(user[req.query.type+'s']); - } else { - return res.json(_.toArray(user.tasks)); - } -}; - -/** - * Get Task - */ -api.getTask = function(req, res, next) { - var task = findTask(req,res); - if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); - return res.status(200).json(task); -}; - - -/* - Update Task -*/ - -//api.deleteTask // see Shared.ops -// api.updateTask // handled in Shared.ops -// api.addTask // handled in Shared.ops -// api.sortTask // handled in Shared.ops #TODO updated api, mention in docs - -/* - ------------------------------------------------------------------------ - Items - ------------------------------------------------------------------------ -*/ -// api.buy // handled in Shard.ops - -api.getBuyList = function (req, res, next) { - var list = shared.updateStore(res.locals.user); - return res.status(200).json(list); -}; - -/* - ------------------------------------------------------------------------ - User - ------------------------------------------------------------------------ -*/ - -/** - * Get User - */ -api.getUser = function(req, res, next) { - var user = res.locals.user.toJSON(); - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - delete user.apiToken; - if (user.auth && user.auth.local) { - delete user.auth.local.hashed_password; - delete user.auth.local.salt; - } - return res.status(200).json(user); -}; - -/** - * Get anonymized User - */ -api.getUserAnonymized = function(req, res, next) { - var user = res.locals.user.toJSON(); - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - - delete user.apiToken; - - if (user.auth) { - delete user.auth.local; - delete user.auth.facebook; - } - - delete user.newMessages; - - delete user.profile; - delete user.purchased.plan; - delete user.contributor; - delete user.invitations; - - delete user.items.special.nyeReceived; - delete user.items.special.valentineReceived; - - delete user.webhooks; - delete user.achievements.challenges; - - _.forEach(user.inbox.messages, function(msg){ - msg.text = "inbox message text"; - }); - - _.forEach(user.tags, function(tag){ - tag.name = "tag"; - tag.challenge = "challenge"; - }); - - function cleanChecklist(task){ - var checklistIndex = 0; - - _.forEach(task.checklist, function(c){ - c.text = "item" + checklistIndex++; - }); - } - - _.forEach(user.habits, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.rewards, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.dailys, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - _.forEach(user.todos, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - return res.status(200).json(user); -}; - -/** - * This tells us for which paths users can call `PUT /user` (or batch-update equiv, which use `User.set()` on our client). - * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) - * FIXME - one-by-one we want to widdle down this list, instead replacing each needed set path with API operations - */ -acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (m, v, leaf) => { - let updatablePaths = 'achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '); - let found = _.find(updatablePaths, (rootPath) => { - return leaf.indexOf(rootPath) === 0; - }); - - if (found) m[leaf] = true; - - return m; -}, {}); - -restrictedPUTSubPaths = 'stats.class'.split(' '); - -_.each(restrictedPUTSubPaths, (removePath) => { - delete acceptablePUTPaths[removePath]; -}); - -let requiresPurchase = { - 'preferences.background': 'background', - 'preferences.shirt': 'shirt', - 'preferences.size': 'size', - 'preferences.skin': 'skin', - 'preferences.chair': 'chair', - 'preferences.hair.bangs': 'hair.bangs', - 'preferences.hair.base': 'hair.base', - 'preferences.hair.beard': 'hair.beard', - 'preferences.hair.color': 'hair.color', - 'preferences.hair.flower': 'hair.flower', - 'preferences.hair.mustache': 'hair.mustache', -}; - -let checkPreferencePurchase = (user, path, item) => { - let itemPath = `${path}.${item}`; - let appearance = _.get(shared.content.appearances, itemPath) - if (!appearance) return false; - if (appearance.price === 0) return true; - - return _.get(user.purchased, itemPath); -}; - -/** - * Update user - * Send up PUT /user as `req.body={path1:val, path2:val, etc}`. Example: - * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false} - * See acceptablePUTPaths for which user paths are supported -*/ -api.update = (req, res, next) => { - let user = res.locals.user; - let errors = []; - - if (_.isEmpty(req.body)) return res.status(200).json(user); - - _.each(req.body, (v, k) => { - let purchasable = requiresPurchase[k]; - - if (purchasable && !checkPreferencePurchase(user, purchasable, v)) { - return errors.push(`Must purchase ${v} to set it on ${k}`); - } - - if (acceptablePUTPaths[k]) { - user.fns.dotSet(k, v); - } else { - errors.push(shared.i18n.t('messageUserOperationProtected', { operation: k })); - } - return true; - }); - - user.save((err) => { - if (!_.isEmpty(errors)) return res.status(401).json({err: errors}); - if (err) { - if (err.name == 'ValidationError') { - let errorMessages = _.map(_.values(err.errors), (error) => { - return error.message; - }); - return res.status(400).json({err: errorMessages}); - } - return next(err); - } - - res.status(200).json(user); - user = errors = null; - }); -}; - -api.cron = function(req, res, next) { - var user = res.locals.user, - progress = user.fns.cron({analytics:utils.analytics, timezoneOffset:req.headers['x-user-timezoneoffset']}), - ranCron = user.isModified(), - quest = shared.content.quests[user.party.quest.key]; - - if (ranCron) res.locals.wasModified = true; - if (!ranCron) return next(null,user); - Group.tavernBoss(user,progress); - if (!quest) return user.save(next); - - // If user is on a quest, roll for boss & player, or handle collections - // FIXME this saves user, runs db updates, loads user. Is there a better way to handle this? - async.waterfall([ - function(cb){ - user.save(cb); // make sure to save the cron effects - }, - function(saved, count, cb){ - var type = quest.boss ? 'boss' : 'collect'; - Group[type+'Quest'](user,progress,cb); - }, - function(){ - var cb = arguments[arguments.length-1]; - // User has been updated in boss-grapple, reload - User.findById(user._id, cb); - } - ], function(err, saved) { - res.locals.user = saved; - next(err,saved); - user = progress = quest = null; - }); -}; - -// api.reroll // Shared.ops -// api.reset // Shared.ops - -api.delete = function(req, res, next) { - var user = res.locals.user; - var plan = user.purchased.plan; - - if (plan && plan.customerId && !plan.dateTerminated){ - return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."}); - } - - Group.find({ - members: { - '$in': [user._id] - } - }, function(err, groups){ - if(err) return next(err); - - async.each(groups, function(group, cb){ - group.leave(user, 'remove-all', cb); - }, function(err){ - if(err) return next(err); - - user.remove(function(err){ - if(err) return next(err); - - firebase.deleteUser(user._id); - res.sendStatus(200); - }); - }); - }); -} - -/* - ------------------------------------------------------------------------ - Development Only Operations - ------------------------------------------------------------------------ - */ -if (nconf.get('NODE_ENV') === 'development') { - - api.addTenGems = function(req, res, next) { - var user = res.locals.user; - - user.balance += 2.5; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; - - api.addHourglass = function(req, res, next) { - var user = res.locals.user; - - user.purchased.plan.consecutive.trinkets += 1; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; -} - -/* - ------------------------------------------------------------------------ - Tags - ------------------------------------------------------------------------ - */ -// api.deleteTag // handled in Shared.ops -// api.addTag // handled in Shared.ops -// api.updateTag // handled in Shared.ops -// api.sortTag // handled in Shared.ops - -/* - ------------------------------------------------------------------------ - Spells - ------------------------------------------------------------------------ - */ -api.cast = function(req, res, next) { - var user = res.locals.user, - targetType = req.query.targetType, - targetId = req.query.targetId, - klass = shared.content.spells.special[req.params.spell] ? 'special' : user.stats.class, - spell = shared.content.spells[klass][req.params.spell]; - - if (!spell) return res.status(404).json({err: 'Spell "' + req.params.spell + '" not found.'}); - if (spell.mana > user.stats.mp) return res.status(400).json({err: 'Not enough mana to cast spell'}); - - var done = function(){ - var err = arguments[0]; - var saved = _.size(arguments == 3) ? arguments[2] : arguments[1]; - if (err) return next(err); - res.json(saved); - user = targetType = targetId = klass = spell = null; - } - - switch (targetType) { - case 'task': - if (!user.tasks[targetId]) return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); - spell.cast(user, user.tasks[targetId]); - user.save(done); - break; - - case 'self': - spell.cast(user); - user.save(done); - break; - - case 'party': - case 'user': - async.waterfall([ - function(cb){ - Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements items.special').exec(cb); - }, - function(group, cb) { - // Solo player? let's just create a faux group for simpler code - var g = group ? group : {members:[user]}; - var series = [], found; - if (targetType == 'party') { - spell.cast(user, g.members); - series = _.transform(g.members, function(m,v,k){ - m.push(function(cb2){v.save(cb2)}); - }); - } else { - found = _.find(g.members, {_id: targetId}) - spell.cast(user, found); - series.push(function(cb2){found.save(cb2)}); - } - - if (group && !spell.silent) { - series.push(function(cb2){ - var message = '`'+user.profile.name+' casts '+spell.text() + (targetType=='user' ? ' on '+found.profile.name : ' for the party')+'.`'; - group.sendChat(message); - group.save(cb2); - }) - } - - series.push(function(cb2){g = group = series = found = null;cb2();}) - - async.series(series, cb); - }, - function(whatever, cb){ - user.save(cb); - } - ], done); - break; - } -} - -// It supports guild too now but we'll stick to partyInvite for backward compatibility -api.sessionPartyInvite = function(req,res,next){ - if (!req.session.partyInvite) return next(); - var inv = res.locals.user.invitations; - if (inv.party && inv.party.id) return next(); // already invited to a party - async.waterfall([ - function(cb){ - Group.findOne({_id:req.session.partyInvite.id, members:{$in:[req.session.partyInvite.inviter]}}) - .select('invites members type').exec(cb); - }, - function(group, cb){ - if (!group){ - // Don't send error as it will prevent users from using the site - delete req.session.partyInvite; - return cb(); - } - - if (group.type == 'guild'){ - inv.guilds.push(req.session.partyInvite); - } else{ - //req.body.type in 'guild', 'party' - inv.party = req.session.partyInvite; - } - inv.party = req.session.partyInvite; - delete req.session.partyInvite; - if (!~group.invites.indexOf(res.locals.user._id)) - group.invites.push(res.locals.user._id); //$addToSt - group.save(cb); - }, - function(saved, cb){ - res.locals.user.save(cb); - } - ], next); -} - -/** - * All other user.ops which can easily be mapped to common/script/index.js, not requiring custom API-wrapping - */ -_.each(shared.wrap({}).ops, function(op,k){ - if (!api[k]) { - api[k] = function(req, res, next) { - res.locals.user.ops[k](req,function(err, response){ - // If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"} - if (err) { - if (!err.code) return next(err); - if (err.code >= 400) return res.status(err.code).json({err:err.message}); - // In the case of 200s, they're friendly alert messages like "You're pet has hatched!" - still send the op - } - res.locals.user.save(function(err){ - if (err) return next(err); - res.status(200).json(response); - }) - }, analytics); - } - } -}) - -/* - ------------------------------------------------------------------------ - Batch Update - Run a bunch of updates all at once - ------------------------------------------------------------------------ -*/ -api.batchUpdate = function(req, res, next) { - if (_.isEmpty(req.body)) req.body = []; // cases of {} or null - if (req.body[0] && req.body[0].data) - return res.status(501).json({err: "API has been updated, please refresh your browser or upgrade your mobile app."}) - - var user = res.locals.user; - var oldSend = res.send; - var oldJson = res.json; - - // Stash user.save, we'll queue the save op till the end (so we don't overload the server) - var oldSave = user.save; - user.save = function(cb){cb(null,user)} - - // Setup the array of functions we're going to call in parallel with async - res.locals.ops = []; - var ops = _.transform(req.body, function(m,_req){ - if (_.isEmpty(_req)) return; - _req.language = req.language; - - m.push(function() { - var cb = arguments[arguments.length-1]; - res.locals.ops.push(_req); - res.send = res.json = function(code, data) { - if (_.isNumber(code) && code >= 500) - return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data))); - return cb(); - }; - if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); } - api[_req.op](_req, res, cb); - }); - }) - // Finally, save user at the end - .concat(function(){ - user.save = oldSave; - user.save(arguments[arguments.length-1]); - }); - - // call all the operations, then return the user object to the requester - async.waterfall(ops, function(err,_user) { - res.json = oldJson; - res.send = oldSend; - if (err) return next(err); - - var response = _user.toJSON(); - response.wasModified = res.locals.wasModified; - - user.fns.nullify(); - user = res.locals.user = oldSend = oldJson = oldSave = null; - - // return only drops & streaks - if (response._tmp && response._tmp.drop){ - res.status(200).json({_tmp: {drop: response._tmp.drop}, _v: response._v}); - - // Fetch full user object - } else if (response.wasModified){ - // Preen 3-day past-completed To-Dos from Angular & mobile app - response.todos = shared.preenTodos(response.todos); - res.status(200).json(response); - - // return only the version number - } else{ - res.status(200).json({_v: response._v}); - } - }); -}; - -function _generateWebhookTaskData(task, direction, delta, stats, user) { - var extendedStats = _.extend(stats, { - toNextLevel: shared.tnl(user.stats.lvl), - maxHealth: shared.maxHealth, - maxMP: user._statsComputed.maxMP - }); - - var userData = { - _id: user._id, - _tmp: user._tmp, - stats: extendedStats - }; - - var taskData = { - details: task, - direction: direction, - delta: delta - } - - return { - task: taskData, - user: userData - } -} diff --git a/website/src/controllers/payments/amazon.js b/website/src/controllers/payments/amazon.js deleted file mode 100644 index 8c01663c10..0000000000 --- a/website/src/controllers/payments/amazon.js +++ /dev/null @@ -1,271 +0,0 @@ -var amazonPayments = require('amazon-payments'); -var mongoose = require('mongoose'); -var moment = require('moment'); -var nconf = require('nconf'); -var async = require('async'); -var User = require('mongoose').model('User'); -var shared = require('../../../../common'); -var payments = require('./index'); -var cc = require('coupon-code'); -var isProd = nconf.get('NODE_ENV') === 'production'; - -var amzPayment = amazonPayments.connect({ - environment: amazonPayments.Environment[isProd ? 'Production' : 'Sandbox'], - sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), - mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'), - mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'), - clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID') -}); - -exports.verifyAccessToken = function(req, res, next){ - if(!req.body || !req.body['access_token']){ - return res.status(400).json({err: 'Access token not supplied.'}); - } - - amzPayment.api.getTokenInfo(req.body['access_token'], function(err, tokenInfo){ - if(err) return res.status(400).json({err:err}); - - res.sendStatus(200); - }); -}; - -exports.createOrderReferenceId = function(req, res, next){ - if(!req.body || !req.body.billingAgreementId){ - return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); - } - - amzPayment.offAmazonPayments.createOrderReferenceForId({ - Id: req.body.billingAgreementId, - IdType: 'BillingAgreement', - ConfirmNow: false - }, function(err, response){ - if(err) return next(err); - if(!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId){ - return next(new Error('Missing attributes in Amazon response.')); - } - - res.json({ - orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId - }); - }); -}; - -exports.checkout = function(req, res, next){ - if(!req.body || !req.body.orderReferenceId){ - return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); - } - - var gift = req.body.gift; - var user = res.locals.user; - var orderReferenceId = req.body.orderReferenceId; - var amount = 5; - - if(gift){ - if(gift.type === 'gems'){ - amount = gift.gems.amount/4; - }else if(gift.type === 'subscription'){ - amount = shared.content.subscriptionBlocks[gift.subscription.key].price; - } - } - - async.series({ - setOrderReferenceDetails: function(cb){ - amzPayment.offAmazonPayments.setOrderReferenceDetails({ - AmazonOrderReferenceId: orderReferenceId, - OrderReferenceAttributes: { - OrderTotal: { - CurrencyCode: 'USD', - Amount: amount - }, - SellerNote: 'HabitRPG Payment', - SellerOrderAttributes: { - SellerOrderId: shared.uuid(), - StoreName: 'HabitRPG' - } - } - }, cb); - }, - - confirmOrderReference: function(cb){ - amzPayment.offAmazonPayments.confirmOrderReference({ - AmazonOrderReferenceId: orderReferenceId - }, cb); - }, - - authorize: function(cb){ - amzPayment.offAmazonPayments.authorize({ - AmazonOrderReferenceId: orderReferenceId, - AuthorizationReferenceId: shared.uuid().substring(0, 32), - AuthorizationAmount: { - CurrencyCode: 'USD', - Amount: amount - }, - SellerAuthorizationNote: 'HabitRPG Payment', - TransactionTimeout: 0, - CaptureNow: true - }, function(err, res){ - if(err) return cb(err); - - if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){ - return cb(new Error('The payment was not successfull.')); - } - - return cb(); - }); - }, - - closeOrderReference: function(cb){ - amzPayment.offAmazonPayments.closeOrderReference({ - AmazonOrderReferenceId: orderReferenceId - }, cb); - }, - - executePayment: function(cb){ - async.waterfall([ - function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); }, - function(member, cb2){ - var data = {user:user, paymentMethod:'Amazon Payments'}; - var method = 'buyGems'; - - if (gift){ - if (gift.type == 'subscription') method = 'createSubscription'; - gift.member = member; - data.gift = gift; - data.paymentMethod = 'Gift'; - } - - payments[method](data, cb2); - } - ], cb); - } - }, function(err, results){ - if(err) return next(err); - - res.sendStatus(200); - }); - -}; - -exports.subscribe = function(req, res, next){ - if(!req.body || !req.body['billingAgreementId']){ - return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); - } - - var billingAgreementId = req.body.billingAgreementId; - var sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false; - var coupon = req.body.coupon; - var user = res.locals.user; - - if(!sub){ - return res.status(400).json({err: 'Subscription plan not found.'}); - } - - async.series({ - applyDiscount: function(cb){ - if (!sub.discount) return cb(); - if (!coupon) return cb(new Error('Please provide a coupon code for this plan.')); - mongoose.model('Coupon').findOne({_id:cc.validate(coupon), event:sub.key}, function(err, coupon){ - if(err) return cb(err); - if(!coupon) return cb(new Error('Coupon code not found.')); - cb(); - }); - }, - - setBillingAgreementDetails: function(cb){ - amzPayment.offAmazonPayments.setBillingAgreementDetails({ - AmazonBillingAgreementId: billingAgreementId, - BillingAgreementAttributes: { - SellerNote: 'HabitRPG Subscription', - SellerBillingAgreementAttributes: { - SellerBillingAgreementId: shared.uuid(), - StoreName: 'HabitRPG', - CustomInformation: 'HabitRPG Subscription' - } - } - }, cb); - }, - - confirmBillingAgreement: function(cb){ - amzPayment.offAmazonPayments.confirmBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId - }, cb); - }, - - authorizeOnBillingAgreeement: function(cb){ - amzPayment.offAmazonPayments.authorizeOnBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId, - AuthorizationReferenceId: shared.uuid().substring(0, 32), - AuthorizationAmount: { - CurrencyCode: 'USD', - Amount: sub.price - }, - SellerAuthorizationNote: 'HabitRPG Subscription Payment', - TransactionTimeout: 0, - CaptureNow: true, - SellerNote: 'HabitRPG Subscription Payment', - SellerOrderAttributes: { - SellerOrderId: shared.uuid(), - StoreName: 'HabitRPG' - } - }, function(err, res){ - if(err) return cb(err); - - if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){ - return cb(new Error('The payment was not successfull.')); - } - - return cb(); - }); - }, - - createSubscription: function(cb){ - payments.createSubscription({ - user: user, - customerId: billingAgreementId, - paymentMethod: 'Amazon Payments', - sub: sub - }, cb); - } - }, function(err, results){ - if(err) return next(err); - - res.sendStatus(200); - }); -}; - -exports.subscribeCancel = function(req, res, next){ - var user = res.locals.user; - if (!user.purchased.plan.customerId) - return res.status(401).json({err: 'User does not have a plan subscription'}); - - var billingAgreementId = user.purchased.plan.customerId; - - async.series({ - closeBillingAgreement: function(cb){ - amzPayment.offAmazonPayments.closeBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId - }, cb); - }, - - cancelSubscription: function(cb){ - var data = { - user: user, - // Date of next bill - nextBill: moment(user.purchased.plan.lastBillingDate).add({days: 30}), - paymentMethod: 'Amazon Payments' - }; - - payments.cancelSubscription(data, cb); - } - }, function(err, results){ - if (err) return next(err); // don't json this, let toString() handle errors - - if(req.query.noRedirect){ - res.sendStatus(200); - }else{ - res.redirect('/'); - } - - user = null; - }); -}; diff --git a/website/src/controllers/payments/iap.js b/website/src/controllers/payments/iap.js deleted file mode 100644 index 829482ed67..0000000000 --- a/website/src/controllers/payments/iap.js +++ /dev/null @@ -1,155 +0,0 @@ -var iap = require('in-app-purchase'); -var async = require('async'); -var payments = require('./index'); -var nconf = require('nconf'); - -var inAppPurchase = require('in-app-purchase'); -inAppPurchase.config({ - // this is the path to the directory containing iap-sanbox/iap-live files - googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR') -}); - -// Validation ERROR Codes -var INVALID_PAYLOAD = 6778001; -var CONNECTION_FAILED = 6778002; -var PURCHASE_EXPIRED = 6778003; - -exports.androidVerify = function(req, res, next) { - var iapBody = req.body; - var user = res.locals.user; - - iap.setup(function (error) { - if (error) { - var resObj = { - ok: false, - data: 'IAP Error' - }; - - return res.json(resObj); - - } - - /* - google receipt must be provided as an object - { - "data": "{stringified data object}", - "signature": "signature from google" - } - */ - var testObj = { - data: iapBody.transaction.receipt, - signature: iapBody.transaction.signature - }; - - // iap is ready - iap.validate(iap.GOOGLE, testObj, function (err, googleRes) { - if (err) { - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString() - } - }; - - return res.json(resObj); - } - - if (iap.isValidated(googleRes)) { - var resObj = { - ok: true, - data: googleRes - }; - - payments.buyGems({user:user, paymentMethod:'IAP GooglePlay', amount: 5.25}); - - return res.json(resObj); - } - }); - }); -}; - -exports.iosVerify = function(req, res, next) { - var iapBody = req.body; - var user = res.locals.user; - - iap.setup(function (error) { - if (error) { - var resObj = { - ok: false, - data: 'IAP Error' - }; - - return res.json(resObj); - - } - - //iap is ready - iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) { - if (err) { - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString() - } - }; - - return res.json(resObj); - } - - if (iap.isValidated(appleRes)) { - var purchaseDataList = iap.getPurchaseData(appleRes); - if (purchaseDataList.length > 0) { - var correctReceipt = true; - for (var index in purchaseDataList) { - switch (purchaseDataList[index].productId) { - case 'com.habitrpg.ios.Habitica.4gems': - payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 1}); - break; - case 'com.habitrpg.ios.Habitica.8gems': - payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 2}); - break; - case 'com.habitrpg.ios.Habitica.20gems': - case 'com.habitrpg.ios.Habitica.21gems': - payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 5.25}); - break; - case 'com.habitrpg.ios.Habitica.42gems': - payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 10.5}); - break; - default: - correctReceipt = false; - } - } - if (correctReceipt) { - var resObj = { - ok: true, - data: appleRes - }; - // yay good! - return res.json(resObj); - } - } - //wrong receipt content - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: 'Incorrect receipt content' - } - }; - return res.json(resObj); - } - //invalid receipt - var resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: 'Invalid receipt' - } - }; - - return res.json(resObj); - }); - }); -}; diff --git a/website/src/controllers/payments/index.js b/website/src/controllers/payments/index.js deleted file mode 100644 index dad53feb13..0000000000 --- a/website/src/controllers/payments/index.js +++ /dev/null @@ -1,207 +0,0 @@ -var _ = require('lodash'); -var shared = require('../../../../common'); -var nconf = require('nconf'); -var utils = require('./../../libs/utils'); -var moment = require('moment'); -var isProduction = nconf.get("NODE_ENV") === "production"; -var stripe = require('./stripe'); -var paypal = require('./paypal'); -var amazon = require('./amazon'); -var members = require('../api-v2/members') -var async = require('async'); -var iap = require('./iap'); -var mongoose= require('mongoose'); -var cc = require('coupon-code'); -var pushNotify = require('./../pushNotifications'); - -function revealMysteryItems(user) { - _.each(shared.content.gear.flat, function(item) { - if ( - item.klass === 'mystery' && - moment().isAfter(shared.content.mystery[item.mystery].start) && - moment().isBefore(shared.content.mystery[item.mystery].end) && - !user.items.gear.owned[item.key] && - !~user.purchased.plan.mysteryItems.indexOf(item.key) - ) { - user.purchased.plan.mysteryItems.push(item.key); - } - }); -} - -exports.createSubscription = function(data, cb) { - var recipient = data.gift ? data.gift.member : data.user; - //if (!recipient.purchased.plan) recipient.purchased.plan = {}; // FIXME double-check, this should never be the case - var p = recipient.purchased.plan; - var block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key]; - var months = +block.months; - - if (data.gift) { - if (p.customerId && !p.dateTerminated) { // User has active plan - p.extraMonths += months; - } else { - p.dateTerminated = moment(p.dateTerminated).add({months: months}).toDate(); - if (!p.dateUpdated) p.dateUpdated = new Date(); - } - if (!p.customerId) p.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId - } else { - _(p).merge({ // override with these values - planId: block.key, - customerId: data.customerId, - dateUpdated: new Date(), - gemsBought: 0, - paymentMethod: data.paymentMethod, - extraMonths: +p.extraMonths - + +(p.dateTerminated ? moment(p.dateTerminated).diff(new Date(),'months',true) : 0), - dateTerminated: null, - // Specify a lastBillingDate just for Amazon Payments - // Resetted every time the subscription restarts - lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined - }).defaults({ // allow non-override if a plan was previously used - dateCreated: new Date(), - mysteryItems: [] - }).value(); - } - - // Block sub perks - var perks = Math.floor(months/3); - if (perks) { - p.consecutive.offset += months; - p.consecutive.gemCapExtra += perks*5; - if (p.consecutive.gemCapExtra > 25) p.consecutive.gemCapExtra = 25; - p.consecutive.trinkets += perks; - } - revealMysteryItems(recipient); - if(isProduction) { - if (!data.gift) utils.txnEmail(data.user, 'subscription-begins'); - - var analyticsData = { - uuid: data.user._id, - itemPurchased: 'Subscription', - sku: data.paymentMethod.toLowerCase() + '-subscription', - purchaseType: 'subscribe', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: !!data.gift, // coerced into a boolean - purchaseValue: block.price - } - utils.analytics.trackPurchase(analyticsData); - } - data.user.purchased.txnCount++; - if (data.gift){ - members.sendMessage(data.user, data.gift.member, data.gift); - - var byUserName = utils.getUserInfo(data.user, ['name']).name; - - if(data.gift.member.preferences.emailNotifications.giftedSubscription !== false){ - utils.txnEmail(data.gift.member, 'gifted-subscription', [ - {name: 'GIFTER', content: byUserName}, - {name: 'X_MONTHS_SUBSCRIPTION', content: months} - ]); - } - - if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), months + " months - by "+ byUserName); - } - } - async.parallel([ - function(cb2){data.user.save(cb2)}, - function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);} - ], cb); -} - -/** - * Sets their subscription to be cancelled later - */ -exports.cancelSubscription = function(data, cb) { - var p = data.user.purchased.plan, - now = moment(), - remaining = data.nextBill ? moment(data.nextBill).diff(new Date, 'days') : 30; - - p.dateTerminated = - moment( now.format('MM') + '/' + moment(p.dateUpdated).format('DD') + '/' + now.format('YYYY') ) - .add({days: remaining}) // end their subscription 1mo from their last payment - .add({months: Math.ceil(p.extraMonths)})// plus any extra time (carry-over, gifted subscription, etc) they have. FIXME: moment can't add months in fractions... - .toDate(); - p.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated - - data.user.save(cb); - utils.txnEmail(data.user, 'cancel-subscription'); - var analyticsData = { - uuid: data.user._id, - gaCategory: 'commerce', - gaLabel: data.paymentMethod, - paymentMethod: data.paymentMethod - } - utils.analytics.track('unsubscribe', analyticsData); -} - -exports.buyGems = function(data, cb) { - var amt = data.amount || 5; - amt = data.gift ? data.gift.gems.amount/4 : amt; - (data.gift ? data.gift.member : data.user).balance += amt; - data.user.purchased.txnCount++; - if(isProduction) { - if (!data.gift) utils.txnEmail(data.user, 'donation'); - - var analyticsData = { - uuid: data.user._id, - itemPurchased: 'Gems', - sku: data.paymentMethod.toLowerCase() + '-checkout', - purchaseType: 'checkout', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: !!data.gift, // coerced into a boolean - purchaseValue: amt - } - utils.analytics.trackPurchase(analyticsData); - } - - if (data.gift){ - var byUsername = utils.getUserInfo(data.user, ['name']).name; - var gemAmount = data.gift.gems.amount || 20; - - members.sendMessage(data.user, data.gift.member, data.gift); - if(data.gift.member.preferences.emailNotifications.giftedGems !== false){ - utils.txnEmail(data.gift.member, 'gifted-gems', [ - {name: 'GIFTER', content: byUsername}, - {name: 'X_GEMS_GIFTED', content: gemAmount} - ]); - } - - if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), gemAmount + ' Gems - by '+byUsername); - } - } - async.parallel([ - function(cb2){data.user.save(cb2)}, - function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);} - ], cb); -} - -exports.validCoupon = function(req, res, next){ - mongoose.model('Coupon').findOne({_id:cc.validate(req.params.code), event:'google_6mo'}, function(err, coupon){ - if (err) return next(err); - if (!coupon) return res.status(401).json({err:"Invalid coupon code"}); - return res.sendStatus(200); - }); -} - -exports.stripeCheckout = stripe.checkout; -exports.stripeSubscribeCancel = stripe.subscribeCancel; -exports.stripeSubscribeEdit = stripe.subscribeEdit; - -exports.paypalSubscribe = paypal.createBillingAgreement; -exports.paypalSubscribeSuccess = paypal.executeBillingAgreement; -exports.paypalSubscribeCancel = paypal.cancelSubscription; -exports.paypalCheckout = paypal.createPayment; -exports.paypalCheckoutSuccess = paypal.executePayment; -exports.paypalIPN = paypal.ipn; - -exports.amazonVerifyAccessToken = amazon.verifyAccessToken; -exports.amazonCreateOrderReferenceId = amazon.createOrderReferenceId; -exports.amazonCheckout = amazon.checkout; -exports.amazonSubscribe = amazon.subscribe; -exports.amazonSubscribeCancel = amazon.subscribeCancel; - -exports.iapAndroidVerify = iap.androidVerify; -exports.iapIosVerify = iap.iosVerify; diff --git a/website/src/controllers/payments/paypal.js b/website/src/controllers/payments/paypal.js deleted file mode 100644 index 3c5258c222..0000000000 --- a/website/src/controllers/payments/paypal.js +++ /dev/null @@ -1,216 +0,0 @@ -var nconf = require('nconf'); -var moment = require('moment'); -var async = require('async'); -var _ = require('lodash'); -var url = require('url'); -var User = require('mongoose').model('User'); -var payments = require('./index'); -var logger = require('../../libs/logging'); -var ipn = require('paypal-ipn'); -var paypal = require('paypal-rest-sdk'); -var shared = require('../../../../common'); -var mongoose = require('mongoose'); -var cc = require('coupon-code'); - -// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have -// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created -// there, get it's plan.id and store it in config.json -_.each(shared.content.subscriptionBlocks, function(block){ - block.paypalKey = nconf.get("PAYPAL:billing_plans:"+block.key); -}); - -paypal.configure({ - 'mode': nconf.get("PAYPAL:mode"), //sandbox or live - 'client_id': nconf.get("PAYPAL:client_id"), - 'client_secret': nconf.get("PAYPAL:client_secret") -}); - -var parseErr = function(res, err){ - //var error = err.response ? err.response.message || err.response.details[0].issue : err; - var error = JSON.stringify(err); - return res.status(400).json({err:error}); -} - -exports.createBillingAgreement = function(req,res,next){ - var sub = shared.content.subscriptionBlocks[req.query.sub]; - async.waterfall([ - function(cb){ - if (!sub.discount) return cb(null, null); - if (!req.query.coupon) return cb('Please provide a coupon code for this plan.'); - mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb); - }, - function(coupon, cb){ - if (sub.discount && !coupon) return cb('Invalid coupon code.'); - var billingPlanTitle = "HabitRPG Subscription" + ' ($'+sub.price+' every '+sub.months+' months, recurring)'; - var billingAgreementAttributes = { - "name": billingPlanTitle, - "description": billingPlanTitle, - "start_date": moment().add({minutes:5}).format(), - "plan": { - "id": sub.paypalKey - }, - "payer": { - "payment_method": "paypal" - } - }; - paypal.billingAgreement.create(billingAgreementAttributes, cb); - } - ], function(err, billingAgreement){ - if (err) return parseErr(res, err); - // For approving subscription via Paypal, first redirect user to: approval_url - req.session.paypalBlock = req.query.sub; - var approval_url = _.find(billingAgreement.links, {rel:'approval_url'}).href; - res.redirect(approval_url); - }); -} - -exports.executeBillingAgreement = function(req,res,next){ - var block = shared.content.subscriptionBlocks[req.session.paypalBlock]; - delete req.session.paypalBlock; - async.auto({ - exec: function (cb) { - paypal.billingAgreement.execute(req.query.token, {}, cb); - }, - get_user: function (cb) { - User.findById(req.session.userId, cb); - }, - create_sub: ['exec', 'get_user', function (cb, results) { - payments.createSubscription({ - user: results.get_user, - customerId: results.exec.id, - paymentMethod: 'Paypal', - sub: block - }, cb); - }] - },function(err){ - if (err) return parseErr(res, err); - res.redirect('/'); - }) -} - -exports.createPayment = function(req, res) { - // if we're gifting to a user, put it in session for the `execute()` - req.session.gift = req.query.gift || undefined; - var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; - var price = !gift ? 5.00 - : gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2) - : Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2); - var description = !gift ? "HabitRPG Gems" - : gift.type=='gems' ? "HabitRPG Gems (Gift)" - : shared.content.subscriptionBlocks[gift.subscription.key].months + "mo. HabitRPG Subscription (Gift)"; - var create_payment = { - "intent": "sale", - "payer": { - "payment_method": "paypal" - }, - "redirect_urls": { - "return_url": nconf.get('BASE_URL') + '/paypal/checkout/success', - "cancel_url": nconf.get('BASE_URL') - }, - "transactions": [{ - "item_list": { - "items": [{ - "name": description, - //"sku": "1", - "price": price, - "currency": "USD", - "quantity": 1 - }] - }, - "amount": { - "currency": "USD", - "total": price - }, - "description": description - }] - }; - paypal.payment.create(create_payment, function (err, payment) { - if (err) return parseErr(res, err); - var link = _.find(payment.links, {rel: 'approval_url'}).href; - res.redirect(link); - }); -} - -exports.executePayment = function(req, res) { - var paymentId = req.query.paymentId, - PayerID = req.query.PayerID, - gift = req.session.gift ? JSON.parse(req.session.gift) : undefined; - delete req.session.gift; - async.waterfall([ - function(cb){ - paypal.payment.execute(paymentId, {payer_id: PayerID}, cb); - }, - function(payment, cb){ - async.parallel([ - function(cb2){ User.findById(req.session.userId, cb2); }, - function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); } - ], cb); - }, - function(results, cb){ - if (_.isEmpty(results[0])) return cb("User not found when completing paypal transaction"); - var data = {user:results[0], customerId:PayerID, paymentMethod:'Paypal', gift:gift} - var method = 'buyGems'; - if (gift) { - gift.member = results[1]; - if (gift.type=='subscription') method = 'createSubscription'; - data.paymentMethod = 'Gift'; - } - payments[method](data, cb); - } - ],function(err){ - if (err) return parseErr(res, err); - res.redirect('/'); - }) -} - -exports.cancelSubscription = function(req, res, next){ - var user = res.locals.user; - if (!user.purchased.plan.customerId) - return res.status(401).json({err: "User does not have a plan subscription"}); - async.auto({ - get_cus: function(cb){ - paypal.billingAgreement.get(user.purchased.plan.customerId, cb); - }, - verify_cus: ['get_cus', function(cb, results){ - var hasntBilledYet = results.get_cus.agreement_details.cycles_completed == "0"; - if (hasntBilledYet) - return cb("The plan hasn't activated yet (due to a PayPal bug). It will begin "+results.get_cus.agreement_details.next_billing_date+", after which you can cancel to retain your full benefits"); - cb(); - }], - del_cus: ['verify_cus', function(cb, results){ - paypal.billingAgreement.cancel(user.purchased.plan.customerId, {note: "Canceling the subscription"}, cb); - }], - cancel_sub: ['get_cus', 'verify_cus', function(cb, results){ - var data = {user: user, paymentMethod: 'Paypal', nextBill: results.get_cus.agreement_details.next_billing_date}; - payments.cancelSubscription(data, cb) - }] - }, function(err){ - if (err) return parseErr(res, err); - res.redirect('/'); - user = null; - }); -} - -/** - * General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their - * recurring paypal payments in their paypal dashboard. Remove this when we can move to webhooks or some other solution - */ -exports.ipn = function(req, res, next) { - console.log('IPN Called'); - res.sendStatus(200); // Must respond to PayPal IPN request with an empty 200 first - ipn.verify(req.body, function(err, msg) { - if (err) return logger.error(msg); - switch (req.body.txn_type) { - // TODO what's the diff b/w the two data.txn_types below? The docs recommend subscr_cancel, but I'm getting the other one instead... - case 'recurring_payment_profile_cancel': - case 'subscr_cancel': - User.findOne({'purchased.plan.customerId':req.body.recurring_payment_id},function(err, user){ - if (err) return logger.error(err); - if (_.isEmpty(user)) return; // looks like the cancellation was already handled properly above (see api.paypalSubscribeCancel) - payments.cancelSubscription({user:user, paymentMethod: 'Paypal'}); - }); - break; - } - }); -}; - diff --git a/website/src/controllers/payments/stripe.js b/website/src/controllers/payments/stripe.js deleted file mode 100644 index 1a1085227c..0000000000 --- a/website/src/controllers/payments/stripe.js +++ /dev/null @@ -1,123 +0,0 @@ -var nconf = require('nconf'); -var stripe = require('stripe')(nconf.get('STRIPE_API_KEY')); -var async = require('async'); -var payments = require('./index'); -var User = require('mongoose').model('User'); -var shared = require('../../../../common'); -var mongoose = require('mongoose'); -var cc = require('coupon-code'); - -/* - Setup Stripe response when posting payment - */ -exports.checkout = function(req, res, next) { - var token = req.body.id; - var user = res.locals.user; - var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; - var sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false; - - async.waterfall([ - function(cb){ - if (sub) { - async.waterfall([ - function(cb2){ - if (!sub.discount) return cb2(null, null); - if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.'); - mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb2); - }, - function(coupon, cb2){ - if (sub.discount && !coupon) return cb2('Invalid coupon code.'); - var customer = { - email: req.body.email, - metadata: {uuid: user._id}, - card: token, - plan: sub.key - }; - stripe.customers.create(customer, cb2); - } - ], cb); - } else { - stripe.charges.create({ - amount: !gift ? '500' //"500" = $5 - : gift.type=='subscription' ? ''+shared.content.subscriptionBlocks[gift.subscription.key].price*100 - : ''+gift.gems.amount/4*100, - currency: 'usd', - card: token - }, cb); - } - }, - function(response, cb) { - if (sub) return payments.createSubscription({user:user, customerId:response.id, paymentMethod:'Stripe', sub:sub}, cb); - async.waterfall([ - function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); }, - function(member, cb2){ - var data = {user:user, customerId:response.id, paymentMethod:'Stripe', gift:gift}; - var method = 'buyGems'; - if (gift) { - gift.member = member; - if (gift.type=='subscription') method = 'createSubscription'; - data.paymentMethod = 'Gift'; - } - payments[method](data, cb2); - } - ], cb); - } - ], function(err){ - if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors - res.sendStatus(200); - user = token = null; - }); -}; - -exports.subscribeCancel = function(req, res, next) { - var user = res.locals.user; - if (!user.purchased.plan.customerId) - return res.status(401).json({err: 'User does not have a plan subscription'}); - - async.auto({ - get_cus: function(cb){ - stripe.customers.retrieve(user.purchased.plan.customerId, cb); - }, - del_cus: ['get_cus', function(cb, results){ - stripe.customers.del(user.purchased.plan.customerId, cb); - }], - cancel_sub: ['get_cus', function(cb, results) { - var data = { - user: user, - nextBill: results.get_cus.subscription.current_period_end*1000, // timestamp is in seconds - paymentMethod: 'Stripe' - }; - payments.cancelSubscription(data, cb); - }] - }, function(err, results){ - if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors - res.redirect('/'); - user = null; - }); -}; - -exports.subscribeEdit = function(req, res, next) { - var token = req.body.id; - var user = res.locals.user; - var user_id = user.purchased.plan.customerId; - var sub_id; - - async.waterfall([ - function(cb){ - stripe.customers.listSubscriptions(user_id, cb); - }, - function(response, cb) { - sub_id = response.data[0].id; - console.warn(sub_id); - console.warn([user_id, sub_id, { card: token }]); - stripe.customers.updateSubscription(user_id, sub_id, { card: token }, cb); - }, - function(response, cb) { - user.save(cb); - } - ], function(err, saved){ - if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors - res.sendStatus(200); - token = user = user_id = sub_id; - }); -}; diff --git a/website/src/middlewares/cors.js b/website/src/middlewares/cors.js deleted file mode 100644 index e72db26981..0000000000 --- a/website/src/middlewares/cors.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = function(req, res, next) { - res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); - res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE"); - res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key"); - if (req.method === 'OPTIONS') return res.sendStatus(200); - return next(); -}; diff --git a/website/src/middlewares/redirects.js b/website/src/middlewares/redirects.js deleted file mode 100644 index ddc135beab..0000000000 --- a/website/src/middlewares/redirects.js +++ /dev/null @@ -1,41 +0,0 @@ -var nconf = require('nconf'); -var IS_PROD = nconf.get('NODE_ENV') === 'production'; -var ignoreRedirect = nconf.get('IGNORE_REDIRECT'); -var BASE_URL = nconf.get('BASE_URL'); - -function isHTTP(req) { - return ( - req.headers['x-forwarded-proto'] && - req.headers['x-forwarded-proto'] !== 'https' && - IS_PROD && - BASE_URL.indexOf('https') === 0 - ); -} - -function isProxied(req) { - return ( - req.headers['x-habitica-lb'] && - req.headers['x-habitica-lb'] === 'Yes' - ); -} - -module.exports.forceSSL = function(req, res, next){ - if(isHTTP(req) && !isProxied(req)) { - return res.redirect(BASE_URL + req.url); - } - - next(); -}; - -// Redirect to habitica for non-api urls - -function nonApiUrl(req) { - return req.url.search(/\/api\//) === -1; -} - -module.exports.forceHabitica = function(req, res, next) { - if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) { - return res.redirect(301, BASE_URL + req.url); - } - next(); -}; diff --git a/website/src/models/challenge.js b/website/src/models/challenge.js deleted file mode 100644 index d44b798bc0..0000000000 --- a/website/src/models/challenge.js +++ /dev/null @@ -1,120 +0,0 @@ -var mongoose = require("mongoose"); -var Schema = mongoose.Schema; -var shared = require('../../../common'); -var _ = require('lodash'); -var TaskSchemas = require('./task'); - -var ChallengeSchema = new Schema({ - _id: {type: String, 'default': shared.uuid}, - name: String, - shortName: String, - description: String, - official: {type: Boolean,'default':false}, - habits: [TaskSchemas.HabitSchema], - dailys: [TaskSchemas.DailySchema], - todos: [TaskSchemas.TodoSchema], - rewards: [TaskSchemas.RewardSchema], - leader: {type: String, ref: 'User'}, - group: {type: String, ref: 'Group'}, - timestamp: {type: Date, 'default': Date.now}, - members: [{type: String, ref: 'User'}], - memberCount: {type: Number, 'default': 0}, - prize: {type: Number, 'default': 0} -}); - -ChallengeSchema.virtual('tasks').get(function () { - var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); - var tasks = _.object(_.pluck(tasks,'id'), tasks); - return tasks; -}); - -ChallengeSchema.methods.toJSON = function(){ - var doc = this.toObject(); - doc._isMember = this._isMember; - return doc; -} - -// -------------- -// Syncing logic -// -------------- - -function syncableAttrs(task) { - var t = (task.toObject) ? task.toObject() : task; // lodash doesn't seem to like _.omit on EmbeddedDocument - // only sync/compare important attrs - var omitAttrs = 'challenge history tags completed streak notes'.split(' '); - if (t.type != 'reward') omitAttrs.push('value'); - return _.omit(t, omitAttrs); -} - -/** - * Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers - */ -function comparableData(obj) { - return JSON.stringify( - _(obj.habits.concat(obj.dailys).concat(obj.todos).concat(obj.rewards)) - .sortBy('id') // we don't want to update if they're sort-order is different - .transform(function(result, task){ - result.push(syncableAttrs(task)); - }) - .value()) -} - -ChallengeSchema.methods.isOutdated = function(newData) { - return comparableData(this) !== comparableData(newData); -} - -/** - * Syncs all new tasks, deleted tasks, etc to the user object - * @param user - * @return nothing, user is modified directly. REMEMBER to save the user! - */ -ChallengeSchema.methods.syncToUser = function(user, cb) { - if (!user) return; - var self = this; - self.shortName = self.shortName || self.name; - - // Add challenge to user.challenges - if (!_.contains(user.challenges, self._id)) { - user.challenges.push(self._id); - } - - // Sync tags - var tags = user.tags || []; - var i = _.findIndex(tags, {id: self._id}) - if (~i) { - if (tags[i].name !== self.shortName) { - // update the name - it's been changed since - user.tags[i].name = self.shortName; - } - } else { - user.tags.push({ - id: self._id, - name: self.shortName, - challenge: true - }); - } - - // Sync new tasks and updated tasks - _.each(self.tasks, function(task){ - var list = user[task.type+'s']; - var userTask = user.tasks[task.id] || (list.push(syncableAttrs(task)), list[list.length-1]); - if (!userTask.notes) userTask.notes = task.notes; // don't override the notes, but provide it if not provided - userTask.challenge = {id:self._id}; - userTask.tags = userTask.tags || {}; - userTask.tags[self._id] = true; - _.merge(userTask, syncableAttrs(task)); - }) - - // Flag deleted tasks as "broken" - _.each(user.tasks, function(task){ - if (task.challenge && task.challenge.id==self._id && !self.tasks[task.id]) { - task.challenge.broken = 'TASK_DELETED'; - } - }) - - user.save(cb); -}; - - -module.exports.schema = ChallengeSchema; -module.exports.model = mongoose.model("Challenge", ChallengeSchema); diff --git a/website/src/models/coupon.js b/website/src/models/coupon.js deleted file mode 100644 index 3b0afb3f2a..0000000000 --- a/website/src/models/coupon.js +++ /dev/null @@ -1,59 +0,0 @@ -var mongoose = require("mongoose"); -var shared = require('../../../common'); -var _ = require('lodash'); -var async = require('async'); -var cc = require('coupon-code'); -var autoinc = require('mongoose-id-autoinc'); - -var CouponSchema = new mongoose.Schema({ - _id: {type: String, 'default': cc.generate}, - event: {type:String, enum:['wondercon','google_6mo']}, - user: {type: 'String', ref: 'User'} -}); - -CouponSchema.statics.generate = function(event, count, callback) { - async.times(count, function(n,cb){ - mongoose.model('Coupon').create({event: event}, cb); - }, callback); -} - -CouponSchema.statics.apply = function(user, code, next){ - async.auto({ - get_coupon: function (cb) { - mongoose.model('Coupon').findById(cc.validate(code), cb); - }, - apply_coupon: ['get_coupon', function (cb, results) { - if (!results.get_coupon) return cb("Invalid coupon code"); - if (results.get_coupon.user) return cb("Coupon already used"); - switch (results.get_coupon.event) { - case 'wondercon': - user.items.gear.owned.eyewear_special_wondercon_red = true; - user.items.gear.owned.eyewear_special_wondercon_black = true; - user.items.gear.owned.back_special_wondercon_black = true; - user.items.gear.owned.back_special_wondercon_red = true; - user.items.gear.owned.body_special_wondercon_red = true; - user.items.gear.owned.body_special_wondercon_black = true; - user.items.gear.owned.body_special_wondercon_gold = true; - user.extra = {signupEvent: 'wondercon'}; - user.save(cb); - break; - } - }], - expire_coupon: ['apply_coupon', function (cb, results) { - results.get_coupon.user = user._id; - results.get_coupon.save(cb); - }] - }, function(err, results){ - if (err) return next(err); - next(null,results.apply_coupon[0]); - }) -} - -CouponSchema.plugin(autoinc.plugin, { - model: 'Coupon', - field: 'seq' -}); - -module.exports.schema = CouponSchema; -module.exports.model = mongoose.model("Coupon", CouponSchema); - diff --git a/website/src/models/emailUnsubscription.js b/website/src/models/emailUnsubscription.js deleted file mode 100644 index 144417f3fc..0000000000 --- a/website/src/models/emailUnsubscription.js +++ /dev/null @@ -1,14 +0,0 @@ -var mongoose = require("mongoose"); -var shared = require('../../../common'); - -// A collection used to store mailing list unsubscription for non registered email addresses -var EmailUnsubscriptionSchema = new mongoose.Schema({ - _id: { - type: String, - 'default': shared.uuid - }, - email: String -}); - -module.exports.schema = EmailUnsubscriptionSchema; -module.exports.model = mongoose.model('EmailUnsubscription', EmailUnsubscriptionSchema); \ No newline at end of file diff --git a/website/src/models/group.js b/website/src/models/group.js deleted file mode 100644 index caed72331e..0000000000 --- a/website/src/models/group.js +++ /dev/null @@ -1,501 +0,0 @@ -var mongoose = require("mongoose"); -var Schema = mongoose.Schema; -var User = require('./user').model; -var shared = require('../../../common'); -var _ = require('lodash'); -var async = require('async'); -var logging = require('../libs/logging'); -var Challenge = require('./../models/challenge').model; -var firebase = require('../libs/firebase'); - -// NOTE any change to groups' members in MongoDB will have to be run through the API -// changes made directly to the db will cause Firebase to get out of sync -var GroupSchema = new Schema({ - _id: {type: String, 'default': shared.uuid}, - name: String, - description: String, - leader: {type: String, ref: 'User'}, - members: [{type: String, ref: 'User'}], - invites: [{type: String, ref: 'User'}], - type: {type: String, "enum": ['guild', 'party']}, - privacy: {type: String, "enum": ['private', 'public'], 'default':'private'}, - //_v: {type: Number,'default': 0}, - chat: Array, - /* - # [{ - # timestamp: Date - # user: String - # text: String - # contributor: String - # uuid: String - # id: String - # }] - */ - leaderOnly: { // restrict group actions to leader (members can't do them) - challenges: {type:Boolean, 'default':false}, - //invites: {type:Boolean, 'default':false} - }, - memberCount: {type: Number, 'default': 0}, - challengeCount: {type: Number, 'default': 0}, - balance: Number, - logo: String, - leaderMessage: String, - challenges: [{type:'String', ref:'Challenge'}], // do we need this? could depend on back-ref instead (Challenge.find({group:GID})) - quest: { - key: String, - active: {type:Boolean, 'default':false}, - leader: {type:String, ref:'User'}, - progress:{ - hp: Number, - collect: {type:Schema.Types.Mixed, 'default':{}}, // {feather: 5, ingot: 3} - rage: Number, // limit break / "energy stored in shell", for explosion-attacks - }, - - //Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click - //'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them. - //TODO when booting user, remove from .joined and check again if we can now start the quest - members: Schema.Types.Mixed, - extra: Schema.Types.Mixed - } -}, { - strict: 'throw', - minimize: false // So empty objects are returned -}); - -/** - * Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration - * to remove duplicates, then take these fucntions out - */ -function removeDuplicates(doc){ - // Remove duplicate members - if (doc.members) { - var uniqMembers = _.uniq(doc.members); - if (uniqMembers.length != doc.members.length) { - doc.members = uniqMembers; - } - } -} - -// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate() -// @see https://github.com/LearnBoost/mongoose/issues/964 -GroupSchema.pre('save', function(next){ - removeDuplicates(this); - this.memberCount = _.size(this.members); - this.challengeCount = _.size(this.challenges); - next(); -}) - -GroupSchema.pre('remove', function(next) { - var group = this; - async.waterfall([ - function(cb) { - var invitationQuery = {}; - var groupType = group.type; - //Add an 's' to group type guild because the model has the plural version - if (group.type == "guild") groupType += "s"; - invitationQuery['invitations.' + groupType + '.id'] = group._id; - User.find(invitationQuery, cb); - }, - function(users, cb) { - if (users) { - users.forEach(function (user, index, array) { - if ( group.type == "party" ) { - user.invitations.party = {}; - } else { - var i = _.findIndex(user.invitations.guilds, {id: group._id}); - user.invitations.guilds.splice(i, 1); - } - user.save(); - }); - } - cb(); - } - ], next); -}); - -GroupSchema.post('remove', function(group) { - firebase.deleteGroup(group._id); -}); - -GroupSchema.methods.toJSON = function(){ - var doc = this.toObject(); - removeDuplicates(doc); - doc._isMember = this._isMember; - - //fix(groups): temp fix to remove chat entries stored as strings (not sure why that's happening..). - // Required as angular 1.3 is strict on dupes, and no message.id to `track by` - _.remove(doc.chat,function(msg){return !msg.id}); - - // @see pre('save') comment above - this.memberCount = _.size(this.members); - this.challengeCount = _.size(this.challenges); - - return doc; -} - -var chatDefaults = module.exports.chatDefaults = function(msg,user){ - var message = { - id: shared.uuid(), - text: msg, - timestamp: +new Date, - likes: {}, - flags: {}, - flagCount: 0 - }; - if (user) { - _.defaults(message, { - uuid: user._id, - contributor: user.contributor && user.contributor.toObject(), - backer: user.backer && user.backer.toObject(), - user: user.profile.name - }); - } else { - message.uuid = 'system'; - } - return message; -} - -var NO_CHAT_NOTIFICATIONS = ['habitrpg'] - -GroupSchema.methods.sendChat = function(message, user){ - var group = this; - group.chat.unshift(chatDefaults(message,user)); - group.chat.splice(200); - // Kick off chat notifications in the background. - var lastSeenUpdate = {$set:{}, $inc:{_v:1}}; - lastSeenUpdate['$set']['newMessages.'+group._id] = {name:group.name,value:true}; - if (NO_CHAT_NOTIFICATIONS.indexOf(group._id) !== -1 || group.memberCount > 5000) { - // TODO For Tavern, only notify them if their name was mentioned - // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names? - // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec(); - } else { - mongoose.model('User').update({_id:{$in:group.members, $ne: user ? user._id : ''}},lastSeenUpdate,{multi:true}).exec(); - } -} - -var cleanQuestProgress = function(merge){ - var clean = { - key: null, - progress: { - up: 0, - down: 0, - collect: {} - }, - completed: null, - RSVPNeeded: false - }; - merge = merge || {progress:{}}; - _.merge(clean, _.omit(merge,'progress')); - _.merge(clean.progress, merge.progress); - return clean; -} -GroupSchema.statics.cleanQuestProgress = cleanQuestProgress; - -// Participants: Grant rewards & achievements, finish quest -GroupSchema.methods.finishQuest = function(quest, cb) { - var group = this; - var questK = quest.key; - var updates = {$inc:{},$set:{}}; - - updates['$inc']['achievements.quests.' + questK] = 1; - updates['$inc']['stats.gp'] = +quest.drop.gp; - updates['$inc']['stats.exp'] = +quest.drop.exp; - updates['$inc']['_v'] = 1; - if (group._id == 'habitrpg') { - updates['$set']['party.quest.completed'] = questK; // Just show the notif - } else { - updates['$set']['party.quest'] = cleanQuestProgress({completed: questK}); // clear quest progress - } - - _.each(quest.drop.items, function(item){ - var dropK = item.key; - switch (item.type) { - case 'gear': - // TODO This means they can lose their new gear on death, is that what we want? - updates['$set']['items.gear.owned.'+dropK] = true; - break; - case 'eggs': - case 'food': - case 'hatchingPotions': - case 'quests': - updates['$inc']['items.'+item.type+'.'+dropK] = _.where(quest.drop.items,{type:item.type,key:item.key}).length; - break; - case 'pets': - updates['$set']['items.pets.'+dropK] = 5; - break; - case 'mounts': - updates['$set']['items.mounts.'+dropK] = true; - break; - } - }) - var q = group._id === 'habitrpg' ? {} : {_id:{$in:_.keys(group.quest.members)}}; - group.quest = {};group.markModified('quest'); - mongoose.model('User').update(q, updates, {multi:true}, cb); -} - -function isOnQuest(user,progress,group){ - return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true; -} - -GroupSchema.statics.collectQuest = function(user, progress, cb) { - this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ - if (!isOnQuest(user,progress,group)) return cb(null); - var quest = shared.content.quests[group.quest.key]; - - _.each(progress.collect,function(v,k){ - group.quest.progress.collect[k] += v; - }); - - var foundText = _.reduce(progress.collect, function(m,v,k){ - m.push(v + ' ' + quest.collect[k].text('en')); - return m; - }, []); - foundText = foundText ? foundText.join(', ') : 'nothing'; - group.sendChat("`" + user.profile.name + " found "+foundText+".`"); - group.markModified('quest.progress.collect'); - - // Still needs completing - if (_.find(shared.content.quests[group.quest.key].collect, function(v,k){ - return group.quest.progress.collect[k] < v.count; - })) return group.save(cb); - - async.series([ - function(cb2){ - group.finishQuest(quest,cb2); - }, - function(cb2){ - group.sendChat('`All items found! Party has received their rewards.`'); - group.save(cb2); - } - ],cb); - }) -} - -// to set a boss: `db.groups.update({_id:'habitrpg'},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` -module.exports.tavernQuest = {}; -var tavernQ = {_id:'habitrpg','quest.key':{$ne:null}}; -process.nextTick(function(){ - mongoose.model('Group').findOne(tavernQ, function(err,tavern){ - if (!tavern) return; // No tavern quest - - var quest = tavern.quest.toObject(); - // Using _assign so we don't lose the reference to the exported tavernQuest - _.assign(module.exports.tavernQuest, quest); - }); -}); - -GroupSchema.statics.tavernBoss = function(user,progress) { - if (!progress) return; - - // hack: prevent crazy damage to world boss - var dmg = Math.min(900, Math.abs(progress.up||0)), - rage = -Math.min(900, Math.abs(progress.down||0)); - - async.waterfall([ - function(cb){ - mongoose.model('Group').findOne(tavernQ,cb); - }, - function(tavern,cb){ - if (!(tavern && tavern.quest && tavern.quest.key)) return cb(true); - - var quest = shared.content.quests[tavern.quest.key]; - if (tavern.quest.progress.hp <= 0) { - tavern.sendChat(quest.completionChat('en')); - tavern.finishQuest(quest, function(){}); - tavern.save(cb); - _.assign(module.exports.tavernQuest, {extra: null}); - } else { - // Deal damage. Note a couple things here, str & def are calculated. If str/def are defined in the database, - // use those first - which allows us to update the boss on the go if things are too easy/hard. - if (!tavern.quest.extra) tavern.quest.extra = {}; - tavern.quest.progress.hp -= dmg / (tavern.quest.extra.def || quest.boss.def); - tavern.quest.progress.rage -= rage * (tavern.quest.extra.str || quest.boss.str); - if (tavern.quest.progress.rage >= quest.boss.rage.value) { - if (!tavern.quest.extra.worldDmg) tavern.quest.extra.worldDmg = {}; - var wd = tavern.quest.extra.worldDmg; - var scene = wd.market ? wd.stables ? wd.bailey ? false : 'bailey' : 'stables' : 'market'; // Be-Wilder attacks Alex, Matt, Bailey - if (!scene) { - tavern.sendChat('`'+quest.boss.name('en')+' tries to unleash '+quest.boss.rage.title('en')+', but is too tired.`'); - tavern.quest.progress.rage = 0 //quest.boss.rage.value; - } else { - tavern.sendChat(quest.boss.rage[scene]('en')); - tavern.quest.extra.worldDmg[scene] = true; - tavern.quest.extra.worldDmg.recent = scene; - tavern.markModified('quest.extra.worldDmg'); - tavern.quest.progress.rage = 0; - if (quest.boss.rage.healing) { - tavern.quest.progress.hp += (quest.boss.rage.healing * tavern.quest.progress.hp); - } - } - } - if (quest.boss.desperation && (tavern.quest.progress.hp < quest.boss.desperation.threshold) && !tavern.quest.extra.desperate) { - tavern.sendChat(quest.boss.desperation.text('en')); - tavern.quest.extra.desperate = true; - tavern.quest.extra.def = quest.boss.desperation.def; - tavern.quest.extra.str = quest.boss.desperation.str; - tavern.markModified('quest.extra'); - } - - _.assign(module.exports.tavernQuest, tavern.quest.toObject()); - tavern.save(cb); - } - } - ],function(err,res){ - if (err === true) return; // no current quest - if (err) return logging.error(err); - dmg = rage = null; - }) -} - -GroupSchema.statics.bossQuest = function(user, progress, cb) { - this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ - if (!isOnQuest(user,progress,group)) return cb(null); - var quest = shared.content.quests[group.quest.key]; - if (!progress || !quest) return cb(null); // FIXME why is this ever happening, progress should be defined at this point - var down = progress.down * quest.boss.str; // multiply by boss strength - - group.quest.progress.hp -= progress.up; - group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage.` `" + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`"); //TODO Create a party preferred language option so emits like this can be localized - - // If boss has Rage, increment Rage as well - if (quest.boss.rage) { - group.quest.progress.rage += Math.abs(down); - if (group.quest.progress.rage >= quest.boss.rage.value) { - group.sendChat(quest.boss.rage.effect('en')); - group.quest.progress.rage = 0; - if (quest.boss.rage.healing) group.quest.progress.hp += (group.quest.progress.hp * quest.boss.rage.healing); //TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage - if (group.quest.progress.hp > quest.boss.hp) group.quest.progress.hp = quest.boss.hp; - } - } - // Everyone takes damage - var series = [ - function(cb2){ - mongoose.models.User.update({_id:{$in: _.keys(group.quest.members)}}, {$inc:{'stats.hp':down, _v:1}}, {multi:true}, cb2); - } - ] - - // Boss slain, finish quest - if (group.quest.progress.hp <= 0) { - group.sendChat('`You defeated ' + quest.boss.name('en') + '! Questing party members receive the rewards of victory.`'); - // Participants: Grant rewards & achievements, finish quest - series.push(function(cb2){ - group.finishQuest(quest,cb2); - }); - } - - series.push(function(cb2){group.save(cb2)}); - async.series(series,cb); - }) -} - -// Remove user from this group -GroupSchema.methods.leave = function(user, keep, mainCb){ - if(!user) return mainCb(new Error('Missing user.')); - - if(keep && typeof keep === 'function'){ - mainCb = keep; - keep = null; - } - if(typeof keep !== 'string') keep = 'keep-all'; // can be also 'remove-all' - - var group = this; - - async.parallel([ - // Remove user from group challenges - function(cb){ - async.waterfall([ - // Find relevant challenges - function(cb2) { - Challenge.find({ - _id: {$in: user.challenges}, // Challenges I am in - group: group._id // that belong to the group I am leaving - }, cb2); - }, - - // Update each challenge - function(challenges, cb2) { - Challenge.update( - {_id: {$in: _.pluck(challenges, '_id')}}, - {$pull: {members: user._id}}, - {multi: true}, - function(err) { - cb2(err, challenges); // pass `challenges` above to cb - } - ); - }, - - // Unlink the challenge tasks from user - function(challenges, cb2) { - async.waterfall(challenges.map(function(chal) { - return function(cb3) { - var i = user.challenges.indexOf(chal._id) - if (~i) user.challenges.splice(i,1); - user.unlink({cid: chal._id, keep: keep}, cb3); - } - }), cb2); - } - ], cb); - }, - - // Update the group - function(cb){ - // If user is the last one in group and group is private, delete it - if(group.members.length === 1 && ( - group.type === 'party' || - (group.type === 'guild' && group.privacy === 'private') - )){ - group.remove(cb) - }else{ // otherwise just remove a member - var update = {$pull: {members: user._id}}; - - // If the leader is leaving (or if the leader previously left, and this wasn't accounted for) - var leader = group.leader; - - if(leader == user._id || !~group.members.indexOf(leader)){ - var seniorMember = _.find(group.members, function (m) {return m != user._id}); - - // could not exist in case of public guild with 1 member who is leaving - if(seniorMember){ - if (leader == user._id || !~group.members.indexOf(leader)) { - update['$set'] = update['$set'] || {}; - update['$set'].leader = seniorMember; - } - } - } - - update['$inc'] = {memberCount: -1}; - Group.update({_id: group._id}, update, cb); - } - } - ], function(err){ - if(err) return mainCb(err); - - firebase.removeUserFromGroup(group._id, user._id); - return mainCb(); - }); -}; - - -GroupSchema.methods.toJSON = function() { - var doc = this.toObject(); - - return doc; -}; - - -module.exports.schema = GroupSchema; -var Group = module.exports.model = mongoose.model("Group", GroupSchema); - -// initialize tavern if !exists (fresh installs) -Group.count({_id: 'habitrpg'}, function(err, ct){ - if (ct > 0) return; - - new Group({ - _id: 'habitrpg', - chat: [], - leader: '9', - name: 'HabitRPG', - type: 'guild', - privacy: 'public' - }).save(); -}); diff --git a/website/src/models/task.js b/website/src/models/task.js deleted file mode 100644 index 97a0e1e7c6..0000000000 --- a/website/src/models/task.js +++ /dev/null @@ -1,113 +0,0 @@ -// User.js -// ======= -// Defines the user data model (schema) for use via the API. - -// Dependencies -// ------------ -var mongoose = require("mongoose"); -var Schema = mongoose.Schema; -var shared = require('../../../common'); -var _ = require('lodash'); -var moment = require('moment'); - -// Task Schema -// ----------- - -var TaskSchema = { - //_id:{type: String,'default': helpers.uuid}, - id: {type: String,'default': shared.uuid}, - dateCreated: {type:Date, 'default':Date.now}, - text: String, - notes: {type: String, 'default': ''}, - tags: {type: Schema.Types.Mixed, 'default': {}}, //{ "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true }, - value: {type: Number, 'default': 0}, // redness - priority: {type: Number, 'default': '1'}, - attribute: {type: String, 'default': "str", enum: ['str','con','int','per']}, - challenge: { - id: {type: 'String', ref:'Challenge'}, - broken: String, // CHALLENGE_DELETED, TASK_DELETED, UNSUBSCRIBED, CHALLENGE_CLOSED - winner: String // user.profile.name - // group: {type: 'Strign', ref: 'Group'} // if we restore this, rename `id` above to `challenge` - }, - reminders: [{ - id: {type:String,'default':shared.uuid}, - startDate: Date, - time: Date - }] -}; - -var HabitSchema = new Schema( - _.defaults({ - type: {type:String, 'default': 'habit'}, - history: Array, // [{date:Date, value:Number}], // this causes major performance problems - up: {type: Boolean, 'default': true}, - down: {type: Boolean, 'default': true} - }, TaskSchema) - , { _id: false, minimize:false } -); - -var collapseChecklist = {type:Boolean, 'default':false}; -var checklist = [{ - completed:{type:Boolean,'default':false}, - text: String, - _id:false, - id: {type:String,'default':shared.uuid} -}]; - -var DailySchema = new Schema( - _.defaults({ - type: {type: String, 'default': 'daily'}, - frequency: {type: String, 'default': 'weekly', enum: ['daily', 'weekly']}, - everyX: {type: Number, 'default': 1}, // e.g. once every X weeks - startDate: {type: Date, 'default': moment().startOf('day').toDate()}, - history: Array, - completed: {type: Boolean, 'default': false}, - repeat: { // used only for 'weekly' frequency, - m: {type: Boolean, 'default': true}, - t: {type: Boolean, 'default': true}, - w: {type: Boolean, 'default': true}, - th: {type: Boolean, 'default': true}, - f: {type: Boolean, 'default': true}, - s: {type: Boolean, 'default': true}, - su: {type: Boolean, 'default': true} - }, - collapseChecklist:collapseChecklist, - checklist:checklist, - streak: {type: Number, 'default': 0} - }, TaskSchema) - , { _id: false, minimize:false } -) - -var TodoSchema = new Schema( - _.defaults({ - type: {type:String, 'default': 'todo'}, - completed: {type: Boolean, 'default': false}, - dateCompleted: Date, - date: String, // due date for todos // FIXME we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date - collapseChecklist:collapseChecklist, - checklist:checklist - }, TaskSchema) - , { _id: false, minimize:false } -); - -var RewardSchema = new Schema( - _.defaults({ - type: {type:String, 'default': 'reward'} - }, TaskSchema) - , { _id: false, minimize:false } -); - -/** - * Workaround for bug when _id & id were out of sync, we can remove this after challenges has been running for a while - */ -//_.each([HabitSchema, DailySchema, TodoSchema, RewardSchema], function(schema){ -// schema.post('init', function(doc){ -// if (!doc.id && doc._id) doc.id = doc._id; -// }) -//}) - -module.exports.TaskSchema = TaskSchema; -module.exports.HabitSchema = HabitSchema; -module.exports.DailySchema = DailySchema; -module.exports.TodoSchema = TodoSchema; -module.exports.RewardSchema = RewardSchema; diff --git a/website/src/models/user.js b/website/src/models/user.js deleted file mode 100644 index ac5f1ba0a2..0000000000 --- a/website/src/models/user.js +++ /dev/null @@ -1,697 +0,0 @@ -// User.js -// ======= -// Defines the user data model (schema) for use via the API. - -// Dependencies -// ------------ -var mongoose = require("mongoose"); -var Schema = mongoose.Schema; -var shared = require('../../../common'); -var _ = require('lodash'); -var TaskSchemas = require('./task'); -var Challenge = require('./challenge').model; -var moment = require('moment'); - -// User Schema -// ----------- - -var UserSchema = new Schema({ - // ### UUID and API Token - _id: { - type: String, - 'default': shared.uuid - }, - apiToken: { - type: String, - 'default': shared.uuid - }, - - // ### Mongoose Update Object - // We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which - // have been updated (http://goo.gl/gQLz41), but we want *every* update - _v: { type: Number, 'default': 0 }, - achievements: { - originalUser: Boolean, - habitSurveys: Number, - ultimateGearSets: Schema.Types.Mixed, - beastMaster: Boolean, - beastMasterCount: Number, - mountMaster: Boolean, - mountMasterCount: Number, - triadBingo: Boolean, - triadBingoCount: Number, - veteran: Boolean, - snowball: Number, - spookDust: Number, - shinySeed: Number, - seafoam: Number, - streak: Number, - challenges: Array, - quests: Schema.Types.Mixed, - rebirths: Number, - rebirthLevel: Number, - perfect: Number, - habitBirthdays: Number, - valentine: Number, - costumeContest: Boolean, // Superseded by costumeContests - nye: Number, - habiticaDays: Number, - greeting: Number, - thankyou: Number, - costumeContests: Number, - birthday: Number, - partyUp: Boolean, - partyOn: Boolean - }, - auth: { - blocked: Boolean, - facebook: Schema.Types.Mixed, - local: { - email: String, - hashed_password: String, - salt: String, - username: String, - lowerCaseUsername: String // Store a lowercase version of username to check for duplicates - }, - timestamps: { - created: {type: Date,'default': Date.now}, - loggedin: {type: Date,'default': Date.now} - } - }, - - backer: { - tier: Number, - npc: String, - tokensApplied: Boolean - }, - - contributor: { - level: Number, // 1-9, see https://trello.com/c/wkFzONhE/277-contributor-gear https://github.com/HabitRPG/habitrpg/issues/3801 - admin: Boolean, - sudo: Boolean, - text: String, // Artisan, Friend, Blacksmith, etc - contributions: String, // a markdown textarea to list their contributions + links - critical: String - }, - - balance: {type: Number, 'default':0}, - filters: {type: Schema.Types.Mixed, 'default': {}}, - - purchased: { - ads: {type: Boolean, 'default': false}, - skin: {type: Schema.Types.Mixed, 'default': {}}, // eg, {skeleton: true, pumpkin: true, eb052b: true} - hair: {type: Schema.Types.Mixed, 'default': {}}, - shirt: {type: Schema.Types.Mixed, 'default': {}}, - background: {type: Schema.Types.Mixed, 'default': {}}, - txnCount: {type: Number, 'default':0}, - mobileChat: Boolean, - plan: { - planId: String, - paymentMethod: String, //enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']} - customerId: String, // Billing Agreement Id in case of Amazon Payments - dateCreated: Date, - dateTerminated: Date, - dateUpdated: Date, - extraMonths: {type:Number, 'default':0}, - gemsBought: {type: Number, 'default': 0}, - mysteryItems: {type: Array, 'default': []}, - lastBillingDate: Date, // Used only for Amazon Payments to keep track of billing date - consecutive: { - count: {type:Number, 'default':0}, - offset: {type:Number, 'default':0}, // when gifted subs, offset++ for each month. offset-- each new-month (cron). count doesn't ++ until offset==0 - gemCapExtra: {type:Number, 'default':0}, - trinkets: {type:Number, 'default':0} - } - } - }, - - flags: { - customizationsNotification: {type: Boolean, 'default': false}, - showTour: {type: Boolean, 'default': true}, - tour: { - // -1 indicates "uninitiated", -2 means "complete", any other number is the current tour step (0-index) - intro: {type: Number, 'default': -1}, - classes: {type: Number, 'default': -1}, - stats: {type: Number, 'default': -1}, - tavern: {type: Number, 'default': -1}, - party: {type: Number, 'default': -1}, - guilds: {type: Number, 'default': -1}, - challenges: {type: Number, 'default': -1}, - market: {type: Number, 'default': -1}, - pets: {type: Number, 'default': -1}, - mounts: {type: Number, 'default': -1}, - hall: {type: Number, 'default': -1}, - equipment: {type: Number, 'default': -1} - }, - tutorial: { - common: { - habits: {type: Boolean, 'default': false}, - dailies: {type: Boolean, 'default': false}, - todos: {type: Boolean, 'default': false}, - rewards: {type: Boolean, 'default': false}, - party: {type: Boolean, 'default': false}, - pets: {type: Boolean, 'default': false}, - gems: {type: Boolean, 'default': false}, - skills: {type: Boolean, 'default': false}, - classes: {type: Boolean, 'default': false}, - tavern: {type: Boolean, 'default': false}, - equipment: {type: Boolean, 'default': false}, - items: {type: Boolean, 'default': false}, - }, - ios: { - addTask: {type: Boolean, 'default': false}, - editTask: {type: Boolean, 'default': false}, - deleteTask: {type: Boolean, 'default': false}, - filterTask: {type: Boolean, 'default': false}, - groupPets: {type: Boolean, 'default': false}, - inviteParty: {type: Boolean, 'default': false}, - } - }, - dropsEnabled: {type: Boolean, 'default': false}, - itemsEnabled: {type: Boolean, 'default': false}, - newStuff: {type: Boolean, 'default': false}, - rewrite: {type: Boolean, 'default': true}, - contributor: Boolean, - classSelected: {type: Boolean, 'default': false}, - mathUpdates: Boolean, - rebirthEnabled: {type: Boolean, 'default': false}, - levelDrops: {type:Schema.Types.Mixed, 'default':{}}, - chatRevoked: Boolean, - // Used to track the status of recapture emails sent to each user, - // can be 0 - no email sent - 1, 2, 3 or 4 - 4 means no more email will be sent to the user - recaptureEmailsPhase: {type: Number, 'default': 0}, - // Needed to track the tip to send inside the email - weeklyRecapEmailsPhase: {type: Number, 'default': 0}, - // Used to track when the next weekly recap should be sent - lastWeeklyRecap: {type: Date, 'default': Date.now}, - // Used to enable weekly recap emails as users login - lastWeeklyRecapDiscriminator: Boolean, - communityGuidelinesAccepted: {type: Boolean, 'default': false}, - cronCount: {type:Number, 'default':0}, - welcomed: {type: Boolean, 'default': false}, - armoireEnabled: {type: Boolean, 'default': false}, - armoireOpened: {type: Boolean, 'default': false}, - armoireEmpty: {type: Boolean, 'default': false}, - cardReceived: {type: Boolean, 'default': false}, - warnedLowHealth: {type: Boolean, 'default': false} - }, - history: { - exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined - todos: Array //[{data: Date, value: Number}] // big peformance issues if these are defined - }, - - invitations: { - guilds: {type: Array, 'default': []}, - party: Schema.Types.Mixed - }, - items: { - gear: { - owned: _.transform(shared.content.gear.flat, function(m,v,k){ - m[v.key] = {type: Boolean}; - if (v.key.match(/[armor|head|shield]_warrior_0/) || v.gearSet === 'glasses') - m[v.key]['default'] = true; - }), - - equipped: { - weapon: String, - armor: {type: String, 'default': 'armor_base_0'}, - head: {type: String, 'default': 'head_base_0'}, - shield: {type: String, 'default': 'shield_base_0'}, - back: String, - headAccessory: String, - eyewear: String, - body: String - }, - costume: { - weapon: String, - armor: {type: String, 'default': 'armor_base_0'}, - head: {type: String, 'default': 'head_base_0'}, - shield: {type: String, 'default': 'shield_base_0'}, - back: String, - headAccessory: String, - eyewear: String, - body: String - } - }, - - special:{ - snowball: {type: Number, 'default': 0}, - spookDust: {type: Number, 'default': 0}, - shinySeed: {type: Number, 'default': 0}, - seafoam: {type: Number, 'default': 0}, - valentine: Number, - valentineReceived: Array, // array of strings, by sender name - nye: Number, - nyeReceived: Array, - greeting: Number, - greetingReceived: Array, - thankyou: Number, - thankyouReceived: Array, - birthday: Number, - birthdayReceived: Array - }, - - // -------------- Animals ------------------- - // Complex bit here. The result looks like: - // pets: { - // 'Wolf-Desert': 0, // 0 means does not own - // 'PandaCub-Red': 10, // Number represents "Growth Points" - // etc... - // } - pets: - _.defaults( - // First transform to a 1D eggs/potions mapping - _.transform(shared.content.pets, function(m,v,k){ m[k] = Number; }), - // Then add additional pets (quest, backer, contributor, premium) - _.transform(shared.content.questPets, function(m,v,k){ m[k] = Number; }), - _.transform(shared.content.specialPets, function(m,v,k){ m[k] = Number; }), - _.transform(shared.content.premiumPets, function(m,v,k){ m[k] = Number; }) - ), - currentPet: String, // Cactus-Desert - - // eggs: { - // 'PandaCub': 0, // 0 indicates "doesn't own" - // 'Wolf': 5 // Number indicates "stacking" - // } - eggs: _.transform(shared.content.eggs, function(m,v,k){ m[k] = Number; }), - - // hatchingPotions: { - // 'Desert': 0, // 0 indicates "doesn't own" - // 'CottonCandyBlue': 5 // Number indicates "stacking" - // } - hatchingPotions: _.transform(shared.content.hatchingPotions, function(m,v,k){ m[k] = Number; }), - - // Food: { - // 'Watermelon': 0, // 0 indicates "doesn't own" - // 'RottenMeat': 5 // Number indicates "stacking" - // } - food: _.transform(shared.content.food, function(m,v,k){ m[k] = Number; }), - - // mounts: { - // 'Wolf-Desert': true, - // 'PandaCub-Red': false, - // etc... - // } - mounts: _.defaults( - // First transform to a 1D eggs/potions mapping - _.transform(shared.content.pets, function(m,v,k){ m[k] = Boolean; }), - // Then add quest and premium pets - _.transform(shared.content.questPets, function(m,v,k){ m[k] = Boolean; }), - _.transform(shared.content.premiumPets, function(m,v,k){ m[k] = Boolean; }), - // Then add additional mounts (backer, contributor) - _.transform(shared.content.specialMounts, function(m,v,k){ m[k] = Boolean; }) - ), - currentMount: String, - - // Quests: { - // 'boss_0': 0, // 0 indicates "doesn't own" - // 'collection_honey': 5 // Number indicates "stacking" - // } - quests: _.transform(shared.content.quests, function(m,v,k){ m[k] = Number; }), - - lastDrop: { - date: {type: Date, 'default': Date.now}, - count: {type: Number, 'default': 0} - } - }, - - lastCron: {type: Date, 'default': Date.now}, - - // {GROUP_ID: Boolean}, represents whether they have unseen chat messages - newMessages: {type: Schema.Types.Mixed, 'default': {}}, - - party: { - // id // FIXME can we use a populated doc instead of fetching party separate from user? - order: {type:String, 'default':'level'}, - orderAscending: {type:String, 'default':'ascending'}, - quest: { - key: String, - progress: { - up: {type: Number, 'default': 0}, - down: {type: Number, 'default': 0}, - collect: {type: Schema.Types.Mixed, 'default': {}} // {feather:1, ingot:2} - }, - completed: String, // When quest is done, we move it from key => completed, and it's a one-time flag (for modal) that they unset by clicking "ok" in browser - RSVPNeeded: {type: Boolean, 'default': false} // Set to true when invite is pending, set to false when quest invite is accepted or rejected, quest starts, or quest is cancelled - } - }, - preferences: { - dayStart: {type:Number, 'default': 0, min: 0, max: 23}, - size: {type:String, enum: ['broad','slim'], 'default': 'slim'}, - hair: { - color: {type: String, 'default': 'red'}, - base: {type: Number, 'default': 3}, - bangs: {type: Number, 'default': 1}, - beard: {type: Number, 'default': 0}, - mustache: {type: Number, 'default': 0}, - flower: {type: Number, 'default': 1} - }, - chair: {type: String, 'default': 'none'}, - hideHeader: {type:Boolean, 'default':false}, - skin: {type:String, 'default':'915533'}, - shirt: {type: String, 'default': 'blue'}, - timezoneOffset: {type: Number, 'default': 0}, - timezoneOffsetAtLastCron: Number, - sound: {type:String, 'default':'off', enum: ['off', 'danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme']}, - language: String, - automaticAllocation: Boolean, - allocationMode: {type:String, enum: ['flat','classbased','taskbased'], 'default': 'flat'}, - autoEquip: {type: Boolean, 'default': true}, - costume: Boolean, - dateFormat: {type: String, enum:['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], 'default': 'MM/dd/yyyy'}, - sleep: {type: Boolean, 'default': false}, - stickyHeader: {type: Boolean, 'default': true}, - disableClasses: {type: Boolean, 'default': false}, - newTaskEdit: {type: Boolean, 'default': false}, - dailyDueDefaultView: {type: Boolean, 'default': false}, - tagsCollapsed: {type: Boolean, 'default': false}, - advancedCollapsed: {type: Boolean, 'default': false}, - toolbarCollapsed: {type:Boolean, 'default':false}, - reverseChatOrder: {type:Boolean, 'default':false}, - background: String, - displayInviteToPartyWhenPartyIs1: { type:Boolean, 'default':true}, - webhooks: {type: Schema.Types.Mixed, 'default': {}}, - // For this fields make sure to use strict comparison when searching for falsey values (=== false) - // As users who didn't login after these were introduced may have them undefined/null - emailNotifications: { - unsubscribeFromAll: {type: Boolean, 'default': false}, - newPM: {type: Boolean, 'default': true}, - kickedGroup: {type: Boolean, 'default': true}, - wonChallenge: {type: Boolean, 'default': true}, - giftedGems: {type: Boolean, 'default': true}, - giftedSubscription: {type: Boolean, 'default': true}, - invitedParty: {type: Boolean, 'default': true}, - invitedGuild: {type: Boolean, 'default': true}, - questStarted: {type: Boolean, 'default': true}, - invitedQuest: {type: Boolean, 'default': true}, - //remindersToLogin: {type: Boolean, 'default': true}, - // Those importantAnnouncements are in fact the recapture emails - importantAnnouncements: {type: Boolean, 'default': true}, - weeklyRecaps: {type: Boolean, 'default': true} - }, - suppressModals: { - levelUp: {type: Boolean, 'default': false}, - hatchPet: {type: Boolean, 'default': false}, - raisePet: {type: Boolean, 'default': false}, - streak: {type: Boolean, 'default': false} - }, - improvementCategories: { - type: Array, - validate: (categories) => { - const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity']; - let isValidCategory = categories.every(category => validCategories.indexOf(category) !== -1); - return isValidCategory; - }} - }, - profile: { - blurb: String, - imageUrl: String, - name: String - }, - stats: { - hp: {type: Number, 'default': shared.maxHealth}, - mp: {type: Number, 'default': 10}, - exp: {type: Number, 'default': 0}, - gp: {type: Number, 'default': 0}, - lvl: {type: Number, 'default': 1}, - - // Class System - 'class': {type: String, enum: ['warrior','rogue','wizard','healer'], 'default': 'warrior'}, - points: {type: Number, 'default': 0}, - str: {type: Number, 'default': 0}, - con: {type: Number, 'default': 0}, - int: {type: Number, 'default': 0}, - per: {type: Number, 'default': 0}, - buffs: { - str: {type: Number, 'default': 0}, - int: {type: Number, 'default': 0}, - per: {type: Number, 'default': 0}, - con: {type: Number, 'default': 0}, - stealth: {type: Number, 'default': 0}, - streaks: {type: Boolean, 'default': false}, - snowball: {type: Boolean, 'default': false}, - spookDust: {type: Boolean, 'default': false}, - shinySeed: {type: Boolean, 'default': false}, - seafoam: {type: Boolean, 'default': false} - }, - training: { - int: {type: Number, 'default': 0}, - per: {type: Number, 'default': 0}, - str: {type: Number, 'default': 0}, - con: {type: Number, 'default': 0} - } - }, - - tags: {type: [{ - _id: false, - id: { type: String, 'default': shared.uuid }, - name: String, - challenge: String - }]}, - - challenges: [{type: 'String', ref:'Challenge'}], - - inbox: { - newMessages: {type:Number, 'default':0}, - blocks: {type:Array, 'default':[]}, - messages: {type:Schema.Types.Mixed, 'default':{}}, //reflist - optOut: {type:Boolean, 'default':false} - }, - - habits: {type:[TaskSchemas.HabitSchema]}, - dailys: {type:[TaskSchemas.DailySchema]}, - todos: {type:[TaskSchemas.TodoSchema]}, - rewards: {type:[TaskSchemas.RewardSchema]}, - - extra: Schema.Types.Mixed, - - pushDevices: {type: [{ - regId: {type: String}, - type: {type: String} - }],'default': []} - -}, { - strict: true, - minimize: false // So empty objects are returned -}); - -UserSchema.methods.deleteTask = function(tid) { - this.ops.deleteTask({params:{id:tid}},function(){}); // TODO remove this whole method, since it just proxies, and change all references to this method -} - -UserSchema.methods.toJSON = function() { - var doc = this.toObject(); - doc.id = doc._id; - - // FIXME? Is this a reference to `doc.filters` or just disabled code? Remove? - doc.filters = {}; - doc._tmp = this._tmp; // be sure to send down drop notifs - - return doc; -}; - -//UserSchema.virtual('tasks').get(function () { -// var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); -// var tasks = _.object(_.pluck(tasks,'id'), tasks); -// return tasks; -//}); - -UserSchema.post('init', function(doc){ - shared.wrap(doc); -}) - -UserSchema.pre('save', function(next) { - - // Populate new users with default content - if (this.isNew){ - _populateDefaultsForNewUser(this); - } - - //this.markModified('tasks'); - if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) { - this.preferences.dayStart = 0; - } - - if (!this.profile.name) { - var fb = this.auth.facebook; - this.profile.name = - (this.auth.local && this.auth.local.username) || - (fb && (fb.displayName || fb.name || fb.username || (fb.first_name && fb.first_name + ' ' + fb.last_name))) || - 'Anonymous'; - } - - // Determines if Beast Master should be awarded - var beastMasterProgress = shared.count.beastMasterProgress(this.items.pets); - if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) { - this.achievements.beastMaster = true; - } - - // Determines if Mount Master should be awarded - var mountMasterProgress = shared.count.mountMasterProgress(this.items.mounts); - - if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) { - this.achievements.mountMaster = true; - } - - // Determines if Triad Bingo should be awarded - - var dropPetCount = shared.count.dropPetsCurrentlyOwned(this.items.pets); - var qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90; - - if (qualifiesForTriad || this.achievements.triadBingoCount > 0) { - this.achievements.triadBingo = true; - } - - // Enable weekly recap emails for old users who sign in - if(this.flags.lastWeeklyRecapDiscriminator){ - // Enable weekly recap emails in 24 hours - this.flags.lastWeeklyRecap = moment().subtract(6, 'days').toDate(); - // Unset the field so this is run only once - this.flags.lastWeeklyRecapDiscriminator = undefined; - } - - // EXAMPLE CODE for allowing all existing and new players to be - // automatically granted an item during a certain time period: - // if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01')) - // this.items.pets['JackOLantern-Base'] = 5; - - //our own version incrementer - if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0; - this._v++; - - next(); -}); - -UserSchema.methods.unlink = function(options, cb) { - var cid = options.cid, keep = options.keep, tid = options.tid; - if (!cid) { - return cb("Could not remove challenge tasks. Please delete them manually."); - } - var self = this; - switch (keep) { - case 'keep': - self.tasks[tid].challenge = {}; - break; - case 'remove': - self.deleteTask(tid); - break; - case 'keep-all': - _.each(self.tasks, function(t){ - if (t.challenge && t.challenge.id == cid) { - t.challenge = {}; - } - }); - break; - case 'remove-all': - _.each(self.tasks, function(t){ - if (t.challenge && t.challenge.id == cid) { - self.deleteTask(t.id); - } - }) - break; - } - self.markModified('habits'); - self.markModified('dailys'); - self.markModified('todos'); - self.markModified('rewards'); - self.save(cb); -} - -function _populateDefaultsForNewUser(user) { - var taskTypes; - - if (user.registeredThrough === "habitica-web" || user.registeredThrough === "habitica-android") { - taskTypes = ['habits', 'dailys', 'todos', 'rewards', 'tags']; - - var tutorialCommonSections = [ - 'habits', - 'dailies', - 'todos', - 'rewards', - 'party', - 'pets', - 'gems', - 'skills', - 'classes', - 'tavern', - 'equipment', - 'items', - 'inviteParty', - ]; - - _.each(tutorialCommonSections, function(section) { - user.flags.tutorial.common[section] = true; - }); - } else { - taskTypes = ['todos', 'tags'] - - user.flags.showTour = false; - - var tourSections = [ - 'showTour', - 'intro', - 'classes', - 'stats', - 'tavern', - 'party', - 'guilds', - 'challenges', - 'market', - 'pets', - 'mounts', - 'hall', - 'equipment', - ]; - - _.each(tourSections, function(section) { - user.flags.tour[section] = -2; - }); - } - - _populateDefaultTasks(user, taskTypes); -} - -function _populateDefaultTasks (user, taskTypes) { - _.each(taskTypes, function(taskType){ - user[taskType] = _.map(shared.content.userDefaults[taskType], function(task){ - var newTask = _.cloneDeep(task); - - // Render task's text and notes in user's language - if(taskType === 'tags'){ - // tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here - newTask.id = shared.uuid(); - newTask.name = newTask.name(user.preferences.language); - }else{ - newTask.text = newTask.text(user.preferences.language); - if(newTask.notes) { - newTask.notes = newTask.notes(user.preferences.language); - } - - if(newTask.checklist){ - newTask.checklist = _.map(newTask.checklist, function(checklistItem){ - checklistItem.text = checklistItem.text(user.preferences.language); - return checklistItem; - }); - } - } - - return newTask; - }); - }); -} - -module.exports.schema = UserSchema; -module.exports.model = mongoose.model("User", UserSchema); -// Initially export an empty object so external requires will get -// the right object by reference when it's defined later -// Otherwise it would remain undefined if requested before the query executes -module.exports.mods = []; - -mongoose.model("User") - .find({'contributor.admin':true}) - .sort('-contributor.level -backer.npc profile.name') - .select('profile contributor backer') - .exec(function(err,mods){ - // Using push to maintain the reference to mods - module.exports.mods.push.apply(module.exports.mods, mods); -}); diff --git a/website/src/routes/api-v1.js b/website/src/routes/api-v1.js deleted file mode 100644 index b3408f523d..0000000000 --- a/website/src/routes/api-v1.js +++ /dev/null @@ -1,173 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var _ = require('lodash'); -var async = require('async'); -var icalendar = require('icalendar'); -var api = require('./../controllers/api-v2/user'); -var auth = require('./../controllers/api-v2/auth'); -var logging = require('./../libs/logging'); -var i18n = require('./../libs/i18n'); -var forceRefresh = require('../middlewares/forceRefresh').middleware; - -/* ---------- Deprecated API ------------*/ - -var initDeprecated = function(req, res, next) { - req.headers['x-api-user'] = req.params.uid; - req.headers['x-api-key'] = req.body.apiToken; - return next(); -}; - -router.post('/v1/users/:uid/tasks/:taskId/:direction', initDeprecated, auth.auth, i18n.getUserLanguage, api.score); - -// FIXME add this back in -router.get('/v1/users/:uid/calendar.ics', i18n.getUserLanguage, function(req, res, next) { - return next() //disable for now - - var apiToken, model, query, uid; - uid = req.params.uid; - apiToken = req.query.apiToken; - model = req.getModel(); - query = model.query('users').withIdAndToken(uid, apiToken); - return query.fetch(function(err, result) { - var formattedIcal, ical, tasks, tasksWithDates; - if (err) { - return res.send(500, err); - } - tasks = result.get('tasks'); - /* tasks = result[0].tasks*/ - - tasksWithDates = _.filter(tasks, function(task) { - return !!task.date; - }); - if (_.isEmpty(tasksWithDates)) { - return res.send(500, "No events found"); - } - ical = new icalendar.iCalendar(); - ical.addProperty('NAME', 'HabitRPG'); - _.each(tasksWithDates, function(task) { - var d, event; - event = new icalendar.VEvent(task.id); - event.setSummary(task.text); - d = new Date(task.date); - d.date_only = true; - event.setDate(d); - ical.addComponent(event); - return true; - }); - res.type('text/calendar'); - formattedIcal = ical.toString().replace(/DTSTART\:/g, 'DTSTART;VALUE=DATE:'); - return res.send(200, formattedIcal); - }); -}); - -/* - ------------------------------------------------------------------------ - Batch Update - This is super-deprecated, and will be removed once apiv2 is running against mobile for a while - ------------------------------------------------------------------------ - */ -var batchUpdate = function(req, res, next) { - var user = res.locals.user; - var oldSend = res.send; - var oldJson = res.json; - var performAction = function(action, cb) { - - // req.body=action.data; delete action.data; _.defaults(req.params, action) - // Would require changing action.dir on mobile app - req.params.id = action.data && action.data.id; - req.params.direction = action.dir; - req.params.type = action.type; - req.body = action.data; - res.send = res.json = function(code, data) { - if (_.isNumber(code) && code >= 400) { - logging.error({ - code: code, - data: data - }); - } - //FIXME send error messages down - return cb(); - }; - switch (action.op) { - case "score": - api.score(req, res); - break; - case "addTask": - api.addTask(req, res); - break; - case "delTask": - api.deleteTask(req, res); - break; - case "revive": - api.revive(req, res); - break; - default: - cb(); - break; - } - }; - - // Setup the array of functions we're going to call in parallel with async - var actions = _.transform(req.body || [], function(result, action) { - if (!_.isEmpty(action)) { - result.push(function(cb) { - performAction(action, cb); - }); - } - }); - - // call all the operations, then return the user object to the requester - async.series(actions, function(err) { - res.json = oldJson; - res.send = oldSend; - if (err) return res.json(500, {err: err}); - var response = user.toJSON(); - response.wasModified = res.locals.wasModified; - if (response._tmp && response._tmp.drop){ - res.json(200, {_tmp: {drop: response._tmp.drop}, _v: response._v}); - }else if(response.wasModified){ - res.json(200, response); - }else{ - res.json(200, {_v: response._v}); - } - }); -}; - -/* - ------------------------------------------------------------------------ - API v1 Routes - ------------------------------------------------------------------------ - */ - - -var cron = api.cron; - -router.get('/status', i18n.getUserLanguage, function(req, res) { - return res.json({ - status: 'up' - }); -}); - -// Scoring -router.post('/user/task/:id/:direction', auth.auth, i18n.getUserLanguage, cron, api.score); -router.post('/user/tasks/:id/:direction', auth.auth, i18n.getUserLanguage, cron, api.score); - -// Tasks -router.get('/user/tasks', auth.auth, i18n.getUserLanguage, cron, api.getTasks); -router.get('/user/task/:id', auth.auth, i18n.getUserLanguage, cron, api.getTask); -router.delete('/user/task/:id', auth.auth, i18n.getUserLanguage, cron, api.deleteTask); -router.post('/user/task', auth.auth, i18n.getUserLanguage, cron, api.addTask); - -// User -router.get('/user', auth.auth, i18n.getUserLanguage, cron, api.getUser); -router.post('/user/revive', auth.auth, i18n.getUserLanguage, cron, api.revive); -router.post('/user/batch-update', forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate); - -function deprecated(req, res) { - res.json(404, {err:'API v1 is no longer supported, please use API v2 instead (https://github.com/HabitRPG/habitrpg/blob/develop/API.md)'}); -} -router.get('*', i18n.getUserLanguage, deprecated); -router.post('*', i18n.getUserLanguage, deprecated); -router.put('*', i18n.getUserLanguage, deprecated); - -module.exports = router; diff --git a/website/src/routes/api-v2/auth.js b/website/src/routes/api-v2/auth.js deleted file mode 100644 index 62e506d097..0000000000 --- a/website/src/routes/api-v2/auth.js +++ /dev/null @@ -1,21 +0,0 @@ -var auth = require('../../controllers/api-v2/auth'); -var express = require('express'); -var i18n = require('../../libs/i18n'); -var router = express.Router(); - -/* auth.auth*/ -auth.setupPassport(router); //FIXME make this consistent with the others -router.post('/api/v2/register', i18n.getUserLanguage, auth.registerUser); -router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal); -router.post('/api/v2/user/auth/social', i18n.getUserLanguage, auth.loginSocial); -router.delete('/api/v2/user/auth/social', i18n.getUserLanguage, auth.auth, auth.deleteSocial); -router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword); -router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword); -router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername); -router.post('/api/v2/user/change-email', i18n.getUserLanguage, auth.auth, auth.changeEmail); -router.post('/api/v2/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken); - -router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); -router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); -router.post('/api/v1/user/auth/social', i18n.getUserLanguage, auth.loginSocial); -module.exports = router; diff --git a/website/src/routes/api-v2/coupon.js b/website/src/routes/api-v2/coupon.js deleted file mode 100644 index 9057758d3a..0000000000 --- a/website/src/routes/api-v2/coupon.js +++ /dev/null @@ -1,12 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../../controllers/api-v2/auth'); -var coupon = require('../../controllers/api-v2/coupon'); -var i18n = require('../../libs/i18n'); - -router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); -router.post('/api/v2/coupons/generate/:event', auth.auth, i18n.getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); -router.post('/api/v2/user/coupon/:code', auth.auth, i18n.getUserLanguage, coupon.enterCode); - -module.exports = router; diff --git a/website/src/routes/api-v2/unsubscription.js b/website/src/routes/api-v2/unsubscription.js deleted file mode 100644 index 882fb10392..0000000000 --- a/website/src/routes/api-v2/unsubscription.js +++ /dev/null @@ -1,8 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var i18n = require('../../libs/i18n'); -var unsubscription = require('../../controllers/api-v2/unsubscription'); - -router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe); - -module.exports = router; diff --git a/website/src/routes/dataexport.js b/website/src/routes/dataexport.js deleted file mode 100644 index 9dd8511e3d..0000000000 --- a/website/src/routes/dataexport.js +++ /dev/null @@ -1,16 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var dataexport = require('../controllers/dataexport'); -var auth = require('../controllers/api-v2/auth'); -var nconf = require('nconf'); -var i18n = require('../libs/i18n'); -var locals = require('../middlewares/locals'); - -/* Data export */ -router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes -router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml); -router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json); -router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage); -router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage); - -module.exports = router; diff --git a/website/src/routes/payments.js b/website/src/routes/payments.js deleted file mode 100644 index c385c835c7..0000000000 --- a/website/src/routes/payments.js +++ /dev/null @@ -1,31 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../controllers/api-v2/auth'); -var payments = require('../controllers/payments'); -var i18n = require('../libs/i18n'); - -router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout); -router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess); -router.get('/paypal/subscribe', auth.authWithUrl, i18n.getUserLanguage, payments.paypalSubscribe); -router.get('/paypal/subscribe/success', i18n.getUserLanguage, payments.paypalSubscribeSuccess); -router.get('/paypal/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.paypalSubscribeCancel); -router.post('/paypal/ipn', i18n.getUserLanguage, payments.paypalIPN); // misc ipn handling - -router.post('/stripe/checkout', auth.auth, i18n.getUserLanguage, payments.stripeCheckout); -router.post('/stripe/subscribe/edit', auth.auth, i18n.getUserLanguage, payments.stripeSubscribeEdit); -//router.get('/stripe/subscribe', auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead -router.get('/stripe/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); - -router.post('/amazon/verifyAccessToken', auth.auth, i18n.getUserLanguage, payments.amazonVerifyAccessToken); -router.post('/amazon/createOrderReferenceId', auth.auth, i18n.getUserLanguage, payments.amazonCreateOrderReferenceId); -router.post('/amazon/checkout', auth.auth, i18n.getUserLanguage, payments.amazonCheckout); -router.post('/amazon/subscribe', auth.auth, i18n.getUserLanguage, payments.amazonSubscribe); -router.get('/amazon/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.amazonSubscribeCancel); - -router.post('/iap/android/verify', auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify); -router.post('/iap/ios/verify', auth.auth, /*i18n.getUserLanguage, */ payments.iapIosVerify); - -router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, i18n.getUserLanguage, */ payments.validCoupon); - -module.exports = router; diff --git a/website/src/server.js b/website/src/server.js deleted file mode 100644 index 7271d85579..0000000000 --- a/website/src/server.js +++ /dev/null @@ -1,177 +0,0 @@ -if (process.env.NODE_ENV !== 'production') { - require('babel-register'); -} -// Only do the minimal amount of work before forking just in case of a dyno restart -var cluster = require("cluster"); -var _ = require('lodash'); -var nconf = require('nconf'); -var utils = require('./libs/utils'); -utils.setupConfig(); -var logging = require('./libs/logging'); -var isProd = nconf.get('NODE_ENV') === 'production'; -var isDev = nconf.get('NODE_ENV') === 'development'; -var DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); -var cores = +nconf.get("WEB_CONCURRENCY") || 0; -var moment = require('moment'); - -if (cores!==0 && cluster.isMaster && (isDev || isProd)) { - // Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production) - _.times(cores, function () { - cluster.fork(); - }); - - cluster.on('disconnect', function(worker, code, signal) { - var w = cluster.fork(); // replace the dead worker - logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); - }); - -} else { - var express = require("express"); - var bodyParser = require('body-parser'); - var session = require('cookie-session'); - var logger = require('morgan'); - var compression = require('compression'); - var favicon = require('serve-favicon'); - - var BODY_PARSER_LIMIT = '1mb'; - - var http = require("http"); - var path = require("path"); - var swagger = require("swagger-node-express"); - var autoinc = require('mongoose-id-autoinc'); - var shared = require('../../common'); - - // Setup translations - var i18n = require('./libs/i18n'); - - var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; - var app = express(); - var server = http.createServer(); - - // ------------ MongoDB Configuration ------------ - var mongoose = require('mongoose'); - var mongooseOptions = !isProd ? {} : { - replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, - server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } } - }; - var db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, function(err) { - if (err) throw err; - logging.info('Connected with Mongoose'); - }); - autoinc.init(db); - - require('./libs/firebase'); - - // load schemas & models - require('./models/challenge'); - require('./models/group'); - require('./models/user'); - - // ------------ Passport Configuration ------------ - var passport = require('passport') - var util = require('util') - var FacebookStrategy = require('passport-facebook').Strategy; - // Passport session setup. - // To support persistent login sessions, Passport needs to be able to - // serialize users into and deserialize users out of the session. Typically, - // this will be as simple as storing the user ID when serializing, and finding - // the user by ID when deserializing. However, since this example does not - // have a database of user records, the complete Facebook profile is serialized - // and deserialized. - passport.serializeUser(function(user, done) { - done(null, user); - }); - - passport.deserializeUser(function(obj, done) { - done(null, obj); - }); - - // FIXME - // This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile) - // The proper fix would be to move to a general OAuth module simply to verify accessTokens - passport.use(new FacebookStrategy({ - clientID: nconf.get("FACEBOOK_KEY"), - clientSecret: nconf.get("FACEBOOK_SECRET"), - //callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" - }, - function(accessToken, refreshToken, profile, done) { - done(null, profile); - } - )); - - // ------------ Server Configuration ------------ - var publicDir = path.join(__dirname, "/../public"); - - app.set("port", nconf.get('PORT')); - - // Setup two different Express apps, one that matches everything except '/api/v3' - // and the other for /api/v3 routes, so we can keep the old an new api versions completely separate - // not sharing a single middleware if we don't want to - var oldApp = express(); // api v1 and v2, and not scoped routes - var newApp = express(); // api v3 - - // Matches all request except the ones going to /api/v3/** - app.all(/^(?!\/api\/v3).+/i, oldApp); - // Matches all requests going to /api/v3 - app.all('/api/v3', newApp); - - require('./middlewares/apiThrottle')(oldApp); - oldApp.use(require('./middlewares/domain')(server,mongoose)); - if (!isProd && !DISABLE_LOGGING) oldApp.use(logger("dev")); - oldApp.use(compression()); - oldApp.set("views", __dirname + "/../views"); - oldApp.set("view engine", "jade"); - oldApp.use(favicon(publicDir + '/favicon.ico')); - oldApp.use(require('./middlewares/cors')); - - var redirects = require('./middlewares/redirects'); - oldApp.use(redirects.forceHabitica); - oldApp.use(redirects.forceSSL); - oldApp.use(bodyParser.urlencoded({ - extended: true, - limit: BODY_PARSER_LIMIT, - })); - oldApp.use(bodyParser.json({ - limit: BODY_PARSER_LIMIT, - })); - oldApp.use(require('method-override')()); - oldApp.use(session({ - name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies - secret: nconf.get('SESSION_SECRET'), - httpOnly: false, - maxAge: TWO_WEEKS - })); - - // Initialize Passport! Also use passport.session() middleware, to support - // persistent login sessions (recommended). - oldApp.use(passport.initialize()); - oldApp.use(passport.session()); - - var maxAge = isProd ? 31536000000 : 0; - oldApp.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); - oldApp.use('/common/dist', express['static'](publicDir + "/../../common/dist", { maxAge: maxAge })); - oldApp.use('/common/audio', express['static'](publicDir + "/../../common/audio", { maxAge: maxAge })); - oldApp.use('/common/script/public', express['static'](publicDir + "/../../common/script/public", { maxAge: maxAge })); - oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge })); - oldApp.use(express['static'](publicDir)); - - // Custom Directives - oldApp.use('/', require('./routes/pages')); - oldApp.use('/', require('./routes/payments')); - oldApp.use('/', require('./routes/api-v2/auth')); - oldApp.use('/', require('./routes/api-v2/coupon')); - oldApp.use('/', require('./routes/api-v2/unsubscription')); - var v2 = express(); - oldApp.use('/api/v2', v2); - oldApp.use('/api/', require('./routes/api-v1')); - oldApp.use('/export', require('./routes/dataexport')); - require('./routes/api-v2/swagger')(swagger, v2); - oldApp.use(require('./middlewares/errorHandler')); - - server.on('request', app); - server.listen(app.get("port"), function() { - return logging.info("Express server listening on port " + app.get("port")); - }); - - module.exports = server; -} diff --git a/website/views/avatar-static.jade b/website/views/avatar-static.jade index 9a3532f726..58e11ae31a 100644 --- a/website/views/avatar-static.jade +++ b/website/views/avatar-static.jade @@ -9,7 +9,7 @@ html(ng-app="habitrpg") meta(name='apple-mobile-web-app-capable', content='yes') // .slice(0).push('user') is to clone the array, - // to be surethat `user` is never avalaible to other requests' env + // to be surethat `user` is never available to other requests' env // TODO does it need only `user` in clientVars, not the others? - clientVars = env.clientVars.slice(0); diff --git a/website/views/main/filters.jade b/website/views/main/filters.jade index b72d424364..40e11817a8 100644 --- a/website/views/main/filters.jade +++ b/website/views/main/filters.jade @@ -31,7 +31,7 @@ li.filters-edit(ng-class='{active: user.filters[tag.id]}', ng-repeat='tag in user.tags', bindonce='user.tags') form.hrpg-input-group input(type='text', ng-model='tag.name', ui-keyup="{13: 'saveOrEdit()'}") - button(type='button', ng-click='user.ops.deleteTag({params:{id:tag.id}})') + button(type='button', ng-click='User.deleteTag({params:{id:tag.id}})') span.glyphicon.glyphicon-trash ul(ng-if='!_editing', hrpg-sort-tags) li.filters-tags(ng-class='{active: user.filters[tag.id], challenge: tag.challenge}', ng-repeat='tag in user.tags', bindonce='user.tags') diff --git a/website/views/options/inventory/drops.jade b/website/views/options/inventory/drops.jade index 134e9ade50..322a5083f5 100644 --- a/website/views/options/inventory/drops.jade +++ b/website/views/options/inventory/drops.jade @@ -57,7 +57,7 @@ ng-click='castStart(Content.special.#{k})') .badge.badge-info.stack-count {{user.items.special.#{k}}} +specialItem('snowball') - +specialItem('spookDust') + +specialItem('spookySparkles') +specialItem('shinySeed') +specialItem('seafoam') @@ -65,7 +65,7 @@ button.customize-option(class='inventory_present inventory_present_{{moment().format("MM")}}', popover=env.t('subscriberItemText'), popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', - ng-click="user.ops.openMysteryItem({})") + ng-click="User.openMysteryItem({})") .badge.badge-info.stack-count {{user.purchased.plan.mysteryItems.length}} div(ng-if='user.purchased.plan.consecutive.trinkets') @@ -199,7 +199,7 @@ button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'), popover-trigger='mouseenter', popover-placement='top', popover-append-to-body='true', - ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})') + ng-click='User.purchase({params:{type:"gems",key:"gem"}})') span.Pet_Currency_Gem.inline-gems .badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}} p diff --git a/website/views/options/inventory/time-travelers.jade b/website/views/options/inventory/time-travelers.jade index 66fc530bd0..be0c3ea7dd 100644 --- a/website/views/options/inventory/time-travelers.jade +++ b/website/views/options/inventory/time-travelers.jade @@ -35,4 +35,4 @@ popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', - ng-click='user.ops.buyMysterySet({params:{key:set.key}})') + ng-click='User.buyMysterySet({params:{key:set.key}})') diff --git a/website/views/options/profile.jade b/website/views/options/profile.jade index d9af640ae8..036d0cf529 100644 --- a/website/views/options/profile.jade +++ b/website/views/options/profile.jade @@ -219,7 +219,7 @@ mixin profileStats input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})') span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation') div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)') - a.btn.btn-primary.btn-xs(ng-click='user.ops.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) + a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) span.glyphicon.glyphicon-download |  =env.t('distributePoints') @@ -227,7 +227,7 @@ mixin profileStats div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') - button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') + button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') hr(ng-if='user.preferences.disableClasses') include ../shared/profiles/achievements diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade index c2608126b3..34c4dff0ca 100644 --- a/website/views/options/settings.jade +++ b/website/views/options/settings.jade @@ -32,7 +32,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') .form-horizontal h5=env.t('language') - select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()') + select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in availableLanguages', ng-change='changeLanguage()') small !=env.t('americanEnglishGovern') br @@ -92,7 +92,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey') button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal') - button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') + button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') hr @@ -133,11 +133,11 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') .panel-body div(ng-if='user.auth.facebook.id') button.btn.btn-primary(disabled='disabled', ng-if='!user.auth.local.username')=env.t('registeredWithFb') - button.btn.btn-danger(ng-click='http("delete","/api/v2/user/auth/social",null,"detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook') + button.btn.btn-danger(ng-click='http("delete", "/api/v3/user/auth/social/facebook", null, "detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook') hr div(ng-if='!user.auth.local.username') p=env.t('addLocalAuth') - form(ng-submit='http("post","/api/v2/register",localAuth,"addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate) + form(ng-submit='http("post", "/api/v3/user/auth/local/register", localAuth, "addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate) //-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted')=env.t('fillAll') .form-group input.form-control(type='text', placeholder=env.t('username'), ng-model='localAuth.username', required) @@ -175,7 +175,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') h5=env.t('changeEmail') form(ng-submit='changeUser("email", emailUpdates)', ng-show='user.auth.local', name='changeEmail', novalidate) .form-group - input.form-control(type='text', placeholder=env.t('newEmail'), ng-model='emailUpdates.email', required) + input.form-control(type='text', placeholder=env.t('newEmail'), ng-model='emailUpdates.newEmail', required) .form-group input.form-control(type='password', placeholder=env.t('password'), ng-model='emailUpdates.password', required) input.btn.btn-default(type='submit', ng-disabled='changeEmail.$invalid', value=env.t('submit')) @@ -218,7 +218,7 @@ script(type='text/ng-template', id='partials/options.settings.promo.html') input.form-control(type='number',ng-model='_codes.count',placeholder="Number of codes to generate (eg, 250)") .form-group button.btn.btn-primary(type='submit')=env.t('generate') - a.btn.btn-default(href='/api/v2/coupons?_id={{user._id}}&apiToken={{user.apiToken}}')=env.t('getCodes') + a.btn.btn-default(href='/api/v3/coupons?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}')=env.t('getCodes') script(type='text/ng-template', id='partials/options.settings.api.html') .container-fluid @@ -229,9 +229,9 @@ script(type='text/ng-template', id='partials/options.settings.api.html') h6=env.t('userId') pre.prettyprint {{user.id}} h6=env.t('APIToken') - pre.prettyprint {{user.apiToken}} + pre.prettyprint {{User.settings.auth.apiToken}} h6=env.t('qrCode') - img.img-rendering-auto(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{user.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode') + img.img-rendering-auto(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{User.settings.auth.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode') br h3=env.t('thirdPartyApps') ul @@ -389,7 +389,7 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') input.form-control(type='text', ng-model='_subscription.coupon', placeholder= env.t('couponPlaceholder')) .form-group button.pull-right.btn.btn-small(type='button',ng-click='applyCoupon(_subscription.coupon)')= env.t("apply") - + div(ng-if='user.purchased.plan.customerId') .btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard') .btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub') @@ -400,9 +400,8 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') .col-xs-4 a.purchase.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key')= env.t('card') .col-xs-4 - a.purchase(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') + a.purchase(href='/paypal/subscribe?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt=env.t('paypal')) .col-xs-4 a.purchase(ng-click="Payments.amazonPayments.init({type: 'subscription', subscription:_subscription.key, coupon:_subscription.coupon})") img(src='https://payments.amazon.com/gp/cba/button',alt=env.t('amazonPayments')) - diff --git a/website/views/options/social/challenge-box.jade b/website/views/options/social/challenge-box.jade index 9a7df490d8..177e049e4d 100644 --- a/website/views/options/social/challenge-box.jade +++ b/website/views/options/social/challenge-box.jade @@ -12,7 +12,7 @@ td a(ui-sref='options.social.challenges.detail({cid:challenge._id, groupIdFilter: group._id})') markdown(text='challenge.name') - div(ng-if='group.challenges.length == 0') + div(ng-if='!group.challenges || group.challenges.length == 0') p |  =env.t('noChallenges') diff --git a/website/views/options/social/challenges.jade b/website/views/options/social/challenges.jade index 3fd8dba61e..ab84189f6f 100644 --- a/website/views/options/social/challenges.jade +++ b/website/views/options/social/challenges.jade @@ -55,7 +55,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.detail.ht // Member List div(bindonce='challenge', ng-if='challenge.members.length > 0') - a.btn.btn-primary.btn-sm.pull-right(ng-href='/api/v2/challenges/{{challenge._id}}/csv') + a.btn.btn-primary.btn-sm.pull-right(ng-href='/api/v3/challenges/{{challenge._id}}/export/csv') =env.t('exportChallengeCSV') h3=env.t('hows') menu @@ -145,7 +145,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') .row .form-group.col-md-6.col-sm-12 - input.form-control(type='text', minlength="3", + input.form-control(type='text', ng-model='newChallenge.shortName', placeholder=env.t('challengeTag'), required ng-disabled='insufficientGemsForTavernChallenge()') |  @@ -160,12 +160,12 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') .Pet_Currency_Gem1x input.form-control(type='number', placeholder=env.t('prize'), ng-disabled='insufficientGemsForTavernChallenge()' - min="{{newChallenge.group=='habitrpg' ? 1 : 0}}", + min="{{newChallenge.group==TAVERN_ID ? 1 : 0}}", max="{{maxPrize}}", ng-model='newChallenge.prize') - a.hint(popover="{{newChallenge.group=='habitrpg' ? env.t('prizePopTavern') : env.t('prizePop')}}", + a.hint(popover="{{newChallenge.group==TAVERN_ID ? env.t('prizePopTavern') : env.t('prizePop')}}", popover-trigger='mouseenter', popover-placement='right') =env.t('moreInfo') - div(ng-show='newChallenge.group=="habitrpg"') + div(ng-show='newChallenge.group==TAVERN_ID') !=env.t('publicChallenges') .form-group(ng-if='user.contributor.admin') @@ -178,7 +178,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') // Challenges list .panel-group - .panel.panel-default(ng-repeat='challenge in challenges|filter:filterChallenges track by challenge._id ') + .panel.panel-default(ng-repeat='challenge in challenges | filter:filterChallenges track by challenge._id ') .panel-heading ul.pull-right.challenge-accordion-header-specs li.bg-transparent(ng-if='challenge.official') @@ -199,10 +199,10 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') p!=env.t('prizeValue', {gemcount: "{{challenge.prize}}", gemicon: ""}) li.bg-transparent // leave / join - a.btn.btn-sm.btn-danger(ng-show='challenge._isMember', ng-click='clickLeave(challenge, $event)') + a.btn.btn-sm.btn-danger(ng-show='isUserMemberOf(challenge)', ng-click='clickLeave(challenge, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-sm.btn-success(ng-hide='challenge._isMember', ng-click='join(challenge)') + a.btn.btn-sm.btn-success(ng-hide='isUserMemberOf(challenge)', ng-click='join(challenge)') span.glyphicon.glyphicon-ok =env.t('join') a.accordion-toggle(id="{{challenge._id}}" ng-click='toggle(challenge._id)') diff --git a/website/views/options/social/chat-box.jade b/website/views/options/social/chat-box.jade index 74fc071dfe..3792070706 100644 --- a/website/views/options/social/chat-box.jade +++ b/website/views/options/social/chat-box.jade @@ -8,7 +8,7 @@ div.chat-form.guidelines-not-accepted(ng-if='!user.flags.communityGuidelinesAcce form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postChat(group,message.content)') div(ng-controller='AutocompleteCtrl') - textarea.form-control(rows=4, ui-keydown='{"meta-enter":"postChat(group,message.content)"}', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete placeholder="{{group._id == 'habitrpg' ? env.t('tavernCommunityGuidelinesPlaceholder') : ''}}", ng-disabled='_sending == true') + textarea.form-control(rows=4, ui-keydown='{"meta-enter":"postChat(group,message.content)"}', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete placeholder="{{group._id == TAVERN_ID ? env.t('tavernCommunityGuidelinesPlaceholder') : ''}}", ng-disabled='_sending == true') span.user-list ul.list-at-user(ng-show="query") li(ng-repeat='msg in response | filter:filterUser | limitTo: 5', ng-click='performCompletion(msg)') diff --git a/website/views/options/social/chat-message.jade b/website/views/options/social/chat-message.jade index 3231726e82..07447b483c 100644 --- a/website/views/options/social/chat-message.jade +++ b/website/views/options/social/chat-message.jade @@ -29,7 +29,7 @@ mixin chatMessages(inbox) a(ng-click="quickReply(message.uuid)") span.glyphicon.glyphicon-share-alt(tooltip=env.t('pm-reply')) span(ng-if='#{inbox ? "true" : ":: user.contributor.admin || message.uuid == user.id"}')     - a(ng-click='#{inbox? "user.ops.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}') + a(ng-click='#{inbox? "User.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}') span.glyphicon.glyphicon-trash(tooltip=env.t('delete')) span(ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')     a(ng-click="flagChatMessage(group._id, message)") diff --git a/website/views/options/social/group.jade b/website/views/options/social/group.jade index 3630ca3d2e..6232990915 100644 --- a/website/views/options/social/group.jade +++ b/website/views/options/social/group.jade @@ -18,10 +18,10 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter h3.panel-title span {{group.name}} span.group-leave-join(ng-if='group') - a.btn.btn-sm.btn-danger.pull-right(ng-if=":: isMemberOfGroup(User.user._id, group)", ng-hide='group._editing', ng-click='clickLeave(group, $event)') + a.btn.btn-sm.btn-danger.pull-right(ng-if="isMemberOfGroup(User.user._id, group)", ng-hide='group._editing', ng-click='clickLeave(group, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-success.pull-right(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join') + a.btn.btn-success.pull-right(ng-if='!isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join') span(ng-if='group.leader._id == user.id') button.btn.btn-sm.btn-primary.pull-right(ng-click='cancelEdit(group)', ng-hide='!group._editing')=env.t('cancel') button.btn.btn-sm.btn-primary.pull-right(ng-click='saveEdit(group)', ng-show='group._editing')=env.t('save') @@ -60,7 +60,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter .text-center(ng-if='group.type === "party"') .row.row-margin: .col-sm-6.col-sm-offset-3 button.btn.btn-success.btn-block( - ng-if='!party.quest.key', + ng-if='!group.quest.key', ng-click='clickStartQuest();' )=env.t('startAQuest') diff --git a/website/views/options/social/hall.jade b/website/views/options/social/hall.jade index 7e17980117..381e5ec8fd 100644 --- a/website/views/options/social/hall.jade +++ b/website/views/options/social/hall.jade @@ -50,7 +50,7 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html') h4 Update Item .form-group.well input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',ng-model='hero.itemPath') - small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. See all paths here. When in doubt, ask Tyler. + small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. See all paths here. When in doubt, ask Tyler. br input.form-control(type='text',placeholder='Value (eg, 5)',ng-model='hero.itemVal') small.muted Enter the item value. E.g., 5 or false or head_warrior_3 (respectively from above examples). diff --git a/website/views/options/social/index.jade b/website/views/options/social/index.jade index f4c3a7043a..754d3d08f6 100644 --- a/website/views/options/social/index.jade +++ b/website/views/options/social/index.jade @@ -45,10 +45,10 @@ script(type='text/ng-template', id='partials/options.social.guilds.public.html') li='{{::group.memberCount}} ' + env.t('members') // join / leave li.bg-transparent - a.btn.btn-sm.btn-danger(ng-if="::group._isMember", ng-click='clickLeave(group, $event)') + a.btn.btn-sm.btn-danger(ng-if="isMemberOfGroup(User.user._id, group)", ng-click='clickLeave(group, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-sm.btn-success(ng-if="::!group._isMember", ng-click='join(group)') + a.btn.btn-sm.btn-success(ng-if="!isMemberOfGroup(User.user._id, group)", ng-click='join(group)') span.glyphicon.glyphicon-ok =env.t('join') h4: a(href='/#/options/groups/guilds/{{::group._id}}') {{::group.name}} diff --git a/website/views/options/social/party/leave-party-and-join-another.jade b/website/views/options/social/party/leave-party-and-join-another.jade index 438feb3786..b6ff6c7862 100644 --- a/website/views/options/social/party/leave-party-and-join-another.jade +++ b/website/views/options/social/party/leave-party-and-join-another.jade @@ -1,5 +1,5 @@ - var newParty = 'User.user.invitations.party' -.containter-fluid(ng-if='#{newParty}.id && party._id') +.containter-fluid(ng-if='#{newParty}.id && group._id') .row.text-center .col-sm-6.col-sm-offset-3.alert.alert-warning p {{::env.t('invitedToNewParty', { partyName: #{newParty}.name })}} diff --git a/website/views/options/social/party/party-invitation.jade b/website/views/options/social/party/party-invitation.jade index f4972e6e41..0bfd72b6d8 100644 --- a/website/views/options/social/party/party-invitation.jade +++ b/website/views/options/social/party/party-invitation.jade @@ -5,5 +5,4 @@ data-type='party', ng-click='join(user.invitations.party)' )=env.t('accept') - a.btn.btn-danger(ng-click='reject()')=env.t('reject') - + a.btn.btn-danger(ng-click='reject(user.invitations.party)')=env.t('reject') diff --git a/website/views/options/social/quests/questActive.jade b/website/views/options/social/quests/questActive.jade index 969861e8c1..b457f8d0e3 100644 --- a/website/views/options/social/quests/questActive.jade +++ b/website/views/options/social/quests/questActive.jade @@ -23,7 +23,7 @@ div(ng-if='group.quest.active===true') include ./ianQuestInfo unless tavern - button.btn.btn-sm.btn-warning(ng-if='::canEditQuest(party)', + button.btn.btn-sm.btn-warning(ng-if='::canEditQuest()', ng-click='questAbort()')=env.t('abort') button.btn.btn-sm.btn-warning(ng-if='!(group.quest.leader && group.quest.leader === user._id) && isMemberOfRunningQuest(user._id,group)', ng-click='questLeave()')=env.t('leaveQuest') diff --git a/website/views/options/social/quests/questNotActive.jade b/website/views/options/social/quests/questNotActive.jade index 3472011e29..c20ae5a226 100644 --- a/website/views/options/social/quests/questNotActive.jade +++ b/website/views/options/social/quests/questNotActive.jade @@ -26,6 +26,6 @@ div(ng-if='group.quest.active===false') button.btn.btn-sm.btn-success(ng-click='questAccept()')=env.t('accept') button.btn.btn-sm.btn-danger(ng-click='questReject()')=env.t('reject') - span(ng-if='::canEditQuest(party)') - button.btn.btn-sm.btn-warning(ng-click='party.$startQuest({"force":true})')=env.t('begin') + span(ng-if='::canEditQuest()') + button.btn.btn-sm.btn-warning(ng-click='questForceStart()')=env.t('begin') button.btn.btn-sm.btn-danger(ng-click='questCancel()')=env.t('cancel') diff --git a/website/views/options/social/tavern.jade b/website/views/options/social/tavern.jade index ed7e4ef6c3..d0f16c187d 100644 --- a/website/views/options/social/tavern.jade +++ b/website/views/options/social/tavern.jade @@ -16,7 +16,7 @@ .popover-content span(ng-if='!env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innText',{name: user.profile.name}) : env.t('danielText')}} span(ng-if='env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innTextBroken',{name: user.profile.name}) : env.t('danielTextBroken')}} - button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') | {{user.preferences.sleep ? env.t('innCheckOut') : env.t('innCheckIn')}} span(ng-if='!user.preferences.sleep && !env.worldDmg.tavern')=env.t('danielText2') span(ng-if='!user.preferences.sleep && env.worldDmg.tavern')=env.t('danielText2Broken') diff --git a/website/views/shared/avatar/appearance.jade b/website/views/shared/avatar/appearance.jade index 7b28273996..14f69414a0 100644 --- a/website/views/shared/avatar/appearance.jade +++ b/website/views/shared/avatar/appearance.jade @@ -11,13 +11,13 @@ mixin avatar(opts) span(ng-if='profile.items.currentMount', class='Mount_Body_{{profile.items.currentMount}}') // Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc - - var visualBuffs = { snowball: 'snowman', spookDust: 'spookman', shinySeed: 'avatar_floral_{{profile.stats.class}}', seafoam: 'seafoam_star' } + - var visualBuffs = { snowball: 'snowman', spookySparkles: 'ghost', shinySeed: 'avatar_floral_{{profile.stats.class}}', seafoam: 'seafoam_star' } each klass, item in visualBuffs span(ng-if='profile.stats.buffs.#{item}', class='#{klass}') // Show avatar only if not currently affected by visual buff - var buffs = '!profile.stats.buffs' - span(ng-if='#{buffs}.snowball && #{buffs}.spookDust && #{buffs}.shinySeed && #{buffs}.seafoam') + span(ng-if='#{buffs}.snowball && #{buffs}.spookySparkles && #{buffs}.shinySeed && #{buffs}.seafoam') +generatedAvatar // Mount Head diff --git a/website/views/shared/footer.jade b/website/views/shared/footer.jade index 0f5bc5c041..0164b16783 100644 --- a/website/views/shared/footer.jade +++ b/website/views/shared/footer.jade @@ -47,8 +47,6 @@ footer.footer(ng-controller='FooterCtrl') a(target='_blank', href='/static/clear-browser-data')=env.t('communityBug') li a(target='_blank', href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')=env.t('communityFeature') - li - a(target='_blank', href='https://habitica.com/static/api')=env.t('API') li a(href='http://habitica.wikia.com/wiki/App_and_Extension_Integrations', target='_blank')=env.t('communityExtensions') li @@ -59,6 +57,14 @@ footer.footer(ng-controller='FooterCtrl') a(target='_blank', href='https://www.facebook.com/Habitica')=env.t('communityFacebook') li a(target='_blank', href='http://www.reddit.com/r/habitrpg/')=env.t('communityReddit') + h4=env.t('footerDevs') + ul.list-unstyled + li + a(target='_blank', href='http://devs.habitica.com')=env.t('devBlog') + ' - The Forge' + li + a(target='_blank', href='/apidoc')=env.t('APIv3') + li + a(target='_blank', href='/static/api-v2')=env.t('APIv2') .col-sm-3 if (env.NODE_ENV === 'production' && !env.IS_MOBILE) h4=env.t('footerSocial') @@ -79,7 +85,7 @@ footer.footer(ng-controller='FooterCtrl') tr td iframe(src='/bower_components/github-buttons/github-btn.html?user=habitrpg&repo=habitrpg&type=watch&count=true', allowtransparency='true', frameborder='0', scrolling='0', width='85px', height='20px') - else if (env.NODE_ENV==='development' || env.NODE_ENV==='test') && !env.isStaticPage + if (env.NODE_ENV==='development' || env.NODE_ENV==='test') && !env.isStaticPage h4 Debug .btn-group-vertical a.btn.btn-default(ng-click='setHealthLow()') Health = 1 @@ -91,7 +97,10 @@ footer.footer(ng-controller='FooterCtrl') a.btn.btn-default(ng-click='addMana()') +MP a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP +MP a.btn.btn-default(ng-click='addOneLevel()') +1 Level - a.btn.btn-default(ng-click='addBossQuestProgressUp()') +1000 Boss Quest Progress Up + a.btn.btn-default(ng-click='addQuestProgress()' tooltip="+1000 to boss quests. 300 items to collection quests") Quest Progress Up + // TODO Re-enable after v3 prod testing + // a.btn.btn-default(ng-click='makeAdmin()') Make Admin + a.btn.btn-default(ng-click='openModifyInventoryModal()') Modify Inventory div(ng-init='deferredScripts()') diff --git a/website/views/shared/header/header.jade b/website/views/shared/header/header.jade index 5c0b847849..6535bb217f 100644 --- a/website/views/shared/header/header.jade +++ b/website/views/shared/header/header.jade @@ -25,13 +25,13 @@ .meter-label(tooltip='Mana', ng-if='user.flags.classSelected && !user.preferences.disableClasses') span.glyphicon.glyphicon-fire .meter.mana(ng-if='user.flags.classSelected && !user.preferences.disableClasses', tooltip='{{Math.round(user.stats.mp * 100) / 100}}') - .bar(ng-style='{"width": (user.stats.mp / user._statsComputed.maxMP * 100) + "%"}') + .bar(ng-style='{"width": (user.stats.mp / user.fns.statsComputed().maxMP * 100) + "%"}') span.meter-text.value span - | {{Math.floor(user.stats.mp)}} / {{user._statsComputed.maxMP}} + | {{Math.floor(user.stats.mp)}} / {{user.fns.statsComputed().maxMP}} // party .party(ng-controller='PartyCtrl') - button.party-invite.btn.btn-primary(ng-click="inviteOrStartParty(group)", + button.party-invite.btn.btn-primary(ng-click="inviteOrStartParty(party)", ng-if="(!party.members || party.memberCount === 1) && user.preferences.displayInviteToPartyWhenPartyIs1", popover="{{!party.members ? env.t('startAParty') : env.t('addToParty')}}", popover-placement="left", popover-trigger="mouseenter") span=env.t("battleWithFriends") diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade index 78f60557ca..d009ebdd4d 100644 --- a/website/views/shared/header/menu.jade +++ b/website/views/shared/header/menu.jade @@ -205,7 +205,7 @@ nav.toolbar(ng-controller='MenuCtrl') span.glyphicon.glyphicon-plus-sign span=env.t('haveUnallocated', {points: '{{user.stats.points}}'}) li(ng-repeat='(k,v) in user.newMessages', ng-if='v.value') - a(ng-click='k === party._id ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k}); ', data-close-menu) + a(ng-click='(k === party._id || k === user.party._id) ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k}); ', data-close-menu) span.glyphicon.glyphicon-comment span {{v.name}} a(ng-click='clearMessages(k)', popover=env.t('clear'),popover-placement='right',popover-trigger='mouseenter',popover-append-to-body='true') diff --git a/website/views/shared/modals/buy-gems.jade b/website/views/shared/modals/buy-gems.jade index d071450cab..4ebebf57ea 100644 --- a/website/views/shared/modals/buy-gems.jade +++ b/website/views/shared/modals/buy-gems.jade @@ -10,7 +10,7 @@ mixin buyGemsDropdown() p small.muted=env.t('paymentMethods') a.purchase.btn.btn-primary(ng-click='Payments.showStripe({})')=env.t('card') - a.purchase(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') + a.purchase(href='/paypal/checkout?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt='Pay now with Paypal') a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})") img(src='https://payments.amazon.com/gp/cba/button',alt='Pay now with Amazon Payments') @@ -34,7 +34,7 @@ script(id='modals/buyGems.html', type='text/ng-template') .container-fluid .row .col-md-3 - button.customize-option(ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})') + button.customize-option(ng-click='User.purchase({params:{type:"gems",key:"gem"}})') span.Pet_Currency_Gem.inline-gems .badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}} p diff --git a/website/views/shared/modals/classes.jade b/website/views/shared/modals/classes.jade index 6484626318..754d6f31f0 100644 --- a/website/views/shared/modals/classes.jade +++ b/website/views/shared/modals/classes.jade @@ -68,6 +68,6 @@ script(type='text/ng-template', id='modals/chooseClass.html') .modal-footer span(popover-placement='left', popover-trigger='mouseenter', popover=env.t('optOutOfClassesText')) - button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()')=env.t('optOutOfClasses') + button.btn.btn-danger(ng-click='User.disableClasses({}); $close()')=env.t('optOutOfClasses') button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select') .pull-left!=env.t('chooseClassLearn') diff --git a/website/views/shared/modals/death.jade b/website/views/shared/modals/death.jade index cfdbced16e..60a92c3802 100644 --- a/website/views/shared/modals/death.jade +++ b/website/views/shared/modals/death.jade @@ -21,5 +21,5 @@ script(type='text/ng-template', id='modals/death.html') h4(style='margin-top:1.5em')=env.t('dontDespair') p(style='margin-top:1.5em')=env.t('deathPenaltyDetails') .modal-footer - a.btn.btn-danger.btn-lg.flex-column(ng-click='user.ops.revive({}); $close()')=env.t('refillHealthTryAgain') + a.btn.btn-danger.btn-lg.flex-column(ng-click='User.revive(); $close()')=env.t('refillHealthTryAgain') h4.text-center!=env.t('dyingOftenTips') diff --git a/website/views/shared/modals/index.jade b/website/views/shared/modals/index.jade index a57ee62189..c492e09ee1 100644 --- a/website/views/shared/modals/index.jade +++ b/website/views/shared/modals/index.jade @@ -19,6 +19,7 @@ include ./level-up.jade include ./hatch-pet.jade include ./raise-pet.jade include ./won-challenge.jade +include ./modify-inventory.jade //- Settings script(type='text/ng-template', id='modals/change-day-start.html') diff --git a/website/views/shared/modals/limited.jade b/website/views/shared/modals/limited.jade index 158fd5a647..0f68a2dcad 100644 --- a/website/views/shared/modals/limited.jade +++ b/website/views/shared/modals/limited.jade @@ -9,4 +9,4 @@ script(id='modals/cards.html', type='text/ng-template') markdown(text='::cardMessage') .modal-footer small.pull-left {{::env.t(cardType + 'CardExplanation')}} - button.btn.btn-default(ng-click='user.ops.readCard({params: {cardType: cardType}}); $close()')=env.t('ok') + button.btn.btn-default(ng-click='User.readCard({params: {cardType: cardType}}); $close()')=env.t('ok') diff --git a/website/views/shared/modals/members.jade b/website/views/shared/modals/members.jade index b40a56eea5..c4741156f8 100644 --- a/website/views/shared/modals/members.jade +++ b/website/views/shared/modals/members.jade @@ -33,9 +33,9 @@ script(type='text/ng-template', id='modals/member.html') include ../profiles/achievements .modal-footer .btn-group.pull-left(ng-if='::user') - button.btn.btn-md.btn-default(ng-if='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') + button.btn.btn-md.btn-default(ng-if='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') span.glyphicon.glyphicon-plus - button.btn.btn-md.btn-default(ng-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip=env.t('block'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') + button.btn.btn-md.btn-default(ng-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip=env.t('block'), ng-click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') span.glyphicon.glyphicon-ban-circle button.btn.btn-md.btn-default(tooltip=env.t('sendPM'), ng-click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right') span.glyphicon.glyphicon-envelope @@ -94,9 +94,9 @@ script(type='text/ng-template', id='modals/send-gift.html') .modal-footer - var fromBal = "gift.type=='gems' && gift.gems.fromBalance" - button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send") + button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id)')=env.t("send") a.btn.btn-primary(ng-hide=fromBal, ng-click='Payments.showStripe({gift:gift, uuid:profile._id})')=env.t('card') - a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal + a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::User.settings.auth.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal .btn.btn-success(ng-hide=fromBal, ng-click="Payments.amazonPayments.init({type: 'single', gift: gift, giftedTo: profile._id})") Amazon Payments button.btn.btn-default(ng-click='$close()')=env.t('cancel') diff --git a/website/views/shared/modals/modify-inventory.jade b/website/views/shared/modals/modify-inventory.jade new file mode 100644 index 0000000000..fd7ae0c61f --- /dev/null +++ b/website/views/shared/modals/modify-inventory.jade @@ -0,0 +1,252 @@ +script(type='text/ng-template', id='modals/modify-inventory.html') + .modal-header + h4 Modify Inventory for {{::user.profile.name}} + .modal-body + .container-fluid + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.gear", ng-click="showInv.gear = true") Show Gear + button.btn.btn-default.pull-right(ng-if="showInv.gear", ng-click="showInv.gear = false") Hide Gear + h4 Gear + div(ng-if="showInv.gear") + button.btn.btn-default(ng-click="setAllItems('gear', true)") Own All + button.btn.btn-default(ng-click="setAllItems('gear', false)") Previously Own All + button.btn.btn-default(ng-click="setAllItems('gear', undefined)") Never Own All + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.gear.flat" ng-init="inv.gear[item.key] = user.items.gear.owned[item.key]") + .pull-left(class="shop_{{::item.key}}" style="margin-right: 10px") + | {{::item.text()}} + + .clearfix + label.radio-inline + input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="true") + | Owned + label.radio-inline + input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="false") + | Previously Owned + label.radio-inline + input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="undefined") + | Never Owned + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.special", ng-click="showInv.special = true") Show Special Items + button.btn.btn-default.pull-right(ng-if="showInv.special", ng-click="showInv.special = false") Hide Special Items + h4 Special Items + div(ng-if="showInv.special") + button.btn.btn-default(ng-click="setAllItems('special', 999)") Set All to 999 + button.btn.btn-default(ng-click="setAllItems('special', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('special', undefined)") Set All to undefined + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.special" ng-init="inv.special[item.key] = user.items.special[item.key]" ng-if="item.value === 15") + .form-inline.clearfix + .pull-left(class="inventory_special_{{::item.key}}" style="margin-right: 10px") + p {{::item.text()}} + input.form-control(type="number" ng-model="inv.special[item.key]") + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.pets", ng-click="showInv.pets = true") Show Pets + button.btn.btn-default.pull-right(ng-if="showInv.pets", ng-click="showInv.pets = false") Hide Pets + h4 Pets + div(ng-if="showInv.pets") + button.btn.btn-default(ng-click="setAllItems('pets', 45)") Set All to 45 + button.btn.btn-default(ng-click="setAllItems('pets', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('pets', -1)") Set All to -1 + button.btn.btn-default(ng-click="setAllItems('pets', undefined)") Set All to undefined + + hr + + h5 Drop Pets + ul.list-group + li.list-group-item(ng-repeat="(pet, value) in Content.pets" ng-init="inv.pets[pet] = user.items.pets[pet]") + .form-inline.clearfix + .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") + p {{::pet}} + input.form-control(type="number" ng-model="inv.pets[pet]") + + h5 Quest Pets + ul.list-group + li.list-group-item(ng-repeat="(pet, value) in Content.questPets" ng-init="inv.pets[pet] = user.items.pets[pet]") + .form-inline.clearfix + .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") + p {{::pet}} + input.form-control(type="number" ng-model="inv.pets[pet]") + + h5 Special Pets + ul.list-group + li.list-group-item(ng-repeat="(pet, value) in Content.specialPets" ng-init="inv.pets[pet] = user.items.pets[pet]") + .form-inline.clearfix + .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") + p {{::pet}} + input.form-control(type="number" ng-model="inv.pets[pet]") + + h5 Premium Pets + ul.list-group + li.list-group-item(ng-repeat="(pet, value) in Content.premiumPets" ng-init="inv.pets[pet] = user.items.pets[pet]") + .form-inline.clearfix + .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") + p {{::pet}} + input.form-control(type="number" ng-model="inv.pets[pet]") + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.mounts", ng-click="showInv.mounts = true") Show Mounts + button.btn.btn-default.pull-right(ng-if="showInv.mounts", ng-click="showInv.mounts = false") Hide Mounts + h4 Mounts + div(ng-if="showInv.mounts") + button.btn.btn-default(ng-click="setAllItems('mounts', true)") Set all to Owned + button.btn.btn-default(ng-click="setAllItems('mounts', undefined)") Set all to Not Owned + + hr + + h5 Drop Mounts + ul.list-group + li.list-group-item(ng-repeat="(mount, value) in Content.mounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") + .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") + | {{::mount}} + .clearfix + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") + | Owned + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") + | Not Owned + + h5 Quest Mounts + ul.list-group + li.list-group-item(ng-repeat="(mount, value) in Content.questMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") + .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") + | {{::mount}} + .clearfix + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") + | Owned + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") + | Not Owned + + h5 Special Mounts + ul.list-group + li.list-group-item(ng-repeat="(mount, value) in Content.specialMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") + .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") + | {{::mount}} + .clearfix + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") + | Owned + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") + | Not Owned + + h5 Premium Mounts + ul.list-group + li.list-group-item(ng-repeat="(mount, value) in Content.premiumMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") + .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") + | {{::mount}} + .clearfix + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") + | Owned + label.radio-inline + input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") + | Not Owned + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.hatchingPotions", ng-click="showInv.hatchingPotions = true") Show Hatching Potions + button.btn.btn-default.pull-right(ng-if="showInv.hatchingPotions", ng-click="showInv.hatchingPotions = false") Hide Hatching Potions + h4 Hatching Potions + div(ng-if="showInv.hatchingPotions") + button.btn.btn-default(ng-click="setAllItems('hatchingPotions', 999)") Set All to 999 + button.btn.btn-default(ng-click="setAllItems('hatchingPotions', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('hatchingPotions', undefined)") Set All to undefined + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.hatchingPotions" ng-init="inv.hatchingPotions[item.key] = user.items.hatchingPotions[item.key]") + .form-inline.clearfix + .pull-left(class="Pet_HatchingPotion_{{::item.key}}" style="margin-right: 10px") + p {{::item.text()}} + input.form-control(type="number" ng-model="inv.hatchingPotions[item.key]") + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.eggs", ng-click="showInv.eggs = true") Show Eggs + button.btn.btn-default.pull-right(ng-if="showInv.eggs", ng-click="showInv.eggs = false") Hide Eggs + h4 Eggs + div(ng-if="showInv.eggs") + button.btn.btn-default(ng-click="setAllItems('eggs', 999)") Set All to 999 + button.btn.btn-default(ng-click="setAllItems('eggs', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('eggs', undefined)") Set All to undefined + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.eggs" ng-init="inv.eggs[item.key] = user.items.eggs[item.key]") + .form-inline.clearfix + .pull-left(class="Pet_Egg_{{::item.key}}" style="margin-right: 10px") + p {{::item.text()}} + input.form-control(type="number" ng-model="inv.eggs[item.key]") + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.food", ng-click="showInv.food = true") Show Food + button.btn.btn-default.pull-right(ng-if="showInv.food", ng-click="showInv.food = false") Hide Food + h4 Food + div(ng-if="showInv.food") + button.btn.btn-default(ng-click="setAllItems('food', 999)") Set All to 999 + button.btn.btn-default(ng-click="setAllItems('food', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('food', undefined)") Set All to undefined + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.food" ng-init="inv.food[item.key] = user.items.food[item.key]") + .form-inline.clearfix + .pull-left(class="Pet_Food_{{::item.key}}" style="margin-right: 10px") + p {{::item.text()}} + input.form-control(type="number" ng-model="inv.food[item.key]") + + hr + + .row + .col-xs-12 + button.btn.btn-default.pull-right(ng-if="!showInv.quests", ng-click="showInv.quests = true") Show Quests + button.btn.btn-default.pull-right(ng-if="showInv.quests", ng-click="showInv.quests = false") Hide Quests + h4 Quests + div(ng-if="showInv.quests") + button.btn.btn-default(ng-click="setAllItems('quests', 999)") Set All to 999 + button.btn.btn-default(ng-click="setAllItems('quests', 0)") Set All to 0 + button.btn.btn-default(ng-click="setAllItems('quests', undefined)") Set All to undefined + + hr + + ul.list-group + li.list-group-item(ng-repeat="item in Content.quests" ng-init="inv.quests[item.key] = user.items.quests[item.key]" ng-if="item.category !== 'world'") + .form-inline.clearfix + .pull-left(class="inventory_quest_scroll_{{::item.key}}" style="margin-right: 10px") + p {{::item.text()}} + input.form-control(type="number" ng-model="inv.quests[item.key]") + .modal-footer + button.btn.btn-default(ng-click="$close()")=env.t('close') + button.btn.btn-primary(ng-click="$close();modifyInventory()") Apply Changes diff --git a/website/views/shared/modals/quests.jade b/website/views/shared/modals/quests.jade index a9dbf6a991..40efa3b5c4 100644 --- a/website/views/shared/modals/quests.jade +++ b/website/views/shared/modals/quests.jade @@ -57,7 +57,7 @@ script(type='text/ng-template', id='modals/buyQuest.html') .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('neverMind') button.btn.btn-primary(ng-if='::selectedQuest.category !== "gold"', ng-click='purchase("quests", quest); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.value}} ' + env.t('gems') - button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='user.ops.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold') + button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='User.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold') script(type='text/ng-template', id='modals/questInvitation.html') .modal-header @@ -104,7 +104,8 @@ script(type='text/ng-template', id='modals/questDrop.html') .quest-icon(class='inventory_quest_scroll_{{::selectedQuest.key}}') h4!=env.t('leveledUpReceivedQuest', {level:'{{user.stats.lvl}}'}) .row(style='margin-top:2em') - button.btn.btn-primary(ng-click='inviteOrStartParty(group); $close()', ng-if='!party.members')=env.t('startAParty') + button.btn.btn-primary(ng-click='inviteOrStartParty(party); $close()', ng-if='!User.user.party._id')=env.t('startAParty') + button.btn.btn-primary(ng-click='inviteOrStartParty(party); $close()', ng-if='!User.user.party._id && !party.members')=env.t('battleWithFriends') button.btn.btn-primary(ng-click='questInit(); $close()', ng-if='party.members')=env.t('inviteParty') button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('questLater') .modal-footer(style='margin-top:0', ng-init='loadWidgets()') diff --git a/website/views/shared/modals/settings.jade b/website/views/shared/modals/settings.jade index 8a263f2df0..5063173a96 100644 --- a/website/views/shared/modals/settings.jade +++ b/website/views/shared/modals/settings.jade @@ -55,11 +55,11 @@ script(type='text/ng-template', id='modals/delete.html') .modal-header h4=env.t('deleteAccount') .modal-body - p!=env.t('deleteText', {deleteWord: 'DELETE'}) + p!=env.t('deleteText', {deleteWord: 'Your password'}) br .row .col-md-6 - input.form-control(type='text', ng-model='_deleteAccount') + input.form-control(type='password', ng-model='_deleteAccount') .modal-footer button.btn.btn-default(ng-click='$close()')=env.t('neverMind') - button.btn.btn-danger(ng-disabled='_deleteAccount != "DELETE"', ng-click='$close(); delete()')=env.t('deleteDo') + button.btn.btn-danger(ng-disabled='!_deleteAccount', ng-click='$close(); delete(_deleteAccount)')=env.t('deleteDo') diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index dfd4cbfcaf..c59174b701 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,26 +1,44 @@ -h2 5/19/2016 - IMPORTANT: UPCOMING MAINTENANCE! +h2 5/21/2016 - WELCOME BACK, HABITICA! hr tr td - h3 Maintenance to Take Place May 21 - p This Saturday, we will be performing important maintenance on Habitica to build out the groundwork for some exciting upcoming features! We'll be doing everything we can to make this as smooth as possible, but unfortunately, there will be significant downtime for much of the day. - br - p.strong We expect that on Saturday, May 21st, Habitica will be unavailable between 1 PM and 10 PM Pacific Time (8 pm - 5 am UTC). - ul - li Don't worry, you will NOT lose any streaks or take any damage during this weekend, not even from Bosses! This maintenance will not harm your accounts. - li If you will need to see your task list on Saturday, we recommend taking a screenshot of your tasks before the maintenance begins so that you can use them as a reference during downtime. - li At the end of the maintenance, to thank people for their patience, everyone will receive a rare Veteran pet! - li This maintenance should not result in any major visible differences to the site; it's all behind-the-scenes work. However, at the end of it, we will release new updates to the mobile apps, which will be required in order for the apps to work properly with the new changes! Be sure to download those updates on Saturday as soon as they are released. - li For more information, please check out our detailed info page about the maintenance! And if you have any further questions or concerns, feel free to reach out to Leslie (leslie@habitica.com), and she will be happy to help you. - p We understand that it's very frustrating to have Habitica unavailable for such a long part of the day. Rest assured that we'll be doing everything we can to make the maintenance go as quickly as possible, but with over a million Habitican accounts to migrate, this is a hefty task! During the maintenance on Saturday we will be posting regular status reports on our Twitter account, so you can follow us for the most accurate updates. - br - p Thank you for your patience, and for using Habitica! + h3 Welcome Back, Everyone! + p Hurrah! After many hours of toil, our valiant blacksmiths were able to complete our planned maintenance ahead of schedule. The site should be working normally again! If you notice any issues or have any questions, please feel free to email us at admin@habitica.com and we will be happy to help. + tr + td + h3 Important Mobile App Updates + p We’ve released an iOS update and an Android update that contain the new code. It’s very important to download these updates immediately, or you may encounter significant bugs! + tr + td + .Pet-Wolf-Veteran.pull-right + h3 Veteran Pets + p To thank you for your patience during the maintenance, we have awarded everyone a special Veteran Pet! You can see it under Inventory > Pets, at the bottom of the screen. If it hasn’t appeared yet, never fear: because there are so many Habiticans, it can sometimes take an hour or two for everyone to receive their pet. You will have it soon! Thanks again for bearing with us during the downtime. + tr + td + h3 Daily Safe Mode + p To protect the accounts of Habiticans in different time zones across the world, we enabled Cron Daily Safe Mode during the maintenance, which will prevent you from taking any damage or losing any streaks for the rest of the weekend. Let us know at admin@habitica.com if you have any questions or concerns! if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 5/19/2016 - IMPORTANT: UPCOMING MAINTENANCE! + tr + td + h3 Maintenance to Take Place May 21 + p This Saturday, we will be performing important maintenance on Habitica to build out the groundwork for some exciting upcoming features! We'll be doing everything we can to make this as smooth as possible, but unfortunately, there will be significant downtime for much of the day. + br + p.strong We expect that on Saturday, May 21st, Habitica will be unavailable between 1 PM and 10 PM Pacific Time (8 pm - 5 am UTC). + ul + li Don't worry, you will NOT lose any streaks or take any damage during this weekend, not even from Bosses! This maintenance will not harm your accounts. + li If you will need to see your task list on Saturday, we recommend taking a screenshot of your tasks before the maintenance begins so that you can use them as a reference during downtime. + li At the end of the maintenance, to thank people for their patience, everyone will receive a rare Veteran pet! + li This maintenance should not result in any major visible differences to the site; it's all behind-the-scenes work. However, at the end of it, we will release new updates to the mobile apps, which will be required in order for the apps to work properly with the new changes! Be sure to download those updates on Saturday as soon as they are released. + li For more information, please check out our detailed info page about the maintenance! And if you have any further questions or concerns, feel free to reach out to Leslie (leslie@habitica.com), and she will be happy to help you. + p We understand that it's very frustrating to have Habitica unavailable for such a long part of the day. Rest assured that we'll be doing everything we can to make the maintenance go as quickly as possible, but with over a million Habitican accounts to migrate, this is a hefty task! During the maintenance on Saturday we will be posting regular status reports on our Twitter account, so you can follow us for the most accurate updates. + br + p Thank you for your patience, and for using Habitica! h2 5/17/2016 - TREELING PET QUEST AND CHALLENGE SPOTLIGHT! tr td @@ -325,7 +343,7 @@ mixin oldNews tr td .promo_spring_classes_2016.pull-right - h3 Limited Edition Class Outfits + h3 Limited Edition Class Outfits p From now until April 30th, limited edition outfits are available in the Rewards column! Depending on your class, you can be a Springing Bunny, Clever Dog, Grand Malkin, or Brave Mouse. You'd better get productive to earn enough Gold before your time runs out... p.small.muted by PainterProphet and Balduranne tr @@ -879,7 +897,7 @@ mixin oldNews p Exciting news - for the next three weeks, we are offering Habitica T-shirts via Teespring! Show your Habitica pride in purple or black. We are also offering an EU run for cheaper shipping to Europe! br p Whether you're getting them for yourself or as a holiday gift, we hope you enjoy these limited-run T-shirts! As always, thanks for supporting Habitica. - + h2 11/5/2015 - HUGE IOS UPDATE AND ANDROID MAILING LIST tr td @@ -909,7 +927,7 @@ mixin oldNews tr td h3 Android Mailing List - p For those of you anxious for news about the new native Android app, we've created a mailing list so that you can be notified about important updates for the Android app. You can sign up here! + p For those of you anxious for news about the new native Android app, we've created a mailing list so that you can be notified about important updates for the Android app. You can sign up here! br p Our staff has been working very hard on it and testing out a new build each week, so progress is definitely advancing. When the beta is ready we will announce it on social media and on the site, but the mailing list is the easiest way to make sure you don't miss it! Thanks very much for your patience. h2 11/3/2015 - NOVEMBER BACKGROUNDS AND ARMOIRE ITEMS, AND AUTO-EQUIP NEW GEAR @@ -970,14 +988,14 @@ mixin oldNews td .promo_mystery_201510.pull-right h3 Last Chance for Horned Goblin Set - p Reminder: this is the final day to subscribe and receive the Horned Goblin Item Set! If you want the Goblin Horns or the Goblin Tail, now's the time! + p Reminder: this is the final day to subscribe and receive the Horned Goblin Item Set! If you want the Goblin Horns or the Goblin Tail, now's the time! br - p Thanks so much for your supporting the site -- you're helping us keep Habitica alive. + p Thanks so much for your supporting the site -- you're helping us keep Habitica alive. tr td .npc_justin.pull-right h3 Happy Habitoween! - p Burnout is nearly defeated, so what could be a better way to speed the celebration than to have some fun? In honor of Habitoween and defiance of the looming threat, all of the remaining NPCs have dressed up as monsters from the Flourishing Fields! Be sure to visit them on the site to admire their outfits. If only the three Exhaust Spirits could join them... + p Burnout is nearly defeated, so what could be a better way to speed the celebration than to have some fun? In honor of Habitoween and defiance of the looming threat, all of the remaining NPCs have dressed up as monsters from the Flourishing Fields! Be sure to visit them on the site to admire their outfits. If only the three Exhaust Spirits could join them... h2 10/27/2015 - BURNOUT STRIKES AGAIN! PLUS, SPOOKY POTIONS VANISHING SOON tr @@ -2681,9 +2699,9 @@ mixin oldNews td h3 Spooky Sparkles .pull-right - .inventory_special_spookDust - .achievement-spookDust - .spookman + .inventory_special_spookySparkles + .achievement-spookySparkles + .ghost p There's a new gold-purchasable item in the Market: Spooky Sparkles! Buy some and then cast it on your friends. I wonder what it will do? br p If you have Spooky Sparkles cast on you, you will receive the "Alarming Friends" badge! Don't worry, any mysterious effects will wear off the next day.... or you can cancel them early by buying an Opaque Potion! diff --git a/website/views/shared/profiles/achievements.jade b/website/views/shared/profiles/achievements.jade index 1b3659a863..cb75e0db32 100644 --- a/website/views/shared/profiles/achievements.jade +++ b/website/views/shared/profiles/achievements.jade @@ -183,11 +183,11 @@ div(ng-if='::profile.achievements.snowball') =env.t('annoyingFriendsText', {snowballs: "{{::profile.achievements.snowball}}"}) hr -div(ng-if='::profile.achievements.spookDust') - .achievement.achievement-spookDust +div(ng-if='::profile.achievements.spookySparkles') + .achievement.achievement-spookySparkles h5=env.t('alarmingFriends') small - =env.t('alarmingFriendsText', {spookDust: "{{::profile.achievements.spookDust}}"}) + =env.t('alarmingFriendsText', {spookySparkles: "{{::profile.achievements.spookySparkles}}"}) hr div(ng-if='::profile.achievements.shinySeed') diff --git a/website/views/shared/profiles/stats/attributes.jade b/website/views/shared/profiles/stats/attributes.jade index 0d222a42b4..bf5b1dbcba 100644 --- a/website/views/shared/profiles/stats/attributes.jade +++ b/website/views/shared/profiles/stats/attributes.jade @@ -7,7 +7,7 @@ table.table.table-striped span.hint(popover-title=env.t(statInfo.title), popover-placement='right', popover=env.t(statInfo.popover), popover-trigger='mouseenter') strong=env.t(statInfo.title) - strong : {{profile._statsComputed.#{stat}}} + strong : {{profile.fns.statsComputed().#{stat}}} td: ul.list-unstyled +statList('statCalc.levelBonus(profile.stats.lvl)', 'levelBonus', 'level', true) diff --git a/website/views/shared/tasks/edit/habits/plus_minus.jade b/website/views/shared/tasks/edit/habits/plus_minus.jade index bbe17b7d4e..0a67ff9001 100644 --- a/website/views/shared/tasks/edit/habits/plus_minus.jade +++ b/website/views/shared/tasks/edit/habits/plus_minus.jade @@ -1,8 +1,8 @@ fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id') legend.option-title=env.t('direction/Actions') span.task-checker - input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up') - label(for='{{obj._id}}_{{task.id}}-option-plus') + input.visuallyhidden.focusable(id='{{obj._id}}_{{task._id}}-option-plus', type='checkbox', ng-model='task.up') + label(for='{{obj._id}}_{{task._id}}-option-plus') span.task-checker - input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down') - label(for='{{obj._id}}_{{task.id}}-option-minus') + input.visuallyhidden.focusable(id='{{obj._id}}_{{task._id}}-option-minus', type='checkbox', ng-model='task.down') + label(for='{{obj._id}}_{{task._id}}-option-minus') diff --git a/website/views/shared/tasks/edit/index.jade b/website/views/shared/tasks/edit/index.jade index ce29ee8896..d7bc217598 100644 --- a/website/views/shared/tasks/edit/index.jade +++ b/website/views/shared/tasks/edit/index.jade @@ -3,12 +3,12 @@ div(ng-if='task._editing') // Broken Challenge .well(ng-if='task.challenge.broken') - div(ng-if='task.challenge.broken=="TASK_DELETED"') + div(ng-if='task.challenge.broken=="TASK_DELETED" || task.challenge.broken=="CHALLENGE_TASK_NOT_FOUND') p=env.t('brokenTask') p a(ng-click='unlink(task, "keep")')=env.t('keepIt') |    - a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt') + a(ng-click="removeTask(task, obj)")=env.t('removeIt') div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"') p |  diff --git a/website/views/shared/tasks/edit/tags.jade b/website/views/shared/tasks/edit/tags.jade index 089aa0c408..8abcedc355 100644 --- a/website/views/shared/tasks/edit/tags.jade +++ b/website/views/shared/tasks/edit/tags.jade @@ -1,5 +1,5 @@ fieldset.option-group(ng-if='!$state.includes("options.social.challenges")') p.option-title.mega(ng-class='{active: task._tags}', ng-click='task._tags = !task._tags', tooltip=env.t('expandCollapse'))=env.t('tags') label.checkbox(ng-repeat='tag in user.tags', ng-if='task._tags') - input(type='checkbox', ng-model='task.tags[tag.id]') + input(type='checkbox', ng-checked="task.tags.indexOf(tag.id) !== -1", ng-click="updateTaskTags(tag.id, task)") markdown(text='tag.name') diff --git a/website/views/shared/tasks/index.jade b/website/views/shared/tasks/index.jade index fc3223b888..f128b0b322 100644 --- a/website/views/shared/tasks/index.jade +++ b/website/views/shared/tasks/index.jade @@ -23,7 +23,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") i.glyphicon.glyphicon-warning-sign   =env.t('dailiesRestingInInn') - button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') | {{env.t('innCheckOut')}} +taskColumnTabs('top') diff --git a/website/views/shared/tasks/meta_controls.jade b/website/views/shared/tasks/meta_controls.jade index 733b37d23a..3feaef0d87 100644 --- a/website/views/shared/tasks/meta_controls.jade +++ b/website/views/shared/tasks/meta_controls.jade @@ -23,15 +23,15 @@ |{{checklistCompletion(task.checklist)}}/{{task.checklist.length}} span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)') // edit - a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit')) + a(ng-hide='task._editing', ng-click='editTask(task, user)', tooltip=env.t('edit')) |   span.glyphicon.glyphicon-pencil(ng-hide='task._editing') |   - a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel')) + a(ng-hide='!task._editing', ng-click='editTask(task, user)', tooltip=env.t('cancel')) span.glyphicon.glyphicon-remove(ng-hide='!task._editing') |   // save - a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save')) + a(ng-hide='!task._editing', ng-click='editTask(task, user);saveTask(task)', tooltip=env.t('save')) span.glyphicon.glyphicon-ok(ng-hide='!task._editing') |   //challenges @@ -43,12 +43,12 @@ span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge')) |   // delete - a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete')) + a(ng-if='!task.challenge.id || obj.leader._id === User.user._id', ng-click='removeTask(task, obj)', tooltip=env.t('delete')) span.glyphicon.glyphicon-trash |   // chart - a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress')) + a(ng-show='task.history', ng-click='toggleChart(obj._id+task._id, task)', tooltip=env.t('progress')) span.glyphicon.glyphicon-signal |   // notes diff --git a/website/views/shared/tasks/task.jade b/website/views/shared/tasks/task.jade index 3a69e1f760..ad8ee4ffe5 100644 --- a/website/views/shared/tasks/task.jade +++ b/website/views/shared/tasks/task.jade @@ -1,4 +1,4 @@ -li(id='task-{{::task.id}}', +li(id='task-{{::task._id}}', ng-repeat='task in obj[list.type+"s"] | filterByTaskInfo: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', @@ -14,4 +14,4 @@ li(id='task-{{::task.id}}', include ./edit/index - div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]') + div(class='{{obj._id}}{{task._id}}-chart', ng-show='charts[obj._id+task._id]') diff --git a/website/views/shared/tasks/task_view/add_new.jade b/website/views/shared/tasks/task_view/add_new.jade index f9baf76499..2d4fd1c869 100644 --- a/website/views/shared/tasks/task_view/add_new.jade +++ b/website/views/shared/tasks/task_view/add_new.jade @@ -1,4 +1,4 @@ -form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"],list)', novalidate) +form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"], list, obj)', novalidate) textarea(rows='6', focus-element='list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolderBulk}}', ng-if='list.bulk', ui-keydown='{"meta-enter ctrl-enter":"addTask(obj[list.type+\'s\'],list)"}', required) input(type='text', focus-element='!list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolder}}', ng-if='!list.bulk', required) button(type='submit', ng-disabled='new{{list.type}}form.$invalid') diff --git a/website/views/shared/tasks/task_view/graph.jade b/website/views/shared/tasks/task_view/graph.jade index 82f062dfde..be2fba13a8 100644 --- a/website/views/shared/tasks/task_view/graph.jade +++ b/website/views/shared/tasks/task_view/graph.jade @@ -1,7 +1,7 @@ span.option-box.pull-right(ng-if='::main') a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;') span.glyphicon.glyphicon-signal - //a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal') + //a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{User.settings.auth.apiToken}}', tooltip='iCal') //-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false') span.glyphicon.glyphicon-calendar // diff --git a/website/views/shared/tasks/task_view/index.jade b/website/views/shared/tasks/task_view/index.jade index 1d151ebd07..16628bfa5d 100644 --- a/website/views/shared/tasks/task_view/index.jade +++ b/website/views/shared/tasks/task_view/index.jade @@ -28,13 +28,13 @@ // Daily & Todos span.task-checker.action-yesno(ng-if='::task.type=="daily" || task.type=="todo"') - input.task-input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox', + input.task-input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task._id}}', type='checkbox', ng-model='task.completed', ng-if='$state.includes("tasks")', - ng-change='task.type=="todo" && pushTask(task,$index,"bottom"); changeCheck(task)' + ng-change='changeCheck(task)' ui-keypress='{13:"task.completed = !task.completed; changeCheck(task)"}' ) - input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox', + input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task._id}}', type='checkbox', ng-if='!$state.includes("tasks")') - label(for='box-{{::obj._id}}_{{::task.id}}') + label(for='box-{{::obj._id}}_{{::task._id}}') // main content .task-text(ng-dblclick='task._editing ? saveTask(task) : editTask(task)') diff --git a/website/views/shared/tasks/task_view/mixins.jade b/website/views/shared/tasks/task_view/mixins.jade index 398813d5ad..2c71074d71 100644 --- a/website/views/shared/tasks/task_view/mixins.jade +++ b/website/views/shared/tasks/task_view/mixins.jade @@ -24,7 +24,7 @@ mixin taskColumnTabs(position) div(ng-show='list.view == "complete"') .alert =env.t('lotOfToDos') - button.task-action-btn.tile.spacious.bright(ng-click='user.ops.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted') + button.task-action-btn.tile.spacious.bright(ng-click='User.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted') // remaining/completed tabs ul.task-filter li(ng-class='{active: list.view == "remaining"}') @@ -32,7 +32,7 @@ mixin taskColumnTabs(position) li(ng-class='{active: list.view == "dated"}') a(ng-click='list.view = "dated"')=env.t('dated') li(ng-class='{active: list.view == "complete"}') - a(ng-click='list.view = "complete"')=env.t('complete') + a(ng-click='list.view = "complete";loadedCompletedTodos()')=env.t('complete') // Rewards Tabs div(ng-if='::main && list.type=="reward"', class='tabbable tabs-below') ul.task-filter diff --git a/website/views/shared/tasks/task_view/skills.jade b/website/views/shared/tasks/task_view/skills.jade index da8cabef47..dccd6d1233 100644 --- a/website/views/shared/tasks/task_view/skills.jade +++ b/website/views/shared/tasks/task_view/skills.jade @@ -1,5 +1,5 @@ // Events -- var seasonalSkills = {'snowball':'salt', 'spookDust':'opaquePotion', 'shinySeed':'petalFreePotion', 'seafoam':'sand'} +- var seasonalSkills = {'snowball':'salt', 'spookySparkles':'opaquePotion', 'shinySeed':'petalFreePotion', 'seafoam':'sand'} ul.items.rewards each dispel,skill in seasonalSkills span(ng-if='main && list.type=="reward" && (user.items.special.#{skill}>0 || user.stats.buffs.#{skill})') diff --git a/website/views/static/api.jade b/website/views/static/api-v2.jade similarity index 91% rename from website/views/static/api.jade rename to website/views/static/api-v2.jade index 8c7ebcce8a..c6e5c034f0 100644 --- a/website/views/static/api.jade +++ b/website/views/static/api-v2.jade @@ -75,6 +75,10 @@ html //.input a#explore(href='#') Explore br + h2 API v3 + p This page contains documentation for version 2 of Habitica's API. A new API version, the third, has been released and its documentation can be found here and an introductory blog post with the most important changes here. + p API v2 is still available to give time to developers to port their apps and integration to the new API but it's considered deprecated and should not be used for new projects. It'll be completely retired shortly. + br h2 Two API Types p Habitica's API is meant for two different audiences: (1) extensions and scripts, and (2) full-fledged applications. Extensions and scripts can utilize Habitica's up/down scoring for individual tasks. An example of this in action is the Chrome Extension, which up-scores you for visiting productive websites, and down-scores you for visiting procrastination websites. Other examples currently in use are Pomodoro, Anki, and Github scripts - which up-score you for good behavior and downscore you for bad behavior - see the list. The second API consumer is for full-fledge applications, which need read / write access to the entire user document. An example of this would be Mobile Apps or Desktop application. h2 Extensions / Scripts @@ -93,7 +97,7 @@ html p All API requests should be prefaced by https://habitica.com. Every authenticated request should include two headers. Your api key (x-api-key) and your user id (x-api-user). Do not include {} braces in your header (-H 'x-api-user: a94b6d9d-6b64-43ae-856c-2c3f211bd426') h2 Requirements: p The base-url for all routes is /api/v2. So /user actions will be at https://habitica.com/api/v2/*. You need to send x-api-user and x-api-key headers for each request. - p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here + p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here p If any of the documentation is lacking or you're having trouble with it, please post an issue to Github #message-bar.swagger-ui-wrap #swagger-ui-container.swagger-ui-wrap diff --git a/website/views/static/front.jade b/website/views/static/front.jade index a77527c549..3a3a06f37f 100644 --- a/website/views/static/front.jade +++ b/website/views/static/front.jade @@ -34,6 +34,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') script(type='text/javascript'). window.env = !{JSON.stringify(env._.pick(env, env.clientVars))}; + != env.getManifestFiles("tmp_static_front") script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js') diff --git a/website/views/static/maintenance-info.jade b/website/views/static/maintenance-info.jade index deba47297e..241aafde3a 100644 --- a/website/views/static/maintenance-info.jade +++ b/website/views/static/maintenance-info.jade @@ -1,4 +1,4 @@ -- var t = t || env.t; +- var t = env ? env.t : translation; title Habitica |  =t('maintenance') diff --git a/website/views/static/maintenance.jade b/website/views/static/maintenance.jade new file mode 100644 index 0000000000..0537913219 --- /dev/null +++ b/website/views/static/maintenance.jade @@ -0,0 +1,18 @@ +- var t = env ? env.t : translation; + +title Habitica |  + =t('maintenance') + +head + link(rel='stylesheet', type='text/css', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css') + +body.text-center + h1=t('habiticaBackSoon') + img.img-rendering-auto.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/scene_maintenance.png') + p!=t('importantMaintenance') + p!=t('twitterMaintenanceUpdates') + ul.lead(style='list-style-position:inside') + li=t('noDamageKeepStreaks') + li=t('veteranPetAward') + p!=t('maintenanceMoreInfo', {linkStart: '', linkEnd: '',}) + p.lead=t('thanksForPatience') From 0a7b6b65e723e46c34ceac437f7da7db539e4a0a Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 07:53:14 -0500 Subject: [PATCH 006/152] Revert "API v3 [WIP] (#6144)" This reverts commit 28f2e9c356d7053884107d90d04e28dde75fa81b. --- .babelrc | 7 +- .bowerrc | 2 +- .eslintignore | 18 +- .eslintrc | 5 +- .gitignore | 11 +- .nodemonignore | 2 +- .travis.yml | 2 +- Gruntfile.js | 26 +- Procfile | 2 +- bower.json | 2 +- common/browserify.js | 2 - common/dist/sprites/spritesmith-main-0.css | 4 +- common/dist/sprites/spritesmith-main-11.css | 10 +- common/dist/sprites/spritesmith-main-11.png | Bin 156933 -> 156807 bytes common/dist/sprites/spritesmith-main-12.css | 850 +++++----- common/dist/sprites/spritesmith-main-12.png | Bin 126575 -> 125673 bytes common/dist/sprites/spritesmith-main-5.css | 96 +- common/dist/sprites/spritesmith-main-5.png | Bin 261236 -> 261273 bytes common/dist/sprites/spritesmith-main-6.css | 4 +- ...Sparkles.png => achievement-spookDust.png} | Bin ...kles2x.png => achievement-spookDust2x.png} | Bin ...es.png => inventory_special_spookDust.png} | Bin .../misc/{ghost.png => spookman.png} | Bin ..._spookySparkles.png => shop_spookDust.png} | Bin .../{shop_healAll.png => shop_heallAll.png} | Bin .../stable/pets/Pet-Lion-Veteran.png | Bin 3801 -> 0 bytes common/index.js | 4 +- common/locales/en/challenge.json | 5 +- common/locales/en/groups.json | 4 +- common/locales/en/limited.json | 2 +- common/locales/en/maintenance.json | 2 +- common/locales/en/pets.json | 1 - common/locales/en/settings.json | 1 - common/locales/en/spells.json | 6 +- common/locales/en/subscriber.json | 2 +- common/locales/en/tasks.json | 1 - common/script/constants.js | 2 - common/script/content/index.js | 9 - common/script/content/spells.js | 79 +- common/script/cron.js | 1 - common/script/fns/autoAllocate.js | 107 +- common/script/fns/crit.js | 15 +- common/script/fns/cron.js | 358 ++++ common/script/fns/dotGet.js | 8 +- common/script/fns/dotSet.js | 18 +- common/script/fns/getItem.js | 11 + common/script/fns/handleTwoHanded.js | 19 +- common/script/fns/index.js | 6 + common/script/fns/nullify.js | 6 +- common/script/fns/predictableRandom.js | 28 +- common/script/fns/preenUserHistory.js | 25 + common/script/fns/randomDrop.js | 114 +- common/script/fns/randomVal.js | 14 +- common/script/fns/resetGear.js | 25 - common/script/fns/ultimateGear.js | 30 +- common/script/fns/updateStats.js | 70 +- common/script/index.js | 281 +--- common/script/libs/appliedTags.js | 19 +- common/script/libs/countExists.js | 7 + common/script/libs/dotGet.js | 10 +- common/script/libs/dotSet.js | 15 +- common/script/libs/encodeiCalLink.js | 9 + common/script/libs/errors.js | 42 - common/script/libs/extendableBuiltin.js | 11 - common/script/libs/friendlyTimestamp.js | 9 + common/script/libs/gold.js | 6 +- common/script/libs/index.js | 46 + common/script/libs/newChatMessages.js | 10 + common/script/libs/noTags.js | 6 +- common/script/libs/percent.js | 10 +- common/script/libs/pickDeep.js | 13 - common/script/libs/preenHistory.js | 43 + common/script/libs/preenTodos.js | 12 +- common/script/libs/refPush.js | 11 +- common/script/libs/removeWhitespace.js | 10 + common/script/libs/silver.js | 9 +- common/script/libs/splitWhitespace.js | 3 +- common/script/libs/statsComputed.js | 28 - common/script/libs/taskClasses.js | 65 +- common/script/libs/taskDefaults.js | 83 +- common/script/libs/updateStore.js | 47 +- common/script/libs/uuid.js | 5 +- common/script/ops/addPushDevice.js | 43 +- common/script/ops/addTag.js | 13 +- common/script/ops/addTask.js | 24 +- common/script/ops/addWebhook.js | 34 +- common/script/ops/allocate.js | 26 +- common/script/ops/allocateNow.js | 15 +- common/script/ops/blockUser.js | 28 +- common/script/ops/buy.js | 133 +- common/script/ops/buyArmoire.js | 115 -- common/script/ops/buyGear.js | 70 - common/script/ops/buyHealthPotion.js | 48 - common/script/ops/buyMysterySet.js | 78 +- common/script/ops/buyQuest.js | 79 +- common/script/ops/buySpecialSpell.js | 46 +- common/script/ops/changeClass.js | 107 +- common/script/ops/clearCompleted.js | 14 +- common/script/ops/clearPMs.js | 10 +- common/script/ops/deletePM.js | 14 +- common/script/ops/deleteTag.js | 38 +- common/script/ops/deleteTask.js | 31 +- common/script/ops/deleteWebhook.js | 15 +- common/script/ops/disableClasses.js | 13 +- common/script/ops/equip.js | 74 +- common/script/ops/feed.js | 126 +- common/script/ops/getTag.js | 23 +- common/script/ops/getTags.js | 6 +- common/script/ops/hatch.js | 53 +- common/script/ops/hourglassPurchase.js | 73 +- common/script/ops/index.js | 13 +- common/script/ops/markPMSRead.js | 14 - common/script/ops/openMysteryItem.js | 58 +- common/script/ops/purchase.js | 156 +- common/script/ops/readCard.js | 36 +- common/script/ops/rebirth.js | 106 +- common/script/ops/releaseBoth.js | 94 +- common/script/ops/releaseMounts.js | 61 +- common/script/ops/releasePets.js | 59 +- common/script/ops/reroll.js | 49 +- common/script/ops/reset.js | 48 +- common/script/ops/revive.js | 113 +- common/script/ops/score.js | 221 +++ common/script/ops/scoreTask.js | 260 --- common/script/ops/sell.js | 46 +- common/script/ops/sleep.js | 9 +- common/script/ops/sortTag.js | 24 +- common/script/ops/sortTask.js | 58 +- common/script/ops/unlock.js | 142 +- common/script/ops/update.js | 12 +- common/script/ops/updateTag.js | 26 +- common/script/ops/updateTask.js | 36 +- common/script/ops/updateWebhook.js | 23 +- common/script/public/config.js | 78 +- common/script/public/userServices.js | 268 +++ config.json.example | 8 +- gulpfile.js | 1 - karma.conf.js | 57 +- migrations/20160521_veteran_ladder.js | 76 - migrations/api_v3/challenges.js | 212 --- migrations/api_v3/challengesMembers.js | 143 -- migrations/api_v3/coupons.js | 136 -- migrations/api_v3/emailUnsubscriptions.js | 137 -- migrations/api_v3/groups.js | 211 --- migrations/api_v3/indexes.js | 52 - migrations/api_v3/users.js | 262 --- migrations/manual_password_reset.js | 2 +- newrelic.js | 27 + package.json | 74 +- tasks/gulp-apidoc.js | 22 - tasks/gulp-build.js | 11 +- tasks/gulp-console.js | 13 +- tasks/gulp-newstuff.js | 2 +- tasks/gulp-start.js | 2 +- tasks/gulp-tests.js | 155 +- tasks/taskHelper.js | 43 +- test/README.md | 5 + test/api-legacy/api-helper.js | 5 +- test/api-legacy/challenges.js | 6 +- test/api-legacy/chat.js | 4 +- test/api-legacy/coupons.js | 4 +- test/api-legacy/inAppPurchases.js | 4 +- test/api-legacy/party.js | 4 +- test/api-legacy/pushNotifications.js | 10 +- test/api-legacy/score.js | 2 +- test/api-legacy/subscriptions.js | 4 +- test/api-legacy/todos.js | 2 +- test/api/README.md | 8 +- test/api/v2/groups/GET-groups.test.js | 13 +- .../v2/groups/POST-groups_id_invite.test.js | 8 +- .../api/v2/groups/POST-groups_id_join.test.js | 13 +- .../v2/groups/POST-groups_id_leave.test.js | 4 +- test/api/v2/user/DELETE-user.test.js | 18 +- .../POST-user_batch-update.test.js | 2 +- .../user/pushDevice/POST-pushDevice.test.js | 2 +- test/api/v2/user/tasks/GET-tasks.test.js | 11 +- .../user/tasks/POST-clear-completed.test.js | 26 - test/api/v2/user/tasks/POST-tasks.test.js | 2 +- test/api/v2/user/tasks/PUT-tasks_id.test.js | 4 +- test/api/v3/README.md | 4 - .../DELETE-challenges_challengeId.test.js | 94 -- .../GET-challenges_challengeId.test.js | 142 -- ...-challenges_challengeId_export_csv.test.js | 74 - ...GET-challenges_challengeId_members.test.js | 109 -- ...enges_challengeId_members_memberId.test.js | 107 -- .../GET-challenges_group_groupid.test.js | 118 -- .../challenges/GET-challenges_user.test.js | 132 -- .../challenges/POST-challenges.test.js | 308 ---- .../POST-challenges_challengeId_join.test.js | 127 -- .../POST-challenges_challengeId_leave.test.js | 123 -- ...lenges_challengeId_winner_winnerId.test.js | 139 -- .../PUT-challenges_challengeId.test.js | 85 - .../integration/chat/DELETE-chat_id.test.js | 81 - test/api/v3/integration/chat/GET-chat.test.js | 65 - .../integration/chat/POST-chat.flag.test.js | 84 - .../integration/chat/POST-chat.like.test.js | 75 - .../api/v3/integration/chat/POST-chat.test.js | 81 - .../integration/chat/POST-chat_seen.test.js | 63 - ...POST-groups_id_chat_id_clear_flags.test.js | 101 -- .../integration/content/GET-content.test.js | 25 - .../integration/coupons/GET-coupons.test.js | 39 - .../coupons/POST-coupons_enter_code.test.js | 62 - .../POST-coupons_generate_event.test.js | 66 - .../POST-coupons_validate_code.test.js | 36 - .../GET-export_avatar-memberId.html.test.js | 35 - .../GET-export_avatar-memberId.png.test.js | 3 - .../dataexport/GET-export_history.csv.test.js | 47 - .../GET-export_userdata.json.test.js | 29 - .../GET-export_userdata.xml.test.js | 42 - .../debug/POST-debug_addHourglass.test.js | 35 - .../debug/POST-debug_addTenGems.test.js | 35 - .../debug/POST-debug_make-admin.test.js | 35 - .../debug/POST-debug_modify-inventory.test.js | 160 -- .../debug/POST-debug_quest-progress.test.js | 63 - .../debug/POST-debug_set-cron.test.js | 39 - .../emails/GET-email-unsubscribe.test.js | 68 - .../v3/integration/groups/GET-groups.test.js | 115 -- .../groups/GET-groups_groupId_invites.test.js | 102 -- .../groups/GET-groups_groupId_members.test.js | 96 -- .../integration/groups/GET-groups_id.test.js | 298 ---- .../v3/integration/groups/POST-groups.test.js | 228 --- .../groups/POST-groups_groupId_join.test.js | 286 ---- .../groups/POST-groups_groupId_leave.js | 210 --- .../groups/POST-groups_groupId_reject.test.js | 113 -- .../POST-groups_id_removeMember.test.js | 130 -- .../groups/POST-groups_invite.test.js | 332 ---- .../v3/integration/groups/PUT-groups.test.js | 46 - .../integration/hall/GET-hall_heroes.test.js | 29 - .../hall/GET-hall_heroes_heroId.test.js | 56 - .../integration/hall/GET-hall_patrons.test.js | 60 - .../hall/PUT-hall_heores_heroId.test.js | 148 -- .../members/GET-members_id.test.js | 49 - .../members/POST-send_private_message.test.js | 107 -- .../members/POST-transfer_gems.test.js | 174 -- .../models/GET-model_paths.test.js | 32 - test/api/v3/integration/notFound.test.js | 13 - ...T-payments_amazon_subscribe_cancel.test.js | 21 - .../GET-payments_paypal_checkout.test.js | 21 - ...T-payments_paypal_checkout_success.test.js | 21 - .../GET-payments_paypal_subscribe.test.js | 21 - ...T-payments_paypal_subscribe_cancel.test.js | 21 - ...-payments_paypal_subscribe_success.test.js | 21 - ...T-payments_stripe_subscribe_cancel.test.js | 21 - .../POST-payments_amazon_checkout.test.js | 20 - ...ents_amazon_createOrderReferenceId.test.js | 22 - .../POST-payments_amazon_subscribe.test.js | 21 - ...-payments_amazon_verifyAccessToken.test.js | 20 - .../payments/POST-payments_paypal_ipn.test.js | 17 - .../POST-payments_stripe_checkout.test.js | 20 - ...OST-payments_stripe_subscribe_edit.test.js | 21 - .../POST-groups_groupId_quests_accept.test.js | 119 -- ...-groups_groupId_quests_force-start.test.js | 126 -- .../POST-groups_groupId_quests_invite.test.js | 192 --- .../POST-groups_groupid_quests_abort.test.js | 126 -- .../POST-groups_groupid_quests_cancel.test.js | 138 -- .../POST-groups_groupid_quests_leave.test.js | 124 -- .../POST-groups_groupid_quests_reject.test.js | 146 -- .../v3/integration/status/GET-status.test.js | 12 - .../integration/tags/DELETE-tags_id.test.js | 27 - test/api/v3/integration/tags/GET-tags.test.js | 22 - .../v3/integration/tags/GET-tags_id.test.js | 20 - .../integration/tags/POST-tag-reorder.test.js | 44 - .../api/v3/integration/tags/POST-tags.test.js | 25 - .../v3/integration/tags/PUT-tags_id.test.js | 28 - .../integration/tasks/DELETE-tasks_id.test.js | 59 - .../GET-tasks_challenge_challengeId.test.js | 75 - .../v3/integration/tasks/GET-tasks_id.test.js | 58 - .../integration/tasks/GET-tasks_user.test.js | 84 - .../POST-tasks_clearCompletedTodos.test.js | 43 - .../POST-tasks_id_score_direction.test.js | 298 ---- ...POST-tasks_move_taskId_to_position.test.js | 84 - .../integration/tasks/POST-tasks_user.test.js | 593 ------- .../PUT-tasks_challenge_challengeId.test.js | 323 ---- .../v3/integration/tasks/PUT-tasks_id.test.js | 396 ----- ...lenge_challengeId_checklist_itemId.test.js | 115 -- ...ETE-tasks_id_challenge_challengeId.test.js | 115 -- .../challenges/GET_tasks_challenge.id.test.js | 86 - ...lenge_challengeId_taskId_checklist.test.js | 119 -- .../POST-tasks_challenge_id.test.js | 125 -- ...allengeId_tasks_id_score_direction.test.js | 140 -- ...allengeId_tasksId_checklist_itemId.test.js | 155 -- ...LETE-tasks_taskId_checklist_itemId.test.js | 74 - .../POST-tasks_taskId_checklist.test.js | 73 - ...asks_taskId_checklist_itemId_score.test.js | 79 - .../PUT-tasks_taskId_checklist_itemId.test.js | 83 - .../DELETE-tasks_taskId_tags_tagId.test.js | 42 - .../tags/POST-tasks_taskId_tags_tagId.test.js | 55 - .../v3/integration/user/DELETE-user.test.js | 176 -- .../user/DELETE-user_delete_webhook.test.js | 23 - .../user/DELETE-user_messages.test.js | 27 - test/api/v3/integration/user/GET-user.test.js | 24 - .../user/GET-user_anonymized.test.js | 90 - .../user/GET-user_inventory_buy.test.js | 26 - .../user/POST-user_addPushDevice.test.js | 35 - .../user/POST-user_add_webhook.test.js | 29 - .../user/POST-user_allocate.test.js | 41 - .../user/POST-user_allocate_now.test.js | 28 - .../integration/user/POST-user_block.test.js | 34 - .../v3/integration/user/POST-user_buy.test.js | 62 - .../user/POST-user_buy_armoire.test.js | 41 - .../user/POST-user_buy_gear.test.js | 45 - .../user/POST-user_buy_health_potion.test.js | 42 - .../user/POST-user_buy_mystery_set.test.js | 38 - .../user/POST-user_buy_quest.test.js | 40 - .../user/POST-user_buy_special_spell.test.js | 43 - .../user/POST-user_change-class.test.js | 30 - .../user/POST-user_class_cast_spellId.test.js | 172 -- .../user/POST-user_custom-day-start.test.js | 47 - .../user/POST-user_disable-classes.test.js | 26 - .../user/POST-user_equip_type_key.test.js | 40 - .../user/POST-user_feed_pet_food.test.js | 45 - ...POST-user_hatch_egg_hatchingPotion.test.js | 31 - .../user/POST-user_mark_pms_read.test.js | 22 - .../user/POST-user_open_mystery_item.test.js | 26 - .../user/POST-user_purchase.test.js | 34 - .../user/POST-user_purchase_hourglass.test.js | 25 - .../user/POST-user_read_card.test.js | 38 - .../user/POST-user_rebirth.test.js | 57 - .../user/POST-user_release_both.test.js | 48 - .../user/POST-user_release_mounts.test.js | 42 - .../user/POST-user_release_pets.test.js | 42 - .../integration/user/POST-user_reroll.test.js | 54 - .../integration/user/POST-user_reset.test.js | 104 -- .../integration/user/POST-user_revive.test.js | 37 - .../integration/user/POST-user_sell.test.js | 41 - .../integration/user/POST-user_sleep.test.js | 25 - .../v3/integration/user/POST-user_unlock.js | 37 - test/api/v3/integration/user/PUT-user.test.js | 201 --- .../user/PUT-user_update_webhook.test.js | 32 - .../DELETE-user_auth_social_network.test.js | 40 - .../integration/user/auth/GET-logout.test.js | 3 - .../user/auth/POST-firebase.test.js | 18 - .../user/auth/POST-login-local.test.js | 69 - .../user/auth/POST-register_local.test.js | 349 ---- .../auth/POST-user_reset_password.test.js | 38 - .../user/auth/PUT-user_update_email.test.js | 79 - .../auth/PUT-user_update_password.test.js | 53 - .../auth/PUT-user_update_username.test.js | 76 - .../api/v3/unit/libs/analyticsService.test.js | 309 ---- test/api/v3/unit/libs/baseModel.test.js | 97 -- test/api/v3/unit/libs/buildManifest.test.js | 19 - .../unit/libs/collectionManipulators.test.js | 88 - test/api/v3/unit/libs/cron.test.js | 573 ------- test/api/v3/unit/libs/email.test.js | 232 --- test/api/v3/unit/libs/encryption.test.js | 15 - test/api/v3/unit/libs/errors.test.js | 122 -- test/api/v3/unit/libs/i18n.test.js | 39 - test/api/v3/unit/libs/logger.js | 57 - test/api/v3/unit/libs/password.test.js | 41 - test/api/v3/unit/libs/payments.test.js | 72 - test/api/v3/unit/libs/preening.test.js | 56 - test/api/v3/unit/libs/setupNconf.test.js | 44 - test/api/v3/unit/libs/webhooks.test.js | 135 -- .../api/v3/unit/middlewares/analytics.test.js | 49 - test/api/v3/unit/middlewares/cors.test.js | 40 - .../api/v3/unit/middlewares/cronMiddleware.js | 177 -- .../middlewares/ensureAccessRight.test.js | 57 - .../unit/middlewares/ensureDevelpmentMode.js | 36 - .../v3/unit/middlewares/errorHandler.test.js | 173 -- test/api/v3/unit/middlewares/language.test.js | 307 ---- .../unit/middlewares/maintenanceMode.test.js | 58 - test/api/v3/unit/middlewares/response.js | 66 - test/api/v3/unit/models/challenge.test.js | 161 -- test/api/v3/unit/models/group.test.js | 300 ---- test/api/v3/unit/models/task.test.js | 74 - test/api/v3/unit/models/user.test.js | 33 - test/common/algos.mocha.js | 1491 +++++++++++++++++ test/common/dailies.js | 499 ++++++ test/common/fns/autoAllocate.test.js | 86 - test/common/fns/crit.test.js | 17 - test/common/fns/handleTwoHanded.js | 38 - test/common/fns/predictableRandom.test.js | 51 - test/common/fns/randomDrop.test.js | 165 -- test/common/fns/randomVal.js | 119 -- test/common/fns/statsComputed.test.js | 28 - test/common/fns/ultimateGear.js | 33 - test/common/fns/updateStats.test.js | 170 -- test/common/libs/appliedTags.test.js | 10 - test/common/libs/gold.test.js | 11 - test/common/libs/noTags.test.js | 13 - test/common/libs/percent.test.js | 19 - test/common/libs/pickDeep.js | 34 - test/common/libs/refPush.js | 53 - test/common/libs/silver.test.js | 19 - test/common/libs/splitWhitespace.test.js | 7 - test/common/libs/taskClasses.test.js | 82 - test/common/libs/taskDefaults.test.js | 61 - test/common/libs/updateStore.js | 57 - test/common/ops/addPushDevice.js | 59 - test/common/ops/addTask.js | 139 -- test/common/ops/addWebhook.test.js | 57 - test/common/ops/allocate.js | 63 - test/common/ops/allocateNow.js | 30 - test/common/ops/blockUser.test.js | 44 - test/common/ops/buy.js | 61 - test/common/ops/buyArmoire.js | 205 --- test/common/ops/buyGear.js | 142 -- test/common/ops/buyHealthPotion.js | 65 - test/common/ops/buyMysterySet.js | 76 - test/common/ops/buyQuest.js | 81 - test/common/ops/buySpecialSpell.js | 79 - test/common/ops/changeClass.js | 139 -- test/common/ops/clearCompleted.js | 37 - test/common/ops/clearPMs.test.js | 20 - test/common/ops/deletePM.test.js | 20 - test/common/ops/deleteWebhook.test.js | 21 - test/common/ops/disableClasses.js | 35 - test/common/ops/equip.js | 87 - test/common/ops/feed.js | 216 --- test/common/ops/hatch.js | 147 -- test/common/ops/hourglassPurchase.js | 145 -- test/common/ops/openMysteryItem.js | 38 - test/common/ops/purchase.js | 194 --- test/common/ops/readCard.js | 48 - test/common/ops/rebirth.js | 236 --- test/common/ops/releaseBoth.js | 98 -- test/common/ops/releaseMounts.js | 57 - test/common/ops/releasePets.js | 57 - test/common/ops/reroll.js | 63 - test/common/ops/reset.js | 79 - test/common/ops/revive.js | 91 - test/common/ops/scoreTask.test.js | 203 --- test/common/ops/sell.js | 79 - test/common/ops/sleep.js | 18 - test/common/ops/unlock.js | 121 -- test/common/ops/updateTask.js | 53 - test/common/ops/updateWebhook.test.js | 42 - test/common/preenTodos.test.js | 76 + test/common/shared.spells.test.js | 103 ++ test/common/simulations/autoAllocate.js | 161 ++ .../simulations/passive_active_attrs.js | 291 ++++ test/common/user.fns.buy.test.js | 288 ++++ test/common/user.fns.ultimateGear.test.js | 37 + test/common/user.fns.updateStats.test.js | 134 ++ test/common/user.ops.buyMysterySet.test.js | 77 + test/common/user.ops.equip.test.js | 102 ++ test/common/user.ops.hatch.js | 129 ++ .../common/user.ops.hourglassPurchase.test.js | 122 ++ test/common/user.ops.test.js | 34 + test/helpers/api-integration/api-classes.js | 25 +- test/helpers/api-integration/mongo.js | 92 + test/helpers/api-integration/requester.js | 79 +- test/helpers/api-integration/translate.js | 3 +- test/helpers/api-integration/v2/index.js | 2 +- .../api-integration/v2/object-generators.js | 43 +- test/helpers/api-integration/v3/index.js | 11 - .../api-integration/v3/object-generators.js | 157 -- test/helpers/api-unit.helper.js | 104 -- test/helpers/api-v3-integration.helper.js | 1 - test/helpers/common.helper.js | 4 +- test/helpers/content.helper.js | 3 +- test/helpers/globals.helper.js | 31 +- test/helpers/mongo.js | 110 -- test/helpers/sleep.js | 7 - ...50605_ultimate_achievement_backfill.coffee | 2 +- test/mocha.opts | 1 + test/server_side/analytics.test.js | 6 +- test/server_side/controllers/groups.test.js | 14 +- test/server_side/controllers/user.test.js | 8 +- test/server_side/webhooks.test.js | 2 +- test/spec/chatServicesSpec.js | 90 + test/spec/controllers/authCtrlSpec.js | 4 +- test/spec/controllers/challengesCtrlSpec.js | 214 +-- .../copyMessageModalControllerSpec.js | 12 +- test/spec/controllers/filtersCtrlSpec.js | 15 +- test/spec/controllers/footerCtrlSpec.js | 19 +- test/spec/controllers/groupCtrlSpec.js | 65 +- test/spec/controllers/headerCtrlSpec.js | 7 +- test/spec/controllers/inventoryCtrlSpec.js | 24 +- .../spec/controllers/inviteToGroupCtrlSpec.js | 165 +- test/spec/controllers/menuCtrlSpec.js | 2 +- test/spec/controllers/partyCtrlSpec.js | 229 +-- test/spec/controllers/settingsCtrlSpec.js | 40 +- test/spec/controllers/tasksCtrlSpec.js | 6 +- test/spec/services/challengeServicesSpec.js | 88 - test/spec/services/chatServicesSpec.js | 73 - test/spec/services/groupServicesSpec.js | 143 +- test/spec/services/memberServicesSpec.js | 78 +- test/spec/services/questServicesSpec.js | 57 +- test/spec/services/statServicesSpec.js | 12 +- test/spec/services/tagServicesSpec.js | 52 - test/spec/services/taskServicesSpec.js | 163 +- test/spec/services/userServicesSpec.js | 4 +- test/spec/specHelper.js | 3 - website/client/js/controllers/guildsCtrl.js | 127 -- website/client/js/controllers/partyCtrl.js | 213 --- website/client/js/controllers/tavernCtrl.js | 18 - .../client/js/services/challengeServices.js | 99 -- website/client/js/services/chatServices.js | 87 - website/client/js/services/groupServices.js | 234 --- website/client/js/services/memberServices.js | 128 -- website/client/js/services/tagsServices.js | 60 - website/client/js/services/taskServices.js | 205 --- website/client/js/services/userServices.js | 583 ------- website/{client => public}/500.html | 0 .../apple-touch-icon-114-precomposed.png | Bin .../apple-touch-icon-144-precomposed.png | Bin .../apple-touch-icon-57-precomposed.png | Bin .../apple-touch-icon-72-precomposed.png | Bin .../apple-touch-icon-precomposed.png | Bin website/{client => public}/cake.png | Bin .../backCorner.png | Bin .../beingHabitican.png | Bin .../consequences.png | Bin .../contributing.png | Bin .../community-guidelines-images/github.gif | Bin .../infractions.png | Bin .../community-guidelines-images/intro.png | Bin .../moderators.png | Bin .../publicGuilds.png | Bin .../publicSpaces.png | Bin .../restoration.png | Bin .../community-guidelines-images/staff.png | Bin .../community-guidelines-images/tavern.png | Bin .../community-guidelines-images/trello.png | Bin .../community-guidelines-images/wiki.png | Bin website/{client => public}/css/README.md | 0 website/{client => public}/css/alerts.styl | 0 website/{client => public}/css/avatar.styl | 0 .../{client => public}/css/challenges.styl | 0 website/{client => public}/css/classes.styl | 0 .../{client => public}/css/customizer.styl | 0 website/{client => public}/css/filters.styl | 0 website/{client => public}/css/footer.styl | 0 website/{client => public}/css/game-pane.styl | 0 .../{client => public}/css/global-colors.styl | 0 .../css/global-modules.styl | 0 website/{client => public}/css/header.styl | 0 website/{client => public}/css/helpers.styl | 0 website/{client => public}/css/index.styl | 0 website/{client => public}/css/inventory.styl | 0 website/{client => public}/css/items.styl | 0 website/{client => public}/css/menu.styl | 0 website/{client => public}/css/no-script.styl | 0 website/{client => public}/css/npcs.styl | 0 website/{client => public}/css/options.styl | 0 website/{client => public}/css/quests.styl | 0 .../{client => public}/css/scrollbars.styl | 0 website/{client => public}/css/shared.styl | 0 website/{client => public}/css/static.styl | 0 website/{client => public}/css/tasks.styl | 0 .../css/variables/screen-size.styl | 0 .../emails/images/10-days-recapture-v1.png | Bin .../images/3-days-1-month-recapture-v1.png | Bin .../images/PROMO-Enchanted-Armoire-v1.png | Bin .../emails/images/android-promo-v1.png | Bin .../emails/images/iphone-promo-v1.png | Bin .../emails/images/one-day-v1.png | Bin .../emails/images/spring-2015-00-v1.png | Bin .../emails/images/spring-2015-01-v1.png | Bin .../subscription-begins-time-travelers-v1.png | Bin .../emails/images/subscription-begins-v1.png | Bin website/{client => public}/favicon.ico | Bin .../{client => public}/favicon_192x192.png | Bin .../{client => public}/fontello/LICENSE.txt | 0 .../{client => public}/fontello/README.txt | 0 .../fontello/css/animation.css | 0 .../fontello/css/fontelico-codes.css | 0 .../fontello/css/fontelico-embedded.css | 0 .../fontello/css/fontelico-ie7-codes.css | 0 .../fontello/css/fontelico-ie7.css | 0 .../fontello/css/fontelico.css | 0 website/{client => public}/fontello/demo.html | 0 .../fontello/font/fontelico.eot | Bin .../fontello/font/fontelico.svg | 0 .../fontello/font/fontelico.ttf | Bin .../fontello/font/fontelico.woff | Bin website/{client => public}/front/README.md | 0 .../front/css/blockScroll.css | 0 .../front/css/bootstrap.min.css | 0 .../front/css/fixed-positioning.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../front/images/Feeding_Time.png | Bin .../front/images/Guilds Sample Screen.png | Bin .../front/images/HabitRPGPromoPostCard6.png | Bin .../front/images/HabitRPGPromoThin.png | Bin .../Habitica_banner_by_uncommoncriminal.png | Bin .../Habitica_map_by_uncommoncriminal.png | Bin .../front/images/Healer.png | Bin .../{client => public}/front/images/Mount.png | Bin .../front/images/Mount_Body_Dragon-Golden.png | Bin .../front/images/Mount_Body_Dragon-Red.png | Bin .../front/images/Mount_Body_Wolf-Base.png | Bin .../front/images/Mount_Head_Dragon-Golden.png | Bin .../front/images/Mount_Head_Dragon-Red.png | Bin .../front/images/Mount_Head_Wolf-Base.png | Bin .../front/images/Party-Header.png | Bin .../front/images/Pet-Dragon-Red.png | Bin .../front/images/Pet-Fox-Red.png | Bin .../front/images/Promo_springclasses2015.png | Bin .../front/images/Quest_dilatory_drag'on.png | Bin .../images/Quest_dilatory_drag'onSmall.png | Bin .../{client => public}/front/images/Rogue.png | Bin .../front/images/SAMPLEadventurers.png | Bin .../front/images/TVreward.png | Bin .../front/images/VICE_by_Baconsaur.png | Bin .../front/images/Warrior.png | Bin .../front/images/Wizard.png | Bin .../front/images/achievement-perfect.png | Bin .../front/images/achievement-triadbingo.png | Bin .../front/images/avatar/Warrior.png | Bin .../front/images/avatar/avatar.png | Bin .../front/images/avatar/avatarstatic.png | Bin .../images/avatar/hair_bangs_1_brown.png | Bin .../front/images/avatar/head_0.png | Bin .../front/images/avatar/head_warrior_3.png | Bin .../front/images/avatar/head_warrior_5.png | Bin .../front/images/avatar/shield_warrior_3.png | Bin .../front/images/avatar/shield_warrior_5.png | Bin .../front/images/avatar/skin_f5a76e.png | Bin .../images/avatar/slim_armor_warrior_3.png | Bin .../images/avatar/slim_armor_warrior_5.png | Bin .../front/images/avatar/slim_shirt_black.png | Bin .../front/images/avatar/weapon_healer_6.png | Bin .../front/images/avatar/weapon_warrior_3.png | Bin .../front/images/avatar/weapon_warrior_5.png | Bin .../blackish_fox_by_kellllly-d7pzd46.png | Bin .../front/images/coding_by_phoneix_faerie.png | Bin .../front/images/devices.png | Bin .../front/images/explosion.jpg | Bin .../front/images/explosion.png | Bin .../front/images/habitrpg_pixel.png | Bin .../front/images/icon175x175.png | Bin .../{client => public}/front/images/intro.jpg | Bin .../{client => public}/front/images/intro.psd | Bin .../front/images/misc/Pet_Food_Cake_Base.png | Bin .../misc/inventory_quest_scroll_harpy.png | Bin .../front/images/misc/rebirth_orb.png | Bin .../front/images/misc/shop_gold.png | Bin .../front/images/misc/shop_potion.png | Bin ...or_habit_by_cosmic_caterpillar-d8mf5mb.png | Bin .../front/images/party/AnnaCosplay.png | Bin .../front/images/party/Ariel_cosplay.png | Bin .../images/party/Big_Daddy_(BioShock).png | Bin .../party/Cosplay_Daenerys_Targaryen.png | Bin .../front/images/party/GrimReaper.png | Bin .../front/images/party/HomeStuckLusus.png | Bin .../front/images/presslogos/Cnetlogo.png | Bin .../images/presslogos/Fast-Company-logo.png | Bin .../front/images/presslogos/Forbes_logo.png | Bin .../front/images/presslogos/GitHub_Logo.png | Bin .../front/images/presslogos/discover_logo.png | Bin .../images/presslogos/ionic-logo-blog.png | Bin .../ionic-logo-horizontal-transparent.png | Bin .../images/presslogos/kickstarter-logo.png | Bin .../landing_slack_hash_wordmark_logo.png | Bin .../front/images/presslogos/lifehacker.png | Bin .../front/images/presslogos/logo_webstorm.png | Bin .../front/images/presslogos/makeuseof.png | Bin .../front/images/presslogos/nyt-logo.png | Bin .../front/images/presslogos/slack.png | Bin .../images/presslogos/trello-logo-blue.png | Bin .../front/images/quest_vice3.png | Bin .../front/images/screenshot.png | Bin .../t_bone_fight_2_by_mortquitue-d8dtxbl.png | Bin .../front/images/testimonial_by_Streak.png | Bin .../front/images/testimonials/16bitFil.png | Bin .../front/images/testimonials/AlexandraSo.png | Bin .../front/images/testimonials/Althaire.png | Bin .../front/images/testimonials/AndeeLiao.png | Bin .../front/images/testimonials/Brenna.png | Bin .../images/testimonials/Drag0nsilver.png | Bin .../front/images/testimonials/Drei-M.png | Bin .../front/images/testimonials/Elmi.png | Bin .../front/images/testimonials/EvaGantz.png | Bin .../front/images/testimonials/Helcura.png | Bin .../front/images/testimonials/InfH.png | Bin .../front/images/testimonials/Kai.png | Bin .../front/images/testimonials/Kazui.png | Bin .../front/images/testimonials/Zelah_Meyer.png | Bin .../images/testimonials/autumnesquirrel.png | Bin .../images/testimonials/frabjabulous.png | Bin .../front/images/testimonials/galarix.png | Bin .../front/images/testimonials/gwyn.blath.png | Bin .../images/testimonials/irishfeet123.png | Bin .../front/images/testimonials/skysailor.png | Bin .../images/testimonials/supermouse35.png | Bin .../images/testimonials/tonitonirocca.png | Bin .../front/images/uses/achievement-bkgd.png | Bin .../uses/clipart-rosemonkeyct-meditation.png | Bin .../uses/clipart-rosemonkeyct-meditation.psd | Bin .../uses/clipart-rosemonkeyct-reading.png | Bin .../front/images/uses/coding.png | Bin .../coding_3_by_phoneix_faerie-d7idtti.png | Bin .../front/images/uses/consequences.png | Bin .../front/images/uses/dusting-bkgd.png | Bin .../front/images/uses/dusting_by_leephon.png | Bin ...ievement_by_cosmic_caterpillar-d7uyv5z.png | Bin .../front/images/uses/meditation-bkgd.png | Bin .../front/images/uses/publicSpaces.png | Bin .../front/images/uses/reading.png | Bin .../front/js/blockScroll.js | 0 .../front/js/bootstrap.min.js | 0 .../front/js/skrollr.min.js | 0 .../front/landingv1Wireframe.jpg | Bin .../{client => public}/front/staticstyle.css | 0 website/{client => public}/front/style.css | 0 .../google280633b772b94345.html | 0 .../google8ca65b6ff3506fb8.html | 0 .../googlef3b1402b0e28338a.html | 0 website/{client => public}/js/.eslintrc | 0 website/{client => public}/js/app.js | 93 +- .../js/controllers/authCtrl.js | 45 +- .../js/controllers/autoCompleteCtrl.js | 0 .../js/controllers/challengesCtrl.js | 228 +-- .../js/controllers/chatCtrl.js | 96 +- .../js/controllers/copyMessageModalCtrl.js | 2 +- .../js/controllers/filtersCtrl.js | 11 +- .../js/controllers/footerCtrl.js | 78 +- .../js/controllers/groupsCtrl.js | 63 +- website/public/js/controllers/guildsCtrl.js | 94 ++ .../js/controllers/hallCtrl.js | 20 +- .../js/controllers/headerCtrl.js | 20 +- .../js/controllers/inventoryCtrl.js | 26 +- .../js/controllers/inviteToGroupCtrl.js | 33 +- .../js/controllers/memberModalCtrl.js | 52 +- .../js/controllers/menuCtrl.js | 2 +- .../js/controllers/notificationCtrl.js | 10 +- website/public/js/controllers/partyCtrl.js | 173 ++ .../js/controllers/rootCtrl.js | 51 +- .../js/controllers/settingsCtrl.js | 47 +- .../js/controllers/sortableInventoryCtrl.js | 0 .../js/controllers/tasksCtrl.js | 128 +- website/public/js/controllers/tavernCtrl.js | 10 + .../js/controllers/userCtrl.js | 10 +- .../js/directives/close-menu.directive.js | 0 .../js/directives/expand-menu.directive.js | 0 .../js/directives/focus-element.directive.js | 0 .../js/directives/from-now.directive.js | 0 .../js/directives/habitrpg-tasks.directive.js | 0 .../hrpg-sort-checklist.directive.js | 0 .../js/directives/hrpg-sort-tags.directive.js | 4 +- .../directives/hrpg-sort-tasks.directive.js | 4 +- .../popover-html-popup.directive.js | 0 .../js/directives/popover-html.directive.js | 0 .../js/directives/when-scrolled.directive.js | 0 website/{client => public}/js/env.js | 0 .../{client => public}/js/filters/money.js | 0 .../js/filters/roundLargeNumbers.js | 0 .../js/filters/taskOrdering.js | 0 .../js/filters/timezoneOffsetToUtc.js | 0 .../js/services/analyticsServices.js | 0 .../public/js/services/challengeServices.js | 26 + website/public/js/services/chatServices.js | 33 + website/public/js/services/groupServices.js | 92 + .../js/services/guideServices.js | 6 +- website/public/js/services/memberServices.js | 60 + .../js/services/notificationServices.js | 8 +- .../js/services/paymentServices.js | 18 +- .../js/services/questServices.js | 48 +- .../js/services/sharedServices.js | 0 .../js/services/socialServices.js | 0 .../js/services/statServices.js | 4 +- website/public/js/services/taskServices.js | 53 + website/{client => public}/js/static.js | 21 +- website/{client => public}/logo.png | Bin .../logo/HABITRPG logo version 1.psd | Bin .../logo/HABITRPG-logo-version-1.gif | Bin website/{client => public}/logo/habitrpg.jpg | Bin .../{client => public}/logo/habitrpg_bl.eps | 0 .../logo/habitrpg_pixel.png | Bin website/{client => public}/manifest.json | 14 +- .../marketing/android_iphone.png | Bin .../{client => public}/marketing/animals.png | Bin .../marketing/challenge.png | Bin .../{client => public}/marketing/devices.png | Bin .../{client => public}/marketing/drops.png | Bin .../marketing/education.png | Bin website/{client => public}/marketing/gear.png | Bin .../{client => public}/marketing/guild.png | Bin .../marketing/guild_small.png | Bin .../marketing/integration.png | Bin .../{client => public}/marketing/lefnire.png | Bin .../marketing/promos/201403_Forest_Walker.png | Bin .../marketing/promos/April14SAMPLE2.png | Bin .../marketing/screenshot.png | Bin .../marketing/social_competitve.png | Bin .../{client => public}/marketing/wellness.png | Bin .../merch/stickermule-logo.png | Bin .../merch/stickermule-logo.svg | 0 .../{client => public}/merch/stickermule.png | Bin .../merch/teespring-eu-logo.png | Bin .../{client => public}/merch/teespring-eu.png | Bin .../merch/teespring-logo.png | Bin .../merch/teespring-logo.svg | 0 .../{client => public}/merch/teespring.png | Bin website/{client => public}/page-loader.gif | Bin .../presskit/Boss - Basi-List.png | Bin .../Boss - Battling the Ghost Stag.png | Bin .../presskit/Boss - Laundromancer.png | Bin .../presskit/Boss - Necro-Vice.png | Bin .../presskit/Boss - SnackLess Monster.png | Bin .../presskit/Boss - Stagnant Dishes.png | Bin .../presskit/Habitica Gryphon.png | Bin .../presskit/Habitica Logo - Android.png | Bin .../Habitica Logo - Icon with Text.png | Bin .../presskit/Habitica Logo - Icon.png | Bin .../presskit/Habitica Logo - Text.png | Bin .../presskit/Habitica Logo - iOS.png | Bin .../presskit/Habitica Promo - Thin.png | Bin .../presskit/Habitica Promo.png | Bin .../presskit/Sample Screen - Boss (iOS).png | Bin .../presskit/Sample Screen - Challenges.png | Bin .../presskit/Sample Screen - Equipment.png | Bin .../presskit/Sample Screen - Guilds.png | Bin .../Sample Screen - Level Up (iOS).png | Bin .../presskit/Sample Screen - Market.png | Bin .../presskit/Sample Screen - Party (iOS).png | Bin .../presskit/Sample Screen - Pets (iOS).png | Bin .../Sample Screen - Tasks Page (iOS).png | Bin .../presskit/Sample Screen - Tasks Page.png | Bin ...World Boss - Dread Drag'on of Dilatory.png | Bin .../{client => public}/presskit/presskit.zip | Bin website/{client => public}/refresh.png | Bin .../server/controllers/api-v2/challenges.js | 428 ----- website/server/controllers/api-v2/user.js | 1053 ------------ website/server/controllers/api-v3/auth.js | 512 ------ .../server/controllers/api-v3/challenges.js | 518 ------ website/server/controllers/api-v3/chat.js | 398 ----- website/server/controllers/api-v3/content.js | 110 -- website/server/controllers/api-v3/coupon.js | 126 -- website/server/controllers/api-v3/debug.js | 190 --- website/server/controllers/api-v3/groups.js | 670 -------- website/server/controllers/api-v3/hall.js | 183 -- website/server/controllers/api-v3/iap.js | 4 - website/server/controllers/api-v3/members.js | 364 ---- .../server/controllers/api-v3/modelsPaths.js | 40 - website/server/controllers/api-v3/quests.js | 451 ----- website/server/controllers/api-v3/status.js | 21 - website/server/controllers/api-v3/tags.js | 190 --- website/server/controllers/api-v3/tasks.js | 971 ----------- website/server/controllers/api-v3/user.js | 1358 --------------- website/server/controllers/top-level/auth.js | 16 - .../controllers/top-level/dataexport.js | 247 --- website/server/controllers/top-level/email.js | 54 - website/server/controllers/top-level/pages.js | 79 - .../controllers/top-level/payments/amazon.js | 256 --- .../controllers/top-level/payments/iap.js | 191 --- .../controllers/top-level/payments/paypal.js | 278 --- .../controllers/top-level/payments/stripe.js | 169 -- website/server/index.js | 46 - website/server/libs/api-v3/amazonPayments.js | 62 - .../server/libs/api-v3/analyticsService.js | 237 --- website/server/libs/api-v3/baseModel.js | 79 - website/server/libs/api-v3/buildManifest.js | 62 - .../libs/api-v3/collectionManipulators.js | 22 - website/server/libs/api-v3/cron.js | 280 ---- website/server/libs/api-v3/csvStringify.js | 11 - website/server/libs/api-v3/email.js | 156 -- website/server/libs/api-v3/encryption.js | 24 - website/server/libs/api-v3/errors.js | 62 - website/server/libs/api-v3/firebase.js | 69 - website/server/libs/api-v3/i18n.js | 105 -- website/server/libs/api-v3/logger.js | 60 - website/server/libs/api-v3/password.js | 18 - website/server/libs/api-v3/payments.js | 185 -- website/server/libs/api-v3/preening.js | 82 - .../server/libs/api-v3/pushNotifications.js | 53 - website/server/libs/api-v3/routes.js | 61 - website/server/libs/api-v3/setupMongoose.js | 28 - website/server/libs/api-v3/setupNconf.js | 17 - website/server/libs/api-v3/setupPassport.js | 24 - website/server/libs/api-v3/webhook.js | 31 - .../server/middlewares/api-v3/analytics.js | 23 - website/server/middlewares/api-v3/auth.js | 87 - website/server/middlewares/api-v3/cors.js | 9 - website/server/middlewares/api-v3/cron.js | 160 -- website/server/middlewares/api-v3/domain.js | 13 - .../middlewares/api-v3/ensureAccessRight.js | 23 - .../api-v3/ensureDevelpmentMode.js | 12 - .../server/middlewares/api-v3/errorHandler.js | 86 - website/server/middlewares/api-v3/index.js | 87 - website/server/middlewares/api-v3/language.js | 91 - website/server/middlewares/api-v3/locals.js | 61 - .../middlewares/api-v3/maintenanceMode.js | 31 - website/server/middlewares/api-v3/notFound.js | 7 - .../server/middlewares/api-v3/redirects.js | 43 - website/server/middlewares/api-v3/response.js | 25 - .../server/middlewares/api-v3/setupBody.js | 5 - website/server/middlewares/api-v3/static.js | 18 - website/server/middlewares/api-v3/v1.js | 19 - website/server/middlewares/api-v3/v2.js | 27 - website/server/middlewares/api-v3/v3.js | 30 - website/server/models/challenge.js | 426 ----- website/server/models/coupon.js | 57 - website/server/models/emailUnsubscription.js | 24 - website/server/models/group.js | 760 --------- website/server/models/tag.js | 27 - website/server/models/task.js | 222 --- website/server/models/user.js | 824 --------- website/server/routes/api-v2/auth.js | 21 - website/server/routes/api-v2/coupon.js | 15 - .../server/routes/api-v2/unsubscription.js | 11 - website/server/routes/payments.js | 34 - website/server/server.js | 35 - .../controllers/api-v2/auth.js | 42 +- website/src/controllers/api-v2/challenges.js | 453 +++++ .../controllers/api-v2/coupon.js | 6 +- .../controllers/api-v2/groups.js | 643 +++---- .../controllers/api-v2/hall.js | 8 +- .../controllers/api-v2/members.js | 19 +- .../controllers/api-v2/unsubscription.js | 12 +- website/src/controllers/api-v2/user.js | 707 ++++++++ .../api-v2 => src/controllers}/dataexport.js | 18 +- website/src/controllers/payments/amazon.js | 271 +++ website/src/controllers/payments/iap.js | 155 ++ website/src/controllers/payments/index.js | 207 +++ website/src/controllers/payments/paypal.js | 216 +++ .../payments}/paypalBillingSetup.js | 6 +- website/src/controllers/payments/stripe.js | 123 ++ .../controllers}/pushNotifications.js | 1 - .../libs/api-v2 => src/libs}/analytics.js | 2 +- .../libs/api-v2 => src/libs}/buildManifest.js | 8 +- .../libs/api-v2 => src/libs}/firebase.js | 16 +- .../{server/libs/api-v2 => src/libs}/i18n.js | 15 +- .../libs/api-v2 => src/libs}/logging.js | 2 +- .../{server/libs/api-v2 => src/libs}/utils.js | 24 +- .../libs/api-v2 => src/libs}/webhook.js | 0 .../middlewares/apiThrottle.js | 4 +- website/src/middlewares/cors.js | 7 + .../api-v2 => src/middlewares}/domain.js | 0 .../middlewares}/errorHandler.js | 2 +- .../middlewares/forceRefresh.js | 2 - .../api-v2 => src/middlewares}/locals.js | 6 +- website/src/middlewares/redirects.js | 41 + website/src/models/challenge.js | 120 ++ website/src/models/coupon.js | 59 + website/src/models/emailUnsubscription.js | 14 + website/src/models/group.js | 501 ++++++ website/src/models/task.js | 113 ++ website/src/models/user.js | 697 ++++++++ website/src/routes/api-v1.js | 173 ++ website/src/routes/api-v2/auth.js | 21 + website/src/routes/api-v2/coupon.js | 12 + .../{server => src}/routes/api-v2/swagger.js | 83 +- website/src/routes/api-v2/unsubscription.js | 8 + website/src/routes/dataexport.js | 16 + website/{server => src}/routes/pages.js | 4 +- website/src/routes/payments.js | 31 + website/src/server.js | 177 ++ website/views/avatar-static.jade | 2 +- website/views/main/filters.jade | 2 +- website/views/options/inventory/drops.jade | 6 +- .../options/inventory/time-travelers.jade | 2 +- website/views/options/profile.jade | 4 +- website/views/options/settings.jade | 21 +- .../views/options/social/challenge-box.jade | 2 +- website/views/options/social/challenges.jade | 16 +- website/views/options/social/chat-box.jade | 2 +- .../views/options/social/chat-message.jade | 2 +- website/views/options/social/group.jade | 6 +- website/views/options/social/hall.jade | 2 +- website/views/options/social/index.jade | 4 +- .../party/leave-party-and-join-another.jade | 2 +- .../social/party/party-invitation.jade | 3 +- .../options/social/quests/questActive.jade | 2 +- .../options/social/quests/questNotActive.jade | 4 +- website/views/options/social/tavern.jade | 2 +- website/views/shared/avatar/appearance.jade | 4 +- website/views/shared/footer.jade | 17 +- website/views/shared/header/header.jade | 6 +- website/views/shared/header/menu.jade | 2 +- website/views/shared/modals/buy-gems.jade | 4 +- website/views/shared/modals/classes.jade | 2 +- website/views/shared/modals/death.jade | 2 +- website/views/shared/modals/index.jade | 1 - website/views/shared/modals/limited.jade | 2 +- website/views/shared/modals/members.jade | 8 +- .../views/shared/modals/modify-inventory.jade | 252 --- website/views/shared/modals/quests.jade | 5 +- website/views/shared/modals/settings.jade | 6 +- website/views/shared/new-stuff.jade | 64 +- .../views/shared/profiles/achievements.jade | 6 +- .../shared/profiles/stats/attributes.jade | 2 +- .../shared/tasks/edit/habits/plus_minus.jade | 8 +- website/views/shared/tasks/edit/index.jade | 4 +- website/views/shared/tasks/edit/tags.jade | 2 +- website/views/shared/tasks/index.jade | 2 +- website/views/shared/tasks/meta_controls.jade | 10 +- website/views/shared/tasks/task.jade | 4 +- .../views/shared/tasks/task_view/add_new.jade | 2 +- .../views/shared/tasks/task_view/graph.jade | 2 +- .../views/shared/tasks/task_view/index.jade | 8 +- .../views/shared/tasks/task_view/mixins.jade | 4 +- .../views/shared/tasks/task_view/skills.jade | 2 +- .../views/static/{api-v2.jade => api.jade} | 6 +- website/views/static/front.jade | 1 - website/views/static/maintenance-info.jade | 2 +- website/views/static/maintenance.jade | 18 - 993 files changed, 12914 insertions(+), 44919 deletions(-) rename common/img/sprites/spritesmith/achievements/{achievement-spookySparkles.png => achievement-spookDust.png} (100%) rename common/img/sprites/spritesmith/achievements/{achievement-spookySparkles2x.png => achievement-spookDust2x.png} (100%) rename common/img/sprites/spritesmith/misc/{inventory_special_spookySparkles.png => inventory_special_spookDust.png} (100%) rename common/img/sprites/spritesmith/misc/{ghost.png => spookman.png} (100%) rename common/img/sprites/spritesmith/shop/{shop_spookySparkles.png => shop_spookDust.png} (100%) rename common/img/sprites/spritesmith/skills/{shop_healAll.png => shop_heallAll.png} (100%) delete mode 100644 common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png create mode 100644 common/script/fns/cron.js create mode 100644 common/script/fns/getItem.js create mode 100644 common/script/fns/preenUserHistory.js delete mode 100644 common/script/fns/resetGear.js create mode 100644 common/script/libs/countExists.js create mode 100644 common/script/libs/encodeiCalLink.js delete mode 100644 common/script/libs/errors.js delete mode 100644 common/script/libs/extendableBuiltin.js create mode 100644 common/script/libs/friendlyTimestamp.js create mode 100644 common/script/libs/index.js create mode 100644 common/script/libs/newChatMessages.js delete mode 100644 common/script/libs/pickDeep.js create mode 100644 common/script/libs/preenHistory.js create mode 100644 common/script/libs/removeWhitespace.js delete mode 100644 common/script/libs/statsComputed.js delete mode 100644 common/script/ops/buyArmoire.js delete mode 100644 common/script/ops/buyGear.js delete mode 100644 common/script/ops/buyHealthPotion.js delete mode 100644 common/script/ops/markPMSRead.js create mode 100644 common/script/ops/score.js delete mode 100644 common/script/ops/scoreTask.js create mode 100644 common/script/public/userServices.js delete mode 100644 migrations/20160521_veteran_ladder.js delete mode 100644 migrations/api_v3/challenges.js delete mode 100644 migrations/api_v3/challengesMembers.js delete mode 100644 migrations/api_v3/coupons.js delete mode 100644 migrations/api_v3/emailUnsubscriptions.js delete mode 100644 migrations/api_v3/groups.js delete mode 100644 migrations/api_v3/indexes.js delete mode 100644 migrations/api_v3/users.js create mode 100644 newrelic.js delete mode 100644 tasks/gulp-apidoc.js create mode 100644 test/README.md delete mode 100644 test/api/v2/user/tasks/POST-clear-completed.test.js delete mode 100644 test/api/v3/README.md delete mode 100644 test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js delete mode 100644 test/api/v3/integration/challenges/GET-challenges_user.test.js delete mode 100644 test/api/v3/integration/challenges/POST-challenges.test.js delete mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js delete mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js delete mode 100644 test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js delete mode 100644 test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js delete mode 100644 test/api/v3/integration/chat/DELETE-chat_id.test.js delete mode 100644 test/api/v3/integration/chat/GET-chat.test.js delete mode 100644 test/api/v3/integration/chat/POST-chat.flag.test.js delete mode 100644 test/api/v3/integration/chat/POST-chat.like.test.js delete mode 100644 test/api/v3/integration/chat/POST-chat.test.js delete mode 100644 test/api/v3/integration/chat/POST-chat_seen.test.js delete mode 100644 test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js delete mode 100644 test/api/v3/integration/content/GET-content.test.js delete mode 100644 test/api/v3/integration/coupons/GET-coupons.test.js delete mode 100644 test/api/v3/integration/coupons/POST-coupons_enter_code.test.js delete mode 100644 test/api/v3/integration/coupons/POST-coupons_generate_event.test.js delete mode 100644 test/api/v3/integration/coupons/POST-coupons_validate_code.test.js delete mode 100644 test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js delete mode 100644 test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js delete mode 100644 test/api/v3/integration/dataexport/GET-export_history.csv.test.js delete mode 100644 test/api/v3/integration/dataexport/GET-export_userdata.json.test.js delete mode 100644 test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_addHourglass.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_addTenGems.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_make-admin.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_modify-inventory.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_quest-progress.test.js delete mode 100644 test/api/v3/integration/debug/POST-debug_set-cron.test.js delete mode 100644 test/api/v3/integration/emails/GET-email-unsubscribe.test.js delete mode 100644 test/api/v3/integration/groups/GET-groups.test.js delete mode 100644 test/api/v3/integration/groups/GET-groups_groupId_invites.test.js delete mode 100644 test/api/v3/integration/groups/GET-groups_groupId_members.test.js delete mode 100644 test/api/v3/integration/groups/GET-groups_id.test.js delete mode 100644 test/api/v3/integration/groups/POST-groups.test.js delete mode 100644 test/api/v3/integration/groups/POST-groups_groupId_join.test.js delete mode 100644 test/api/v3/integration/groups/POST-groups_groupId_leave.js delete mode 100644 test/api/v3/integration/groups/POST-groups_groupId_reject.test.js delete mode 100644 test/api/v3/integration/groups/POST-groups_id_removeMember.test.js delete mode 100644 test/api/v3/integration/groups/POST-groups_invite.test.js delete mode 100644 test/api/v3/integration/groups/PUT-groups.test.js delete mode 100644 test/api/v3/integration/hall/GET-hall_heroes.test.js delete mode 100644 test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js delete mode 100644 test/api/v3/integration/hall/GET-hall_patrons.test.js delete mode 100644 test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js delete mode 100644 test/api/v3/integration/members/GET-members_id.test.js delete mode 100644 test/api/v3/integration/members/POST-send_private_message.test.js delete mode 100644 test/api/v3/integration/members/POST-transfer_gems.test.js delete mode 100644 test/api/v3/integration/models/GET-model_paths.test.js delete mode 100644 test/api/v3/integration/notFound.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js delete mode 100644 test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js delete mode 100644 test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js delete mode 100644 test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js delete mode 100644 test/api/v3/integration/status/GET-status.test.js delete mode 100644 test/api/v3/integration/tags/DELETE-tags_id.test.js delete mode 100644 test/api/v3/integration/tags/GET-tags.test.js delete mode 100644 test/api/v3/integration/tags/GET-tags_id.test.js delete mode 100644 test/api/v3/integration/tags/POST-tag-reorder.test.js delete mode 100644 test/api/v3/integration/tags/POST-tags.test.js delete mode 100644 test/api/v3/integration/tags/PUT-tags_id.test.js delete mode 100644 test/api/v3/integration/tasks/DELETE-tasks_id.test.js delete mode 100644 test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js delete mode 100644 test/api/v3/integration/tasks/GET-tasks_id.test.js delete mode 100644 test/api/v3/integration/tasks/GET-tasks_user.test.js delete mode 100644 test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js delete mode 100644 test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js delete mode 100644 test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js delete mode 100644 test/api/v3/integration/tasks/POST-tasks_user.test.js delete mode 100644 test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js delete mode 100644 test/api/v3/integration/tasks/PUT-tasks_id.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js delete mode 100644 test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js delete mode 100644 test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js delete mode 100644 test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js delete mode 100644 test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js delete mode 100644 test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js delete mode 100644 test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js delete mode 100644 test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js delete mode 100644 test/api/v3/integration/user/DELETE-user.test.js delete mode 100644 test/api/v3/integration/user/DELETE-user_delete_webhook.test.js delete mode 100644 test/api/v3/integration/user/DELETE-user_messages.test.js delete mode 100644 test/api/v3/integration/user/GET-user.test.js delete mode 100644 test/api/v3/integration/user/GET-user_anonymized.test.js delete mode 100644 test/api/v3/integration/user/GET-user_inventory_buy.test.js delete mode 100644 test/api/v3/integration/user/POST-user_addPushDevice.test.js delete mode 100644 test/api/v3/integration/user/POST-user_add_webhook.test.js delete mode 100644 test/api/v3/integration/user/POST-user_allocate.test.js delete mode 100644 test/api/v3/integration/user/POST-user_allocate_now.test.js delete mode 100644 test/api/v3/integration/user/POST-user_block.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_armoire.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_gear.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_health_potion.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_mystery_set.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_quest.test.js delete mode 100644 test/api/v3/integration/user/POST-user_buy_special_spell.test.js delete mode 100644 test/api/v3/integration/user/POST-user_change-class.test.js delete mode 100644 test/api/v3/integration/user/POST-user_class_cast_spellId.test.js delete mode 100644 test/api/v3/integration/user/POST-user_custom-day-start.test.js delete mode 100644 test/api/v3/integration/user/POST-user_disable-classes.test.js delete mode 100644 test/api/v3/integration/user/POST-user_equip_type_key.test.js delete mode 100644 test/api/v3/integration/user/POST-user_feed_pet_food.test.js delete mode 100644 test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js delete mode 100644 test/api/v3/integration/user/POST-user_mark_pms_read.test.js delete mode 100644 test/api/v3/integration/user/POST-user_open_mystery_item.test.js delete mode 100644 test/api/v3/integration/user/POST-user_purchase.test.js delete mode 100644 test/api/v3/integration/user/POST-user_purchase_hourglass.test.js delete mode 100644 test/api/v3/integration/user/POST-user_read_card.test.js delete mode 100644 test/api/v3/integration/user/POST-user_rebirth.test.js delete mode 100644 test/api/v3/integration/user/POST-user_release_both.test.js delete mode 100644 test/api/v3/integration/user/POST-user_release_mounts.test.js delete mode 100644 test/api/v3/integration/user/POST-user_release_pets.test.js delete mode 100644 test/api/v3/integration/user/POST-user_reroll.test.js delete mode 100644 test/api/v3/integration/user/POST-user_reset.test.js delete mode 100644 test/api/v3/integration/user/POST-user_revive.test.js delete mode 100644 test/api/v3/integration/user/POST-user_sell.test.js delete mode 100644 test/api/v3/integration/user/POST-user_sleep.test.js delete mode 100644 test/api/v3/integration/user/POST-user_unlock.js delete mode 100644 test/api/v3/integration/user/PUT-user.test.js delete mode 100644 test/api/v3/integration/user/PUT-user_update_webhook.test.js delete mode 100644 test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js delete mode 100644 test/api/v3/integration/user/auth/GET-logout.test.js delete mode 100644 test/api/v3/integration/user/auth/POST-firebase.test.js delete mode 100644 test/api/v3/integration/user/auth/POST-login-local.test.js delete mode 100644 test/api/v3/integration/user/auth/POST-register_local.test.js delete mode 100644 test/api/v3/integration/user/auth/POST-user_reset_password.test.js delete mode 100644 test/api/v3/integration/user/auth/PUT-user_update_email.test.js delete mode 100644 test/api/v3/integration/user/auth/PUT-user_update_password.test.js delete mode 100644 test/api/v3/integration/user/auth/PUT-user_update_username.test.js delete mode 100644 test/api/v3/unit/libs/analyticsService.test.js delete mode 100644 test/api/v3/unit/libs/baseModel.test.js delete mode 100644 test/api/v3/unit/libs/buildManifest.test.js delete mode 100644 test/api/v3/unit/libs/collectionManipulators.test.js delete mode 100644 test/api/v3/unit/libs/cron.test.js delete mode 100644 test/api/v3/unit/libs/email.test.js delete mode 100644 test/api/v3/unit/libs/encryption.test.js delete mode 100644 test/api/v3/unit/libs/errors.test.js delete mode 100644 test/api/v3/unit/libs/i18n.test.js delete mode 100644 test/api/v3/unit/libs/logger.js delete mode 100644 test/api/v3/unit/libs/password.test.js delete mode 100644 test/api/v3/unit/libs/payments.test.js delete mode 100644 test/api/v3/unit/libs/preening.test.js delete mode 100644 test/api/v3/unit/libs/setupNconf.test.js delete mode 100644 test/api/v3/unit/libs/webhooks.test.js delete mode 100644 test/api/v3/unit/middlewares/analytics.test.js delete mode 100644 test/api/v3/unit/middlewares/cors.test.js delete mode 100644 test/api/v3/unit/middlewares/cronMiddleware.js delete mode 100644 test/api/v3/unit/middlewares/ensureAccessRight.test.js delete mode 100644 test/api/v3/unit/middlewares/ensureDevelpmentMode.js delete mode 100644 test/api/v3/unit/middlewares/errorHandler.test.js delete mode 100644 test/api/v3/unit/middlewares/language.test.js delete mode 100644 test/api/v3/unit/middlewares/maintenanceMode.test.js delete mode 100644 test/api/v3/unit/middlewares/response.js delete mode 100644 test/api/v3/unit/models/challenge.test.js delete mode 100644 test/api/v3/unit/models/group.test.js delete mode 100644 test/api/v3/unit/models/task.test.js delete mode 100644 test/api/v3/unit/models/user.test.js create mode 100644 test/common/algos.mocha.js create mode 100644 test/common/dailies.js delete mode 100644 test/common/fns/autoAllocate.test.js delete mode 100644 test/common/fns/crit.test.js delete mode 100644 test/common/fns/handleTwoHanded.js delete mode 100644 test/common/fns/predictableRandom.test.js delete mode 100644 test/common/fns/randomDrop.test.js delete mode 100644 test/common/fns/randomVal.js delete mode 100644 test/common/fns/statsComputed.test.js delete mode 100644 test/common/fns/ultimateGear.js delete mode 100644 test/common/fns/updateStats.test.js delete mode 100644 test/common/libs/appliedTags.test.js delete mode 100644 test/common/libs/gold.test.js delete mode 100644 test/common/libs/noTags.test.js delete mode 100644 test/common/libs/percent.test.js delete mode 100644 test/common/libs/pickDeep.js delete mode 100644 test/common/libs/refPush.js delete mode 100644 test/common/libs/silver.test.js delete mode 100644 test/common/libs/splitWhitespace.test.js delete mode 100644 test/common/libs/taskClasses.test.js delete mode 100644 test/common/libs/taskDefaults.test.js delete mode 100644 test/common/libs/updateStore.js delete mode 100644 test/common/ops/addPushDevice.js delete mode 100644 test/common/ops/addTask.js delete mode 100644 test/common/ops/addWebhook.test.js delete mode 100644 test/common/ops/allocate.js delete mode 100644 test/common/ops/allocateNow.js delete mode 100644 test/common/ops/blockUser.test.js delete mode 100644 test/common/ops/buy.js delete mode 100644 test/common/ops/buyArmoire.js delete mode 100644 test/common/ops/buyGear.js delete mode 100644 test/common/ops/buyHealthPotion.js delete mode 100644 test/common/ops/buyMysterySet.js delete mode 100644 test/common/ops/buyQuest.js delete mode 100644 test/common/ops/buySpecialSpell.js delete mode 100644 test/common/ops/changeClass.js delete mode 100644 test/common/ops/clearCompleted.js delete mode 100644 test/common/ops/clearPMs.test.js delete mode 100644 test/common/ops/deletePM.test.js delete mode 100644 test/common/ops/deleteWebhook.test.js delete mode 100644 test/common/ops/disableClasses.js delete mode 100644 test/common/ops/equip.js delete mode 100644 test/common/ops/feed.js delete mode 100644 test/common/ops/hatch.js delete mode 100644 test/common/ops/hourglassPurchase.js delete mode 100644 test/common/ops/openMysteryItem.js delete mode 100644 test/common/ops/purchase.js delete mode 100644 test/common/ops/readCard.js delete mode 100644 test/common/ops/rebirth.js delete mode 100644 test/common/ops/releaseBoth.js delete mode 100644 test/common/ops/releaseMounts.js delete mode 100644 test/common/ops/releasePets.js delete mode 100644 test/common/ops/reroll.js delete mode 100644 test/common/ops/reset.js delete mode 100644 test/common/ops/revive.js delete mode 100644 test/common/ops/scoreTask.test.js delete mode 100644 test/common/ops/sell.js delete mode 100644 test/common/ops/sleep.js delete mode 100644 test/common/ops/unlock.js delete mode 100644 test/common/ops/updateTask.js delete mode 100644 test/common/ops/updateWebhook.test.js create mode 100644 test/common/preenTodos.test.js create mode 100644 test/common/shared.spells.test.js create mode 100644 test/common/simulations/autoAllocate.js create mode 100644 test/common/simulations/passive_active_attrs.js create mode 100644 test/common/user.fns.buy.test.js create mode 100644 test/common/user.fns.ultimateGear.test.js create mode 100644 test/common/user.fns.updateStats.test.js create mode 100644 test/common/user.ops.buyMysterySet.test.js create mode 100644 test/common/user.ops.equip.test.js create mode 100644 test/common/user.ops.hatch.js create mode 100644 test/common/user.ops.hourglassPurchase.test.js create mode 100644 test/common/user.ops.test.js create mode 100644 test/helpers/api-integration/mongo.js delete mode 100644 test/helpers/api-integration/v3/index.js delete mode 100644 test/helpers/api-integration/v3/object-generators.js delete mode 100644 test/helpers/api-unit.helper.js delete mode 100644 test/helpers/api-v3-integration.helper.js delete mode 100644 test/helpers/mongo.js delete mode 100644 test/helpers/sleep.js create mode 100644 test/spec/chatServicesSpec.js delete mode 100644 test/spec/services/challengeServicesSpec.js delete mode 100644 test/spec/services/chatServicesSpec.js delete mode 100644 test/spec/services/tagServicesSpec.js delete mode 100644 website/client/js/controllers/guildsCtrl.js delete mode 100644 website/client/js/controllers/partyCtrl.js delete mode 100644 website/client/js/controllers/tavernCtrl.js delete mode 100644 website/client/js/services/challengeServices.js delete mode 100644 website/client/js/services/chatServices.js delete mode 100644 website/client/js/services/groupServices.js delete mode 100644 website/client/js/services/memberServices.js delete mode 100644 website/client/js/services/tagsServices.js delete mode 100644 website/client/js/services/taskServices.js delete mode 100644 website/client/js/services/userServices.js rename website/{client => public}/500.html (100%) rename website/{client => public}/apple-touch-icon-114-precomposed.png (100%) rename website/{client => public}/apple-touch-icon-144-precomposed.png (100%) rename website/{client => public}/apple-touch-icon-57-precomposed.png (100%) rename website/{client => public}/apple-touch-icon-72-precomposed.png (100%) rename website/{client => public}/apple-touch-icon-precomposed.png (100%) rename website/{client => public}/cake.png (100%) rename website/{client => public}/community-guidelines-images/backCorner.png (100%) rename website/{client => public}/community-guidelines-images/beingHabitican.png (100%) rename website/{client => public}/community-guidelines-images/consequences.png (100%) rename website/{client => public}/community-guidelines-images/contributing.png (100%) rename website/{client => public}/community-guidelines-images/github.gif (100%) rename website/{client => public}/community-guidelines-images/infractions.png (100%) rename website/{client => public}/community-guidelines-images/intro.png (100%) rename website/{client => public}/community-guidelines-images/moderators.png (100%) rename website/{client => public}/community-guidelines-images/publicGuilds.png (100%) rename website/{client => public}/community-guidelines-images/publicSpaces.png (100%) rename website/{client => public}/community-guidelines-images/restoration.png (100%) rename website/{client => public}/community-guidelines-images/staff.png (100%) rename website/{client => public}/community-guidelines-images/tavern.png (100%) rename website/{client => public}/community-guidelines-images/trello.png (100%) rename website/{client => public}/community-guidelines-images/wiki.png (100%) rename website/{client => public}/css/README.md (100%) rename website/{client => public}/css/alerts.styl (100%) rename website/{client => public}/css/avatar.styl (100%) rename website/{client => public}/css/challenges.styl (100%) rename website/{client => public}/css/classes.styl (100%) rename website/{client => public}/css/customizer.styl (100%) rename website/{client => public}/css/filters.styl (100%) rename website/{client => public}/css/footer.styl (100%) rename website/{client => public}/css/game-pane.styl (100%) rename website/{client => public}/css/global-colors.styl (100%) rename website/{client => public}/css/global-modules.styl (100%) rename website/{client => public}/css/header.styl (100%) rename website/{client => public}/css/helpers.styl (100%) rename website/{client => public}/css/index.styl (100%) rename website/{client => public}/css/inventory.styl (100%) rename website/{client => public}/css/items.styl (100%) rename website/{client => public}/css/menu.styl (100%) rename website/{client => public}/css/no-script.styl (100%) rename website/{client => public}/css/npcs.styl (100%) rename website/{client => public}/css/options.styl (100%) rename website/{client => public}/css/quests.styl (100%) rename website/{client => public}/css/scrollbars.styl (100%) rename website/{client => public}/css/shared.styl (100%) rename website/{client => public}/css/static.styl (100%) rename website/{client => public}/css/tasks.styl (100%) rename website/{client => public}/css/variables/screen-size.styl (100%) rename website/{client => public}/emails/images/10-days-recapture-v1.png (100%) rename website/{client => public}/emails/images/3-days-1-month-recapture-v1.png (100%) rename website/{client => public}/emails/images/PROMO-Enchanted-Armoire-v1.png (100%) rename website/{client => public}/emails/images/android-promo-v1.png (100%) rename website/{client => public}/emails/images/iphone-promo-v1.png (100%) rename website/{client => public}/emails/images/one-day-v1.png (100%) rename website/{client => public}/emails/images/spring-2015-00-v1.png (100%) rename website/{client => public}/emails/images/spring-2015-01-v1.png (100%) rename website/{client => public}/emails/images/subscription-begins-time-travelers-v1.png (100%) rename website/{client => public}/emails/images/subscription-begins-v1.png (100%) rename website/{client => public}/favicon.ico (100%) rename website/{client => public}/favicon_192x192.png (100%) rename website/{client => public}/fontello/LICENSE.txt (100%) rename website/{client => public}/fontello/README.txt (100%) rename website/{client => public}/fontello/css/animation.css (100%) rename website/{client => public}/fontello/css/fontelico-codes.css (100%) rename website/{client => public}/fontello/css/fontelico-embedded.css (100%) rename website/{client => public}/fontello/css/fontelico-ie7-codes.css (100%) rename website/{client => public}/fontello/css/fontelico-ie7.css (100%) rename website/{client => public}/fontello/css/fontelico.css (100%) rename website/{client => public}/fontello/demo.html (100%) rename website/{client => public}/fontello/font/fontelico.eot (100%) rename website/{client => public}/fontello/font/fontelico.svg (100%) rename website/{client => public}/fontello/font/fontelico.ttf (100%) rename website/{client => public}/fontello/font/fontelico.woff (100%) rename website/{client => public}/front/README.md (100%) rename website/{client => public}/front/css/blockScroll.css (100%) rename website/{client => public}/front/css/bootstrap.min.css (100%) rename website/{client => public}/front/css/fixed-positioning.css (100%) rename website/{client => public}/front/fonts/glyphicons-halflings-regular.eot (100%) rename website/{client => public}/front/fonts/glyphicons-halflings-regular.svg (100%) rename website/{client => public}/front/fonts/glyphicons-halflings-regular.ttf (100%) rename website/{client => public}/front/fonts/glyphicons-halflings-regular.woff (100%) rename website/{client => public}/front/fonts/glyphicons-halflings-regular.woff2 (100%) rename website/{client => public}/front/images/Feeding_Time.png (100%) rename website/{client => public}/front/images/Guilds Sample Screen.png (100%) rename website/{client => public}/front/images/HabitRPGPromoPostCard6.png (100%) rename website/{client => public}/front/images/HabitRPGPromoThin.png (100%) rename website/{client => public}/front/images/Habitica_banner_by_uncommoncriminal.png (100%) rename website/{client => public}/front/images/Habitica_map_by_uncommoncriminal.png (100%) rename website/{client => public}/front/images/Healer.png (100%) rename website/{client => public}/front/images/Mount.png (100%) rename website/{client => public}/front/images/Mount_Body_Dragon-Golden.png (100%) rename website/{client => public}/front/images/Mount_Body_Dragon-Red.png (100%) rename website/{client => public}/front/images/Mount_Body_Wolf-Base.png (100%) rename website/{client => public}/front/images/Mount_Head_Dragon-Golden.png (100%) rename website/{client => public}/front/images/Mount_Head_Dragon-Red.png (100%) rename website/{client => public}/front/images/Mount_Head_Wolf-Base.png (100%) rename website/{client => public}/front/images/Party-Header.png (100%) rename website/{client => public}/front/images/Pet-Dragon-Red.png (100%) rename website/{client => public}/front/images/Pet-Fox-Red.png (100%) rename website/{client => public}/front/images/Promo_springclasses2015.png (100%) rename website/{client => public}/front/images/Quest_dilatory_drag'on.png (100%) rename website/{client => public}/front/images/Quest_dilatory_drag'onSmall.png (100%) rename website/{client => public}/front/images/Rogue.png (100%) rename website/{client => public}/front/images/SAMPLEadventurers.png (100%) rename website/{client => public}/front/images/TVreward.png (100%) rename website/{client => public}/front/images/VICE_by_Baconsaur.png (100%) rename website/{client => public}/front/images/Warrior.png (100%) rename website/{client => public}/front/images/Wizard.png (100%) rename website/{client => public}/front/images/achievement-perfect.png (100%) rename website/{client => public}/front/images/achievement-triadbingo.png (100%) rename website/{client => public}/front/images/avatar/Warrior.png (100%) rename website/{client => public}/front/images/avatar/avatar.png (100%) rename website/{client => public}/front/images/avatar/avatarstatic.png (100%) rename website/{client => public}/front/images/avatar/hair_bangs_1_brown.png (100%) rename website/{client => public}/front/images/avatar/head_0.png (100%) rename website/{client => public}/front/images/avatar/head_warrior_3.png (100%) rename website/{client => public}/front/images/avatar/head_warrior_5.png (100%) rename website/{client => public}/front/images/avatar/shield_warrior_3.png (100%) rename website/{client => public}/front/images/avatar/shield_warrior_5.png (100%) rename website/{client => public}/front/images/avatar/skin_f5a76e.png (100%) rename website/{client => public}/front/images/avatar/slim_armor_warrior_3.png (100%) rename website/{client => public}/front/images/avatar/slim_armor_warrior_5.png (100%) rename website/{client => public}/front/images/avatar/slim_shirt_black.png (100%) rename website/{client => public}/front/images/avatar/weapon_healer_6.png (100%) rename website/{client => public}/front/images/avatar/weapon_warrior_3.png (100%) rename website/{client => public}/front/images/avatar/weapon_warrior_5.png (100%) rename website/{client => public}/front/images/blackish_fox_by_kellllly-d7pzd46.png (100%) rename website/{client => public}/front/images/coding_by_phoneix_faerie.png (100%) rename website/{client => public}/front/images/devices.png (100%) rename website/{client => public}/front/images/explosion.jpg (100%) rename website/{client => public}/front/images/explosion.png (100%) rename website/{client => public}/front/images/habitrpg_pixel.png (100%) rename website/{client => public}/front/images/icon175x175.png (100%) rename website/{client => public}/front/images/intro.jpg (100%) rename website/{client => public}/front/images/intro.psd (100%) rename website/{client => public}/front/images/misc/Pet_Food_Cake_Base.png (100%) rename website/{client => public}/front/images/misc/inventory_quest_scroll_harpy.png (100%) rename website/{client => public}/front/images/misc/rebirth_orb.png (100%) rename website/{client => public}/front/images/misc/shop_gold.png (100%) rename website/{client => public}/front/images/misc/shop_potion.png (100%) rename website/{client => public}/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png (100%) rename website/{client => public}/front/images/party/AnnaCosplay.png (100%) rename website/{client => public}/front/images/party/Ariel_cosplay.png (100%) rename website/{client => public}/front/images/party/Big_Daddy_(BioShock).png (100%) rename website/{client => public}/front/images/party/Cosplay_Daenerys_Targaryen.png (100%) rename website/{client => public}/front/images/party/GrimReaper.png (100%) rename website/{client => public}/front/images/party/HomeStuckLusus.png (100%) rename website/{client => public}/front/images/presslogos/Cnetlogo.png (100%) rename website/{client => public}/front/images/presslogos/Fast-Company-logo.png (100%) rename website/{client => public}/front/images/presslogos/Forbes_logo.png (100%) rename website/{client => public}/front/images/presslogos/GitHub_Logo.png (100%) rename website/{client => public}/front/images/presslogos/discover_logo.png (100%) rename website/{client => public}/front/images/presslogos/ionic-logo-blog.png (100%) rename website/{client => public}/front/images/presslogos/ionic-logo-horizontal-transparent.png (100%) rename website/{client => public}/front/images/presslogos/kickstarter-logo.png (100%) rename website/{client => public}/front/images/presslogos/landing_slack_hash_wordmark_logo.png (100%) rename website/{client => public}/front/images/presslogos/lifehacker.png (100%) rename website/{client => public}/front/images/presslogos/logo_webstorm.png (100%) rename website/{client => public}/front/images/presslogos/makeuseof.png (100%) rename website/{client => public}/front/images/presslogos/nyt-logo.png (100%) rename website/{client => public}/front/images/presslogos/slack.png (100%) rename website/{client => public}/front/images/presslogos/trello-logo-blue.png (100%) rename website/{client => public}/front/images/quest_vice3.png (100%) rename website/{client => public}/front/images/screenshot.png (100%) rename website/{client => public}/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png (100%) rename website/{client => public}/front/images/testimonial_by_Streak.png (100%) rename website/{client => public}/front/images/testimonials/16bitFil.png (100%) rename website/{client => public}/front/images/testimonials/AlexandraSo.png (100%) rename website/{client => public}/front/images/testimonials/Althaire.png (100%) rename website/{client => public}/front/images/testimonials/AndeeLiao.png (100%) rename website/{client => public}/front/images/testimonials/Brenna.png (100%) rename website/{client => public}/front/images/testimonials/Drag0nsilver.png (100%) rename website/{client => public}/front/images/testimonials/Drei-M.png (100%) rename website/{client => public}/front/images/testimonials/Elmi.png (100%) rename website/{client => public}/front/images/testimonials/EvaGantz.png (100%) rename website/{client => public}/front/images/testimonials/Helcura.png (100%) rename website/{client => public}/front/images/testimonials/InfH.png (100%) rename website/{client => public}/front/images/testimonials/Kai.png (100%) rename website/{client => public}/front/images/testimonials/Kazui.png (100%) rename website/{client => public}/front/images/testimonials/Zelah_Meyer.png (100%) rename website/{client => public}/front/images/testimonials/autumnesquirrel.png (100%) rename website/{client => public}/front/images/testimonials/frabjabulous.png (100%) rename website/{client => public}/front/images/testimonials/galarix.png (100%) rename website/{client => public}/front/images/testimonials/gwyn.blath.png (100%) rename website/{client => public}/front/images/testimonials/irishfeet123.png (100%) rename website/{client => public}/front/images/testimonials/skysailor.png (100%) rename website/{client => public}/front/images/testimonials/supermouse35.png (100%) rename website/{client => public}/front/images/testimonials/tonitonirocca.png (100%) rename website/{client => public}/front/images/uses/achievement-bkgd.png (100%) rename website/{client => public}/front/images/uses/clipart-rosemonkeyct-meditation.png (100%) rename website/{client => public}/front/images/uses/clipart-rosemonkeyct-meditation.psd (100%) rename website/{client => public}/front/images/uses/clipart-rosemonkeyct-reading.png (100%) rename website/{client => public}/front/images/uses/coding.png (100%) rename website/{client => public}/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png (100%) rename website/{client => public}/front/images/uses/consequences.png (100%) rename website/{client => public}/front/images/uses/dusting-bkgd.png (100%) rename website/{client => public}/front/images/uses/dusting_by_leephon.png (100%) rename website/{client => public}/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png (100%) rename website/{client => public}/front/images/uses/meditation-bkgd.png (100%) rename website/{client => public}/front/images/uses/publicSpaces.png (100%) rename website/{client => public}/front/images/uses/reading.png (100%) rename website/{client => public}/front/js/blockScroll.js (100%) rename website/{client => public}/front/js/bootstrap.min.js (100%) rename website/{client => public}/front/js/skrollr.min.js (100%) rename website/{client => public}/front/landingv1Wireframe.jpg (100%) rename website/{client => public}/front/staticstyle.css (100%) rename website/{client => public}/front/style.css (100%) rename website/{client => public}/google280633b772b94345.html (100%) rename website/{client => public}/google8ca65b6ff3506fb8.html (100%) rename website/{client => public}/googlef3b1402b0e28338a.html (100%) rename website/{client => public}/js/.eslintrc (100%) rename website/{client => public}/js/app.js (73%) rename website/{client => public}/js/controllers/authCtrl.js (71%) rename website/{client => public}/js/controllers/autoCompleteCtrl.js (100%) rename website/{client => public}/js/controllers/challengesCtrl.js (61%) rename website/{client => public}/js/controllers/chatCtrl.js (62%) rename website/{client => public}/js/controllers/copyMessageModalCtrl.js (89%) rename website/{client => public}/js/controllers/filtersCtrl.js (81%) rename website/{client => public}/js/controllers/footerCtrl.js (64%) rename website/{client => public}/js/controllers/groupsCtrl.js (72%) create mode 100644 website/public/js/controllers/guildsCtrl.js rename website/{client => public}/js/controllers/hallCtrl.js (66%) rename website/{client => public}/js/controllers/headerCtrl.js (79%) rename website/{client => public}/js/controllers/inventoryCtrl.js (92%) rename website/{client => public}/js/controllers/inviteToGroupCtrl.js (63%) rename website/{client => public}/js/controllers/memberModalCtrl.js (50%) rename website/{client => public}/js/controllers/menuCtrl.js (97%) rename website/{client => public}/js/controllers/notificationCtrl.js (97%) create mode 100644 website/public/js/controllers/partyCtrl.js rename website/{client => public}/js/controllers/rootCtrl.js (86%) rename website/{client => public}/js/controllers/settingsCtrl.js (89%) rename website/{client => public}/js/controllers/sortableInventoryCtrl.js (100%) rename website/{client => public}/js/controllers/tasksCtrl.js (71%) create mode 100644 website/public/js/controllers/tavernCtrl.js rename website/{client => public}/js/controllers/userCtrl.js (90%) rename website/{client => public}/js/directives/close-menu.directive.js (100%) rename website/{client => public}/js/directives/expand-menu.directive.js (100%) rename website/{client => public}/js/directives/focus-element.directive.js (100%) rename website/{client => public}/js/directives/from-now.directive.js (100%) rename website/{client => public}/js/directives/habitrpg-tasks.directive.js (100%) rename website/{client => public}/js/directives/hrpg-sort-checklist.directive.js (100%) rename website/{client => public}/js/directives/hrpg-sort-tags.directive.js (88%) rename website/{client => public}/js/directives/hrpg-sort-tasks.directive.js (89%) rename website/{client => public}/js/directives/popover-html-popup.directive.js (100%) rename website/{client => public}/js/directives/popover-html.directive.js (100%) rename website/{client => public}/js/directives/when-scrolled.directive.js (100%) rename website/{client => public}/js/env.js (100%) rename website/{client => public}/js/filters/money.js (100%) rename website/{client => public}/js/filters/roundLargeNumbers.js (100%) rename website/{client => public}/js/filters/taskOrdering.js (100%) rename website/{client => public}/js/filters/timezoneOffsetToUtc.js (100%) rename website/{client => public}/js/services/analyticsServices.js (100%) create mode 100644 website/public/js/services/challengeServices.js create mode 100644 website/public/js/services/chatServices.js create mode 100644 website/public/js/services/groupServices.js rename website/{client => public}/js/services/guideServices.js (97%) create mode 100644 website/public/js/services/memberServices.js rename website/{client => public}/js/services/notificationServices.js (95%) rename website/{client => public}/js/services/paymentServices.js (95%) rename website/{client => public}/js/services/questServices.js (80%) rename website/{client => public}/js/services/sharedServices.js (100%) rename website/{client => public}/js/services/socialServices.js (100%) rename website/{client => public}/js/services/statServices.js (94%) create mode 100644 website/public/js/services/taskServices.js rename website/{client => public}/js/static.js (77%) rename website/{client => public}/logo.png (100%) rename website/{client => public}/logo/HABITRPG logo version 1.psd (100%) rename website/{client => public}/logo/HABITRPG-logo-version-1.gif (100%) rename website/{client => public}/logo/habitrpg.jpg (100%) rename website/{client => public}/logo/habitrpg_bl.eps (100%) rename website/{client => public}/logo/habitrpg_pixel.png (100%) rename website/{client => public}/manifest.json (94%) rename website/{client => public}/marketing/android_iphone.png (100%) rename website/{client => public}/marketing/animals.png (100%) rename website/{client => public}/marketing/challenge.png (100%) rename website/{client => public}/marketing/devices.png (100%) rename website/{client => public}/marketing/drops.png (100%) rename website/{client => public}/marketing/education.png (100%) rename website/{client => public}/marketing/gear.png (100%) rename website/{client => public}/marketing/guild.png (100%) rename website/{client => public}/marketing/guild_small.png (100%) rename website/{client => public}/marketing/integration.png (100%) rename website/{client => public}/marketing/lefnire.png (100%) rename website/{client => public}/marketing/promos/201403_Forest_Walker.png (100%) rename website/{client => public}/marketing/promos/April14SAMPLE2.png (100%) rename website/{client => public}/marketing/screenshot.png (100%) rename website/{client => public}/marketing/social_competitve.png (100%) rename website/{client => public}/marketing/wellness.png (100%) rename website/{client => public}/merch/stickermule-logo.png (100%) rename website/{client => public}/merch/stickermule-logo.svg (100%) rename website/{client => public}/merch/stickermule.png (100%) rename website/{client => public}/merch/teespring-eu-logo.png (100%) rename website/{client => public}/merch/teespring-eu.png (100%) rename website/{client => public}/merch/teespring-logo.png (100%) rename website/{client => public}/merch/teespring-logo.svg (100%) rename website/{client => public}/merch/teespring.png (100%) rename website/{client => public}/page-loader.gif (100%) rename website/{client => public}/presskit/Boss - Basi-List.png (100%) rename website/{client => public}/presskit/Boss - Battling the Ghost Stag.png (100%) rename website/{client => public}/presskit/Boss - Laundromancer.png (100%) rename website/{client => public}/presskit/Boss - Necro-Vice.png (100%) rename website/{client => public}/presskit/Boss - SnackLess Monster.png (100%) rename website/{client => public}/presskit/Boss - Stagnant Dishes.png (100%) rename website/{client => public}/presskit/Habitica Gryphon.png (100%) rename website/{client => public}/presskit/Habitica Logo - Android.png (100%) rename website/{client => public}/presskit/Habitica Logo - Icon with Text.png (100%) rename website/{client => public}/presskit/Habitica Logo - Icon.png (100%) rename website/{client => public}/presskit/Habitica Logo - Text.png (100%) rename website/{client => public}/presskit/Habitica Logo - iOS.png (100%) rename website/{client => public}/presskit/Habitica Promo - Thin.png (100%) rename website/{client => public}/presskit/Habitica Promo.png (100%) rename website/{client => public}/presskit/Sample Screen - Boss (iOS).png (100%) rename website/{client => public}/presskit/Sample Screen - Challenges.png (100%) rename website/{client => public}/presskit/Sample Screen - Equipment.png (100%) rename website/{client => public}/presskit/Sample Screen - Guilds.png (100%) rename website/{client => public}/presskit/Sample Screen - Level Up (iOS).png (100%) rename website/{client => public}/presskit/Sample Screen - Market.png (100%) rename website/{client => public}/presskit/Sample Screen - Party (iOS).png (100%) rename website/{client => public}/presskit/Sample Screen - Pets (iOS).png (100%) rename website/{client => public}/presskit/Sample Screen - Tasks Page (iOS).png (100%) rename website/{client => public}/presskit/Sample Screen - Tasks Page.png (100%) rename website/{client => public}/presskit/World Boss - Dread Drag'on of Dilatory.png (100%) rename website/{client => public}/presskit/presskit.zip (100%) rename website/{client => public}/refresh.png (100%) delete mode 100644 website/server/controllers/api-v2/challenges.js delete mode 100644 website/server/controllers/api-v2/user.js delete mode 100644 website/server/controllers/api-v3/auth.js delete mode 100644 website/server/controllers/api-v3/challenges.js delete mode 100644 website/server/controllers/api-v3/chat.js delete mode 100644 website/server/controllers/api-v3/content.js delete mode 100644 website/server/controllers/api-v3/coupon.js delete mode 100644 website/server/controllers/api-v3/debug.js delete mode 100644 website/server/controllers/api-v3/groups.js delete mode 100644 website/server/controllers/api-v3/hall.js delete mode 100644 website/server/controllers/api-v3/iap.js delete mode 100644 website/server/controllers/api-v3/members.js delete mode 100644 website/server/controllers/api-v3/modelsPaths.js delete mode 100644 website/server/controllers/api-v3/quests.js delete mode 100644 website/server/controllers/api-v3/status.js delete mode 100644 website/server/controllers/api-v3/tags.js delete mode 100644 website/server/controllers/api-v3/tasks.js delete mode 100644 website/server/controllers/api-v3/user.js delete mode 100644 website/server/controllers/top-level/auth.js delete mode 100644 website/server/controllers/top-level/dataexport.js delete mode 100644 website/server/controllers/top-level/email.js delete mode 100644 website/server/controllers/top-level/pages.js delete mode 100644 website/server/controllers/top-level/payments/amazon.js delete mode 100644 website/server/controllers/top-level/payments/iap.js delete mode 100644 website/server/controllers/top-level/payments/paypal.js delete mode 100644 website/server/controllers/top-level/payments/stripe.js delete mode 100644 website/server/index.js delete mode 100644 website/server/libs/api-v3/amazonPayments.js delete mode 100644 website/server/libs/api-v3/analyticsService.js delete mode 100644 website/server/libs/api-v3/baseModel.js delete mode 100644 website/server/libs/api-v3/buildManifest.js delete mode 100644 website/server/libs/api-v3/collectionManipulators.js delete mode 100644 website/server/libs/api-v3/cron.js delete mode 100644 website/server/libs/api-v3/csvStringify.js delete mode 100644 website/server/libs/api-v3/email.js delete mode 100644 website/server/libs/api-v3/encryption.js delete mode 100644 website/server/libs/api-v3/errors.js delete mode 100644 website/server/libs/api-v3/firebase.js delete mode 100644 website/server/libs/api-v3/i18n.js delete mode 100644 website/server/libs/api-v3/logger.js delete mode 100644 website/server/libs/api-v3/password.js delete mode 100644 website/server/libs/api-v3/payments.js delete mode 100644 website/server/libs/api-v3/preening.js delete mode 100644 website/server/libs/api-v3/pushNotifications.js delete mode 100644 website/server/libs/api-v3/routes.js delete mode 100644 website/server/libs/api-v3/setupMongoose.js delete mode 100644 website/server/libs/api-v3/setupNconf.js delete mode 100644 website/server/libs/api-v3/setupPassport.js delete mode 100644 website/server/libs/api-v3/webhook.js delete mode 100644 website/server/middlewares/api-v3/analytics.js delete mode 100644 website/server/middlewares/api-v3/auth.js delete mode 100644 website/server/middlewares/api-v3/cors.js delete mode 100644 website/server/middlewares/api-v3/cron.js delete mode 100644 website/server/middlewares/api-v3/domain.js delete mode 100644 website/server/middlewares/api-v3/ensureAccessRight.js delete mode 100644 website/server/middlewares/api-v3/ensureDevelpmentMode.js delete mode 100644 website/server/middlewares/api-v3/errorHandler.js delete mode 100644 website/server/middlewares/api-v3/index.js delete mode 100644 website/server/middlewares/api-v3/language.js delete mode 100644 website/server/middlewares/api-v3/locals.js delete mode 100644 website/server/middlewares/api-v3/maintenanceMode.js delete mode 100644 website/server/middlewares/api-v3/notFound.js delete mode 100644 website/server/middlewares/api-v3/redirects.js delete mode 100644 website/server/middlewares/api-v3/response.js delete mode 100644 website/server/middlewares/api-v3/setupBody.js delete mode 100644 website/server/middlewares/api-v3/static.js delete mode 100644 website/server/middlewares/api-v3/v1.js delete mode 100644 website/server/middlewares/api-v3/v2.js delete mode 100644 website/server/middlewares/api-v3/v3.js delete mode 100644 website/server/models/challenge.js delete mode 100644 website/server/models/coupon.js delete mode 100644 website/server/models/emailUnsubscription.js delete mode 100644 website/server/models/group.js delete mode 100644 website/server/models/tag.js delete mode 100644 website/server/models/task.js delete mode 100644 website/server/models/user.js delete mode 100644 website/server/routes/api-v2/auth.js delete mode 100644 website/server/routes/api-v2/coupon.js delete mode 100644 website/server/routes/api-v2/unsubscription.js delete mode 100644 website/server/routes/payments.js delete mode 100644 website/server/server.js rename website/{server => src}/controllers/api-v2/auth.js (94%) create mode 100644 website/src/controllers/api-v2/challenges.js rename website/{server => src}/controllers/api-v2/coupon.js (90%) rename website/{server => src}/controllers/api-v2/groups.js (69%) rename website/{server => src}/controllers/api-v2/hall.js (96%) rename website/{server => src}/controllers/api-v2/members.js (90%) rename website/{server => src}/controllers/api-v2/unsubscription.js (80%) create mode 100644 website/src/controllers/api-v2/user.js rename website/{server/controllers/api-v2 => src/controllers}/dataexport.js (89%) create mode 100644 website/src/controllers/payments/amazon.js create mode 100644 website/src/controllers/payments/iap.js create mode 100644 website/src/controllers/payments/index.js create mode 100644 website/src/controllers/payments/paypal.js rename {scripts => website/src/controllers/payments}/paypalBillingSetup.js (99%) create mode 100644 website/src/controllers/payments/stripe.js rename website/{server/controllers/api-v2 => src/controllers}/pushNotifications.js (98%) rename website/{server/libs/api-v2 => src/libs}/analytics.js (98%) rename website/{server/libs/api-v2 => src/libs}/buildManifest.js (91%) rename website/{server/libs/api-v2 => src/libs}/firebase.js (87%) rename website/{server/libs/api-v2 => src/libs}/i18n.js (90%) rename website/{server/libs/api-v2 => src/libs}/logging.js (94%) rename website/{server/libs/api-v2 => src/libs}/utils.js (90%) rename website/{server/libs/api-v2 => src/libs}/webhook.js (100%) rename website/{server => src}/middlewares/apiThrottle.js (74%) create mode 100644 website/src/middlewares/cors.js rename website/{server/middlewares/api-v2 => src/middlewares}/domain.js (100%) rename website/{server/middlewares/api-v2 => src/middlewares}/errorHandler.js (95%) rename website/{server => src}/middlewares/forceRefresh.js (83%) rename website/{server/middlewares/api-v2 => src/middlewares}/locals.js (94%) create mode 100644 website/src/middlewares/redirects.js create mode 100644 website/src/models/challenge.js create mode 100644 website/src/models/coupon.js create mode 100644 website/src/models/emailUnsubscription.js create mode 100644 website/src/models/group.js create mode 100644 website/src/models/task.js create mode 100644 website/src/models/user.js create mode 100644 website/src/routes/api-v1.js create mode 100644 website/src/routes/api-v2/auth.js create mode 100644 website/src/routes/api-v2/coupon.js rename website/{server => src}/routes/api-v2/swagger.js (91%) create mode 100644 website/src/routes/api-v2/unsubscription.js create mode 100644 website/src/routes/dataexport.js rename website/{server => src}/routes/pages.js (94%) create mode 100644 website/src/routes/payments.js create mode 100644 website/src/server.js delete mode 100644 website/views/shared/modals/modify-inventory.jade rename website/views/static/{api-v2.jade => api.jade} (91%) delete mode 100644 website/views/static/maintenance.jade diff --git a/.babelrc b/.babelrc index 988e0d6f03..abdb3b030b 100644 --- a/.babelrc +++ b/.babelrc @@ -1,9 +1,4 @@ { "presets": ["es2015"], - "plugins": [ - ["transform-async-to-module-method", { - "module": "bluebird", - "method": "coroutine" - }] - ] + "plugins": ["syntax-async-functions","transform-regenerator"] } diff --git a/.bowerrc b/.bowerrc index 552e7d2622..4a52096b99 100644 --- a/.bowerrc +++ b/.bowerrc @@ -1,3 +1,3 @@ { - "directory": "website/client/bower_components" + "directory": "website/public/bower_components" } diff --git a/.eslintignore b/.eslintignore index 5b7664ac76..595ad011ae 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,26 +3,24 @@ common/dist/ common/transpiled-babel/ coverage/ database_reports/ +migrations/ website/build/ website/transpiled-babel/ -migrations/* - -# The files in website/client/js should be moved out and browserified -website/client/ +# The files in website/public/js should be moved out and browserified +website/public/ # Temporarilly disabled. These should be removed when the linting errors are fixed +common/script/index.js common/script/content/index.js +common/script/ops/**/*.js +common/script/fns/**/*.js +common/script/libs/**/*.js common/script/public/**/*.js -website/server/**/api-v2/**/*.js -website/server/routes/payments.js -website/server/routes/pages.js -website/server/middlewares/apiThrottle.js -website/server/middlewares/forceRefresh.js +website/src/**/*.js debug-scripts/* -scripts/* tasks/*.js gulpfile.js Gruntfile.js diff --git a/.eslintrc b/.eslintrc index bcccde1ef6..111772a5a3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,8 +2,5 @@ "extends": [ "habitrpg/server", "habitrpg/babel" - ], - "globals": { - "Promise": true - } + ] } diff --git a/.gitignore b/.gitignore index 4e3d867081..a8e969e620 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .DS_Store -website/client/gen -website/client/common -website/client/apidoc +website/public/gen +website/public/common website/transpiled-babel/ common/transpiled-babel/ node_modules @@ -10,8 +9,8 @@ node_modules config.json npm-debug.log* lib -website/client/bower_components -website/client/new-stuff.html +website/public/bower_components +website/public/new-stuff.html website/build newrelic_agent.log .bower-tmp @@ -25,7 +24,7 @@ src/*/*.map src/*/*/*.map test/*.js test/*.map -website/client/docs +website/public/docs *.sublime-workspace coverage coverage.html diff --git a/.nodemonignore b/.nodemonignore index c698b88598..5aa436cfa3 100644 --- a/.nodemonignore +++ b/.nodemonignore @@ -2,7 +2,7 @@ node_modules/** .bower-cache/** .bower-tmp/** .bower-registry/** -website/client/** +website/public/** website/views/** website/build/** .git/** diff --git a/.travis.yml b/.travis.yml index dd5a397986..86de1b5103 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: node_js node_js: - '4.3.1' before_install: - - "npm install -g npm@3" + - "npm install -g npm@2" - "npm install -g gulp" - "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10" - "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list" diff --git a/Gruntfile.js b/Gruntfile.js index 8f716518fe..b5f507c50a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -43,11 +43,11 @@ module.exports = function(grunt) { options: { compress: false, // AFTER 'include css': true, - paths: ['website/client'] + paths: ['website/public'] }, files: { - 'website/build/app.css': ['website/client/css/index.styl'], - 'website/build/static.css': ['website/client/css/static.styl'] + 'website/build/app.css': ['website/public/css/index.styl'], + 'website/build/static.css': ['website/public/css/static.styl'] } } }, @@ -55,13 +55,13 @@ module.exports = function(grunt) { copy: { build: { files: [ - {expand: true, cwd: 'website/client/', src: 'favicon.ico', dest: 'website/build/'}, - {expand: true, cwd: 'website/client/', src: 'favicon_192x192.png', dest: 'website/build/'}, + {expand: true, cwd: 'website/public/', src: 'favicon.ico', dest: 'website/build/'}, + {expand: true, cwd: 'website/public/', src: 'favicon_192x192.png', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/dist/sprites/spritesmith*.png', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/backer-only/*.gif', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/npc_ian.gif', dest: 'website/build/'}, {expand: true, cwd: '', src: 'common/img/sprites/quest_*.gif', dest: 'website/build/'}, - {expand: true, cwd: 'website/client/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'} + {expand: true, cwd: 'website/public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'} ] } }, @@ -88,9 +88,9 @@ module.exports = function(grunt) { } }); - //Load build files from client/manifest.json - grunt.registerTask('loadManifestFiles', 'Load all build files from client/manifest.json', function(){ - var files = grunt.file.readJSON('./website/client/manifest.json'); + //Load build files from public/manifest.json + grunt.registerTask('loadManifestFiles', 'Load all build files from public/manifest.json', function(){ + var files = grunt.file.readJSON('./website/public/manifest.json'); var uglify = {}; var cssmin = {}; @@ -101,7 +101,7 @@ module.exports = function(grunt) { _.each(files[key].js, function(val){ var path = "./"; if( val.indexOf('common/') == -1) - path = './website/client/'; + path = './website/public/'; js.push(path + val); }); @@ -110,7 +110,7 @@ module.exports = function(grunt) { _.each(files[key].css, function(val){ var path = "./"; if( val.indexOf('common/') == -1) { - path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client/'; + path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/public/'; } css.push(path + val) }); @@ -122,7 +122,7 @@ module.exports = function(grunt) { grunt.config.set('cssmin.build.files', cssmin); // Rewrite urls to relative path - grunt.config.set('cssmin.build.options', {'target': 'website/client/css/whatever-css.css'}); + grunt.config.set('cssmin.build.options', {'target': 'website/public/css/whatever-css.css'}); }); // Register tasks. @@ -131,7 +131,7 @@ module.exports = function(grunt) { grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']); grunt.registerTask('test:prepare:translations', function() { - var i18n = require('./website/server/libs/api-v3/i18n'), + var i18n = require('./website/src/libs/i18n'), fs = require('fs'); fs.writeFileSync('test/spec/mocks/translations.js', "if(!window.env) window.env = {};\n" + diff --git a/Procfile b/Procfile index 72e2be7947..5283f654f1 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: node ./website/transpiled-babel/index.js +web: node ./website/transpiled-babel/server.js diff --git a/bower.json b/bower.json index 7e6e6f6790..8788581cd1 100644 --- a/bower.json +++ b/bower.json @@ -9,7 +9,7 @@ "ignore": [ "**/.*", "node_modules", - "website/client/bower_components", + "public/bower_components", "test", "tests" ], diff --git a/common/browserify.js b/common/browserify.js index 3653cb81b0..0530144839 100644 --- a/common/browserify.js +++ b/common/browserify.js @@ -1,5 +1,3 @@ -require('babel-polyfill'); - var shared = require('./script/index'); var _ = require('lodash'); var moment = require('moment'); diff --git a/common/dist/sprites/spritesmith-main-0.css b/common/dist/sprites/spritesmith-main-0.css index 28970a6d42..33de4f76f8 100644 --- a/common/dist/sprites/spritesmith-main-0.css +++ b/common/dist/sprites/spritesmith-main-0.css @@ -346,13 +346,13 @@ width: 48px; height: 52px; } -.achievement-spookySparkles { +.achievement-spookDust { background-image: url(spritesmith-main-0.png); background-position: -25px -1601px; width: 24px; height: 26px; } -.achievement-spookySparkles2x { +.achievement-spookDust2x { background-image: url(spritesmith-main-0.png); background-position: -980px -1548px; width: 48px; diff --git a/common/dist/sprites/spritesmith-main-11.css b/common/dist/sprites/spritesmith-main-11.css index 6c97f196e0..0c5b59a361 100644 --- a/common/dist/sprites/spritesmith-main-11.css +++ b/common/dist/sprites/spritesmith-main-11.css @@ -1960,31 +1960,31 @@ width: 81px; height: 99px; } -.Pet-Lion-Veteran { +.Pet-LionCub-Base { background-image: url(spritesmith-main-11.png); background-position: -1640px -700px; width: 81px; height: 99px; } -.Pet-LionCub-Base { +.Pet-LionCub-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1640px -800px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyBlue { +.Pet-LionCub-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1640px -900px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyPink { +.Pet-LionCub-Desert { background-image: url(spritesmith-main-11.png); background-position: -1640px -1000px; width: 81px; height: 99px; } -.Pet-LionCub-Desert { +.Pet-LionCub-Floral { background-image: url(spritesmith-main-11.png); background-position: -1640px -1100px; width: 81px; diff --git a/common/dist/sprites/spritesmith-main-11.png b/common/dist/sprites/spritesmith-main-11.png index 8f1f71ebf3f8894b42c38996a821660174f70e54..1a3e1517449e0dfe18b775cfdd8eb683b3b0d800 100644 GIT binary patch delta 84161 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 z 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%0DJ_slSr5%dBKc}S%|9{ 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Þ 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 K9@6gPQU3?!@;s#g diff --git a/common/dist/sprites/spritesmith-main-12.css b/common/dist/sprites/spritesmith-main-12.css index f5cff687d1..1909b7da65 100644 --- a/common/dist/sprites/spritesmith-main-12.css +++ b/common/dist/sprites/spritesmith-main-12.css @@ -1,1554 +1,1548 @@ -.Pet-LionCub-Floral { +.Pet-LionCub-Golden { background-image: url(spritesmith-main-12.png); background-position: -82px 0px; width: 81px; height: 99px; } -.Pet-LionCub-Golden { +.Pet-LionCub-Peppermint { background-image: url(spritesmith-main-12.png); background-position: -984px -900px; width: 81px; height: 99px; } -.Pet-LionCub-Peppermint { +.Pet-LionCub-Red { background-image: url(spritesmith-main-12.png); background-position: -164px 0px; width: 81px; height: 99px; } -.Pet-LionCub-Red { +.Pet-LionCub-Shade { background-image: url(spritesmith-main-12.png); background-position: 0px -100px; width: 81px; height: 99px; } -.Pet-LionCub-Shade { +.Pet-LionCub-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -82px -100px; width: 81px; height: 99px; } -.Pet-LionCub-Skeleton { +.Pet-LionCub-Spooky { background-image: url(spritesmith-main-12.png); background-position: -164px -100px; width: 81px; height: 99px; } -.Pet-LionCub-Spooky { +.Pet-LionCub-White { background-image: url(spritesmith-main-12.png); background-position: -246px 0px; width: 81px; height: 99px; } -.Pet-LionCub-White { +.Pet-LionCub-Zombie { background-image: url(spritesmith-main-12.png); background-position: -246px -100px; width: 81px; height: 99px; } -.Pet-LionCub-Zombie { +.Pet-MagicalBee-Base { background-image: url(spritesmith-main-12.png); background-position: 0px -200px; width: 81px; height: 99px; } -.Pet-MagicalBee-Base { +.Pet-Mammoth-Base { background-image: url(spritesmith-main-12.png); background-position: -82px -200px; width: 81px; height: 99px; } -.Pet-Mammoth-Base { +.Pet-MantisShrimp-Base { background-image: url(spritesmith-main-12.png); background-position: -164px -200px; width: 81px; height: 99px; } -.Pet-MantisShrimp-Base { +.Pet-Monkey-Base { background-image: url(spritesmith-main-12.png); background-position: -246px -200px; width: 81px; height: 99px; } -.Pet-Monkey-Base { +.Pet-Monkey-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -328px 0px; width: 81px; height: 99px; } -.Pet-Monkey-CottonCandyBlue { +.Pet-Monkey-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -328px -100px; width: 81px; height: 99px; } -.Pet-Monkey-CottonCandyPink { +.Pet-Monkey-Desert { background-image: url(spritesmith-main-12.png); background-position: -328px -200px; width: 81px; height: 99px; } -.Pet-Monkey-Desert { +.Pet-Monkey-Golden { background-image: url(spritesmith-main-12.png); background-position: 0px -300px; width: 81px; height: 99px; } -.Pet-Monkey-Golden { +.Pet-Monkey-Red { background-image: url(spritesmith-main-12.png); background-position: -82px -300px; width: 81px; height: 99px; } -.Pet-Monkey-Red { +.Pet-Monkey-Shade { background-image: url(spritesmith-main-12.png); background-position: -164px -300px; width: 81px; height: 99px; } -.Pet-Monkey-Shade { +.Pet-Monkey-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -246px -300px; width: 81px; height: 99px; } -.Pet-Monkey-Skeleton { +.Pet-Monkey-White { background-image: url(spritesmith-main-12.png); background-position: -328px -300px; width: 81px; height: 99px; } -.Pet-Monkey-White { +.Pet-Monkey-Zombie { background-image: url(spritesmith-main-12.png); background-position: -410px 0px; width: 81px; height: 99px; } -.Pet-Monkey-Zombie { +.Pet-Octopus-Base { background-image: url(spritesmith-main-12.png); background-position: -410px -100px; width: 81px; height: 99px; } -.Pet-Octopus-Base { +.Pet-Octopus-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -410px -200px; width: 81px; height: 99px; } -.Pet-Octopus-CottonCandyBlue { +.Pet-Octopus-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -410px -300px; width: 81px; height: 99px; } -.Pet-Octopus-CottonCandyPink { +.Pet-Octopus-Desert { background-image: url(spritesmith-main-12.png); background-position: -492px 0px; width: 81px; height: 99px; } -.Pet-Octopus-Desert { +.Pet-Octopus-Golden { background-image: url(spritesmith-main-12.png); background-position: -492px -100px; width: 81px; height: 99px; } -.Pet-Octopus-Golden { +.Pet-Octopus-Red { background-image: url(spritesmith-main-12.png); background-position: -492px -200px; width: 81px; height: 99px; } -.Pet-Octopus-Red { +.Pet-Octopus-Shade { background-image: url(spritesmith-main-12.png); background-position: -492px -300px; width: 81px; height: 99px; } -.Pet-Octopus-Shade { +.Pet-Octopus-Skeleton { background-image: url(spritesmith-main-12.png); background-position: 0px -400px; width: 81px; height: 99px; } -.Pet-Octopus-Skeleton { +.Pet-Octopus-White { background-image: url(spritesmith-main-12.png); background-position: -82px -400px; width: 81px; height: 99px; } -.Pet-Octopus-White { +.Pet-Octopus-Zombie { background-image: url(spritesmith-main-12.png); background-position: -164px -400px; width: 81px; height: 99px; } -.Pet-Octopus-Zombie { +.Pet-Owl-Base { background-image: url(spritesmith-main-12.png); background-position: -246px -400px; width: 81px; height: 99px; } -.Pet-Owl-Base { +.Pet-Owl-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -328px -400px; width: 81px; height: 99px; } -.Pet-Owl-CottonCandyBlue { +.Pet-Owl-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -410px -400px; width: 81px; height: 99px; } -.Pet-Owl-CottonCandyPink { +.Pet-Owl-Desert { background-image: url(spritesmith-main-12.png); background-position: -492px -400px; width: 81px; height: 99px; } -.Pet-Owl-Desert { +.Pet-Owl-Golden { background-image: url(spritesmith-main-12.png); background-position: -574px 0px; width: 81px; height: 99px; } -.Pet-Owl-Golden { +.Pet-Owl-Red { background-image: url(spritesmith-main-12.png); background-position: -574px -100px; width: 81px; height: 99px; } -.Pet-Owl-Red { +.Pet-Owl-Shade { background-image: url(spritesmith-main-12.png); background-position: -574px -200px; width: 81px; height: 99px; } -.Pet-Owl-Shade { +.Pet-Owl-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -574px -300px; width: 81px; height: 99px; } -.Pet-Owl-Skeleton { +.Pet-Owl-White { background-image: url(spritesmith-main-12.png); background-position: -574px -400px; width: 81px; height: 99px; } -.Pet-Owl-White { +.Pet-Owl-Zombie { background-image: url(spritesmith-main-12.png); background-position: 0px -500px; width: 81px; height: 99px; } -.Pet-Owl-Zombie { +.Pet-PandaCub-Base { background-image: url(spritesmith-main-12.png); background-position: -82px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Base { +.Pet-PandaCub-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -164px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-CottonCandyBlue { +.Pet-PandaCub-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -246px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-CottonCandyPink { +.Pet-PandaCub-Desert { background-image: url(spritesmith-main-12.png); background-position: -328px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Desert { +.Pet-PandaCub-Floral { background-image: url(spritesmith-main-12.png); background-position: -410px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Floral { +.Pet-PandaCub-Golden { background-image: url(spritesmith-main-12.png); background-position: -492px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Golden { +.Pet-PandaCub-Peppermint { background-image: url(spritesmith-main-12.png); background-position: -574px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Peppermint { +.Pet-PandaCub-Red { background-image: url(spritesmith-main-12.png); background-position: -656px 0px; width: 81px; height: 99px; } -.Pet-PandaCub-Red { +.Pet-PandaCub-Shade { background-image: url(spritesmith-main-12.png); background-position: -656px -100px; width: 81px; height: 99px; } -.Pet-PandaCub-Shade { +.Pet-PandaCub-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -656px -200px; width: 81px; height: 99px; } -.Pet-PandaCub-Skeleton { +.Pet-PandaCub-Spooky { background-image: url(spritesmith-main-12.png); background-position: -656px -300px; width: 81px; height: 99px; } -.Pet-PandaCub-Spooky { +.Pet-PandaCub-White { background-image: url(spritesmith-main-12.png); background-position: -656px -400px; width: 81px; height: 99px; } -.Pet-PandaCub-White { +.Pet-PandaCub-Zombie { background-image: url(spritesmith-main-12.png); background-position: -656px -500px; width: 81px; height: 99px; } -.Pet-PandaCub-Zombie { +.Pet-Parrot-Base { background-image: url(spritesmith-main-12.png); background-position: 0px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Base { +.Pet-Parrot-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -82px -600px; width: 81px; height: 99px; } -.Pet-Parrot-CottonCandyBlue { +.Pet-Parrot-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -164px -600px; width: 81px; height: 99px; } -.Pet-Parrot-CottonCandyPink { +.Pet-Parrot-Desert { background-image: url(spritesmith-main-12.png); background-position: -246px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Desert { +.Pet-Parrot-Golden { background-image: url(spritesmith-main-12.png); background-position: -328px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Golden { +.Pet-Parrot-Red { background-image: url(spritesmith-main-12.png); background-position: -410px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Red { +.Pet-Parrot-Shade { background-image: url(spritesmith-main-12.png); background-position: -492px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Shade { +.Pet-Parrot-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -574px -600px; width: 81px; height: 99px; } -.Pet-Parrot-Skeleton { +.Pet-Parrot-White { background-image: url(spritesmith-main-12.png); background-position: -656px -600px; width: 81px; height: 99px; } -.Pet-Parrot-White { +.Pet-Parrot-Zombie { background-image: url(spritesmith-main-12.png); background-position: -738px 0px; width: 81px; height: 99px; } -.Pet-Parrot-Zombie { +.Pet-Penguin-Base { background-image: url(spritesmith-main-12.png); background-position: -738px -100px; width: 81px; height: 99px; } -.Pet-Penguin-Base { +.Pet-Penguin-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -738px -200px; width: 81px; height: 99px; } -.Pet-Penguin-CottonCandyBlue { +.Pet-Penguin-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -738px -300px; width: 81px; height: 99px; } -.Pet-Penguin-CottonCandyPink { +.Pet-Penguin-Desert { background-image: url(spritesmith-main-12.png); background-position: -738px -400px; width: 81px; height: 99px; } -.Pet-Penguin-Desert { +.Pet-Penguin-Golden { background-image: url(spritesmith-main-12.png); background-position: -738px -500px; width: 81px; height: 99px; } -.Pet-Penguin-Golden { +.Pet-Penguin-Red { background-image: url(spritesmith-main-12.png); background-position: -738px -600px; width: 81px; height: 99px; } -.Pet-Penguin-Red { +.Pet-Penguin-Shade { background-image: url(spritesmith-main-12.png); background-position: 0px -700px; width: 81px; height: 99px; } -.Pet-Penguin-Shade { +.Pet-Penguin-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -82px -700px; width: 81px; height: 99px; } -.Pet-Penguin-Skeleton { +.Pet-Penguin-White { background-image: url(spritesmith-main-12.png); background-position: -164px -700px; width: 81px; height: 99px; } -.Pet-Penguin-White { +.Pet-Penguin-Zombie { background-image: url(spritesmith-main-12.png); background-position: -246px -700px; width: 81px; height: 99px; } -.Pet-Penguin-Zombie { +.Pet-Phoenix-Base { background-image: url(spritesmith-main-12.png); background-position: -328px -700px; width: 81px; height: 99px; } -.Pet-Phoenix-Base { +.Pet-Rat-Base { background-image: url(spritesmith-main-12.png); background-position: -410px -700px; width: 81px; height: 99px; } -.Pet-Rat-Base { +.Pet-Rat-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -492px -700px; width: 81px; height: 99px; } -.Pet-Rat-CottonCandyBlue { +.Pet-Rat-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -574px -700px; width: 81px; height: 99px; } -.Pet-Rat-CottonCandyPink { +.Pet-Rat-Desert { background-image: url(spritesmith-main-12.png); background-position: -656px -700px; width: 81px; height: 99px; } -.Pet-Rat-Desert { +.Pet-Rat-Golden { background-image: url(spritesmith-main-12.png); background-position: -738px -700px; width: 81px; height: 99px; } -.Pet-Rat-Golden { +.Pet-Rat-Red { background-image: url(spritesmith-main-12.png); background-position: -820px 0px; width: 81px; height: 99px; } -.Pet-Rat-Red { +.Pet-Rat-Shade { background-image: url(spritesmith-main-12.png); background-position: -820px -100px; width: 81px; height: 99px; } -.Pet-Rat-Shade { +.Pet-Rat-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -820px -200px; width: 81px; height: 99px; } -.Pet-Rat-Skeleton { +.Pet-Rat-White { background-image: url(spritesmith-main-12.png); background-position: -820px -300px; width: 81px; height: 99px; } -.Pet-Rat-White { +.Pet-Rat-Zombie { background-image: url(spritesmith-main-12.png); background-position: -820px -400px; width: 81px; height: 99px; } -.Pet-Rat-Zombie { +.Pet-Rock-Base { background-image: url(spritesmith-main-12.png); background-position: -820px -500px; width: 81px; height: 99px; } -.Pet-Rock-Base { +.Pet-Rock-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -820px -600px; width: 81px; height: 99px; } -.Pet-Rock-CottonCandyBlue { +.Pet-Rock-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -820px -700px; width: 81px; height: 99px; } -.Pet-Rock-CottonCandyPink { +.Pet-Rock-Desert { background-image: url(spritesmith-main-12.png); background-position: 0px -800px; width: 81px; height: 99px; } -.Pet-Rock-Desert { +.Pet-Rock-Golden { background-image: url(spritesmith-main-12.png); background-position: -82px -800px; width: 81px; height: 99px; } -.Pet-Rock-Golden { +.Pet-Rock-Red { background-image: url(spritesmith-main-12.png); background-position: -164px -800px; width: 81px; height: 99px; } -.Pet-Rock-Red { +.Pet-Rock-Shade { background-image: url(spritesmith-main-12.png); background-position: -246px -800px; width: 81px; height: 99px; } -.Pet-Rock-Shade { +.Pet-Rock-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -328px -800px; width: 81px; height: 99px; } -.Pet-Rock-Skeleton { +.Pet-Rock-White { background-image: url(spritesmith-main-12.png); background-position: -410px -800px; width: 81px; height: 99px; } -.Pet-Rock-White { +.Pet-Rock-Zombie { background-image: url(spritesmith-main-12.png); background-position: -492px -800px; width: 81px; height: 99px; } -.Pet-Rock-Zombie { +.Pet-Rooster-Base { background-image: url(spritesmith-main-12.png); background-position: -574px -800px; width: 81px; height: 99px; } -.Pet-Rooster-Base { +.Pet-Rooster-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -656px -800px; width: 81px; height: 99px; } -.Pet-Rooster-CottonCandyBlue { +.Pet-Rooster-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -738px -800px; width: 81px; height: 99px; } -.Pet-Rooster-CottonCandyPink { +.Pet-Rooster-Desert { background-image: url(spritesmith-main-12.png); background-position: -820px -800px; width: 81px; height: 99px; } -.Pet-Rooster-Desert { +.Pet-Rooster-Golden { background-image: url(spritesmith-main-12.png); background-position: -902px 0px; width: 81px; height: 99px; } -.Pet-Rooster-Golden { +.Pet-Rooster-Red { background-image: url(spritesmith-main-12.png); background-position: -902px -100px; width: 81px; height: 99px; } -.Pet-Rooster-Red { +.Pet-Rooster-Shade { background-image: url(spritesmith-main-12.png); background-position: -902px -200px; width: 81px; height: 99px; } -.Pet-Rooster-Shade { +.Pet-Rooster-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -902px -300px; width: 81px; height: 99px; } -.Pet-Rooster-Skeleton { +.Pet-Rooster-White { background-image: url(spritesmith-main-12.png); background-position: -902px -400px; width: 81px; height: 99px; } -.Pet-Rooster-White { +.Pet-Rooster-Zombie { background-image: url(spritesmith-main-12.png); background-position: -902px -500px; width: 81px; height: 99px; } -.Pet-Rooster-Zombie { +.Pet-Sabretooth-Base { background-image: url(spritesmith-main-12.png); background-position: -902px -600px; width: 81px; height: 99px; } -.Pet-Sabretooth-Base { +.Pet-Sabretooth-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -902px -700px; width: 81px; height: 99px; } -.Pet-Sabretooth-CottonCandyBlue { +.Pet-Sabretooth-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -902px -800px; width: 81px; height: 99px; } -.Pet-Sabretooth-CottonCandyPink { +.Pet-Sabretooth-Desert { background-image: url(spritesmith-main-12.png); background-position: -984px 0px; width: 81px; height: 99px; } -.Pet-Sabretooth-Desert { +.Pet-Sabretooth-Golden { background-image: url(spritesmith-main-12.png); background-position: -984px -100px; width: 81px; height: 99px; } -.Pet-Sabretooth-Golden { +.Pet-Sabretooth-Red { background-image: url(spritesmith-main-12.png); background-position: -984px -200px; width: 81px; height: 99px; } -.Pet-Sabretooth-Red { +.Pet-Sabretooth-Shade { background-image: url(spritesmith-main-12.png); background-position: -984px -300px; width: 81px; height: 99px; } -.Pet-Sabretooth-Shade { +.Pet-Sabretooth-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -984px -400px; width: 81px; height: 99px; } -.Pet-Sabretooth-Skeleton { +.Pet-Sabretooth-White { background-image: url(spritesmith-main-12.png); background-position: -984px -500px; width: 81px; height: 99px; } -.Pet-Sabretooth-White { +.Pet-Sabretooth-Zombie { background-image: url(spritesmith-main-12.png); background-position: -984px -600px; width: 81px; height: 99px; } -.Pet-Sabretooth-Zombie { +.Pet-Seahorse-Base { background-image: url(spritesmith-main-12.png); background-position: -984px -700px; width: 81px; height: 99px; } -.Pet-Seahorse-Base { +.Pet-Seahorse-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -984px -800px; width: 81px; height: 99px; } -.Pet-Seahorse-CottonCandyBlue { +.Pet-Seahorse-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: 0px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-CottonCandyPink { +.Pet-Seahorse-Desert { background-image: url(spritesmith-main-12.png); background-position: -82px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Desert { +.Pet-Seahorse-Golden { background-image: url(spritesmith-main-12.png); background-position: -164px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Golden { +.Pet-Seahorse-Red { background-image: url(spritesmith-main-12.png); background-position: -246px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Red { +.Pet-Seahorse-Shade { background-image: url(spritesmith-main-12.png); background-position: -328px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Shade { +.Pet-Seahorse-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -410px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Skeleton { +.Pet-Seahorse-White { background-image: url(spritesmith-main-12.png); background-position: -492px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-White { +.Pet-Seahorse-Zombie { background-image: url(spritesmith-main-12.png); background-position: -574px -900px; width: 81px; height: 99px; } -.Pet-Seahorse-Zombie { +.Pet-Sheep-Base { background-image: url(spritesmith-main-12.png); background-position: -656px -900px; width: 81px; height: 99px; } -.Pet-Sheep-Base { +.Pet-Sheep-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -738px -900px; width: 81px; height: 99px; } -.Pet-Sheep-CottonCandyBlue { +.Pet-Sheep-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -820px -900px; width: 81px; height: 99px; } -.Pet-Sheep-CottonCandyPink { +.Pet-Sheep-Desert { background-image: url(spritesmith-main-12.png); background-position: -902px -900px; width: 81px; height: 99px; } -.Pet-Sheep-Desert { +.Pet-Sheep-Golden { background-image: url(spritesmith-main-12.png); background-position: 0px 0px; width: 81px; height: 99px; } -.Pet-Sheep-Golden { +.Pet-Sheep-Red { background-image: url(spritesmith-main-12.png); background-position: -1066px 0px; width: 81px; height: 99px; } -.Pet-Sheep-Red { +.Pet-Sheep-Shade { background-image: url(spritesmith-main-12.png); background-position: -1066px -100px; width: 81px; height: 99px; } -.Pet-Sheep-Shade { +.Pet-Sheep-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1066px -200px; width: 81px; height: 99px; } -.Pet-Sheep-Skeleton { +.Pet-Sheep-White { background-image: url(spritesmith-main-12.png); background-position: -1066px -300px; width: 81px; height: 99px; } -.Pet-Sheep-White { +.Pet-Sheep-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1066px -400px; width: 81px; height: 99px; } -.Pet-Sheep-Zombie { +.Pet-Slime-Base { background-image: url(spritesmith-main-12.png); background-position: -1066px -500px; width: 81px; height: 99px; } -.Pet-Slime-Base { +.Pet-Slime-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1066px -600px; width: 81px; height: 99px; } -.Pet-Slime-CottonCandyBlue { +.Pet-Slime-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1066px -700px; width: 81px; height: 99px; } -.Pet-Slime-CottonCandyPink { +.Pet-Slime-Desert { background-image: url(spritesmith-main-12.png); background-position: -1066px -800px; width: 81px; height: 99px; } -.Pet-Slime-Desert { +.Pet-Slime-Golden { background-image: url(spritesmith-main-12.png); background-position: -1066px -900px; width: 81px; height: 99px; } -.Pet-Slime-Golden { +.Pet-Slime-Red { background-image: url(spritesmith-main-12.png); background-position: 0px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Red { +.Pet-Slime-Shade { background-image: url(spritesmith-main-12.png); background-position: -82px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Shade { +.Pet-Slime-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -164px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Skeleton { +.Pet-Slime-White { background-image: url(spritesmith-main-12.png); background-position: -246px -1000px; width: 81px; height: 99px; } -.Pet-Slime-White { +.Pet-Slime-Zombie { background-image: url(spritesmith-main-12.png); background-position: -328px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Zombie { +.Pet-Snail-Base { background-image: url(spritesmith-main-12.png); background-position: -410px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Base { +.Pet-Snail-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -492px -1000px; width: 81px; height: 99px; } -.Pet-Snail-CottonCandyBlue { +.Pet-Snail-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -574px -1000px; width: 81px; height: 99px; } -.Pet-Snail-CottonCandyPink { +.Pet-Snail-Desert { background-image: url(spritesmith-main-12.png); background-position: -656px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Desert { +.Pet-Snail-Golden { background-image: url(spritesmith-main-12.png); background-position: -738px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Golden { +.Pet-Snail-Red { background-image: url(spritesmith-main-12.png); background-position: -820px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Red { +.Pet-Snail-Shade { background-image: url(spritesmith-main-12.png); background-position: -902px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Shade { +.Pet-Snail-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -984px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Skeleton { +.Pet-Snail-White { background-image: url(spritesmith-main-12.png); background-position: -1066px -1000px; width: 81px; height: 99px; } -.Pet-Snail-White { +.Pet-Snail-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1148px 0px; width: 81px; height: 99px; } -.Pet-Snail-Zombie { +.Pet-Snake-Base { background-image: url(spritesmith-main-12.png); background-position: -1148px -100px; width: 81px; height: 99px; } -.Pet-Snake-Base { +.Pet-Snake-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1148px -200px; width: 81px; height: 99px; } -.Pet-Snake-CottonCandyBlue { +.Pet-Snake-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1148px -300px; width: 81px; height: 99px; } -.Pet-Snake-CottonCandyPink { +.Pet-Snake-Desert { background-image: url(spritesmith-main-12.png); background-position: -1148px -400px; width: 81px; height: 99px; } -.Pet-Snake-Desert { +.Pet-Snake-Golden { background-image: url(spritesmith-main-12.png); background-position: -1148px -500px; width: 81px; height: 99px; } -.Pet-Snake-Golden { +.Pet-Snake-Red { background-image: url(spritesmith-main-12.png); background-position: -1148px -600px; width: 81px; height: 99px; } -.Pet-Snake-Red { +.Pet-Snake-Shade { background-image: url(spritesmith-main-12.png); background-position: -1148px -700px; width: 81px; height: 99px; } -.Pet-Snake-Shade { +.Pet-Snake-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1148px -800px; width: 81px; height: 99px; } -.Pet-Snake-Skeleton { +.Pet-Snake-White { background-image: url(spritesmith-main-12.png); background-position: -1148px -900px; width: 81px; height: 99px; } -.Pet-Snake-White { +.Pet-Snake-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1148px -1000px; width: 81px; height: 99px; } -.Pet-Snake-Zombie { +.Pet-Spider-Base { background-image: url(spritesmith-main-12.png); background-position: 0px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Base { +.Pet-Spider-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -82px -1100px; width: 81px; height: 99px; } -.Pet-Spider-CottonCandyBlue { +.Pet-Spider-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -164px -1100px; width: 81px; height: 99px; } -.Pet-Spider-CottonCandyPink { +.Pet-Spider-Desert { background-image: url(spritesmith-main-12.png); background-position: -246px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Desert { +.Pet-Spider-Golden { background-image: url(spritesmith-main-12.png); background-position: -328px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Golden { +.Pet-Spider-Red { background-image: url(spritesmith-main-12.png); background-position: -410px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Red { +.Pet-Spider-Shade { background-image: url(spritesmith-main-12.png); background-position: -492px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Shade { +.Pet-Spider-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -574px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Skeleton { +.Pet-Spider-White { background-image: url(spritesmith-main-12.png); background-position: -656px -1100px; width: 81px; height: 99px; } -.Pet-Spider-White { +.Pet-Spider-Zombie { background-image: url(spritesmith-main-12.png); background-position: -738px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -820px -1100px; - width: 81px; - height: 99px; -} .Pet-TRex-Base { - background-image: url(spritesmith-main-12.png); - background-position: -656px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -738px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -820px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -902px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -984px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Red { - background-image: url(spritesmith-main-12.png); - background-position: -1066px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -1148px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-White { - background-image: url(spritesmith-main-12.png); - background-position: -1312px 0px; - width: 81px; - height: 99px; -} -.Pet-TRex-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -1312px -100px; - width: 81px; - height: 99px; -} -.Pet-Tiger-Veteran { - background-image: url(spritesmith-main-12.png); - background-position: -902px -1100px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Base { - background-image: url(spritesmith-main-12.png); - background-position: -984px -1100px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -1066px -1100px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -1148px -1100px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -1230px 0px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Floral { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -100px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -200px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Peppermint { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -300px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Red { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -400px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -500px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -600px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Spooky { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -700px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-White { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -800px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -900px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Base { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1000px; - width: 81px; - height: 99px; -} -.Pet-Treeling-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1100px; - width: 81px; - height: 99px; -} -.Pet-Treeling-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: 0px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -82px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -164px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Red { - background-image: url(spritesmith-main-12.png); - background-position: -246px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -328px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -410px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-White { - background-image: url(spritesmith-main-12.png); - background-position: -492px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Zombie { background-image: url(spritesmith-main-12.png); background-position: -574px -1200px; width: 81px; height: 99px; } +.Pet-TRex-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -656px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -738px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -820px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -902px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Red { + background-image: url(spritesmith-main-12.png); + background-position: -984px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -1148px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-White { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1200px; + width: 81px; + height: 99px; +} +.Pet-TRex-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -1312px 0px; + width: 81px; + height: 99px; +} +.Pet-Tiger-Veteran { + background-image: url(spritesmith-main-12.png); + background-position: -820px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Base { + background-image: url(spritesmith-main-12.png); + background-position: -902px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -984px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -1148px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Floral { + background-image: url(spritesmith-main-12.png); + background-position: -1230px 0px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Peppermint { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -200px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Red { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -300px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -400px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -500px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Spooky { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -600px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-White { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -700px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -800px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Base { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -900px; + width: 81px; + height: 99px; +} +.Pet-Treeling-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1000px; + width: 81px; + height: 99px; +} +.Pet-Treeling-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1100px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Desert { + background-image: url(spritesmith-main-12.png); + background-position: 0px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -82px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Red { + background-image: url(spritesmith-main-12.png); + background-position: -164px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -246px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -328px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-White { + background-image: url(spritesmith-main-12.png); + background-position: -410px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -492px -1200px; + width: 81px; + height: 99px; +} .Pet-Turkey-Base { background-image: url(spritesmith-main-12.png); - background-position: -1312px -200px; + background-position: -1312px -100px; width: 81px; height: 99px; } .Pet-Turkey-Gilded { background-image: url(spritesmith-main-12.png); - background-position: -1312px -300px; + background-position: -1312px -200px; width: 81px; height: 99px; } .Pet-Unicorn-Base { background-image: url(spritesmith-main-12.png); - background-position: -1312px -400px; + background-position: -1312px -300px; width: 81px; height: 99px; } .Pet-Unicorn-CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1312px -500px; + background-position: -1312px -400px; width: 81px; height: 99px; } .Pet-Unicorn-CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: -1312px -600px; + background-position: -1312px -500px; width: 81px; height: 99px; } .Pet-Unicorn-Desert { background-image: url(spritesmith-main-12.png); - background-position: -1312px -700px; + background-position: -1312px -600px; width: 81px; height: 99px; } .Pet-Unicorn-Golden { background-image: url(spritesmith-main-12.png); - background-position: -1312px -800px; + background-position: -1312px -700px; width: 81px; height: 99px; } .Pet-Unicorn-Red { background-image: url(spritesmith-main-12.png); - background-position: -1312px -900px; + background-position: -1312px -800px; width: 81px; height: 99px; } .Pet-Unicorn-Shade { background-image: url(spritesmith-main-12.png); - background-position: -1312px -1000px; + background-position: -1312px -900px; width: 81px; height: 99px; } .Pet-Unicorn-Skeleton { background-image: url(spritesmith-main-12.png); - background-position: -1312px -1100px; + background-position: -1312px -1000px; width: 81px; height: 99px; } .Pet-Unicorn-White { background-image: url(spritesmith-main-12.png); - background-position: -1312px -1200px; + background-position: -1312px -1100px; width: 81px; height: 99px; } .Pet-Unicorn-Zombie { background-image: url(spritesmith-main-12.png); - background-position: -1394px 0px; + background-position: -1312px -1200px; width: 81px; height: 99px; } .Pet-Whale-Base { background-image: url(spritesmith-main-12.png); - background-position: -1394px -100px; + background-position: -1394px 0px; width: 81px; height: 99px; } .Pet-Whale-CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1394px -200px; + background-position: -1394px -100px; width: 81px; height: 99px; } .Pet-Whale-CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: -1394px -300px; + background-position: -1394px -200px; width: 81px; height: 99px; } .Pet-Whale-Desert { background-image: url(spritesmith-main-12.png); - background-position: -1394px -400px; + background-position: -1394px -300px; width: 81px; height: 99px; } .Pet-Whale-Golden { background-image: url(spritesmith-main-12.png); - background-position: -1394px -500px; + background-position: -1394px -400px; width: 81px; height: 99px; } .Pet-Whale-Red { background-image: url(spritesmith-main-12.png); - background-position: -1394px -600px; + background-position: -1394px -500px; width: 81px; height: 99px; } .Pet-Whale-Shade { background-image: url(spritesmith-main-12.png); - background-position: -1394px -700px; + background-position: -1394px -600px; width: 81px; height: 99px; } .Pet-Whale-Skeleton { background-image: url(spritesmith-main-12.png); - background-position: -1394px -800px; + background-position: -1394px -700px; width: 81px; height: 99px; } .Pet-Whale-White { background-image: url(spritesmith-main-12.png); - background-position: -1394px -900px; + background-position: -1394px -800px; width: 81px; height: 99px; } .Pet-Whale-Zombie { background-image: url(spritesmith-main-12.png); - background-position: -1394px -1000px; + background-position: -1394px -900px; width: 81px; height: 99px; } .Pet-Wolf-Base { background-image: url(spritesmith-main-12.png); - background-position: -1394px -1100px; + background-position: -1394px -1000px; width: 81px; height: 99px; } .Pet-Wolf-CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1394px -1200px; + background-position: -1394px -1100px; width: 81px; height: 99px; } .Pet-Wolf-CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: 0px -1300px; + background-position: -1394px -1200px; width: 81px; height: 99px; } .Pet-Wolf-Desert { background-image: url(spritesmith-main-12.png); - background-position: -82px -1300px; + background-position: 0px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Floral { background-image: url(spritesmith-main-12.png); - background-position: -164px -1300px; + background-position: -82px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Golden { background-image: url(spritesmith-main-12.png); - background-position: -246px -1300px; + background-position: -164px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Peppermint { background-image: url(spritesmith-main-12.png); - background-position: -328px -1300px; + background-position: -246px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Red { background-image: url(spritesmith-main-12.png); - background-position: -410px -1300px; + background-position: -328px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Shade { background-image: url(spritesmith-main-12.png); - background-position: -492px -1300px; + background-position: -410px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Skeleton { background-image: url(spritesmith-main-12.png); - background-position: -574px -1300px; + background-position: -492px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Spooky { background-image: url(spritesmith-main-12.png); - background-position: -656px -1300px; + background-position: -574px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Veteran { background-image: url(spritesmith-main-12.png); - background-position: -738px -1300px; + background-position: -656px -1300px; width: 81px; height: 99px; } .Pet-Wolf-White { background-image: url(spritesmith-main-12.png); - background-position: -820px -1300px; + background-position: -738px -1300px; width: 81px; height: 99px; } .Pet-Wolf-Zombie { background-image: url(spritesmith-main-12.png); - background-position: -902px -1300px; + background-position: -820px -1300px; width: 81px; height: 99px; } .Pet_HatchingPotion_Base { background-image: url(spritesmith-main-12.png); - background-position: -1033px -1300px; + background-position: -951px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1278px -1300px; + background-position: -1196px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: -1082px -1300px; + background-position: -1000px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Desert { background-image: url(spritesmith-main-12.png); - background-position: -1131px -1300px; + background-position: -1049px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Floral { background-image: url(spritesmith-main-12.png); - background-position: -1180px -1300px; + background-position: -1098px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Golden { background-image: url(spritesmith-main-12.png); - background-position: -1229px -1300px; + background-position: -1147px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Peppermint { background-image: url(spritesmith-main-12.png); - background-position: -984px -1300px; + background-position: -902px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Red { background-image: url(spritesmith-main-12.png); - background-position: -1327px -1300px; + background-position: -1245px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Shade { background-image: url(spritesmith-main-12.png); - background-position: -1376px -1300px; + background-position: -1294px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Skeleton { background-image: url(spritesmith-main-12.png); - background-position: -1425px -1300px; + background-position: -1343px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_Spooky { background-image: url(spritesmith-main-12.png); - background-position: 0px -1400px; + background-position: -1392px -1300px; width: 48px; height: 51px; } .Pet_HatchingPotion_White { background-image: url(spritesmith-main-12.png); - background-position: -49px -1400px; + background-position: 0px -1400px; width: 48px; height: 51px; } .Pet_HatchingPotion_Zombie { background-image: url(spritesmith-main-12.png); - background-position: -98px -1400px; + background-position: -49px -1400px; width: 48px; height: 51px; } diff --git a/common/dist/sprites/spritesmith-main-12.png b/common/dist/sprites/spritesmith-main-12.png index 7938d749c1d47b1013855132ce32a638fb723bb8..50e98d9e058b9cbe9fcdfdf29d1b2083ae1d2602 100644 GIT binary patch literal 125673 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ν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

k5qUoaSjK(Q!%IFBrzBF0VObmEW3wO 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 HQ$iB}NTGIM literal 126575 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 diff --git a/common/dist/sprites/spritesmith-main-5.css b/common/dist/sprites/spritesmith-main-5.css index 50f8d8a69a..e7245d2efd 100644 --- a/common/dist/sprites/spritesmith-main-5.css +++ b/common/dist/sprites/spritesmith-main-5.css @@ -270,7 +270,7 @@ } .shop_armor_special_candycane { background-image: url(spritesmith-main-5.png); - background-position: -1674px -451px; + background-position: -1674px -410px; width: 40px; height: 40px; } @@ -282,19 +282,19 @@ } .shop_armor_special_snowflake { background-image: url(spritesmith-main-5.png); - background-position: -1674px -656px; + background-position: -1674px -615px; width: 40px; height: 40px; } .shop_armor_special_winter2015Healer { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1230px; + background-position: -1674px -1189px; width: 40px; height: 40px; } .shop_armor_special_winter2015Mage { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1271px; + background-position: -1674px -1230px; width: 40px; height: 40px; } @@ -456,13 +456,13 @@ } .shop_shield_special_winter2015Warrior { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1517px; + background-position: -1674px -1558px; width: 40px; height: 40px; } .shop_shield_special_winter2016Healer { background-image: url(spritesmith-main-5.png); - background-position: -1599px -1599px; + background-position: -1674px -1476px; width: 40px; height: 40px; } @@ -1098,103 +1098,103 @@ } .shop_head_rogue_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1025px; + background-position: -1674px -984px; width: 40px; height: 40px; } .shop_head_rogue_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -984px; + background-position: -1674px -943px; width: 40px; height: 40px; } .shop_head_rogue_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -943px; + background-position: -1674px -902px; width: 40px; height: 40px; } .shop_head_special_0 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -902px; + background-position: -1674px -861px; width: 40px; height: 40px; } .shop_head_special_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -861px; + background-position: -1674px -820px; width: 40px; height: 40px; } .shop_head_special_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -779px; + background-position: -1674px -738px; width: 40px; height: 40px; } .shop_head_special_fireCoralCirclet { background-image: url(spritesmith-main-5.png); - background-position: -1674px -738px; + background-position: -1674px -697px; width: 40px; height: 40px; } .shop_head_warrior_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -697px; + background-position: -1674px -656px; width: 40px; height: 40px; } .shop_head_warrior_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -615px; + background-position: -1674px -574px; width: 40px; height: 40px; } .shop_head_warrior_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -574px; + background-position: -1674px -533px; width: 40px; height: 40px; } .shop_head_warrior_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -533px; + background-position: -1674px -492px; width: 40px; height: 40px; } .shop_head_warrior_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -492px; + background-position: -1674px -451px; width: 40px; height: 40px; } .shop_head_wizard_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -410px; + background-position: -1674px -369px; width: 40px; height: 40px; } .shop_head_wizard_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -369px; + background-position: -1674px -328px; width: 40px; height: 40px; } .shop_head_wizard_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -328px; + background-position: -1674px -287px; width: 40px; height: 40px; } .shop_head_wizard_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -287px; + background-position: -1674px -246px; width: 40px; height: 40px; } .shop_head_wizard_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -246px; + background-position: -1674px -205px; width: 40px; height: 40px; } @@ -1296,7 +1296,7 @@ } .shop_headAccessory_special_bearEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -205px; + background-position: -1674px -164px; width: 40px; height: 40px; } @@ -1314,31 +1314,31 @@ } .shop_headAccessory_special_lionEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px 0px; + background-position: -1599px -1599px; width: 40px; height: 40px; } .shop_headAccessory_special_pandaEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -41px; + background-position: -1674px 0px; width: 40px; height: 40px; } .shop_headAccessory_special_pigEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -82px; + background-position: -1674px -41px; width: 40px; height: 40px; } .shop_headAccessory_special_tigerEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -123px; + background-position: -1674px -82px; width: 40px; height: 40px; } .shop_headAccessory_special_wolfEars { background-image: url(spritesmith-main-5.png); - background-position: -1674px -164px; + background-position: -1674px -123px; width: 40px; height: 40px; } @@ -1464,55 +1464,55 @@ } .shop_shield_healer_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1066px; + background-position: -1674px -1025px; width: 40px; height: 40px; } .shop_shield_healer_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1107px; + background-position: -1674px -1066px; width: 40px; height: 40px; } .shop_shield_healer_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1148px; + background-position: -1674px -1107px; width: 40px; height: 40px; } .shop_shield_healer_4 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1189px; + background-position: -1674px -1148px; width: 40px; height: 40px; } .shop_shield_healer_5 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1312px; + background-position: -1674px -1271px; width: 40px; height: 40px; } .shop_shield_rogue_0 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1353px; + background-position: -1674px -1312px; width: 40px; height: 40px; } .shop_shield_rogue_1 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1394px; + background-position: -1674px -1353px; width: 40px; height: 40px; } .shop_shield_rogue_2 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1435px; + background-position: -1674px -1394px; width: 40px; height: 40px; } .shop_shield_rogue_3 { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1476px; + background-position: -1674px -1435px; width: 40px; height: 40px; } @@ -2056,12 +2056,6 @@ width: 64px; height: 54px; } -.ghost { - background-image: url(spritesmith-main-5.png); - background-position: -455px -1144px; - width: 90px; - height: 90px; -} .inventory_present { background-image: url(spritesmith-main-5.png); background-position: -1616px -1049px; @@ -2178,7 +2172,7 @@ } .inventory_special_opaquePotion { background-image: url(spritesmith-main-5.png); - background-position: -1674px -820px; + background-position: -1674px -779px; width: 40px; height: 40px; } @@ -2200,7 +2194,7 @@ width: 57px; height: 54px; } -.inventory_special_spookySparkles { +.inventory_special_spookDust { background-image: url(spritesmith-main-5.png); background-position: -1299px -1508px; width: 57px; @@ -2244,17 +2238,23 @@ } .seafoam_star { background-image: url(spritesmith-main-5.png); - background-position: -364px -1144px; + background-position: -455px -1144px; width: 90px; height: 90px; } .shop_armoire { background-image: url(spritesmith-main-5.png); - background-position: -1674px -1558px; + background-position: -1674px -1517px; width: 40px; height: 40px; } .snowman { + background-image: url(spritesmith-main-5.png); + background-position: -364px -1144px; + width: 90px; + height: 90px; +} +.spookman { background-image: url(spritesmith-main-5.png); background-position: -273px -1144px; width: 90px; diff --git a/common/dist/sprites/spritesmith-main-5.png b/common/dist/sprites/spritesmith-main-5.png index 80a66cd2abc10bfdb13863b0ecdbe413bbaf5c5a..cf85f9fe03b0b3943311b1fb93ece7bb2fcea371 100644 GIT binary patch literal 261273 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 z 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%0A`6N2Y;eFShAMKc6|`f}zFc=Qk)QTlywdXC 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

cYY8H)>e!Qwq|+yPRDx=O*G{{JZD`SSW-HyGI) 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»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 b|5&A;kcF@}bo4kP;OF$y!TqH_1YG?O)-?;7 literal 261236 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 BC%z4OL%+<<%; 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

gd{uBQCe{D4MxBzuzAStWK-$$SIfZ`JkL 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

3zJ_zTQ-Rci5JM07#@%c8qjqcb?IQ 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 O9}82PA4>l2dFS5{H|(7N diff --git a/common/dist/sprites/spritesmith-main-6.css b/common/dist/sprites/spritesmith-main-6.css index 498fc4512c..60fa7a0a5f 100644 --- a/common/dist/sprites/spritesmith-main-6.css +++ b/common/dist/sprites/spritesmith-main-6.css @@ -682,7 +682,7 @@ width: 32px; height: 32px; } -.shop_spookySparkles { +.shop_spookDust { background-image: url(spritesmith-main-6.png); background-position: -790px -1176px; width: 32px; @@ -730,7 +730,7 @@ width: 40px; height: 40px; } -.shop_healAll { +.shop_heallAll { background-image: url(spritesmith-main-6.png); background-position: -791px -1643px; width: 40px; diff --git a/common/img/sprites/spritesmith/achievements/achievement-spookySparkles.png b/common/img/sprites/spritesmith/achievements/achievement-spookDust.png similarity index 100% rename from common/img/sprites/spritesmith/achievements/achievement-spookySparkles.png rename to common/img/sprites/spritesmith/achievements/achievement-spookDust.png diff --git a/common/img/sprites/spritesmith/achievements/achievement-spookySparkles2x.png b/common/img/sprites/spritesmith/achievements/achievement-spookDust2x.png similarity index 100% rename from common/img/sprites/spritesmith/achievements/achievement-spookySparkles2x.png rename to common/img/sprites/spritesmith/achievements/achievement-spookDust2x.png diff --git a/common/img/sprites/spritesmith/misc/inventory_special_spookySparkles.png b/common/img/sprites/spritesmith/misc/inventory_special_spookDust.png similarity index 100% rename from common/img/sprites/spritesmith/misc/inventory_special_spookySparkles.png rename to common/img/sprites/spritesmith/misc/inventory_special_spookDust.png diff --git a/common/img/sprites/spritesmith/misc/ghost.png b/common/img/sprites/spritesmith/misc/spookman.png similarity index 100% rename from common/img/sprites/spritesmith/misc/ghost.png rename to common/img/sprites/spritesmith/misc/spookman.png diff --git a/common/img/sprites/spritesmith/shop/shop_spookySparkles.png b/common/img/sprites/spritesmith/shop/shop_spookDust.png similarity index 100% rename from common/img/sprites/spritesmith/shop/shop_spookySparkles.png rename to common/img/sprites/spritesmith/shop/shop_spookDust.png diff --git a/common/img/sprites/spritesmith/skills/shop_healAll.png b/common/img/sprites/spritesmith/skills/shop_heallAll.png similarity index 100% rename from common/img/sprites/spritesmith/skills/shop_healAll.png rename to common/img/sprites/spritesmith/skills/shop_heallAll.png diff --git a/common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png b/common/img/sprites/spritesmith/stable/pets/Pet-Lion-Veteran.png deleted file mode 100644 index 83e3f90f39cd2eca1cfeb71a6f428764218b6e6c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3801 zcmV;~4kq!5P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000C8Nkl@DFthbpjWu7~mq#Ad>?=;=M%x)g< zmv83nuIu~0L)E7POaYU~Br=H%f|SRyS~dxq?p8}#2N9F(3bFz8``WyCb7wJmb9_4t;VTT zyD`RfHOW?-=$htfl<<0Nu7Qog*U z=dA~A31K|%_+*)tNSvdJvxvZxAtM5%QmJO%&hdu>66*ZPR(w^txaWzg@ES^*6kLjSbHXZ-^n>j{ue2DKHK$lPI zpu%?G3W1eKoFjmT?0^=+J+h>*b5_Ui1fCZ=>a0ZK9FxU%qi{oK%w^4pNsf%IEt~6+=4n)0jFreF zHiyJkm~`XG^E^KY6C1QMBk$B0+hT*m`g%H!CXAn*$yNgAzcrBv-1`^u`aAMQ6>+9J z2fC*X+UV4Ud^@0R#&&E}k00jD#}Qi;B3w`ViG)L~;+RMzp2l=;l&OO>)}@ zGd+F~+T)XsU#nBI5{YxfaJE5GIxfv4N$QGAc%m*|(LX6KpX;9*Y?0w(F!i9#N+iw^ zaOZkZwGl3=CvNJFboOqq6TE2GG1sS_BEo9?Y59d3_w-(_8EsY~ac&NUZiW3#;lg_l zdd7I07-?ZIAFgkMw&0NAcmJe<(oB P00000NkvXXu0mjfq_jD? diff --git a/common/index.js b/common/index.js index 04189fa8ab..475d444df3 100644 --- a/common/index.js +++ b/common/index.js @@ -1,6 +1,4 @@ -'use strict'; - -let pathToCommon; +var pathToCommon; if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env pathToCommon = './transpiled-babel/index'; diff --git a/common/locales/en/challenge.json b/common/locales/en/challenge.json index c014e1f6db..cffe8c4a38 100644 --- a/common/locales/en/challenge.json +++ b/common/locales/en/challenge.json @@ -74,10 +74,9 @@ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", "onlyLeaderUpdateChal": "Only the challenge leader can update it.", "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", - "shortNameTooShort": "Tag Name must have at least 3 characters." + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." } diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json index 2e0f245232..4d8ea0716c 100644 --- a/common/locales/en/groups.json +++ b/common/locales/en/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "User ID invites must be an array.", - "emailsMustBeAnArray": "Email address invites must be an array.", + "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", + "emailsMustBeAnArray": "Email invites must be a an Array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } diff --git a/common/locales/en/limited.json b/common/locales/en/limited.json index d9f3ff5ba0..2407b788fe 100644 --- a/common/locales/en/limited.json +++ b/common/locales/en/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Annoying Friends", "annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.", "alarmingFriends": "Alarming Friends", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.", "agriculturalFriends": "Agricultural Friends", "agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.", "aquaticFriends": "Aquatic Friends", diff --git a/common/locales/en/maintenance.json b/common/locales/en/maintenance.json index 20b3410de2..efdb524cd2 100644 --- a/common/locales/en/maintenance.json +++ b/common/locales/en/maintenance.json @@ -1,6 +1,6 @@ { "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until 10pm Pacific Time (5am UTC).", + "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", "maintenance": "Maintenance", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", diff --git a/common/locales/en/pets.json b/common/locales/en/pets.json index 7d13c8bc53..7cd716b72d 100644 --- a/common/locales/en/pets.json +++ b/common/locales/en/pets.json @@ -12,7 +12,6 @@ "etherealLion": "Ethereal Lion", "veteranWolf": "Veteran Wolf", "veteranTiger": "Veteran Tiger", - "veteranLion": "Veteran Lion", "cerberusPup": "Cerberus Pup", "hydra": "Hydra", "mantisShrimp": "Mantis Shrimp", diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json index 727c04774c..daffc88b5a 100644 --- a/common/locales/en/settings.json +++ b/common/locales/en/settings.json @@ -47,7 +47,6 @@ "customDayStart": "Custom Day Start", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", - "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Misc", diff --git a/common/locales/en/spells.json b/common/locales/en/spells.json index 5eb5d18be1..37dc7eaad4 100644 --- a/common/locales/en/spells.json +++ b/common/locales/en/spells.json @@ -52,8 +52,8 @@ "spellSpecialSaltText": "Salt", "spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookDustText": "Spooky Sparkles", + "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Opaque Potion", "spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.", @@ -70,7 +70,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 8c0d453727..197994da08 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -134,7 +134,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unknown card type.", + "cardTypeNotAllowed": "Unkown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/en/tasks.json b/common/locales/en/tasks.json index b9180af3bf..5b32829f81 100644 --- a/common/locales/en/tasks.json +++ b/common/locales/en/tasks.json @@ -73,7 +73,6 @@ "clearTags": "Clear", "hideTags": "Hide", "showTags": "Show", - "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.", diff --git a/common/script/constants.js b/common/script/constants.js index 040b968f8f..d6e0aa9384 100644 --- a/common/script/constants.js +++ b/common/script/constants.js @@ -1,5 +1,3 @@ export const MAX_HEALTH = 50; export const MAX_LEVEL = 100; export const MAX_STAT_POINTS = MAX_LEVEL; -export const ATTRIBUTES = ['str', 'int', 'per', 'con']; -export const TAVERN_ID = '00000000-0000-4000-A000-000000000000'; diff --git a/common/script/content/index.js b/common/script/content/index.js index 8164d4e0e6..18b46786bb 100644 --- a/common/script/content/index.js +++ b/common/script/content/index.js @@ -426,7 +426,6 @@ api.specialPets = { 'Phoenix-Base': 'phoenix', 'Turkey-Gilded': 'gildedTurkey', 'MagicalBee-Base': 'magicalBee', - 'Lion-Veteran': 'veteranLion', }; api.specialMounts = { @@ -602,14 +601,6 @@ api.questMounts = _.transform(api.questEggs, function(m, egg) { })); }); -api.premiumMounts = _.transform(api.dropEggs, function(m, egg) { - return _.defaults(m, _.transform(api.hatchingPotions, function(m2, pot) { - if (pot.premium) { - return m2[egg.key + "-" + pot.key] = true; - } - })); -}); - api.food = { Meat: { text: t('foodMeat'), diff --git a/common/script/content/spells.js b/common/script/content/spells.js index 9b0514974d..3150bd0da9 100644 --- a/common/script/content/spells.js +++ b/common/script/content/spells.js @@ -1,6 +1,6 @@ import t from './translation'; import _ from 'lodash'; -import { NotAuthorized } from '../libs/errors'; + /* --------------------------------------------------------------- Spells @@ -15,7 +15,7 @@ import { NotAuthorized } from '../libs/errors'; web, this function can be performed on the client and on the server. `user` param is self (needed for determining your own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells, you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the - spell is correct. Take a look at habitrpg/website/server/models/user.js and habitrpg/website/server/models/task.js for what attributes are + spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users, so you'll want to iterate over them like: `_.each(target,function(member){...})` @@ -40,12 +40,14 @@ spells.wizard = { lvl: 11, target: 'task', notes: t('spellWizardFireballNotes'), - cast (user, target, req) { + cast (user, target) { let bonus = user._statsComputed.int * user.fns.crit('per'); bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075); user.stats.exp += diminishingReturns(bonus, 75); if (!user.party.quest.progress) user.party.quest.progress = 0; user.party.quest.progress.up += Math.ceil(user._statsComputed.int * 0.1); + // TODO change, pass req to spell? + let req = {language: user.preferences.language}; user.fns.updateStats(user.stats, req); }, }, @@ -164,11 +166,12 @@ spells.rogue = { lvl: 12, target: 'task', notes: t('spellRogueBackStabNotes'), - cast (user, target, req) { + cast (user, target) { let _crit = user.fns.crit('str', 0.3); let bonus = calculateBonus(target.value, user._statsComputed.str, _crit); user.stats.exp += diminishingReturns(bonus, 75, 50); user.stats.gp += diminishingReturns(bonus, 18, 75); + let req = {language: user.preferences.language}; user.fns.updateStats(user.stats, req); }, }, @@ -194,7 +197,7 @@ spells.rogue = { notes: t('spellRogueStealthNotes'), cast (user) { if (!user.stats.buffs.stealth) user.stats.buffs.stealth = 0; - user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.tasksOrder.dailys.length * 0.64, 55)); + user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.dailys.length * 0.64, 55)); }, }, }; @@ -215,10 +218,10 @@ spells.healer = { text: t('spellHealerBrightnessText'), mana: 15, lvl: 12, - target: 'tasks', + target: 'self', notes: t('spellHealerBrightnessNotes'), - cast (user, tasks) { - _.each(tasks, (task) => { + cast (user) { + _.each(user.tasks, (task) => { if (task.type !== 'reward') { task.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40)); } @@ -239,7 +242,7 @@ spells.healer = { }); }, }, - healAll: { // Blessing + heallAll: { // Blessing text: t('spellHealerHealAllText'), mana: 25, lvl: 14, @@ -259,13 +262,11 @@ spells.special = { text: t('spellSpecialSnowballAuraText'), mana: 0, value: 15, - previousPurchase: true, target: 'user', notes: t('spellSpecialSnowballAuraNotes'), - cast (user, target, req) { - if (!user.items.special.snowball) throw new NotAuthorized(t('spellNotOwned')(req.language)); + cast (user, target) { target.stats.buffs.snowball = true; - target.stats.buffs.spookySparkles = false; + target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; if (!target.achievements.snowball) target.achievements.snowball = 0; @@ -285,22 +286,20 @@ spells.special = { user.stats.gp -= 5; }, }, - spookySparkles: { - text: t('spellSpecialSpookySparklesText'), + spookDust: { + text: t('spellSpecialSpookDustText'), mana: 0, value: 15, - previousPurchase: true, target: 'user', - notes: t('spellSpecialSpookySparklesNotes'), - cast (user, target, req) { - if (!user.items.special.spookySparkles) throw new NotAuthorized(t('spellNotOwned')(req.language)); + notes: t('spellSpecialSpookDustNotes'), + cast (user, target) { target.stats.buffs.snowball = false; - target.stats.buffs.spookySparkles = true; + target.stats.buffs.spookDust = true; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = false; - if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0; - target.achievements.spookySparkles++; - user.items.special.spookySparkles--; + if (!target.achievements.spookDust) target.achievements.spookDust = 0; + target.achievements.spookDust++; + user.items.special.spookDust--; }, }, opaquePotion: { @@ -311,7 +310,7 @@ spells.special = { target: 'self', notes: t('spellSpecialOpaquePotionNotes'), cast (user) { - user.stats.buffs.spookySparkles = false; + user.stats.buffs.spookDust = false; user.stats.gp -= 5; }, }, @@ -319,13 +318,11 @@ spells.special = { text: t('spellSpecialShinySeedText'), mana: 0, value: 15, - previousPurchase: true, target: 'user', notes: t('spellSpecialShinySeedNotes'), - cast (user, target, req) { - if (!user.items.special.shinySeed) throw new NotAuthorized(t('spellNotOwned')(req.language)); + cast (user, target) { target.stats.buffs.snowball = false; - target.stats.buffs.spookySparkles = false; + target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = true; target.stats.buffs.seafoam = false; if (!target.achievements.shinySeed) target.achievements.shinySeed = 0; @@ -349,13 +346,11 @@ spells.special = { text: t('spellSpecialSeafoamText'), mana: 0, value: 15, - previousPurchase: true, target: 'user', notes: t('spellSpecialSeafoamNotes'), - cast (user, target, req) { - if (!user.items.special.seafoam) throw new NotAuthorized(t('spellNotOwned')(req.language)); + cast (user, target) { target.stats.buffs.snowball = false; - target.stats.buffs.spookySparkles = false; + target.stats.buffs.spookDust = false; target.stats.buffs.shinySeed = false; target.stats.buffs.seafoam = true; if (!target.achievements.seafoam) target.achievements.seafoam = 0; @@ -396,10 +391,7 @@ spells.special = { if (!target.items.special.nyeReceived) target.items.special.nyeReceived = []; target.items.special.nyeReceived.push(user.profile.name); - - if (!target.flags) target.flags = {}; target.flags.cardReceived = true; - user.stats.gp -= 10; }, }, @@ -424,10 +416,7 @@ spells.special = { if (!target.items.special.valentineReceived) target.items.special.valentineReceived = []; target.items.special.valentineReceived.push(user.profile.name); - - if (!target.flags) target.flags = {}; target.flags.cardReceived = true; - user.stats.gp -= 10; }, }, @@ -451,10 +440,7 @@ spells.special = { if (!target.items.special.greetingReceived) target.items.special.greetingReceived = []; target.items.special.greetingReceived.push(user.profile.name); - - if (!target.flags) target.flags = {}; target.flags.cardReceived = true; - user.stats.gp -= 10; }, }, @@ -479,10 +465,7 @@ spells.special = { if (!target.items.special.thankyouReceived) target.items.special.thankyouReceived = []; target.items.special.thankyouReceived.push(user.profile.name); - - if (!target.flags) target.flags = {}; target.flags.cardReceived = true; - user.stats.gp -= 10; }, }, @@ -504,13 +487,9 @@ spells.special = { u.achievements.birthday++; }); } - if (!target.items.special.birthdayReceived) target.items.special.birthdayReceived = []; target.items.special.birthdayReceived.push(user.profile.name); - - if (!target.flags) target.flags = {}; target.flags.cardReceived = true; - user.stats.gp -= 10; }, }, @@ -520,8 +499,8 @@ _.each(spells, (spellClass) => { _.each(spellClass, (spell, key) => { spell.key = key; let _cast = spell.cast; - spell.cast = function castSpell (user, target, req) { - _cast(user, target, req); + spell.cast = function castSpell (user, target) { + _cast(user, target); user.stats.mp -= spell.mana; }; }); diff --git a/common/script/cron.js b/common/script/cron.js index a0f28f9d5a..2c0760fcc9 100644 --- a/common/script/cron.js +++ b/common/script/cron.js @@ -1,4 +1,3 @@ -// TODO what can be moved to /website/server? /* ------------------------------------------------------ Cron and time / day functions diff --git a/common/script/fns/autoAllocate.js b/common/script/fns/autoAllocate.js index 71a4898031..ab037e628b 100644 --- a/common/script/fns/autoAllocate.js +++ b/common/script/fns/autoAllocate.js @@ -7,69 +7,50 @@ import splitWhitespace from '../libs/splitWhitespace'; {update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately */ -function getStatToAllocate (user) { - let suggested; - - let statsObj = user.stats.toObject ? user.stats.toObject() : user.stats; - - switch (user.preferences.allocationMode) { - case 'flat': { - let stats = _.pick(statsObj, splitWhitespace('con str per int')); - return _.invert(stats)[_.min(stats)]; - } - case 'classbased': { - let lvlDiv7 = statsObj.lvl / 7; - let ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7]; - - let preference; - switch (statsObj.class) { - case 'wizard': { - preference = ['int', 'per', 'con', 'str']; - break; +module.exports = function(user) { + return user.stats[(function() { + var diff, ideal, lvlDiv7, preference, stats, suggested; + switch (user.preferences.allocationMode) { + case "flat": + stats = _.pick(user.stats, splitWhitespace('con str per int')); + return _.invert(stats)[_.min(stats)]; + case "classbased": + lvlDiv7 = user.stats.lvl / 7; + ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7]; + preference = (function() { + switch (user.stats["class"]) { + case "wizard": + return ["int", "per", "con", "str"]; + case "rogue": + return ["per", "str", "int", "con"]; + case "healer": + return ["con", "int", "str", "per"]; + default: + return ["str", "con", "per", "int"]; + } + })(); + diff = [user.stats[preference[0]] - ideal[0], user.stats[preference[1]] - ideal[1], user.stats[preference[2]] - ideal[2], user.stats[preference[3]] - ideal[3]]; + suggested = _.findIndex(diff, (function(val) { + if (val === _.min(diff)) { + return true; + } + })); + if (~suggested) { + return preference[suggested]; + } else { + return "str"; } - case 'rogue': { - preference = ['per', 'str', 'int', 'con']; - break; - } - case 'healer': { - preference = ['con', 'int', 'str', 'per']; - break; - } - default: { - preference = ['str', 'con', 'per', 'int']; - } - } - - let diff = [ - statsObj[preference[0]] - ideal[0], - statsObj[preference[1]] - ideal[1], - statsObj[preference[2]] - ideal[2], - statsObj[preference[3]] - ideal[3], - ]; - - suggested = _.findIndex(diff, (val) => { - if (val === _.min(diff)) return true; - }); - - return suggested !== -1 ? preference[suggested] : 'str'; + case "taskbased": + suggested = _.invert(user.stats.training)[_.max(user.stats.training)]; + _.merge(user.stats.training, { + str: 0, + int: 0, + con: 0, + per: 0 + }); + return suggested || "str"; + default: + return "str"; } - case 'taskbased': { - suggested = _.invert(statsObj.training)[_.max(statsObj.training)]; - - let training = statsObj.training; - training.str = 0; - training.int = 0; - training.con = 0; - training.per = 0; - - return suggested || 'str'; - } - default: { - return 'str'; - } - } -} - -module.exports = function autoAllocate (user) { - return user.stats[getStatToAllocate(user)]++; + })()]++; }; diff --git a/common/script/fns/crit.js b/common/script/fns/crit.js index 65a0cbb062..69ac9e5b93 100644 --- a/common/script/fns/crit.js +++ b/common/script/fns/crit.js @@ -1,8 +1,13 @@ -import predictableRandom from './predictableRandom'; - -module.exports = function crit (user, stat = 'str', chance = 0.03) { - let s = user._statsComputed[stat]; - if (predictableRandom(user) <= chance * (1 + s / 100)) { +module.exports = function(user, stat, chance) { + var s; + if (stat == null) { + stat = 'str'; + } + if (chance == null) { + chance = .03; + } + s = user._statsComputed[stat]; + if (user.fns.predictableRandom() <= chance * (1 + s / 100)) { return 1.5 + 4 * s / (s + 200); } else { return 1; diff --git a/common/script/fns/cron.js b/common/script/fns/cron.js new file mode 100644 index 0000000000..1e3470d7f9 --- /dev/null +++ b/common/script/fns/cron.js @@ -0,0 +1,358 @@ +import moment from 'moment'; +import _ from 'lodash'; +import { + daysSince, + shouldDo, +} from '../cron'; +import { + capByLevel, + toNextLevel, +} from '../statHelpers'; +/* + ------------------------------------------------------ + Cron + ------------------------------------------------------ + */ + +/* + At end of day, add value to all incomplete Daily & Todo tasks (further incentive) + For incomplete Dailys, deduct experience + Make sure to run this function once in a while as server will not take care of overnight calculations. + And you have to run it every time client connects. + {user} + */ + +module.exports = function(user, options) { + var _progress, analyticsData, base, base1, base2, base3, base4, clearBuffs, dailyChecked, dailyDueUnchecked, daysMissed, expTally, lvl, lvlDiv2, multiDaysCountAsOneDay, now, perfect, plan, progress, ref, ref1, ref2, ref3, todoTally, timezoneOffsetFromUserPrefs, timezoneOffsetFromBrowser, timezoneOffsetAtLastCron; + if (options == null) { + options = {}; + } + now = +options.now || +(new Date); + + // If the user's timezone has changed (due to travel or daylight savings), + // cron can be triggered twice in one day, so we check for that and use + // both timezones to work out if cron should run. + // CDS = Custom Day Start time. + timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; + timezoneOffsetAtLastCron = (_.isFinite(user.preferences.timezoneOffsetAtLastCron)) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; + timezoneOffsetFromBrowser = (_.isFinite(+options.timezoneOffset)) ? +options.timezoneOffset : timezoneOffsetFromUserPrefs; + // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults + + if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { + // The user's browser has just told Habitica that the user's timezone has + // changed so store and use the new zone. + user.preferences.timezoneOffset = timezoneOffsetFromBrowser; + timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; + } + + // How many days have we missed using the user's current timezone: + daysMissed = daysSince(user.lastCron, _.defaults({ + now: now + }, user.preferences)); + + if (timezoneOffsetAtLastCron != timezoneOffsetFromUserPrefs) { + // Since cron last ran, the user's timezone has changed. + // How many days have we missed using the old timezone: + let daysMissedNewZone = daysMissed; + let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ + now: now, + timezoneOffsetOverride: timezoneOffsetAtLastCron, + }, user.preferences)); + + if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { + // The timezone change was in the unsafe direction. + // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). + // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). + // Local time changed from, for example, 03:00 to 02:00. + + if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { + // Both old and new timezones indicate that we SHOULD run cron, so + // it is safe to do so immediately. + daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); + // use minimum value to be nice to user + } + else if (daysMissedOldZone > 0) { + // The old timezone says that cron should run; the new timezone does not. + // This should be impossible for this direction of timezone change, but + // just in case I'm wrong... + console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens + } + else if (daysMissedNewZone > 0) { + // The old timezone says that cron should NOT run -- i.e., cron has + // already run today, from the old timezone's point of view. + // The new timezone says that cron SHOULD run, but this is almost + // certainly incorrect. + // This happens when cron occurred at a time soon after the CDS. When + // you reinterpret that time in the new timezone, it looks like it + // was before the CDS, because local time has stepped backwards. + // To fix this, rewrite the cron time to a time that the new + // timezone interprets as being in today. + + daysMissed = 0; // prevent cron running now + let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; + // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 + + user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); + // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. + // From now on we can ignore the old timezone: + user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; + } + else { + // Both old and new timezones indicate that cron should + // NOT run. + daysMissed = 0; // prevent cron running now + } + } + else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { + daysMissed = daysMissedNewZone; + // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). + } + } + + if (!(daysMissed > 0)) { + return; + } + user.auth.timestamps.loggedin = new Date(); + user.lastCron = now; + user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; + if (user.items.lastDrop.count > 0) { + user.items.lastDrop.count = 0; + } + perfect = true; + clearBuffs = { + str: 0, + int: 0, + per: 0, + con: 0, + stealth: 0, + streaks: false + }; + plan = (ref = user.purchased) != null ? ref.plan : void 0; + if (plan != null ? plan.customerId : void 0) { + if (typeof plan.dateUpdated === "undefined") { + // partial compensation for bug in subscription creation - https://github.com/HabitRPG/habitrpg/issues/6682 + plan.dateUpdated = new Date(); + } + if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) { + plan.gemsBought = 0; + plan.dateUpdated = new Date(); + _.defaults(plan.consecutive, { + count: 0, + offset: 0, + trinkets: 0, + gemCapExtra: 0 + }); + plan.consecutive.count++; + if (plan.consecutive.offset > 0) { + plan.consecutive.offset--; + } else if (plan.consecutive.count % 3 === 0) { + plan.consecutive.trinkets++; + plan.consecutive.gemCapExtra += 5; + if (plan.consecutive.gemCapExtra > 25) { + plan.consecutive.gemCapExtra = 25; + } + } + } + if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) { + _.merge(plan, { + planId: null, + customerId: null, + paymentMethod: null + }); + _.merge(plan.consecutive, { + count: 0, + offset: 0, + gemCapExtra: 0 + }); + if (typeof user.markModified === "function") { + user.markModified('purchased.plan'); + } + } + } + if (user.preferences.sleep === true) { + user.stats.buffs = clearBuffs; + user.dailys.forEach(function(daily) { + var completed, repeat, thatDay; + completed = daily.completed, repeat = daily.repeat; + thatDay = moment(now).subtract({ + days: 1 + }); + if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { + _.each(daily.checklist, (function(box) { + box.completed = false; + return true; + })); + } + return daily.completed = false; + }); + return; + } + multiDaysCountAsOneDay = true; + todoTally = 0; + user.todos.forEach(function(task) { + var absVal, completed, delta, id; + if (!task) { + return; + } + id = task.id, completed = task.completed; + delta = user.ops.score({ + params: { + id: task.id, + direction: 'down' + }, + query: { + times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : { + 1: daysMissed + }, + cron: true + } + }); + absVal = completed ? Math.abs(task.value) : task.value; + return todoTally += absVal; + }); + dailyChecked = 0; + dailyDueUnchecked = 0; + if ((base = user.party.quest.progress).down == null) { + base.down = 0; + } + user.dailys.forEach(function(task) { + var EvadeTask, completed, delta, fractionChecked, id, j, n, ref1, ref2, scheduleMisses, thatDay; + if (!task) { + return; + } + id = task.id, completed = task.completed; + EvadeTask = 0; + scheduleMisses = daysMissed; + if (completed) { + dailyChecked += 1; + } else { + scheduleMisses = 0; + for (n = j = 0, ref1 = daysMissed; 0 <= ref1 ? j < ref1 : j > ref1; n = 0 <= ref1 ? ++j : --j) { + thatDay = moment(now).subtract({ + days: n + 1 + }); + if (shouldDo(thatDay.toDate(), task, user.preferences)) { + scheduleMisses++; + if (user.stats.buffs.stealth) { + user.stats.buffs.stealth--; + EvadeTask++; + } + if (multiDaysCountAsOneDay) { + break; + } + } + } + if (scheduleMisses > EvadeTask) { + perfect = false; + if (((ref2 = task.checklist) != null ? ref2.length : void 0) > 0) { + fractionChecked = _.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 0) / task.checklist.length; + dailyDueUnchecked += 1 - fractionChecked; + dailyChecked += fractionChecked; + } else { + dailyDueUnchecked += 1; + } + delta = user.ops.score({ + params: { + id: task.id, + direction: 'down' + }, + query: { + times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : { + 1: scheduleMisses - EvadeTask + }, + cron: true + } + }); + user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); + } + } + (task.history != null ? task.history : task.history = []).push({ + date: +(new Date), + value: task.value + }); + task.completed = false; + if (completed || (scheduleMisses > 0)) { + return _.each(task.checklist, (function(i) { + i.completed = false; + return true; + })); + } + }); + user.habits.forEach(function(task) { + if (task.up === false || task.down === false) { + if (Math.abs(task.value) < 0.1) { + return task.value = 0; + } else { + return task.value = task.value / 2; + } + } + }); + ((base1 = (user.history != null ? user.history : user.history = {})).todos != null ? base1.todos : base1.todos = []).push({ + date: now, + value: todoTally + }); + expTally = user.stats.exp; + lvl = 0; + while (lvl < (user.stats.lvl - 1)) { + lvl++; + expTally += toNextLevel(lvl); + } + ((base2 = user.history).exp != null ? base2.exp : base2.exp = []).push({ + date: now, + value: expTally + }); + if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) { + user.fns.preenUserHistory(); + if (typeof user.markModified === "function") { + user.markModified('history'); + } + if (typeof user.markModified === "function") { + user.markModified('dailys'); + } + } + user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(capByLevel(user.stats.lvl) / 2), { + str: lvlDiv2, + int: lvlDiv2, + per: lvlDiv2, + con: lvlDiv2, + stealth: 0, + streaks: false + }) : clearBuffs; + if (dailyDueUnchecked === 0 && dailyChecked === 0) { + dailyChecked = 1; + } + user.stats.mp += _.max([10, .1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked); + if (user.stats.mp > user._statsComputed.maxMP) { + user.stats.mp = user._statsComputed.maxMP; + } + progress = user.party.quest.progress; + _progress = _.cloneDeep(progress); + _.merge(progress, { + down: 0, + up: 0 + }); + progress.collect = _.transform(progress.collect, (function(m, v, k) { + return m[k] = 0; + })); + if ((base4 = user.flags).cronCount == null) { + base4.cronCount = 0; + } + user.flags.cronCount++; + analyticsData = { + category: 'behavior', + gaLabel: 'Cron Count', + gaValue: user.flags.cronCount, + uuid: user._id, + user: user, + resting: user.preferences.sleep, + cronCount: user.flags.cronCount, + progressUp: _.min([_progress.up, 900]), + progressDown: _progress.down + }; + if ((ref3 = options.analytics) != null) { + ref3.track('Cron', analyticsData); + } + return _progress; +}; diff --git a/common/script/fns/dotGet.js b/common/script/fns/dotGet.js index 3b45e54c71..c95ba55656 100644 --- a/common/script/fns/dotGet.js +++ b/common/script/fns/dotGet.js @@ -1,7 +1,5 @@ -import _ from 'lodash'; +import dotGet from '../libs/dotGet'; -// TODO remove completely, use _.get, only used in client - -module.exports = function dotGet (user, path) { - return _.get(user, path); +module.exports = function(user, path) { + return dotGet(user, path); }; diff --git a/common/script/fns/dotSet.js b/common/script/fns/dotSet.js index ceb21605af..283e7a50d0 100644 --- a/common/script/fns/dotSet.js +++ b/common/script/fns/dotSet.js @@ -1,14 +1,12 @@ -import _ from 'lodash'; +import dotSet from '../libs/dotSet'; /* - This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as - user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value}, - so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where - Angular sets object properties directly - in which case, this function will be used. -*/ +This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as +user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value}, +so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where +Angular sets object properties directly - in which case, this function will be used. + */ -// TODO use directly _.set and remove this fn, only used in client - -module.exports = function dotSet (user, path, val) { - return _.set(user, path, val); +module.exports = function(user, path, val) { + return dotSet(user, path, val); }; diff --git a/common/script/fns/getItem.js b/common/script/fns/getItem.js new file mode 100644 index 0000000000..b73ecf9073 --- /dev/null +++ b/common/script/fns/getItem.js @@ -0,0 +1,11 @@ +import content from '../content/index'; +import i18n from '../i18n'; + +module.exports = function(user, type) { + var item; + item = content.gear.flat[user.items.gear.equipped[type]]; + if (!item) { + return content.gear.flat[type + "_base_0"]; + } + return item; +}; diff --git a/common/script/fns/handleTwoHanded.js b/common/script/fns/handleTwoHanded.js index a861a10e68..c700346988 100644 --- a/common/script/fns/handleTwoHanded.js +++ b/common/script/fns/handleTwoHanded.js @@ -1,23 +1,24 @@ import content from '../content/index'; import i18n from '../i18n'; -module.exports = function handleTwoHanded (user, item, type = 'equipped', req = {}) { - let currentShield = content.gear.flat[user.items.gear[type].shield]; - let currentWeapon = content.gear.flat[user.items.gear[type].weapon]; +module.exports = function(user, item, type, req) { + var message, currentWeapon, currentShield; + if (type == null) { + type = 'equipped'; + } + currentShield = content.gear.flat[user.items.gear[type].shield]; + currentWeapon = content.gear.flat[user.items.gear[type].weapon]; - let message; - - if (item.type === 'shield' && (currentWeapon ? currentWeapon.twoHanded : false)) { + if (item.type === "shield" && (currentWeapon ? currentWeapon.twoHanded : false)) { user.items.gear[type].weapon = 'weapon_base_0'; message = i18n.t('messageTwoHandedUnequip', { twoHandedText: currentWeapon.text(req.language), offHandedText: item.text(req.language), }, req.language); - } else if (item.twoHanded && (currentShield && user.items.gear[type].shield !== 'shield_base_0')) { - user.items.gear[type].shield = 'shield_base_0'; + } else if (item.twoHanded && (currentShield && user.items.gear[type].shield != "shield_base_0")) { + user.items.gear[type].shield = "shield_base_0"; message = i18n.t('messageTwoHandedEquip', { twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language), }, req.language); } - return message; }; diff --git a/common/script/fns/index.js b/common/script/fns/index.js index 04fddb2d75..24f3ab604b 100644 --- a/common/script/fns/index.js +++ b/common/script/fns/index.js @@ -1,3 +1,4 @@ +import getItem from './getItem'; import handleTwoHanded from './handleTwoHanded'; import predictableRandom from './predictableRandom'; import crit from './crit'; @@ -7,10 +8,13 @@ import dotGet from './dotGet'; import randomDrop from './randomDrop'; import autoAllocate from './autoAllocate'; import updateStats from './updateStats'; +import cron from './cron'; +import preenUserHistory from './preenUserHistory'; import ultimateGear from './ultimateGear'; import nullify from './nullify'; module.exports = { + getItem, handleTwoHanded, predictableRandom, crit, @@ -20,6 +24,8 @@ module.exports = { randomDrop, autoAllocate, updateStats, + cron, + preenUserHistory, ultimateGear, nullify, }; diff --git a/common/script/fns/nullify.js b/common/script/fns/nullify.js index 38753071fc..b6e30aa3b9 100644 --- a/common/script/fns/nullify.js +++ b/common/script/fns/nullify.js @@ -1,7 +1,5 @@ -// TODO remove once v2 is retired - -module.exports = function nullify (user) { +module.exports = function(user) { user.ops = null; user.fns = null; - user = null; + return user = null; }; diff --git a/common/script/fns/predictableRandom.js b/common/script/fns/predictableRandom.js index e67daf3b62..64c8153746 100644 --- a/common/script/fns/predictableRandom.js +++ b/common/script/fns/predictableRandom.js @@ -1,24 +1,20 @@ import _ from 'lodash'; +/* +Because the same op needs to be performed on the client and the server (critical hits, item drops, etc), +we need things to be "random", but technically predictable so that they don't go out-of-sync + */ -// Because the same op needs to be performed on the client and the server (critical hits, item drops, etc), -// we need things to be "random", but technically predictable so that they don't go out-of-sync - -module.exports = function predictableRandom (user, seed) { +module.exports = function(user, seed) { + var x; if (!seed || seed === Math.PI) { - let stats = user.stats.toObject ? user.stats.toObject() : user.stats; - // These items are not part of the stat object but exists on the server (see controllers/user#getUser) - // we remove them in order to use the same user.stats both on server and on client - stats = _.omit(stats, 'toNextLevel', 'maxHealth', 'maxMP'); - - seed = _.reduce(stats, (accumulator, val) => { - if (_.isNumber(val)) { - return accumulator + val; + seed = _.reduce(user.stats, (function(m, v) { + if (_.isNumber(v)) { + return m + v; } else { - return accumulator; + return m; } - }, 0); + }), 0); } - - let x = Math.sin(seed++) * 10000; + x = Math.sin(seed++) * 10000; return x - Math.floor(x); }; diff --git a/common/script/fns/preenUserHistory.js b/common/script/fns/preenUserHistory.js new file mode 100644 index 0000000000..a45dd82719 --- /dev/null +++ b/common/script/fns/preenUserHistory.js @@ -0,0 +1,25 @@ +import _ from 'lodash'; +import preenHistory from '../libs/preenHistory'; + +module.exports = function(user, minHistLen) { + if (minHistLen == null) { + minHistLen = 7; + } + _.each(user.habits.concat(user.dailys), function(task) { + var ref; + if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) { + task.history = preenHistory(task.history); + } + return true; + }); + _.defaults(user.history, { + todos: [], + exp: [] + }); + if (user.history.exp.length > minHistLen) { + user.history.exp = preenHistory(user.history.exp); + } + if (user.history.todos.length > minHistLen) { + return user.history.todos = preenHistory(user.history.todos); + } +}; diff --git a/common/script/fns/randomDrop.js b/common/script/fns/randomDrop.js index 92064e62f1..b0121b157b 100644 --- a/common/script/fns/randomDrop.js +++ b/common/script/fns/randomDrop.js @@ -3,118 +3,80 @@ import content from '../content/index'; import i18n from '../i18n'; import { daysSince } from '../cron'; import { diminishingReturns } from '../statHelpers'; -import predictableRandom from './predictableRandom'; -import randomVal from './randomVal'; // Clone a drop object maintaining its functions so that we can change it without affecting the original item function cloneDropItem (drop) { - return _.cloneDeep(drop, (val) => { + return _.cloneDeep(drop, function (val) { return _.isFunction(val) ? val : undefined; // undefined will be handled by lodash }); } -module.exports = function randomDrop (user, modifiers, req = {}) { - let acceptableDrops; - let chance; - let drop; - let dropK; - let dropMultiplier; - let quest; - let rarity; - let task; - +module.exports = function(user, modifiers, req) { + var acceptableDrops, base, base1, base2, chance, drop, dropK, dropMultiplier, name, name1, name2, quest, rarity, ref, ref1, ref2, ref3, task; task = modifiers.task; - - chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + 0.02; - chance *= task.priority * // Task priority: +50% for Medium, +100% for Hard - (1 + (task.streak / 100 || 0)) * // Streak bonus: +1% per streak - (1 + user._statsComputed.per / 100) * // PERception: +1% per point - (1 + (user.contributor.level / 40 || 0)) * // Contrib levels: +2.5% per level - (1 + (user.achievements.rebirths / 20 || 0)) * // Rebirths: +5% per achievement - (1 + (user.achievements.streak / 200 || 0)) * // Streak achievements: +0.5% per achievement - (user._tmp.crit || 1) * (1 + 0.5 * (_.reduce(task.checklist, (m, i) => { - return m + (i.completed ? 1 : 0); // +50% per checklist item complete. TODO: make this into X individual drop chances instead - }, 0) || 0)); + chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + .02; + chance *= task.priority * (1 + (task.streak / 100 || 0)) * (1 + (user._statsComputed.per / 100)) * (1 + (user.contributor.level / 40 || 0)) * (1 + (user.achievements.rebirths / 20 || 0)) * (1 + (user.achievements.streak / 200 || 0)) * (user._tmp.crit || 1) * (1 + .5 * (_.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 0) || 0)); chance = diminishingReturns(chance, 0.75); - - if (user.party.quest.key) - quest = content.quests[user.party.quest.key]; - if (quest && quest.collect && predictableRandom(user, user.stats.gp) < chance) { - dropK = randomVal(user, quest.collect, { - key: true, + quest = content.quests[(ref = user.party.quest) != null ? ref.key : void 0]; + if ((quest != null ? quest.collect : void 0) && user.fns.predictableRandom(user.stats.gp) < chance) { + dropK = user.fns.randomVal(quest.collect, { + key: true }); - if (!user.party.quest.progress.collect[dropK]) - user.party.quest.progress.collect[dropK] = 0; user.party.quest.progress.collect[dropK]++; - user.markModified('party.quest.progress'); + if (typeof user.markModified === "function") { + user.markModified('party.quest.progress'); + } } - - if (user.purchased && user.purchased.plan && user.purchased.plan.customerId) { - dropMultiplier = 2; - } else { - dropMultiplier = 1; - } - - if (daysSince(user.items.lastDrop.date, user.preferences) === 0 && - user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0))) { + dropMultiplier = ((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0) ? 2 : 1; + if ((daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) { return; } - - if (user.flags && user.flags.dropsEnabled && predictableRandom(user, user.stats.exp) < chance) { - rarity = predictableRandom(user, user.stats.gp); - - if (rarity > 0.6) { // food 40% chance - drop = cloneDropItem(randomVal(user, _.where(content.food, { - canDrop: true, + if (((ref3 = user.flags) != null ? ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) { + rarity = user.fns.predictableRandom(user.stats.gp); + if (rarity > .6) { + drop = cloneDropItem(user.fns.randomVal(_.where(content.food, { + canDrop: true }))); - - if (!user.items.food[drop.key]) { - user.items.food[drop.key] = 0; + if ((base = user.items.food)[name = drop.key] == null) { + base[name] = 0; } user.items.food[drop.key] += 1; drop.type = 'Food'; drop.dialog = i18n.t('messageDropFood', { dropArticle: drop.article, dropText: drop.text(req.language), - dropNotes: drop.notes(req.language), + dropNotes: drop.notes(req.language) }, req.language); - } else if (rarity > 0.3) { // eggs 30% chance - drop = cloneDropItem(randomVal(user, content.dropEggs)); - if (!user.items.eggs[drop.key]) { - user.items.eggs[drop.key] = 0; + } else if (rarity > .3) { + drop = cloneDropItem(user.fns.randomVal(content.dropEggs)); + if ((base1 = user.items.eggs)[name1 = drop.key] == null) { + base1[name1] = 0; } user.items.eggs[drop.key]++; drop.type = 'Egg'; drop.dialog = i18n.t('messageDropEgg', { dropText: drop.text(req.language), - dropNotes: drop.notes(req.language), + dropNotes: drop.notes(req.language) }, req.language); - } else { // Hatching Potion, 30% chance - break down by rarity. - if (rarity < 0.02) { // Very Rare: 10% (of 30%) - acceptableDrops = ['Golden']; - } else if (rarity < 0.09) { // Rare: 20% of 30% - acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']; - } else if (rarity < 0.18) { // uncommon: 30% of 30% - acceptableDrops = ['Red', 'Shade', 'Skeleton']; - } else { // common, 40% of 30% - acceptableDrops = ['Base', 'White', 'Desert']; - } - drop = cloneDropItem(randomVal(user, _.pick(content.hatchingPotions, (v, k) => { + } else { + acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert']; + drop = cloneDropItem(user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) { return acceptableDrops.indexOf(k) >= 0; - }))); - if (!user.items.hatchingPotions[drop.key]) { - user.items.hatchingPotions[drop.key] = 0; + })))); + if ((base2 = user.items.hatchingPotions)[name2 = drop.key] == null) { + base2[name2] = 0; } user.items.hatchingPotions[drop.key]++; drop.type = 'HatchingPotion'; drop.dialog = i18n.t('messageDropPotion', { dropText: drop.text(req.language), - dropNotes: drop.notes(req.language), + dropNotes: drop.notes(req.language) }, req.language); } - user._tmp.drop = drop; - user.items.lastDrop.date = Number(new Date()); - user.items.lastDrop.count++; + user.items.lastDrop.date = +(new Date); + return user.items.lastDrop.count++; } }; diff --git a/common/script/fns/randomVal.js b/common/script/fns/randomVal.js index 2244d04558..3c2b5b82e8 100644 --- a/common/script/fns/randomVal.js +++ b/common/script/fns/randomVal.js @@ -1,12 +1,14 @@ import _ from 'lodash'; -import predictableRandom from './predictableRandom'; -// Get a random property from an object -// returns random property (the value) +/* + Get a random property from an object + returns random property (the value) + */ -module.exports = function randomVal (user, obj, options = {}) { - let array = options.key ? _.keys(obj) : _.values(obj); - let rand = predictableRandom(user, options.seed); +module.exports = function(user, obj, options) { + var array, rand; + array = (options != null ? options.key : void 0) ? _.keys(obj) : _.values(obj); + rand = user.fns.predictableRandom(options != null ? options.seed : void 0); array.sort(); return array[Math.floor(rand * array.length)]; }; diff --git a/common/script/fns/resetGear.js b/common/script/fns/resetGear.js deleted file mode 100644 index 2625f5c9b8..0000000000 --- a/common/script/fns/resetGear.js +++ /dev/null @@ -1,25 +0,0 @@ -import _ from 'lodash'; -import content from '../content/index'; - -module.exports = function resetGear (user) { - let gear = user.items.gear; - - _.each(['equipped', 'costume'], function resetUserGear (type) { - gear[type] = {}; - gear[type].armor = 'armor_base_0'; - gear[type].weapon = 'weapon_warrior_0'; - gear[type].head = 'head_base_0'; - gear[type].shield = 'shield_base_0'; - }); - - // Gear.owned is a Mongo object so the _.each function iterates over hidden properties. - // The content.gear.flat[k] check should prevent this causing an error - _.each(gear.owned, function resetOwnedGear (v, k) { - if (gear.owned[k] && content.gear.flat[k] && content.gear.flat[k].value) { - gear.owned[k] = false; - } - }); - - gear.owned.weapon_warrior_0 = true; // eslint-disable-line camelcase - user.preferences.costume = false; -}; diff --git a/common/script/fns/ultimateGear.js b/common/script/fns/ultimateGear.js index be5553201d..1333e8cc38 100644 --- a/common/script/fns/ultimateGear.js +++ b/common/script/fns/ultimateGear.js @@ -1,23 +1,33 @@ import content from '../content/index'; import _ from 'lodash'; -module.exports = function ultimateGear (user) { - let owned = typeof window !== 'undefined' ? user.items.gear.owned : user.items.gear.owned.toObject(); - - content.classes.forEach((klass) => { +module.exports = function(user) { + var base, owned; + owned = typeof window !== "undefined" && window !== null ? user.items.gear.owned : user.items.gear.owned.toObject(); + if ((base = user.achievements).ultimateGearSets == null) { + base.ultimateGearSets = { + healer: false, + wizard: false, + rogue: false, + warrior: false + }; + } + content.classes.forEach(function(klass) { if (user.achievements.ultimateGearSets[klass] !== true) { - user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], (soFarGood, type) => { - let found = _.find(content.gear.tree[type][klass], { - last: true, + return user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], function(soFarGood, type) { + var found; + found = _.find(content.gear.tree[type][klass], { + last: true }); return soFarGood && (!found || owned[found.key] === true); }, true); } }); - + if (typeof user.markModified === "function") { + user.markModified('achievements.ultimateGearSets'); + } if (_.contains(user.achievements.ultimateGearSets, true) && user.flags.armoireEnabled !== true) { user.flags.armoireEnabled = true; + return typeof user.markModified === "function" ? user.markModified('flags') : void 0; } - - return; }; diff --git a/common/script/fns/updateStats.js b/common/script/fns/updateStats.js index 6061f717ab..3ce83ed664 100644 --- a/common/script/fns/updateStats.js +++ b/common/script/fns/updateStats.js @@ -1,19 +1,21 @@ import _ from 'lodash'; import { MAX_HEALTH, - MAX_STAT_POINTS, + MAX_STAT_POINTS } from '../constants'; import { toNextLevel } from '../statHelpers'; -import autoAllocate from './autoAllocate'; - -module.exports = function updateStats (user, stats, req = {}, analytics) { +module.exports = function (user, stats, req, analytics) { let allocatedStatPoints; let totalStatPoints; let experienceToNextLevel; - user.stats.hp = stats.hp > 0 ? stats.hp : 0; - user.stats.gp = stats.gp > 0 ? stats.gp : 0; - if (!user._tmp) user._tmp = {}; + if (stats.hp <= 0) { + user.stats.hp = 0; + return user.stats.hp; + } + + user.stats.hp = stats.hp; + user.stats.gp = stats.gp >= 0 ? stats.gp : 0; experienceToNextLevel = toNextLevel(user.stats.lvl); @@ -33,7 +35,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { continue; // eslint-disable-line no-continue } if (user.preferences.automaticAllocation) { - autoAllocate(user); + user.fns.autoAllocate(); } else { user.stats.points = user.stats.lvl - allocatedStatPoints; totalStatPoints = user.stats.points + allocatedStatPoints; @@ -50,6 +52,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { } user.stats.exp = stats.exp; + user.flags = user.flags || {}; if (!user.flags.customizationsNotification && (user.stats.exp > 5 || user.stats.lvl > 1)) { user.flags.customizationsNotification = true; @@ -59,39 +62,48 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { } if (!user.flags.dropsEnabled && user.stats.lvl >= 3) { user.flags.dropsEnabled = true; - if (user.items.eggs.Wolf > 0) { - user.items.eggs.Wolf++; + if (user.items.eggs["Wolf"] > 0) { + user.items.eggs["Wolf"]++; } else { - user.items.eggs.Wolf = 1; + user.items.eggs["Wolf"] = 1; } } + if (!user.flags.classSelected && user.stats.lvl >= 10) { + user.flags.classSelected; + } _.each({ vice1: 30, atom1: 15, moonstone1: 60, - goldenknight1: 40, - }, (lvl, k) => { - if (user.stats.lvl >= lvl && !user.flags.levelDrops[k]) { - user.flags.levelDrops[k] = true; - if (!user.items.quests[k]) - user.items.quests[k] = 0; - user.items.quests[k]++; - user.markModified('flags.levelDrops'); - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: k, - acquireMethod: 'Level Drop', - category: 'behavior', - }); + goldenknight1: 40 + }, function(lvl, k) { + var analyticsData, base, base1, ref; + if (!((ref = user.flags.levelDrops) != null ? ref[k] : void 0) && user.stats.lvl >= lvl) { + if ((base = user.items.quests)[k] == null) { + base[k] = 0; } - user._tmp.drop = { + user.items.quests[k]++; + ((base1 = user.flags).levelDrops != null ? base1.levelDrops : base1.levelDrops = {})[k] = true; + if (typeof user.markModified === "function") { + user.markModified('flags.levelDrops'); + } + analyticsData = { + uuid: user._id, + itemKey: k, + acquireMethod: 'Level Drop', + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); + } + if (!user._tmp) user._tmp = {} + return user._tmp.drop = { type: 'Quest', - key: k, + key: k }; } }); if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) { - user.flags.rebirthEnabled = true; + return user.flags.rebirthEnabled = true; } }; diff --git a/common/script/index.js b/common/script/index.js index 50b01bf762..ea502e9a7d 100644 --- a/common/script/index.js +++ b/common/script/index.js @@ -1,197 +1,85 @@ +import moment from 'moment'; import _ from 'lodash'; -// When using a common module from the website or the server NEVER import the module directly -// but access it through `api` (the main common) module, otherwise you would require the non transpiled version of the file in production. -let api = module.exports = {}; - -import content from './content/index'; -api.content = content; - -import * as errors from './libs/errors'; -api.errors = errors; -import i18n from './i18n'; -api.i18n = i18n; - -// TODO under api.libs.cron? -import { shouldDo, daysSince } from './cron'; -api.shouldDo = shouldDo; -api.daysSince = daysSince; - -// TODO under api.constants? and capitalize exported names too +import { + daysSince, + shouldDo, +} from './cron'; import { MAX_HEALTH, MAX_LEVEL, MAX_STAT_POINTS, - TAVERN_ID, } from './constants'; -api.maxLevel = MAX_LEVEL; -api.maxHealth = MAX_HEALTH; -api.maxStatPoints = MAX_STAT_POINTS; -api.TAVERN_ID = TAVERN_ID; - -// TODO under api.libs.statHelpers? import * as statHelpers from './statHelpers'; + +import importedLibs from './libs'; + +var $w, preenHistory, sortOrder, + indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + +import content from './content/index'; +import i18n from './i18n'; + +let api = module.exports = {}; + +api.i18n = i18n; +api.shouldDo = shouldDo; + +api.maxLevel = MAX_LEVEL; api.capByLevel = statHelpers.capByLevel; +api.maxHealth = MAX_HEALTH; api.tnl = statHelpers.toNextLevel; api.diminishingReturns = statHelpers.diminishingReturns; -import splitWhitespace from './libs/splitWhitespace'; -api.$w = splitWhitespace; +$w = api.$w = importedLibs.splitWhitespace; +api.dotSet = importedLibs.dotSet; +api.dotGet = importedLibs.dotGet; +api.refPush = importedLibs.refPush; +api.planGemLimits = importedLibs.planGemLimits; -import dotSet from './libs/dotSet'; -api.dotSet = dotSet; +preenHistory = importedLibs.preenHistory; -import dotGet from './libs/dotGet'; -api.dotGet = dotGet; +api.preenTodos = importedLibs.preenTodos; +api.updateStore = importedLibs.updateStore; -import refPush from './libs/refPush'; -api.refPush = refPush; -import planGemLimits from './libs/planGemLimits'; -api.planGemLimits = planGemLimits; +/* +------------------------------------------------------ +Content +------------------------------------------------------ + */ -import preenTodos from './libs/preenTodos'; -api.preenTodos = preenTodos; +api.content = content; -import updateStore from './libs/updateStore'; -api.updateStore = updateStore; -import uuid from './libs/uuid'; -api.uuid = uuid; +/* +------------------------------------------------------ +Misc Helpers +------------------------------------------------------ + */ -import taskDefaults from './libs/taskDefaults'; -api.taskDefaults = taskDefaults; +api.uuid = importedLibs.uuid; +api.countExists = importedLibs.countExists; +api.taskDefaults = importedLibs.taskDefaults; +api.percent = importedLibs.percent; +api.removeWhitespace = importedLibs.removeWhitespace; +api.encodeiCalLink = importedLibs.encodeiCalLink; +api.gold = importedLibs.gold; +api.silver = importedLibs.silver; +api.taskClasses = importedLibs.taskClasses; +api.friendlyTimestamp = importedLibs.friendlyTimestamp; +api.newChatMessages = importedLibs.newChatMessages; +api.noTags = importedLibs.noTags; +api.appliedTags = importedLibs.appliedTags; -import percent from './libs/percent'; -api.percent = percent; -import gold from './libs/gold'; -api.gold = gold; - -import silver from './libs/silver'; -api.silver = silver; - -import taskClasses from './libs/taskClasses'; -api.taskClasses = taskClasses; - -import noTags from './libs/noTags'; -api.noTags = noTags; - -import appliedTags from './libs/appliedTags'; -api.appliedTags = appliedTags; - -import pickDeep from './libs/pickDeep'; -api.pickDeep = pickDeep; +/* +Various counting functions + */ import count from './count'; api.count = count; -import statsComputed from './libs/statsComputed'; -api.statsComputed = statsComputed; - -import autoAllocate from './fns/autoAllocate'; -import crit from './fns/crit'; -import handleTwoHanded from './fns/handleTwoHanded'; -import predictableRandom from './fns/predictableRandom'; -import randomDrop from './fns/randomDrop'; -import randomVal from './fns/randomVal'; -import resetGear from './fns/resetGear'; -import ultimateGear from './fns/ultimateGear'; -import updateStats from './fns/updateStats'; - -api.fns = { - autoAllocate, - crit, - handleTwoHanded, - predictableRandom, - randomDrop, - randomVal, - resetGear, - ultimateGear, - updateStats, -}; - -import scoreTask from './ops/scoreTask'; -import sleep from './ops/sleep'; -import allocate from './ops/allocate'; -import buy from './ops/buy'; -import buyGear from './ops/buyGear'; -import buyHealthPotion from './ops/buyHealthPotion'; -import buyArmoire from './ops/buyArmoire'; -import buyMysterySet from './ops/buyMysterySet'; -import buyQuest from './ops/buyQuest'; -import buySpecialSpell from './ops/buySpecialSpell'; -import allocateNow from './ops/allocateNow'; -import hatch from './ops/hatch'; -import feed from './ops/feed'; -import equip from './ops/equip'; -import changeClass from './ops/changeClass'; -import disableClasses from './ops/disableClasses'; -import purchase from './ops/purchase'; -import purchaseHourglass from './ops/hourglassPurchase'; -import readCard from './ops/readCard'; -import openMysteryItem from './ops/openMysteryItem'; -import addWebhook from './ops/addWebhook'; -import updateWebhook from './ops/updateWebhook'; -import deleteWebhook from './ops/deleteWebhook'; -import releasePets from './ops/releasePets'; -import releaseBoth from './ops/releaseBoth'; -import releaseMounts from './ops/releaseMounts'; -import updateTask from './ops/updateTask'; -import clearCompleted from './ops/clearCompleted'; -import sell from './ops/sell'; -import unlock from './ops/unlock'; -import revive from './ops/revive'; -import rebirth from './ops/rebirth'; -import blockUser from './ops/blockUser'; -import clearPMs from './ops/clearPMs'; -import deletePM from './ops/deletePM'; -import reroll from './ops/reroll'; -import addPushDevice from './ops/addPushDevice'; -import reset from './ops/reset'; -import markPmsRead from './ops/markPMSRead'; - -api.ops = { - scoreTask, - sleep, - allocate, - buy, - buyGear, - buyHealthPotion, - buyArmoire, - buyMysterySet, - buySpecialSpell, - buyQuest, - allocateNow, - hatch, - feed, - equip, - changeClass, - disableClasses, - purchase, - purchaseHourglass, - readCard, - openMysteryItem, - addWebhook, - updateWebhook, - deleteWebhook, - releasePets, - releaseBoth, - releaseMounts, - updateTask, - clearCompleted, - sell, - unlock, - revive, - rebirth, - blockUser, - clearPMs, - deletePM, - reroll, - addPushDevice, - reset, - markPmsRead, -}; /* ------------------------------------------------------ @@ -199,9 +87,10 @@ User (prototype wrapper to give it ops, helper funcs, and virtuals ------------------------------------------------------ */ + /* User is now wrapped (both on client and server), adding a few new properties: - * getters (_statsComputed) + * getters (_statsComputed, tasks, etc) * user.fns, which is a bunch of helper functions These were originally up above, but they make more sense belonging to the user object so we don't have to pass the user object all over the place. In fact, we should pull in more functions such as cron(), updateStats(), etc. @@ -232,16 +121,14 @@ TODO import importedOps from './ops'; import importedFns from './fns'; -// TODO Kept for the client side -api.wrap = function wrapUser (user, main = true) { - if (user._wrapped) return; - user._wrapped = true; - - // Make markModified available on the client side as a noop function - if (!user.markModified) { - user.markModified = function noopMarkModified () {}; +api.wrap = function(user, main) { + if (main == null) { + main = true; } - + if (user._wrapped) { + return; + } + user._wrapped = true; if (main) { user.ops = { update: _.partial(importedOps.update, user), @@ -276,9 +163,6 @@ api.wrap = function wrapUser (user, main = true) { releaseMounts: _.partial(importedOps.releaseMounts, user), releaseBoth: _.partial(importedOps.releaseBoth, user), buy: _.partial(importedOps.buy, user), - buyHealthPotion: _.partial(importedOps.buyHealthPotion, user), - buyArmoire: _.partial(importedOps.buyArmoire, user), - buyGear: _.partial(importedOps.buyGear, user), buyQuest: _.partial(importedOps.buyQuest, user), buyMysterySet: _.partial(importedOps.buyMysterySet, user), hourglassPurchase: _.partial(importedOps.hourglassPurchase, user), @@ -291,12 +175,11 @@ api.wrap = function wrapUser (user, main = true) { allocate: _.partial(importedOps.allocate, user), readCard: _.partial(importedOps.readCard, user), openMysteryItem: _.partial(importedOps.openMysteryItem, user), - score: _.partial(importedOps.scoreTask, user), - markPmsRead: _.partial(importedOps.markPmsRead, user), + score: _.partial(importedOps.score, user), }; } - user.fns = { + getItem: _.partial(importedFns.getItem, user), handleTwoHanded: _.partial(importedFns.handleTwoHanded, user), predictableRandom: _.partial(importedFns.predictableRandom, user), crit: _.partial(importedFns.crit, user), @@ -306,14 +189,34 @@ api.wrap = function wrapUser (user, main = true) { randomDrop: _.partial(importedFns.randomDrop, user), autoAllocate: _.partial(importedFns.autoAllocate, user), updateStats: _.partial(importedFns.updateStats, user), - statsComputed: _.partial(statsComputed, user), + cron: _.partial(importedFns.cron, user), + preenUserHistory: _.partial(importedFns.preenUserHistory, user), ultimateGear: _.partial(importedFns.ultimateGear, user), nullify: _.partial(importedFns.nullify, user), }; - Object.defineProperty(user, '_statsComputed', { - get () { - return statsComputed(user); - }, + get: function() { + var computed; + computed = _.reduce(['per', 'con', 'str', 'int'], (function(_this) { + return function(m, stat) { + m[stat] = _.reduce($w('stats stats.buffs items.gear.equipped.weapon items.gear.equipped.armor items.gear.equipped.head items.gear.equipped.shield'), function(m2, path) { + var item, val; + val = user.fns.dotGet(path); + return m2 + (~path.indexOf('items.gear') ? (item = content.gear.flat[val], (+(item != null ? item[stat] : void 0) || 0) * ((item != null ? item.klass : void 0) === user.stats["class"] || (item != null ? item.specialClass : void 0) === user.stats["class"] ? 1.5 : 1)) : +val[stat] || 0); + }, 0); + m[stat] += Math.floor(api.capByLevel(user.stats.lvl) / 2); + return m; + }; + })(this), {}); + computed.maxMP = computed.int * 2 + 30; + return computed; + } + }); + return Object.defineProperty(user, 'tasks', { + get: function() { + var tasks; + tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards); + return _.object(_.pluck(tasks, "id"), tasks); + } }); }; diff --git a/common/script/libs/appliedTags.js b/common/script/libs/appliedTags.js index ded2a9de23..31302fbe54 100644 --- a/common/script/libs/appliedTags.js +++ b/common/script/libs/appliedTags.js @@ -1,14 +1,19 @@ +import _ from 'lodash'; + /* Are there tags applied? */ -// TODO move to client - -module.exports = function appliedTags (userTags, taskTags = []) { - let arr = userTags.filter(tag => { - return taskTags.indexOf(tag.id) !== -1; - }).map(tag => { - return tag.name; +module.exports = function(userTags, taskTags) { + var arr; + arr = []; + _.each(userTags, function(t) { + if (t == null) { + return; + } + if (taskTags != null ? taskTags[t.id] : void 0) { + return arr.push(t.name); + } }); return arr.join(', '); }; diff --git a/common/script/libs/countExists.js b/common/script/libs/countExists.js new file mode 100644 index 0000000000..964e4e286f --- /dev/null +++ b/common/script/libs/countExists.js @@ -0,0 +1,7 @@ +import _ from 'lodash'; + +module.exports = function(items) { + return _.reduce(items, (function(m, v) { + return m + (v ? 1 : 0); + }), 0); +}; diff --git a/common/script/libs/dotGet.js b/common/script/libs/dotGet.js index d8ce026b82..4585d8fd53 100644 --- a/common/script/libs/dotGet.js +++ b/common/script/libs/dotGet.js @@ -1,5 +1,9 @@ import _ from 'lodash'; -// TODO remove completely, only used in client - -module.exports = _.get; +module.exports = function(obj, path) { + return _.reduce(path.split('.'), ((function(_this) { + return function(curr, next) { + return curr != null ? curr[next] : void 0; + }; + })(this)), obj); +}; diff --git a/common/script/libs/dotSet.js b/common/script/libs/dotSet.js index b664c54d05..40165078aa 100644 --- a/common/script/libs/dotSet.js +++ b/common/script/libs/dotSet.js @@ -1,5 +1,14 @@ import _ from 'lodash'; -// TODO remove completely, only used in client - -module.exports = _.set; +module.exports = function(obj, path, val) { + var arr; + arr = path.split('.'); + return _.reduce(arr, (function(_this) { + return function(curr, next, index) { + if ((arr.length - 1) === index) { + curr[next] = val; + } + return curr[next] != null ? curr[next] : curr[next] = {}; + }; + })(this), obj); +}; diff --git a/common/script/libs/encodeiCalLink.js b/common/script/libs/encodeiCalLink.js new file mode 100644 index 0000000000..4a85badd59 --- /dev/null +++ b/common/script/libs/encodeiCalLink.js @@ -0,0 +1,9 @@ +/* +Encode the download link for .ics iCal file + */ + +module.exports = function(uid, apiToken) { + var loc, ref; + loc = (typeof window !== "undefined" && window !== null ? window.location.host : void 0) || (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.BASE_URL : void 0 : void 0) || ''; + return encodeURIComponent("http://" + loc + "/v1/users/" + uid + "/calendar.ics?apiToken=" + apiToken); +}; diff --git a/common/script/libs/errors.js b/common/script/libs/errors.js deleted file mode 100644 index cb780d215b..0000000000 --- a/common/script/libs/errors.js +++ /dev/null @@ -1,42 +0,0 @@ -import extendableBuiltin from './extendableBuiltin'; - -// Base class for custom application errors -// It extends Error and capture the stack trace -export class CustomError extends extendableBuiltin(Error) { - constructor () { - super(); - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor); - } - } -} - -// We specify an httpCode for all errors so that they can be used in the API too - -export class NotAuthorized extends CustomError { - constructor (customMessage) { - super(); - this.name = this.constructor.name; - this.httpCode = 401; - this.message = customMessage || 'Not authorized.'; - } -} - -export class BadRequest extends CustomError { - constructor (customMessage) { - super(); - this.name = this.constructor.name; - this.httpCode = 400; - this.message = customMessage || 'Bad request.'; - } -} - -export class NotFound extends CustomError { - constructor (customMessage) { - super(); - this.name = this.constructor.name; - this.httpCode = 404; - this.message = customMessage || 'Not found.'; - } -} diff --git a/common/script/libs/extendableBuiltin.js b/common/script/libs/extendableBuiltin.js deleted file mode 100644 index 56186301c4..0000000000 --- a/common/script/libs/extendableBuiltin.js +++ /dev/null @@ -1,11 +0,0 @@ -// Babel 6 doesn't support extending native class (Error, Array, ...) -// This function makes it possible to extend native classes with the same results as Babel 5 -module.exports = function extendableBuiltin (klass) { - function ExtendableBuiltin () { - klass.apply(this, arguments); - } - ExtendableBuiltin.prototype = Object.create(klass.prototype); - Object.setPrototypeOf(ExtendableBuiltin, klass); - - return ExtendableBuiltin; -}; diff --git a/common/script/libs/friendlyTimestamp.js b/common/script/libs/friendlyTimestamp.js new file mode 100644 index 0000000000..dfda6fbed7 --- /dev/null +++ b/common/script/libs/friendlyTimestamp.js @@ -0,0 +1,9 @@ +import moment from 'moment'; + +/* +Friendly timestamp + */ + +module.exports = function(timestamp) { + return moment(timestamp).format('MM/DD h:mm:ss a'); +}; diff --git a/common/script/libs/gold.js b/common/script/libs/gold.js index 83d9531d5e..8016e2cff9 100644 --- a/common/script/libs/gold.js +++ b/common/script/libs/gold.js @@ -1,9 +1,7 @@ -// TODO move to client - -module.exports = function gold (num) { +module.exports = function(num) { if (num) { return Math.floor(num); } else { - return '0'; + return "0"; } }; diff --git a/common/script/libs/index.js b/common/script/libs/index.js new file mode 100644 index 0000000000..dd0c420d8d --- /dev/null +++ b/common/script/libs/index.js @@ -0,0 +1,46 @@ +import uuid from './uuid'; +import taskDefaults from './taskDefaults'; +import refPush from './refPush'; +import splitWhitespace from './splitWhitespace'; +import planGemLimits from './planGemLimits'; +import preenTodos from './preenTodos'; +import dotSet from './dotSet'; +import dotGet from './dotGet'; +import preenHistory from './preenHistory'; +import countExists from './countExists'; +import updateStore from './updateStore'; + +import appliedTags from './appliedTags'; +import encodeiCalLink from './encodeiCalLink'; +import friendlyTimestamp from './friendlyTimestamp'; +import gold from './gold'; +import newChatMessages from './newChatMessages'; +import noTags from './noTags'; +import percent from './percent'; +import removeWhitespace from './removeWhitespace'; +import silver from './silver'; +import taskClasses from './taskClasses'; + +module.exports = { + uuid, + taskDefaults, + refPush, + splitWhitespace, + planGemLimits, + preenTodos, + dotSet, + dotGet, + preenHistory, + countExists, + updateStore, + appliedTags, + encodeiCalLink, + friendlyTimestamp, + gold, + newChatMessages, + noTags, + percent, + removeWhitespace, + silver, + taskClasses, +}; diff --git a/common/script/libs/newChatMessages.js b/common/script/libs/newChatMessages.js new file mode 100644 index 0000000000..abe7680edf --- /dev/null +++ b/common/script/libs/newChatMessages.js @@ -0,0 +1,10 @@ +/* +Does user have new chat messages? + */ + +module.exports = function(messages, lastMessageSeen) { + if (!((messages != null ? messages.length : void 0) > 0)) { + return false; + } + return (messages != null ? messages[0] : void 0) && (messages[0].id !== lastMessageSeen); +}; diff --git a/common/script/libs/noTags.js b/common/script/libs/noTags.js index 16a59d1d31..c3abb9054e 100644 --- a/common/script/libs/noTags.js +++ b/common/script/libs/noTags.js @@ -4,10 +4,8 @@ import _ from 'lodash'; are any tags active? */ -// TODO move to client - -module.exports = function noTags (tags) { - return _.isEmpty(tags) || _.isEmpty(_.filter(tags, (t) => { +module.exports = function(tags) { + return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) { return t; })); }; diff --git a/common/script/libs/percent.js b/common/script/libs/percent.js index d7622474dd..7439b22285 100644 --- a/common/script/libs/percent.js +++ b/common/script/libs/percent.js @@ -1,12 +1,10 @@ -// TODO move to client - -module.exports = function percent (x, y, dir) { - let roundFn; +module.exports = function(x, y, dir) { + var roundFn; switch (dir) { - case 'up': + case "up": roundFn = Math.ceil; break; - case 'down': + case "down": roundFn = Math.floor; break; default: diff --git a/common/script/libs/pickDeep.js b/common/script/libs/pickDeep.js deleted file mode 100644 index 919d926854..0000000000 --- a/common/script/libs/pickDeep.js +++ /dev/null @@ -1,13 +0,0 @@ -// An utility to pick deep properties from an object. -// Works like _.pick but supports nested props (ie pickDeep(obj, ['deep.property'])) - -import _ from 'lodash'; - -module.exports = function pickDeep (obj, properties) { - if (!_.isArray(properties)) throw new Error('"properties" must be an array'); - - let result = {}; - _.each(properties, (prop) => _.set(result, prop, _.get(obj, prop))); - - return result; -}; diff --git a/common/script/libs/preenHistory.js b/common/script/libs/preenHistory.js new file mode 100644 index 0000000000..0d354c238c --- /dev/null +++ b/common/script/libs/preenHistory.js @@ -0,0 +1,43 @@ +import moment from 'moment'; +import _ from 'lodash'; + +/* +Preen history for users with > 7 history entries +This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array +of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week +of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite] + */ + +module.exports = function(history) { + var newHistory, preen, thisMonth; + history = _.filter(history, function(h) { + return !!h; + }); + newHistory = []; + preen = function(amount, groupBy) { + var groups; + groups = _.chain(history).groupBy(function(h) { + return moment(h.date).format(groupBy); + }).sortBy(function(h, k) { + return k; + }).value(); + groups = groups.slice(-amount); + groups.pop(); + return _.each(groups, function(group) { + newHistory.push({ + date: moment(group[0].date).toDate(), + value: _.reduce(group, (function(m, obj) { + return m + obj.value; + }), 0) / group.length + }); + return true; + }); + }; + preen(50, "YYYY"); + preen(moment().format('MM'), "YYYYMM"); + thisMonth = moment().format('YYYYMM'); + newHistory = newHistory.concat(_.filter(history, function(h) { + return moment(h.date).format('YYYYMM') === thisMonth; + })); + return newHistory; +}; diff --git a/common/script/libs/preenTodos.js b/common/script/libs/preenTodos.js index fcf8775d5f..f07a49c9d0 100644 --- a/common/script/libs/preenTodos.js +++ b/common/script/libs/preenTodos.js @@ -1,12 +1,14 @@ import moment from 'moment'; import _ from 'lodash'; -// TODO used only in v2 +/* + Preen 3-day past-completed To-Dos from Angular & mobile app + */ -module.exports = function preenTodos (tasks) { - return _.filter(tasks, (t) => { - return !t.completed || t.challenge && t.challenge.id || moment(t.dateCompleted).isAfter(moment().subtract({ - days: 3, +module.exports = function(tasks) { + return _.filter(tasks, function(t) { + return !t.completed || (t.challenge && t.challenge.id) || moment(t.dateCompleted).isAfter(moment().subtract({ + days: 3 })); }); }; diff --git a/common/script/libs/refPush.js b/common/script/libs/refPush.js index 1bdddc9edf..06b3617014 100644 --- a/common/script/libs/refPush.js +++ b/common/script/libs/refPush.js @@ -7,14 +7,13 @@ import uuid from './uuid'; no problem. To maintain sorting, we use these helper functions: */ -module.exports = function refPush (reflist, item) { +module.exports = function(reflist, item, prune) { + if (prune == null) { + prune = 0; + } item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1; - if (!(item.id && !reflist[item.id])) { item.id = uuid(); } - - reflist[item.id] = item; - - return reflist[item.id]; + return reflist[item.id] = item; }; diff --git a/common/script/libs/removeWhitespace.js b/common/script/libs/removeWhitespace.js new file mode 100644 index 0000000000..1015beda54 --- /dev/null +++ b/common/script/libs/removeWhitespace.js @@ -0,0 +1,10 @@ +/* +Remove whitespace #FIXME are we using this anywwhere? Should we be? + */ + +module.exports = function(str) { + if (!str) { + return ''; + } + return str.replace(/\s/g, ''); +}; diff --git a/common/script/libs/silver.js b/common/script/libs/silver.js index 1d3620f602..0dbae97b05 100644 --- a/common/script/libs/silver.js +++ b/common/script/libs/silver.js @@ -2,13 +2,10 @@ Silver amount from their money */ -// TODO move to client - -module.exports = function silver (num) { +module.exports = function(num) { if (num) { - let centCount = Math.floor((num - Math.floor(num)) * 100); - return `0${centCount}`.slice(-2); + return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2); } else { - return '00'; + return "00"; } }; diff --git a/common/script/libs/splitWhitespace.js b/common/script/libs/splitWhitespace.js index 2f8276bcb3..1ef3d513aa 100644 --- a/common/script/libs/splitWhitespace.js +++ b/common/script/libs/splitWhitespace.js @@ -1,4 +1,3 @@ - -module.exports = function splitWhitespace (s) { +module.exports = function(s) { return s.split(' '); }; diff --git a/common/script/libs/statsComputed.js b/common/script/libs/statsComputed.js deleted file mode 100644 index a239b2039c..0000000000 --- a/common/script/libs/statsComputed.js +++ /dev/null @@ -1,28 +0,0 @@ -import _ from 'lodash'; -import content from '../content/index'; -import * as statHelpers from '../statHelpers'; - -module.exports = function statsComputed (user) { - let paths = ['stats', 'stats.buffs', 'items.gear.equipped.weapon', 'items.gear.equipped.armor', - 'items.gear.equipped.head', 'items.gear.equipped.shield']; - let computed = _.reduce(['per', 'con', 'str', 'int'], (m, stat) => { - m[stat] = _.reduce(paths, (m2, path) => { - let val = _.get(user, path); - let item = content.gear.flat[val]; - if (!item) item = {}; - if (!item[stat]) { - item[stat] = 0; - } else { - item[stat] = Number(item[stat]); - } - let thisMultiplier = item.klass === user.stats.class || item.specialClass === user.stats.class ? 1.5 : 1; - let thisReturn = path.indexOf('items.gear') !== -1 ? item[stat] * thisMultiplier : Number(val[stat]); - return m2 + thisReturn || 0; - }, 0); - m[stat] += Math.floor(statHelpers.capByLevel(user.stats.lvl) / 2); - return m; - }, {}); - - computed.maxMP = computed.int * 2 + 30; - return computed; -}; diff --git a/common/script/libs/taskClasses.js b/common/script/libs/taskClasses.js index 9ef223d9f7..21bed1ad63 100644 --- a/common/script/libs/taskClasses.js +++ b/common/script/libs/taskClasses.js @@ -1,45 +1,51 @@ import { - shouldDo, + shouldDo } from '../cron'; - /* Task classes given everything about the class */ - -// TODO move to the client - -module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCron = Number(new Date()), showCompleted = false, main = false) { - if (!task) { - return ''; +module.exports = function(task, filters, dayStart, lastCron, showCompleted, main) { + var classes, completed, enabled, filter, priority, ref, repeat, type, value; + if (filters == null) { + filters = []; } - let type = task.type; - let classes = task.type; - let completed = task.completed; - let value = task.value; - let priority = task.priority; - - if (main && !task._editing) { - for (let filter in filters) { - let enabled = filters[filter]; - if (!task.tags) task.tags = []; - if (enabled && task.tags.indexOf(filter) === -1) { - return 'hidden'; + if (dayStart == null) { + dayStart = 0; + } + if (lastCron == null) { + lastCron = +(new Date); + } + if (showCompleted == null) { + showCompleted = false; + } + if (main == null) { + main = false; + } + if (!task) { + return; + } + type = task.type, completed = task.completed, value = task.value, repeat = task.repeat, priority = task.priority; + if (main) { + if (!task._editing) { + for (filter in filters) { + enabled = filters[filter]; + if (enabled && !((ref = task.tags) != null ? ref[filter] : void 0)) { + return 'hidden'; + } } } } - - classes = task.type; + classes = type; if (task._editing) { - classes += ' beingEdited'; + classes += " beingEdited"; } - if (type === 'todo' || type === 'daily') { - if (completed || (type === 'daily' && !shouldDo(Number(new Date()), task, { // eslint-disable-line no-extra-parens - dayStart, + if (completed || (type === 'daily' && !shouldDo(+(new Date), task, { + dayStart: dayStart }))) { - classes += ' completed'; + classes += " completed"; } else { - classes += ' uncompleted'; + classes += " uncompleted"; } } else if (type === 'habit') { if (task.down && task.up) { @@ -49,7 +55,6 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro classes += ' habit-narrow'; } } - if (priority === 0.1) { classes += ' difficulty-trivial'; } else if (priority === 1) { @@ -59,7 +64,6 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro } else if (priority === 2) { classes += ' difficulty-hard'; } - if (value < -20) { classes += ' color-worst'; } else if (value < -10) { @@ -75,6 +79,5 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro } else { classes += ' color-best'; } - return classes; }; diff --git a/common/script/libs/taskDefaults.js b/common/script/libs/taskDefaults.js index e6bdba3def..69c815e4fd 100644 --- a/common/script/libs/taskDefaults.js +++ b/common/script/libs/taskDefaults.js @@ -1,74 +1,71 @@ -import { v4 as uuid } from 'uuid'; +import uuid from './uuid'; import _ from 'lodash'; -import moment from 'moment'; -// Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before -// sending up to the server for performance +/* +Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before +sending up to the server for performance + */ -// TODO move to client code? +// TODO revisit -const tasksTypes = ['habit', 'daily', 'todo', 'reward']; - -module.exports = function taskDefaults (task = {}) { - if (!task.type || tasksTypes.indexOf(task.type) === -1) { +module.exports = function(task) { + var defaults, ref, ref1, ref2; + if (task == null) { + task = {}; + } + if (!(task.type && ((ref = task.type) === 'habit' || ref === 'daily' || ref === 'todo' || ref === 'reward'))) { task.type = 'habit'; } - - let defaultId = uuid(); - let defaults = { - _id: defaultId, - text: task._id || defaultId, + defaults = { + id: uuid(), + text: task.id != null ? task.id : '', notes: '', - tags: [], - value: task.type === 'reward' ? 10 : 0, priority: 1, challenge: {}, - reminders: [], attribute: 'str', - createdAt: new Date(), // TODO these are going to be overwritten by the server... - updatedAt: new Date(), + dateCreated: new Date() }; - _.defaults(task, defaults); - - if (task.type === 'habit' || task.type === 'daily') { - _.defaults(task, { - history: [], - }); - } - - if (task.type === 'todo' || task.type === 'daily') { - _.defaults(task, { - completed: false, - collapseChecklist: false, - checklist: [], - }); - } - if (task.type === 'habit') { _.defaults(task, { up: true, - down: true, + down: true + }); + } + if ((ref1 = task.type) === 'habit' || ref1 === 'daily') { + _.defaults(task, { + history: [] + }); + } + if ((ref2 = task.type) === 'daily' || ref2 === 'todo') { + _.defaults(task, { + completed: false }); } - if (task.type === 'daily') { _.defaults(task, { streak: 0, repeat: { + su: true, m: true, t: true, w: true, th: true, f: true, - s: true, - su: true, - }, - startDate: moment().startOf('day').toDate(), + s: true + } + }, { + startDate: new Date(), everyX: 1, - frequency: 'weekly', + frequency: 'weekly' }); } - + task._id = task.id; + if (task.value == null) { + task.value = task.type === 'reward' ? 10 : 0; + } + if (!_.isNumber(task.priority)) { + task.priority = 1; + } return task; }; diff --git a/common/script/libs/updateStore.js b/common/script/libs/updateStore.js index f6593de8fd..05d009fd9d 100644 --- a/common/script/libs/updateStore.js +++ b/common/script/libs/updateStore.js @@ -1,31 +1,36 @@ import _ from 'lodash'; import content from '../content/index'; -// Return the list of gear items available for purchase +/* + Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there + */ -let sortOrder = _.reduce(content.gearTypes, (accumulator, val, key) => { - accumulator[val] = key; - return accumulator; -}, {}); +var sortOrder = _.reduce(content.gearTypes, (function(m, v, k) { + m[v] = k; + return m; +}), {}); -module.exports = function updateStore (user) { - let changes = []; - - _.each(content.gearTypes, (type) => { - let found = _.find(content.gear.tree[type][user.stats.class], (item) => { +module.exports = function(user) { + var changes; + if (!user) { + return; + } + changes = []; + _.each(content.gearTypes, function(type) { + var found; + found = _.find(content.gear.tree[type][user.stats["class"]], function(item) { return !user.items.gear.owned[item.key]; }); - - if (found) changes.push(found); - }); - - changes = changes.concat(_.filter(content.gear.flat, (val) => { - if (['special', 'mystery', 'armoire'].indexOf(val.klass) !== -1 && !user.items.gear.owned[val.key] && (val.canOwn ? val.canOwn(user) : false)) { - return true; - } else { - return false; + if (found) { + changes.push(found); } + return true; + }); + changes = changes.concat(_.filter(content.gear.flat, function(v) { + var ref; + return ((ref = v.klass) === 'special' || ref === 'mystery' || ref === 'armoire') && !user.items.gear.owned[v.key] && (typeof v.canOwn === "function" ? v.canOwn(user) : void 0); })); - - return _.sortBy(changes, (change) => sortOrder[change.type]); + return _.sortBy(changes, function(c) { + return sortOrder[c.type]; + }); }; diff --git a/common/script/libs/uuid.js b/common/script/libs/uuid.js index 63f75cf398..ca20dcec4e 100644 --- a/common/script/libs/uuid.js +++ b/common/script/libs/uuid.js @@ -1,4 +1 @@ -import uuid from 'uuid'; - -// TODO remove this file completely -module.exports = uuid.v4; +module.exports = require('uuid').v4; diff --git a/common/script/ops/addPushDevice.js b/common/script/ops/addPushDevice.js index a909fe9feb..d96cf249cf 100644 --- a/common/script/ops/addPushDevice.js +++ b/common/script/ops/addPushDevice.js @@ -1,41 +1,20 @@ import _ from 'lodash'; -import i18n from '../i18n'; -import { - BadRequest, - NotAuthorized, -} from '../libs/errors'; - -// TODO move to server code -module.exports = function addPushDevice (user, req = {}) { - let regId = _.get(req, 'body.regId'); - if (!regId) throw new BadRequest(i18n.t('regIdRequired', req.language)); - - let type = _.get(req, 'body.type'); - if (!type) throw new BadRequest(i18n.t('typeRequired', req.language)); +module.exports = function(user, req, cb) { + var i, item, pd; if (!user.pushDevices) { user.pushDevices = []; } - - let pushDevices = user.pushDevices; - - let item = { - regId, - type, + pd = user.pushDevices; + item = { + regId: req.body.regId, + type: req.body.type }; - - let indexOfPushDevice = _.findIndex(pushDevices, { - regId: item.regId, + i = _.findIndex(pd, { + regId: item.regId }); - - if (indexOfPushDevice !== -1) { - throw new NotAuthorized(i18n.t('pushDeviceAlreadyAdded', req.language)); + if (i === -1) { + pd.push(item); } - - pushDevices.push(item); - - return [ - user.pushDevices, - i18n.t('pushDeviceAdded', req.language), - ]; + return typeof cb === "function" ? cb(null, user.pushDevices) : void 0; }; diff --git a/common/script/ops/addTag.js b/common/script/ops/addTag.js index b44ead8d2e..a020a5fbaf 100644 --- a/common/script/ops/addTag.js +++ b/common/script/ops/addTag.js @@ -1,17 +1,12 @@ import uuid from '../libs/uuid'; -import _ from 'lodash'; -// TODO used only in client, move there? - -module.exports = function addTag (user, req = {}) { - if (!user.tags) { +module.exports = function(user, req, cb) { + if (user.tags == null) { user.tags = []; } - user.tags.push({ name: req.body.name, - id: _.get(req, 'body.id') || uuid(), + id: req.body.id || uuid() }); - - return user.tags; + return typeof cb === "function" ? cb(null, user.tags) : void 0; }; diff --git a/common/script/ops/addTask.js b/common/script/ops/addTask.js index 592f877248..a2d1895e1e 100644 --- a/common/script/ops/addTask.js +++ b/common/script/ops/addTask.js @@ -1,23 +1,27 @@ import taskDefaults from '../libs/taskDefaults'; +import i18n from '../i18n'; -// TODO move to client since it's only used there? - -module.exports = function addTask (user, req = {body: {}}) { - let task = taskDefaults(req.body); - user.tasksOrder[`${task.type}s`].unshift(task._id); - user[`${task.type}s`].unshift(task); - +module.exports = function(user, req, cb) { + var task; + task = taskDefaults(req.body); + if (user.tasks[task.id] != null) { + return typeof cb === "function" ? cb({ + code: 409, + message: i18n.t('messageDuplicateTaskID', req.language) + }) : void 0; + } + user[task.type + "s"].unshift(task); if (user.preferences.newTaskEdit) { task._editing = true; } - if (user.preferences.tagsCollapsed) { task._tags = true; } - if (!user.preferences.advancedCollapsed) { task._advanced = true; } - + if (typeof cb === "function") { + cb(null, task); + } return task; }; diff --git a/common/script/ops/addWebhook.js b/common/script/ops/addWebhook.js index c308d1b9e5..99eaf49bba 100644 --- a/common/script/ops/addWebhook.js +++ b/common/script/ops/addWebhook.js @@ -1,27 +1,15 @@ import refPush from '../libs/refPush'; -import validator from 'validator'; -import i18n from '../i18n'; -import { - BadRequest, -} from '../libs/errors'; -import _ from 'lodash'; -module.exports = function addWebhook (user, req = {}) { - let wh = user.preferences.webhooks; - - if (!validator.isURL(_.get(req, 'body.url'))) throw new BadRequest(i18n.t('invalidUrl', req.language)); - if (!validator.isBoolean(_.get(req, 'body.enabled'))) throw new BadRequest(i18n.t('invalidEnabled', req.language)); - - user.markModified('preferences.webhooks'); - - if (req.v2 === true) { - return user.preferences.webhooks; - } else { - return [ - refPush(wh, { - url: req.body.url, - enabled: req.body.enabled, - }), - ]; +module.exports = function(user, req, cb) { + var wh; + wh = user.preferences.webhooks; + refPush(wh, { + url: req.body.url, + enabled: req.body.enabled || true, + id: req.body.id + }); + if (typeof user.markModified === "function") { + user.markModified('preferences.webhooks'); } + return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; }; diff --git a/common/script/ops/allocate.js b/common/script/ops/allocate.js index 8e07e09589..92b5ae53fa 100644 --- a/common/script/ops/allocate.js +++ b/common/script/ops/allocate.js @@ -1,31 +1,15 @@ import _ from 'lodash'; -import { - ATTRIBUTES, -} from '../constants'; -import { - BadRequest, - NotAuthorized, -} from '../libs/errors'; -import i18n from '../i18n'; - -module.exports = function allocate (user, req = {}) { - let stat = _.get(req, 'query.stat', 'str'); - - if (ATTRIBUTES.indexOf(stat) === -1) { - throw new BadRequest(i18n.t('invalidAttribute', {attr: stat}, req.language)); - } +import splitWhitespace from '../libs/splitWhitespace'; +module.exports = function(user, req, cb) { + var stat; + stat = req.query.stat || 'str'; if (user.stats.points > 0) { user.stats[stat]++; user.stats.points--; if (stat === 'int') { user.stats.mp++; } - } else { - throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language)); } - - return [ - user.stats, - ]; + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats'))) : void 0; }; diff --git a/common/script/ops/allocateNow.js b/common/script/ops/allocateNow.js index e8ae5d249c..815c0b8959 100644 --- a/common/script/ops/allocateNow.js +++ b/common/script/ops/allocateNow.js @@ -1,15 +1,10 @@ import _ from 'lodash'; -import autoAllocate from '../fns/autoAllocate'; -module.exports = function allocateNow (user, req = {}) { - _.times(user.stats.points, () => autoAllocate(user)); +module.exports = function(user, req, cb) { + _.times(user.stats.points, user.fns.autoAllocate); user.stats.points = 0; - - if (req.v2 === true) { - return _.pick(user, 'stats'); - } else { - return [ - user.stats, - ]; + if (typeof user.markModified === "function") { + user.markModified('stats'); } + return typeof cb === "function" ? cb(null, user.stats) : void 0; }; diff --git a/common/script/ops/blockUser.js b/common/script/ops/blockUser.js index 5c123735ed..dd08925640 100644 --- a/common/script/ops/blockUser.js +++ b/common/script/ops/blockUser.js @@ -1,21 +1,13 @@ -import validator from 'validator'; -import i18n from '../i18n'; -import { - BadRequest, -} from '../libs/errors'; - -module.exports = function blockUser (user, req = {}) { - if (!validator.isUUID(req.params.uuid)) throw new BadRequest(i18n.t('invalidUUID', req.language)); - - let i = user.inbox.blocks.indexOf(req.params.uuid); - if (i === -1) { - user.inbox.blocks.push(req.params.uuid); - } else { +module.exports = function(user, req, cb) { + var i; + i = user.inbox.blocks.indexOf(req.params.uuid); + if (~i) { user.inbox.blocks.splice(i, 1); + } else { + user.inbox.blocks.push(req.params.uuid); } - - user.markModified('inbox.blocks'); - return [ - user.inbox.blocks, - ]; + if (typeof user.markModified === "function") { + user.markModified('inbox.blocks'); + } + return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0; }; diff --git a/common/script/ops/buy.js b/common/script/ops/buy.js index ded5b034d2..1686316264 100644 --- a/common/script/ops/buy.js +++ b/common/script/ops/buy.js @@ -1,24 +1,119 @@ +import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; -import { - BadRequest, -} from '../libs/errors'; -import buyHealthPotion from './buyHealthPotion'; -import buyArmoire from './buyArmoire'; -import buyGear from './buyGear'; +import count from '../count'; +import splitWhitespace from '../libs/splitWhitespace'; -module.exports = function buy (user, req = {}, analytics) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); - - let buyRes; - if (key === 'potion') { - buyRes = buyHealthPotion(user, req, analytics); - } else if (key === 'armoire') { - buyRes = buyArmoire(user, req, analytics); - } else { - buyRes = buyGear(user, req, analytics); +module.exports = function(user, req, cb, analytics) { + var analyticsData, armoireExp, armoireResp, armoireResult, base, buyResp, drop, eligibleEquipment, item, key, message, name; + key = req.params.key; + item = key === 'potion' ? content.potion : key === 'armoire' ? content.armoire : content.gear.flat[key]; + if (!item) { + return typeof cb === "function" ? cb({ + code: 404, + message: "Item '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" + }) : void 0; } - - return buyRes; + if (user.stats.gp < item.value) { + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageNotEnoughGold', req.language) + }) : void 0; + } + if ((item.canOwn != null) && !item.canOwn(user)) { + return typeof cb === "function" ? cb({ + code: 401, + message: "You can't buy this item" + }) : void 0; + } + armoireResp = void 0; + if (item.key === 'potion') { + user.stats.hp += 15; + if (user.stats.hp > 50) { + user.stats.hp = 50; + } + } else if (item.key === 'armoire') { + armoireResult = user.fns.predictableRandom(user.stats.gp); + eligibleEquipment = _.filter(content.gear.flat, (function(i) { + return i.klass === 'armoire' && !user.items.gear.owned[i.key]; + })); + if (!_.isEmpty(eligibleEquipment) && (armoireResult < .6 || !user.flags.armoireOpened)) { + eligibleEquipment.sort(); + drop = user.fns.randomVal(eligibleEquipment); + user.items.gear.owned[drop.key] = true; + user.flags.armoireOpened = true; + message = i18n.t('armoireEquipment', { + image: '', + dropText: drop.text(req.language) + }, req.language); + if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) { + user.flags.armoireEmpty = true; + } + armoireResp = { + type: "gear", + dropKey: drop.key, + dropText: drop.text(req.language) + }; + } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < .8) || armoireResult < .5) { + drop = user.fns.randomVal(_.where(content.food, { + canDrop: true + })); + if ((base = user.items.food)[name = drop.key] == null) { + base[name] = 0; + } + user.items.food[drop.key] += 1; + message = i18n.t('armoireFood', { + image: '', + dropArticle: drop.article, + dropText: drop.text(req.language) + }, req.language); + armoireResp = { + type: "food", + dropKey: drop.key, + dropArticle: drop.article, + dropText: drop.text(req.language) + }; + } else { + armoireExp = Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10); + user.stats.exp += armoireExp; + message = i18n.t('armoireExp', req.language); + armoireResp = { + "type": "experience", + "value": armoireExp + }; + } + } else { + if (user.preferences.autoEquip) { + user.items.gear.equipped[item.type] = item.key; + message = user.fns.handleTwoHanded(item, null, req); + } + user.items.gear.owned[item.key] = true; + if (message == null) { + message = i18n.t('messageBought', { + itemText: item.text(req.language) + }, req.language); + } + if (item.last) { + user.fns.ultimateGear(); + } + } + user.stats.gp -= item.value; + analyticsData = { + uuid: user._id, + itemKey: key, + acquireMethod: 'Gold', + goldCost: item.value, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); + } + buyResp = _.pick(user, splitWhitespace('items achievements stats flags')); + if (armoireResp) { + buyResp["armoire"] = armoireResp; + } + return typeof cb === "function" ? cb({ + code: 200, + message: message + }, buyResp) : void 0; }; diff --git a/common/script/ops/buyArmoire.js b/common/script/ops/buyArmoire.js deleted file mode 100644 index e183c06984..0000000000 --- a/common/script/ops/buyArmoire.js +++ /dev/null @@ -1,115 +0,0 @@ -import content from '../content/index'; -import i18n from '../i18n'; -import _ from 'lodash'; -import count from '../count'; -import splitWhitespace from '../libs/splitWhitespace'; -import { - NotAuthorized, -} from '../libs/errors'; -import predictableRandom from '../fns/predictableRandom'; -import randomVal from '../fns/randomVal'; - -module.exports = function buyArmoire (user, req = {}, analytics) { - let item = content.armoire; - - if (user.stats.gp < item.value) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); - } - - if (item.canOwn && !item.canOwn(user)) { - throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); - } - - let armoireResp; - let armoireResult; - let eligibleEquipment; - let drop; - let message; - - armoireResult = predictableRandom(user, user.stats.gp); - eligibleEquipment = _.filter(content.gear.flat, (eligible) => { - return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]; - }); - - if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) { - eligibleEquipment.sort(); - drop = randomVal(user, eligibleEquipment); - - if (user.items.gear.owned[drop.key]) { - throw new NotAuthorized(i18n.t('equipmentAlradyOwned', req.language)); - } - - user.items.gear.owned[drop.key] = true; - user.flags.armoireOpened = true; - message = i18n.t('armoireEquipment', { - image: ``, - dropText: drop.text(req.language), - }, req.language); - - if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) { - user.flags.armoireEmpty = true; - } - - armoireResp = { - type: 'gear', - dropKey: drop.key, - dropText: drop.text(req.language), - }; - } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens - drop = randomVal(user, _.where(content.food, { - canDrop: true, - })); - user.items.food[drop.key] = user.items.food[drop.key] || 0; - user.items.food[drop.key] += 1; - - message = i18n.t('armoireFood', { - image: ``, - dropArticle: drop.article, - dropText: drop.text(req.language), - }, req.language); - armoireResp = { - type: 'food', - dropKey: drop.key, - dropArticle: drop.article, - dropText: drop.text(req.language), - }; - } else { - let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10); - user.stats.exp += armoireExp; - message = i18n.t('armoireExp', req.language); - armoireResp = { - type: 'experience', - value: armoireExp, - }; - } - - user.stats.gp -= item.value; - - if (!message) { - message = i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language); - } - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: 'Armoire', - acquireMethod: 'Gold', - goldCost: item.value, - category: 'behavior', - }); - } - - let resData = _.pick(user, splitWhitespace('items flags')); - if (armoireResp) resData.armoire = armoireResp; - - if (req.v2 === true) { - return resData; - } else { - return [ - resData, - message, - ]; - } -}; diff --git a/common/script/ops/buyGear.js b/common/script/ops/buyGear.js deleted file mode 100644 index e4f4eb3b68..0000000000 --- a/common/script/ops/buyGear.js +++ /dev/null @@ -1,70 +0,0 @@ -import content from '../content/index'; -import i18n from '../i18n'; -import _ from 'lodash'; -import splitWhitespace from '../libs/splitWhitespace'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; -import handleTwoHanded from '../fns/handleTwoHanded'; -import ultimateGear from '../fns/ultimateGear'; - -module.exports = function buyGear (user, req = {}, analytics) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); - - let item = content.gear.flat[key]; - - if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language)); - - if (user.stats.gp < item.value) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); - } - - if (item.canOwn && !item.canOwn(user)) { - throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); - } - - let message; - - if (user.items.gear.owned[item.key]) { - throw new NotAuthorized(i18n.t('equipmentAlreadyOwned', req.language)); - } - - if (user.preferences.autoEquip) { - user.items.gear.equipped[item.type] = item.key; - message = handleTwoHanded(user, item, undefined, req); - } - - user.items.gear.owned[item.key] = true; - - if (item.last) ultimateGear(user); - - user.stats.gp -= item.value; - - if (!message) { - message = i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language); - } - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: key, - acquireMethod: 'Gold', - goldCost: item.value, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('items achievements stats flags')); - } else { - return [ - _.pick(user, splitWhitespace('items achievements stats flags')), - message, - ]; - } -}; diff --git a/common/script/ops/buyHealthPotion.js b/common/script/ops/buyHealthPotion.js deleted file mode 100644 index 1a6c8b0e18..0000000000 --- a/common/script/ops/buyHealthPotion.js +++ /dev/null @@ -1,48 +0,0 @@ -import content from '../content/index'; -import i18n from '../i18n'; -import { - NotAuthorized, -} from '../libs/errors'; - -module.exports = function buyHealthPotion (user, req = {}, analytics) { - let item = content.potion; - - if (user.stats.gp < item.value) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); - } - - if (item.canOwn && !item.canOwn(user)) { - throw new NotAuthorized(i18n.t('cannotBuyItem', req.language)); - } - - user.stats.hp += 15; - if (user.stats.hp > 50) { - user.stats.hp = 50; - } - - user.stats.gp -= item.value; - - let message = i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language); - - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: 'Potion', - acquireMethod: 'Gold', - goldCost: item.value, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return user.stats; - } else { - return [ - user.stats, - message, - ]; - } -}; diff --git a/common/script/ops/buyMysterySet.js b/common/script/ops/buyMysterySet.js index acf0014279..44ccdb9aaf 100644 --- a/common/script/ops/buyMysterySet.js +++ b/common/script/ops/buyMysterySet.js @@ -2,54 +2,42 @@ import i18n from '../i18n'; import content from '../content/index'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; -import pickDeep from '../libs/pickDeep'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; - -module.exports = function buyMysterySet (user, req = {}, analytics) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); +module.exports = function(user, req, cb, analytics) { + var mysterySet, ref; if (!(user.purchased.plan.consecutive.trinkets > 0)) { - throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughHourglasses', req.language) + }) : void 0; } - - let ref = content.timeTravelerStore(user.items.gear.owned); - let mysterySet = ref ? ref[key] : undefined; - - if (!mysterySet) { - throw new NotFound(i18n.t('mysterySetNotFound', req.language)); - } - - if (typeof window !== 'undefined' && window.confirm) { // TODO move to client - if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) return; - } - - _.each(mysterySet.items, item => { - user.items.gear.owned[item.key] = true; - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: item.key, - itemType: 'Subscriber Gear', - acquireMethod: 'Hourglass', - category: 'behavior', - }); + mysterySet = (ref = content.timeTravelerStore(user.items.gear.owned)) != null ? ref[req.params.key] : void 0; + if ((typeof window !== "undefined" && window !== null ? window.confirm : void 0) != null) { + if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) { + return; } - }); - - user.purchased.plan.consecutive.trinkets--; - - - if (req.v2 === true) { - return pickDeep(user, splitWhitespace('items purchased.plan.consecutive')); - } else { - return [ - { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, - i18n.t('hourglassPurchaseSet', req.language), - ]; } + if (!mysterySet) { + return typeof cb === "function" ? cb({ + code: 404, + message: "Mystery set not found, or set already owned" + }) : void 0; + } + _.each(mysterySet.items, function(i) { + var analyticsData; + user.items.gear.owned[i.key] = true; + analyticsData = { + uuid: user._id, + itemKey: i.key, + itemType: 'Subscriber Gear', + acquireMethod: 'Hourglass', + category: 'behavior' + }; + return analytics != null ? analytics.track('acquire item', analyticsData) : void 0; + }); + user.purchased.plan.consecutive.trinkets--; + return typeof cb === "function" ? cb({ + code: 200, + message: i18n.t('hourglassPurchaseSet', req.language) + }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0; }; diff --git a/common/script/ops/buyQuest.js b/common/script/ops/buyQuest.js index af7c384419..b7653d43ce 100644 --- a/common/script/ops/buyQuest.js +++ b/common/script/ops/buyQuest.js @@ -1,50 +1,49 @@ import i18n from '../i18n'; import content from '../content/index'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; -import _ from 'lodash'; - -// buy a quest with gold -module.exports = function buyQuest (user, req = {}, analytics) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); - - let item = content.quests[key]; - if (!item) throw new NotFound(i18n.t('questNotFound', {key}, req.language)); +module.exports = function(user, req, cb, analytics) { + var analyticsData, base, item, key, message, name; + key = req.params.key; + item = content.quests[key]; + if (!item) { + return typeof cb === "function" ? cb({ + code: 404, + message: "Quest '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" + }) : void 0; + } if (!(item.category === 'gold' && item.goldValue)) { - throw new NotAuthorized(i18n.t('questNotGoldPurchasable', {key}, req.language)); + return typeof cb === "function" ? cb({ + code: 404, + message: "Quest '" + key + " is not a Gold-purchasable quest (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)" + }) : void 0; } if (user.stats.gp < item.goldValue) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageNotEnoughGold', req.language) + }) : void 0; } - - user.items.quests[item.key] = user.items.quests[item.key] || 0; - user.items.quests[item.key]++; + message = i18n.t('messageBought', { + itemText: item.text(req.language) + }, req.language); + if ((base = user.items.quests)[name = item.key] == null) { + base[name] = 0; + } + user.items.quests[item.key] += 1; user.stats.gp -= item.goldValue; - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: item.key, - itemType: 'Market', - goldCost: item.goldValue, - acquireMethod: 'Gold', - category: 'behavior', - }); - } - - if (req.v2 === true) { - return user.items.quests; - } else { - return [ - user.items.quests, - i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language), - ]; + analyticsData = { + uuid: user._id, + itemKey: item.key, + itemType: 'Market', + goldCost: item.goldValue, + acquireMethod: 'Gold', + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); } + return typeof cb === "function" ? cb({ + code: 200, + message: message + }, user.items.quests) : void 0; }; diff --git a/common/script/ops/buySpecialSpell.js b/common/script/ops/buySpecialSpell.js index 20ea0251aa..e2a9dc8deb 100644 --- a/common/script/ops/buySpecialSpell.js +++ b/common/script/ops/buySpecialSpell.js @@ -2,34 +2,30 @@ import i18n from '../i18n'; import content from '../content/index'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; - -module.exports = function buySpecialSpell (user, req = {}) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); - - let item = content.special[key]; - if (!item) throw new NotFound(i18n.t('spellNotFound', {spellId: key}, req.language)); +module.exports = function(user, req, cb) { + var base, item, key, message; + key = req.params.key; + item = content.special[key]; if (user.stats.gp < item.value) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageNotEnoughGold', req.language) + }) : void 0; } user.stats.gp -= item.value; - - user.items.special[key]++; - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('items stats')); - } else { - return [ - _.pick(user, splitWhitespace('items stats')), - i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language), - ]; + if ((base = user.items.special)[key] == null) { + base[key] = 0; } + user.items.special[key]++; + if (typeof user.markModified === "function") { + user.markModified('items.special'); + } + message = i18n.t('messageBought', { + itemText: item.text(req.language) + }, req.language); + return typeof cb === "function" ? cb({ + code: 200, + message: message + }, _.pick(user, splitWhitespace('items stats'))) : void 0; }; diff --git a/common/script/ops/changeClass.js b/common/script/ops/changeClass.js index 4b3eb6b289..98fd8fd58d 100644 --- a/common/script/ops/changeClass.js +++ b/common/script/ops/changeClass.js @@ -2,77 +2,58 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; import { capByLevel } from '../statHelpers'; -import { - NotAuthorized, -} from '../libs/errors'; -module.exports = function changeClass (user, req = {}, analytics) { - let klass = _.get(req, 'query.class'); - - // user.flags.classSelected is set to false after the user paid the 3 gems - if (user.stats.lvl < 10) { - throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language)); - } else if (!user.flags.classSelected && (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer')) { - user.stats.class = klass; - user.flags.classSelected = true; - - _.each(['weapon', 'armor', 'shield', 'head'], (type) => { - let foundKey = false; - _.findLast(user.items.gear.owned, (val, key) => { - if (key.indexOf(`${type}_${klass}`) !== -1 && val === true) { - foundKey = key; - return true; - } - }); - - if (!foundKey) { - if (type === 'weapon') { - foundKey = `weapon_${klass}_0`; - } else if (type === 'shield' && klass === 'rogue') { - foundKey = 'shield_rogue_0'; - } else { - foundKey = `${type}_base_0`; - } - } - - user.items.gear.equipped[type] = foundKey; - - if (type === 'weapon' || (type === 'shield' && klass === 'rogue')) { // eslint-disable-line no-extra-parens - user.items.gear.owned[`${type}_${klass}_0`] = true; - } - }); - - if (analytics) { - analytics.track('change class', { - uuid: user._id, - class: klass, - acquireMethod: 'Gems', - gemCost: 3, - category: 'behavior', - }); +module.exports = function(user, req, cb, analytics) { + var analyticsData, klass, ref; + klass = (ref = req.query) != null ? ref["class"] : void 0; + if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') { + analyticsData = { + uuid: user._id, + "class": klass, + acquireMethod: 'Gems', + gemCost: 3, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('change class', analyticsData); } + user.stats["class"] = klass; + user.flags.classSelected = true; + _.each(["weapon", "armor", "shield", "head"], function(type) { + var foundKey; + foundKey = false; + _.findLast(user.items.gear.owned, function(v, k) { + if (~k.indexOf(type + "_" + klass) && v === true) { + return foundKey = k; + } + }); + user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : type + "_base_0"; + if (type === "weapon" || (type === "shield" && klass === "rogue")) { + user.items.gear.owned[type + "_" + klass + "_0"] = true; + } + return true; + }); } else { if (user.preferences.disableClasses) { user.preferences.disableClasses = false; user.preferences.autoAllocate = false; } else { - if (user.balance < 0.75) throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); - user.balance -= 0.75; + if (!(user.balance >= .75)) { + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; + } + user.balance -= .75; } - - user.stats.str = 0; - user.stats.con = 0; - user.stats.per = 0; - user.stats.int = 0; - user.stats.points = capByLevel(user.stats.lvl); + _.merge(user.stats, { + str: 0, + con: 0, + per: 0, + int: 0, + points: capByLevel(user.stats.lvl) + }); user.flags.classSelected = false; } - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('stats flags items preferences')); - } else { - return [ - _.pick(user, splitWhitespace('stats flags items preferences')), - ]; - } + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags items preferences'))) : void 0; }; diff --git a/common/script/ops/clearCompleted.js b/common/script/ops/clearCompleted.js index 26fb1727d9..d60f12704f 100644 --- a/common/script/ops/clearCompleted.js +++ b/common/script/ops/clearCompleted.js @@ -1,10 +1,12 @@ import _ from 'lodash'; -// TODO move to client since it's only used there? -// TODO rename file to clearCompletedTodos - -module.exports = function clearCompletedTodos (todos) { - _.remove(todos, todo => { - return todo.completed && (!todo.challenge || !todo.challenge.id || todo.challenge.broken); +module.exports = function(user, req, cb) { + _.remove(user.todos, function(t) { + var ref; + return t.completed && !((ref = t.challenge) != null ? ref.id : void 0); }); + if (typeof user.markModified === "function") { + user.markModified('todos'); + } + return typeof cb === "function" ? cb(null, user.todos) : void 0; }; diff --git a/common/script/ops/clearPMs.js b/common/script/ops/clearPMs.js index 765ecc3b56..47e0a6ebc6 100644 --- a/common/script/ops/clearPMs.js +++ b/common/script/ops/clearPMs.js @@ -1,7 +1,7 @@ -module.exports = function clearPMs (user) { +module.exports = function(user, req, cb) { user.inbox.messages = {}; - user.markModified('inbox.messages'); - return [ - user.inbox.messages, - ]; + if (typeof user.markModified === "function") { + user.markModified('inbox.messages'); + } + return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0; }; diff --git a/common/script/ops/deletePM.js b/common/script/ops/deletePM.js index 84bb7ee33a..ad95bc9ae0 100644 --- a/common/script/ops/deletePM.js +++ b/common/script/ops/deletePM.js @@ -1,9 +1,7 @@ -import _ from 'lodash'; - -module.exports = function deletePM (user, req = {}) { - delete user.inbox.messages[_.get(req, 'params.id')]; - user.markModified(`inbox.messages.${req.params.id}`); - return [ - user.inbox.messages, - ]; +module.exports = function(user, req, cb) { + delete user.inbox.messages[req.params.id]; + if (typeof user.markModified === "function") { + user.markModified('inbox.messages.' + req.params.id); + } + return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0; }; diff --git a/common/script/ops/deleteTag.js b/common/script/ops/deleteTag.js index c40fe79ba5..a82af59e86 100644 --- a/common/script/ops/deleteTag.js +++ b/common/script/ops/deleteTag.js @@ -1,32 +1,26 @@ import i18n from '../i18n'; import _ from 'lodash'; -import { NotFound } from '../libs/errors'; -// TODO used only in client, move there? - -module.exports = function deleteTag (user, req = {}) { - let tid = _.get(req, 'params.id'); - - let index = _.findIndex(user.tags, { - id: tid, +module.exports = function(user, req, cb) { + var i, tag, tid; + tid = req.params.id; + i = _.findIndex(user.tags, { + id: tid }); - - if (index === -1) { - throw new NotFound(i18n.t('messageTagNotFound', req.language)); + if (!~i) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTagNotFound', req.language) + }) : void 0; } - - let tag = user.tags[index]; + tag = user.tags[i]; delete user.filters[tag.id]; - - user.tags.splice(index, 1); - - _.each(user.tasks, (task) => { + user.tags.splice(i, 1); + _.each(user.tasks, function(task) { return delete task.tags[tag.id]; }); - - _.each(['habits', 'dailys', 'todos', 'rewards'], (type) => { - user.markModified(type); + _.each(['habits', 'dailys', 'todos', 'rewards'], function(type) { + return typeof user.markModified === "function" ? user.markModified(type) : void 0; }); - - return user.tags; + return typeof cb === "function" ? cb(null, user.tags) : void 0; }; diff --git a/common/script/ops/deleteTask.js b/common/script/ops/deleteTask.js index a641763de5..715b102241 100644 --- a/common/script/ops/deleteTask.js +++ b/common/script/ops/deleteTask.js @@ -1,22 +1,17 @@ import i18n from '../i18n'; -import { NotFound } from '../libs/errors'; -import _ from 'lodash'; -// TODO used only in client, move there? - -module.exports = function deleteTask (user, req = {}) { - let tid = _.get(req, 'params.id'); - let taskType = _.get(req, 'params.taskType'); - - let index = _.findIndex(user[`${taskType}s`], function findById (task) { - return task._id === tid; - }); - - if (index === -1) { - throw new NotFound(i18n.t('messageTaskNotFound', req.language)); +module.exports = function(user, req, cb) { + var i, ref, task; + task = user.tasks[(ref = req.params) != null ? ref.id : void 0]; + if (!task) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTaskNotFound', req.language) + }) : void 0; } - - user[`${taskType}s`].splice(index, 1); - - return {}; + i = user[task.type + "s"].indexOf(task); + if (~i) { + user[task.type + "s"].splice(i, 1); + } + return typeof cb === "function" ? cb(null, {}) : void 0; }; diff --git a/common/script/ops/deleteWebhook.js b/common/script/ops/deleteWebhook.js index 9c4f67eba8..a187f08770 100644 --- a/common/script/ops/deleteWebhook.js +++ b/common/script/ops/deleteWebhook.js @@ -1,10 +1,7 @@ -import _ from 'lodash'; - -module.exports = function deleteWebhook (user, req) { - delete user.preferences.webhooks[_.get(req, 'params.id')]; - user.markModified('preferences.webhooks'); - - return [ - user.preferences.webhooks, - ]; +module.exports = function(user, req, cb) { + delete user.preferences.webhooks[req.params.id]; + if (typeof user.markModified === "function") { + user.markModified('preferences.webhooks'); + } + return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; }; diff --git a/common/script/ops/disableClasses.js b/common/script/ops/disableClasses.js index e611bb0872..89636ed52d 100644 --- a/common/script/ops/disableClasses.js +++ b/common/script/ops/disableClasses.js @@ -2,19 +2,12 @@ import splitWhitespace from '../libs/splitWhitespace'; import { capByLevel } from '../statHelpers'; import _ from 'lodash'; -module.exports = function disableClasses (user, req = {}) { - user.stats.class = 'warrior'; +module.exports = function(user, req, cb) { + user.stats["class"] = 'warrior'; user.flags.classSelected = true; user.preferences.disableClasses = true; user.preferences.autoAllocate = true; user.stats.str = capByLevel(user.stats.lvl); user.stats.points = 0; - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('stats flags preferences')); - } else { - return [ - _.pick(user, splitWhitespace('stats flags preferences')), - ]; - } + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags preferences'))) : void 0; }; diff --git a/common/script/ops/equip.js b/common/script/ops/equip.js index 9c614915a7..8c07a364b1 100644 --- a/common/script/ops/equip.js +++ b/common/script/ops/equip.js @@ -1,68 +1,52 @@ import content from '../content/index'; import i18n from '../i18n'; -import handleTwoHanded from '../fns/handleTwoHanded'; -import { - NotFound, - BadRequest, -} from '../libs/errors'; -import _ from 'lodash'; - -module.exports = function equip (user, req = {}) { - // Being type a parameter followed by another parameter - // when using the API it must be passes specifically in the URL, it's won't default to equipped - let type = _.get(req, 'params.type', 'equipped'); - let key = _.get(req, 'params.key'); - - if (!key || !type) throw new BadRequest(i18n.t('missingTypeKeyEquip', req.language)); - if (['mount', 'pet', 'costume', 'equipped'].indexOf(type) === -1) { - throw new BadRequest(i18n.t('invalidTypeEquip', req.language)); - } - - let message; +module.exports = function(user, req, cb) { + var item, key, message, ref, type; + ref = [req.params.type || 'equipped', req.params.key], type = ref[0], key = ref[1]; switch (type) { - case 'mount': { + case 'mount': if (!user.items.mounts[key]) { - throw new NotFound(i18n.t('mountNotOwned', req.language)); + return typeof cb === "function" ? cb({ + code: 404, + message: ":You do not own this mount." + }) : void 0; } - user.items.currentMount = user.items.currentMount === key ? '' : key; break; - } - case 'pet': { + case 'pet': if (!user.items.pets[key]) { - throw new NotFound(i18n.t('petNotOwned', req.language)); + return typeof cb === "function" ? cb({ + code: 404, + message: ":You do not own this pet." + }) : void 0; } - user.items.currentPet = user.items.currentPet === key ? '' : key; break; - } case 'costume': - case 'equipped': { + case 'equipped': + item = content.gear.flat[key]; if (!user.items.gear.owned[key]) { - throw new NotFound(i18n.t('gearNotOwned', req.language)); + return typeof cb === "function" ? cb({ + code: 404, + message: ":You do not own this gear." + }) : void 0; } - - let item = content.gear.flat[key]; - if (user.items.gear[type][item.type] === key) { - user.items.gear[type][item.type] = `${item.type}_base_0`; + user.items.gear[type][item.type] = item.type + "_base_0"; message = i18n.t('messageUnEquipped', { - itemText: item.text(req.language), + itemText: item.text(req.language) }, req.language); } else { user.items.gear[type][item.type] = item.key; - message = handleTwoHanded(user, item, type, req); + message = user.fns.handleTwoHanded(item, type, req); + } + if (typeof user.markModified === "function") { + user.markModified("items.gear." + type); } - break; - } - } - - if (req.v2 === true) { - return user.items; - } else { - let res = [user.items]; - if (message) res.push(message); - return res; } + return typeof cb === "function" ? cb((message ? { + code: 200, + message: message + } : null), user.items) : void 0; }; diff --git a/common/script/ops/feed.js b/common/script/ops/feed.js index 637d9b3e1d..3c9b0f87fd 100644 --- a/common/script/ops/feed.js +++ b/common/script/ops/feed.js @@ -1,102 +1,78 @@ import content from '../content/index'; import i18n from '../i18n'; -import _ from 'lodash'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; -function evolve (user, pet, petDisplayName, req) { - user.items.pets[pet] = -1; - user.items.mounts[pet] = true; - - if (pet === user.items.currentPet) { - user.items.currentPet = ''; - } - - return i18n.t('messageEvolve', { - egg: petDisplayName, - }, req.language); -} - -module.exports = function feed (user, req = {}) { - let pet = _.get(req, 'params.pet'); - let foodK = _.get(req, 'params.food'); - - if (!pet || !foodK) throw new BadRequest(i18n.t('missingPetFoodFeed', req.language)); - - if (pet.indexOf('-') === -1) { - throw new BadRequest(i18n.t('invalidPetName', req.language)); - } - - let food = content.food[foodK]; - if (!food) { - throw new NotFound(i18n.t('messageFoodNotFound', req.language)); - } - - let userPets = user.items.pets; - - if (!userPets[pet]) { - throw new NotFound(i18n.t('messagePetNotFound', req.language)); - } - - let [egg, potion] = pet.split('-'); - - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text(req.language) : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text(req.language) : egg; - - let petDisplayName = i18n.t('petName', { +module.exports = function(user, req, cb) { + var egg, eggText, evolve, food, message, pet, petDisplayName, potion, potionText, ref, ref1, ref2, userPets; + ref = req.params, pet = ref.pet, food = ref.food; + food = content.food[food]; + ref1 = pet.split('-'), egg = ref1[0], potion = ref1[1]; + userPets = user.items.pets; + potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; + eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; + petDisplayName = i18n.t('petName', { potion: potionText, - egg: eggText, - }, req.language); - - if (!user.items.food[food.key]) { - throw new NotFound(i18n.t('messageFoodNotFound', req.language)); + egg: eggText + }); + if (!userPets[pet]) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messagePetNotFound', req.language) + }) : void 0; + } + if (!((ref2 = user.items.food) != null ? ref2[food.key] : void 0)) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageFoodNotFound', req.language) + }) : void 0; } - if (content.specialPets[pet]) { - throw new NotAuthorized(i18n.t('messageCannotFeedPet', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageCannotFeedPet', req.language) + }) : void 0; } - if (user.items.mounts[pet]) { - throw new NotAuthorized(i18n.t('messageAlreadyMount', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageAlreadyMount', req.language) + }) : void 0; } - - let message; - + message = ''; + evolve = function() { + userPets[pet] = -1; + user.items.mounts[pet] = true; + if (pet === user.items.currentPet) { + user.items.currentPet = ""; + } + return message = i18n.t('messageEvolve', { + egg: petDisplayName + }, req.language); + }; if (food.key === 'Saddle') { - message = evolve(user, pet, petDisplayName, req); + evolve(); } else { if (food.target === potion || content.hatchingPotions[potion].premium) { userPets[pet] += 5; message = i18n.t('messageLikesFood', { egg: petDisplayName, - foodText: food.text(req.language), + foodText: food.text(req.language) }, req.language); } else { userPets[pet] += 2; message = i18n.t('messageDontEnjoyFood', { egg: petDisplayName, - foodText: food.text(req.language), + foodText: food.text(req.language) }, req.language); } - if (userPets[pet] >= 50 && !user.items.mounts[pet]) { - message = evolve(user, pet, petDisplayName, req); + evolve(); } } - user.items.food[food.key]--; - - if (req.v2 === true) { - return { - value: userPets[pet], - }; - } else { - return [ - userPets[pet], - message, - ]; - } + return typeof cb === "function" ? cb({ + code: 200, + message: message + }, { + value: userPets[pet] + }) : void 0; }; diff --git a/common/script/ops/getTag.js b/common/script/ops/getTag.js index 4a7db63128..06f3f24110 100644 --- a/common/script/ops/getTag.js +++ b/common/script/ops/getTag.js @@ -1,18 +1,17 @@ import _ from 'lodash'; import i18n from '../i18n'; -import { NotFound } from '../libs/errors'; -// TODO used only in client, move there? - -module.exports = function getTag (user, req = {}) { - let tid = _.get(req, 'params.id'); - - let index = _.findIndex(user.tags, { - id: tid, +module.exports = function(user, req, cb) { + var i, tid; + tid = req.params.id; + i = _.findIndex(user.tags, { + id: tid }); - if (index === -1) { - throw new NotFound(i18n.t('messageTagNotFound', req.language)); + if (!~i) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTagNotFound', req.language) + }) : void 0; } - - return user.tags[index]; + return typeof cb === "function" ? cb(null, user.tags[i]) : void 0; }; diff --git a/common/script/ops/getTags.js b/common/script/ops/getTags.js index a96589a831..af9419b050 100644 --- a/common/script/ops/getTags.js +++ b/common/script/ops/getTags.js @@ -1,5 +1,3 @@ -// TODO used only in client, move there? - -module.exports = function getTags (user) { - return user.tags; +module.exports = function(user, req, cb) { + return typeof cb === "function" ? cb(null, user.tags) : void 0; }; diff --git a/common/script/ops/hatch.js b/common/script/ops/hatch.js index 87adbf3276..6292e24bcb 100644 --- a/common/script/ops/hatch.js +++ b/common/script/ops/hatch.js @@ -1,44 +1,39 @@ import content from '../content/index'; import i18n from '../i18n'; -import _ from 'lodash'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../libs/errors'; - -module.exports = function hatch (user, req = {}) { - let egg = _.get(req, 'params.egg'); - let hatchingPotion = _.get(req, 'params.hatchingPotion'); +module.exports = function(user, req, cb) { + var egg, hatchingPotion, pet, ref; + ref = req.params, egg = ref.egg, hatchingPotion = ref.hatchingPotion; if (!(egg && hatchingPotion)) { - throw new BadRequest(i18n.t('missingEggHatchingPotionHatch', req.language)); + return typeof cb === "function" ? cb({ + code: 400, + message: "Please specify query.egg & query.hatchingPotion" + }) : void 0; } - if (!(user.items.eggs[egg] > 0 && user.items.hatchingPotions[hatchingPotion] > 0)) { - throw new NotFound(i18n.t('messageMissingEggPotion', req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('messageMissingEggPotion', req.language) + }) : void 0; } - if (content.hatchingPotions[hatchingPotion].premium && !content.dropEggs[egg]) { - throw new BadRequest(i18n.t('messageInvalidEggPotionCombo', req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('messageInvalidEggPotionCombo', req.language) + }) : void 0; } - - let pet = `${egg}-${hatchingPotion}`; - + pet = egg + "-" + hatchingPotion; if (user.items.pets[pet] && user.items.pets[pet] > 0) { - throw new NotAuthorized(i18n.t('messageAlreadyPet', req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('messageAlreadyPet', req.language) + }) : void 0; } - user.items.pets[pet] = 5; user.items.eggs[egg]--; user.items.hatchingPotions[hatchingPotion]--; - - if (req.v2 === true) { - return user.items; - } else { - return [ - user.items, - i18n.t('messageHatched', req.language), - ]; - } + return typeof cb === "function" ? cb({ + code: 200, + message: i18n.t('messageHatched', req.language) + }, user.items) : void 0; }; diff --git a/common/script/ops/hourglassPurchase.js b/common/script/ops/hourglassPurchase.js index e1d07bb482..fa0b9e672a 100644 --- a/common/script/ops/hourglassPurchase.js +++ b/common/script/ops/hourglassPurchase.js @@ -1,61 +1,54 @@ import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; -import { - BadRequest, - NotAuthorized, -} from '../libs/errors'; import splitWhitespace from '../libs/splitWhitespace'; -module.exports = function purchaseHourglass (user, req = {}, analytics) { - let key = _.get(req, 'params.key'); - if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); - - let type = _.get(req, 'params.type'); - if (!type) throw new BadRequest(i18n.t('missingTypeParam', req.language)); - +module.exports = function(user, req, cb, analytics) { + var analyticsData, key, ref, type; + ref = req.params, type = ref.type, key = ref.key; if (!content.timeTravelStable[type]) { - throw new NotAuthorized(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language) + }) : void 0; } - if (!_.contains(_.keys(content.timeTravelStable[type]), key)) { - throw new NotAuthorized(i18n.t('notAllowedHourglass', req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t(type + 'NotAllowedHourglass', req.language) + }) : void 0; } - if (user.items[type][key]) { - throw new NotAuthorized(i18n.t(`${type}AlreadyOwned`, req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t(type + 'AlreadyOwned', req.language) + }) : void 0; } - - if (user.purchased.plan.consecutive.trinkets <= 0) { - throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); + if (!(user.purchased.plan.consecutive.trinkets > 0)) { + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('notEnoughHourglasses', req.language) + }) : void 0; } - user.purchased.plan.consecutive.trinkets--; - if (type === 'pets') { user.items.pets[key] = 5; } - if (type === 'mounts') { user.items.mounts[key] = true; } - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: key, - itemType: type, - acquireMethod: 'Hourglass', - category: 'behavior', - }); - } - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('items purchased.plan.consecutive')); - } else { - return [ - { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, - i18n.t('hourglassPurchase', req.language), - ]; + analyticsData = { + uuid: user._id, + itemKey: key, + itemType: type, + acquireMethod: 'Hourglass', + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); } + return typeof cb === "function" ? cb({ + code: 200, + message: i18n.t('hourglassPurchase', req.language) + }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0; }; diff --git a/common/script/ops/index.js b/common/script/ops/index.js index 2e8bca246d..1bfd3d55f2 100644 --- a/common/script/ops/index.js +++ b/common/script/ops/index.js @@ -30,9 +30,6 @@ import releasePets from './releasePets'; import releaseMounts from './releaseMounts'; import releaseBoth from './releaseBoth'; import buy from './buy'; -import buyGear from './buyGear'; -import buyHealthPotion from './buyHealthPotion'; -import buyArmoire from './buyArmoire'; import buyQuest from './buyQuest'; import buyMysterySet from './buyMysterySet'; import hourglassPurchase from './hourglassPurchase'; @@ -45,9 +42,7 @@ import disableClasses from './disableClasses'; import allocate from './allocate'; import readCard from './readCard'; import openMysteryItem from './openMysteryItem'; -import scoreTask from './scoreTask'; -import markPmsRead from './markPMSRead'; - +import score from './score'; module.exports = { update, @@ -82,9 +77,6 @@ module.exports = { releaseMounts, releaseBoth, buy, - buyGear, - buyHealthPotion, - buyArmoire, buyQuest, buyMysterySet, hourglassPurchase, @@ -97,6 +89,5 @@ module.exports = { allocate, readCard, openMysteryItem, - scoreTask, - markPmsRead, + score, }; diff --git a/common/script/ops/markPMSRead.js b/common/script/ops/markPMSRead.js deleted file mode 100644 index add9f49de5..0000000000 --- a/common/script/ops/markPMSRead.js +++ /dev/null @@ -1,14 +0,0 @@ -import i18n from '../i18n'; - -module.exports = function markPmsRead (user, req = {}) { - user.inbox.newMessages = 0; - - if (req.v2 === true) { - return user; - } else { - return [ - user.inbox.newMessages, - i18n.t('pmsMarkedRead'), - ]; - } -}; diff --git a/common/script/ops/openMysteryItem.js b/common/script/ops/openMysteryItem.js index 743104c48b..eb28c605e0 100644 --- a/common/script/ops/openMysteryItem.js +++ b/common/script/ops/openMysteryItem.js @@ -1,44 +1,32 @@ import content from '../content/index'; -import i18n from '../i18n'; -import { - BadRequest, -} from '../libs/errors'; -import _ from 'lodash'; - -module.exports = function openMysteryItem (user, req = {}, analytics) { - let item = user.purchased.plan.mysteryItems.shift(); +module.exports = function(user, req, cb, analytics) { + var analyticsData, item, ref, ref1; + item = (ref = user.purchased.plan) != null ? (ref1 = ref.mysteryItems) != null ? ref1.shift() : void 0 : void 0; if (!item) { - throw new BadRequest(i18n.t('mysteryItemIsEmpty', req.language)); + return typeof cb === "function" ? cb({ + code: 400, + message: "Empty" + }) : void 0; } - - item = _.cloneDeep(content.gear.flat[item]); - item.notificationType = 'Mystery'; + item = content.gear.flat[item]; user.items.gear.owned[item.key] = true; - - user.markModified('purchased.plan.mysteryItems'); - - if (analytics) { - analytics.track('open mystery item', { - uuid: user._id, - itemKey: item, - itemType: 'Subscriber Gear', - acquireMethod: 'Subscriber', - category: 'behavior', - }); + if (typeof user.markModified === "function") { + user.markModified('purchased.plan.mysteryItems'); + } + item.notificationType = 'Mystery'; + analyticsData = { + uuid: user._id, + itemKey: item, + itemType: 'Subscriber Gear', + acquireMethod: 'Subscriber', + category: 'behavior' + }; + if (analytics != null) { + analytics.track('open mystery item', analyticsData); } - if (typeof window !== 'undefined') { - if (!user._tmp) user._tmp = {}; - user._tmp.drop = item; - } - - if (req.v2 === true) { - return user.items.gear.owned; - } else { - return [ - user.items.gear.owned, - i18n.t('mysteryItemOpened', req.language), - ]; + (user._tmp != null ? user._tmp : user._tmp = {}).drop = item; } + return typeof cb === "function" ? cb(null, user.items.gear.owned) : void 0; }; diff --git a/common/script/ops/purchase.js b/common/script/ops/purchase.js index 79eb0475d4..d4d0f78816 100644 --- a/common/script/ops/purchase.js +++ b/common/script/ops/purchase.js @@ -3,125 +3,105 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; import planGemLimits from '../libs/planGemLimits'; -import { - NotFound, - NotAuthorized, - BadRequest, -} from '../libs/errors'; - -module.exports = function purchase (user, req = {}, analytics) { - let type = _.get(req.params, 'type'); - let key = _.get(req.params, 'key'); - let item; - let price; - - if (!type) { - throw new BadRequest(i18n.t('typeRequired', req.language)); - } - - if (!key) { - throw new BadRequest(i18n.t('keyRequired', req.language)); - } +module.exports = function(user, req, cb, analytics) { + var analyticsData, convCap, convRate, item, key, price, ref, ref1, ref2, ref3, type; + ref = req.params, type = ref.type, key = ref.key; if (type === 'gems' && key === 'gem') { - let convRate = planGemLimits.convRate; - let convCap = planGemLimits.convCap; + ref1 = planGemLimits, convRate = ref1.convRate, convCap = ref1.convCap; convCap += user.purchased.plan.consecutive.gemCapExtra; - - if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) { - throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language)); + if (!((ref2 = user.purchased) != null ? (ref3 = ref2.plan) != null ? ref3.customerId : void 0 : void 0)) { + return typeof cb === "function" ? cb({ + code: 401, + message: "Must subscribe to purchase gems with GP" + }, req) : void 0; } - - if (user.stats.gp < convRate) { - throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); + if (!(user.stats.gp >= convRate)) { + return typeof cb === "function" ? cb({ + code: 401, + message: "Not enough Gold" + }) : void 0; } - if (user.purchased.plan.gemsBought >= convCap) { - throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: "You've reached the Gold=>Gem conversion cap (" + convCap + ") for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month." + }) : void 0; } - - user.balance += 0.25; + user.balance += .25; user.purchased.plan.gemsBought++; user.stats.gp -= convRate; - - if (analytics) { - analytics.track('purchase gems', { - uuid: user._id, - itemKey: key, - acquireMethod: 'Gold', - goldCost: convRate, - category: 'behavior', - }); + analyticsData = { + uuid: user._id, + itemKey: key, + acquireMethod: 'Gold', + goldCost: convRate, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('purchase gems', analyticsData); } - - return [ - _.pick(user, splitWhitespace('stats balance')), - i18n.t('plusOneGem'), - ]; + return typeof cb === "function" ? cb({ + code: 200, + message: "+1 Gem" + }, _.pick(user, splitWhitespace('stats balance'))) : void 0; } - - let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear']; - if (acceptedTypes.indexOf(type) === -1) { - throw new NotFound(i18n.t('notAccteptedType', req.language)); + if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'gear') { + return typeof cb === "function" ? cb({ + code: 404, + message: ":type must be in [eggs,hatchingPotions,food,quests,gear]" + }, req) : void 0; } - if (type === 'gear') { item = content.gear.flat[key]; - - if (!item) { - throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language)); - } - if (user.items.gear.owned[key]) { - throw new NotAuthorized(i18n.t('alreadyHave', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('alreadyHave', req.language) + }) : void 0; } - price = (item.twoHanded || item.gearSet === 'animal' ? 2 : 1) / 4; } else { item = content[type][key]; - - if (!item) { - throw new NotFound(i18n.t('contentKeyNotFound', {type}, req.language)); - } - price = item.value / 4; } - + if (!item) { + return typeof cb === "function" ? cb({ + code: 404, + message: ":key not found for Content." + type + }, req) : void 0; + } if (!item.canBuy(user)) { - throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('messageNotAvailable', req.language) + }) : void 0; } - - if (!user.balance || user.balance < price) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + if ((user.balance < price) || !user.balance) { + return typeof cb === "function" ? cb({ + code: 403, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } - user.balance -= price; - if (type === 'gear') { user.items.gear.owned[key] = true; } else { - if (!user.items[type][key] || user.items[type][key] < 0) { + if (!(user.items[type][key] > 0)) { user.items[type][key] = 0; } user.items[type][key]++; } - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: key, - itemType: 'Market', - acquireMethod: 'Gems', - gemCost: item.value, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('items balance')); - } else { - return [ - _.pick(user, splitWhitespace('items balance')), - ]; + analyticsData = { + uuid: user._id, + itemKey: key, + itemType: 'Market', + acquireMethod: 'Gems', + gemCost: item.value, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); } + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('items balance'))) : void 0; }; diff --git a/common/script/ops/readCard.js b/common/script/ops/readCard.js index 57b0da4b00..a6eb5c05f1 100644 --- a/common/script/ops/readCard.js +++ b/common/script/ops/readCard.js @@ -1,32 +1,10 @@ -import splitWhitespace from '../libs/splitWhitespace'; -import _ from 'lodash'; -import i18n from '../i18n'; -import { - BadRequest, - NotAuthorized, -} from '../libs/errors'; -import content from '../content/index'; - -module.exports = function readCard (user, req = {}) { - let cardType = _.get(req.params, 'cardType'); - - if (!cardType) { - throw new BadRequest(i18n.t('cardTypeRequired', req.language)); +module.exports = function(user, req, cb) { + var cardType; + cardType = req.params.cardType; + user.items.special[cardType + "Received"].shift(); + if (typeof user.markModified === "function") { + user.markModified("items.special." + cardType + "Received"); } - - if (_.keys(content.cardTypes).indexOf(cardType) === -1) { - throw new NotAuthorized(i18n.t('cardTypeNotAllowed', req.language)); - } - - user.items.special[`${cardType}Received`].shift(); user.flags.cardReceived = false; - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('items.special flags.cardReceived')); - } else { - return [ - { specialItems: user.items.special, cardReceived: user.flags.cardReceived }, - i18n.t('readCard', {cardType}, req.language), - ]; - } + return typeof cb === "function" ? cb(null, 'items.special flags.cardReceived') : void 0; }; diff --git a/common/script/ops/rebirth.js b/common/script/ops/rebirth.js index 54f9533fc5..1ddb75aba1 100644 --- a/common/script/ops/rebirth.js +++ b/common/script/ops/rebirth.js @@ -1,25 +1,21 @@ +import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; import { capByLevel } from '../statHelpers'; import { MAX_LEVEL } from '../constants'; -import { - NotAuthorized, -} from '../libs/errors'; -import resetGear from '../fns/resetGear'; -import equip from './equip'; -const USERSTATSLIST = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp']; - -module.exports = function rebirth (user, tasks = [], req = {}, analytics) { +module.exports = function(user, req, cb, analytics) { + var analyticsData, flags, gear, lvl, stats; if (user.balance < 2 && user.stats.lvl < MAX_LEVEL) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } - - let analyticsData = { + analyticsData = { uuid: user._id, - category: 'behavior', + category: 'behavior' }; - if (user.stats.lvl < MAX_LEVEL) { user.balance -= 2; analyticsData.acquireMethod = 'Gems'; @@ -28,55 +24,63 @@ module.exports = function rebirth (user, tasks = [], req = {}, analytics) { analyticsData.gemCost = 0; analyticsData.acquireMethod = '> 100'; } - - if (analytics) { + if (analytics != null) { analytics.track('Rebirth', analyticsData); } - - let lvl = capByLevel(user.stats.lvl); - - _.each(tasks, function resetTasks (task) { - if (!task.challenge || !task.challenge.id || task.challenge.broken) { - if (task.type !== 'reward') { - task.value = 0; - } - if (task.type === 'daily') { - task.streak = 0; - } + lvl = capByLevel(user.stats.lvl); + _.each(user.tasks, function(task) { + if (task.type !== 'reward') { + task.value = 0; + } + if (task.type === 'daily') { + return task.streak = 0; } }); - - let stats = user.stats; + stats = user.stats; stats.buffs = {}; stats.hp = 50; stats.lvl = 1; - stats.class = 'warrior'; - - _.each(USERSTATSLIST, function resetStats (value) { - stats[value] = 0; + stats["class"] = 'warrior'; + _.each(['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'], function(value) { + return stats[value] = 0; + }); + // TODO during refactoring: move all gear code from rebirth() to its own function and then call it in reset() as well + gear = user.items.gear; + _.each(['equipped', 'costume'], function(type) { + gear[type] = {}; + gear[type].armor = 'armor_base_0'; + gear[type].weapon = 'weapon_warrior_0'; + gear[type].head = 'head_base_0'; + return gear[type].shield = 'shield_base_0'; }); - - resetGear(user); - if (user.items.currentPet) { - equip(user, { + user.ops.equip({ params: { type: 'pet', - key: user.items.currentPet, - }, + key: user.items.currentPet + } }); } - if (user.items.currentMount) { - equip(user, { + user.ops.equip({ params: { type: 'mount', - key: user.items.currentMount, - }, + key: user.items.currentMount + } }); } - - let flags = user.flags; + _.each(gear.owned, function(v, k) { + if (gear.owned[k] && content.gear.flat[k].value) { + gear.owned[k] = false; + return true; + } + }); + gear.owned.weapon_warrior_0 = true; + if (typeof user.markModified === "function") { + user.markModified('items.gear.owned'); + } + user.preferences.costume = false; + flags = user.flags; if (!user.achievements.beastMaster) { flags.rebirthEnabled = false; } @@ -84,23 +88,13 @@ module.exports = function rebirth (user, tasks = [], req = {}, analytics) { flags.dropsEnabled = false; flags.classSelected = false; flags.levelDrops = {}; - if (!user.achievements.rebirths) { user.achievements.rebirths = 1; user.achievements.rebirthLevel = lvl; - } else if (lvl > user.achievements.rebirthLevel || lvl === MAX_LEVEL) { + } else if (lvl > user.achievements.rebirthLevel || lvl === 100) { user.achievements.rebirths++; user.achievements.rebirthLevel = lvl; } - user.stats.buffs = {}; - - if (req.v2 === true) { - return user; - } else { - return [ - {user, tasks}, - i18n.t('rebirthComplete'), - ]; - } + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/releaseBoth.js b/common/script/ops/releaseBoth.js index cf7d2267ca..a782581f85 100644 --- a/common/script/ops/releaseBoth.js +++ b/common/script/ops/releaseBoth.js @@ -1,68 +1,50 @@ import content from '../content/index'; import i18n from '../i18n'; -import { - NotAuthorized, -} from '../libs/errors'; -import splitWhitespace from '../libs/splitWhitespace'; -import _ from 'lodash'; - -module.exports = function releaseBoth (user, req = {}, analytics) { - let animal; +module.exports = function(user, req, cb, analytics) { + var analyticsData, animal, giveTriadBingo; if (user.balance < 1.5 && !user.achievements.triadBingo) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); - } - - let giveTriadBingo = true; - - if (!user.achievements.triadBingo) { - if (analytics) { - analytics.track('release pets & mounts', { + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; + } else { + giveTriadBingo = true; + if (!user.achievements.triadBingo) { + analyticsData = { uuid: user._id, acquireMethod: 'Gems', gemCost: 6, - category: 'behavior', - }); + category: 'behavior' + }; + if (typeof analytics !== "undefined" && analytics !== null) { + analytics.track('release pets & mounts', analyticsData); + } + user.balance -= 1.5; } - - user.balance -= 1.5; - } - - user.items.currentMount = ''; - user.items.currentPet = ''; - - for (animal in content.pets) { - if (user.items.pets[animal] === -1) { - giveTriadBingo = false; + user.items.currentMount = ""; + user.items.currentPet = ""; + for (animal in content.pets) { + if (user.items.pets[animal] === -1) { + giveTriadBingo = false; + } + user.items.pets[animal] = 0; + user.items.mounts[animal] = null; } - - user.items.pets[animal] = 0; - user.items.mounts[animal] = null; - } - - if (!user.achievements.beastMasterCount) { - user.achievements.beastMasterCount = 0; - } - user.achievements.beastMasterCount++; - - if (!user.achievements.mountMasterCount) { - user.achievements.mountMasterCount = 0; - } - user.achievements.mountMasterCount++; - - if (giveTriadBingo) { - if (!user.achievements.triadBingoCount) { - user.achievements.triadBingoCount = 0; + if (!user.achievements.beastMasterCount) { + user.achievements.beastMasterCount = 0; + } + user.achievements.beastMasterCount++; + if (!user.achievements.mountMasterCount) { + user.achievements.mountMasterCount = 0; + } + user.achievements.mountMasterCount++; + if (giveTriadBingo) { + if (!user.achievements.triadBingoCount) { + user.achievements.triadBingoCount = 0; + } + user.achievements.triadBingoCount++; } - user.achievements.triadBingoCount++; - } - - if (req.v2 === true) { - return user; - } else { - return [ - _.pick(user, splitWhitespace('achievements items balance')), - i18n.t('mountsAndPetsReleased'), - ]; } + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/releaseMounts.js b/common/script/ops/releaseMounts.js index d8b8dde659..4aefab6b40 100644 --- a/common/script/ops/releaseMounts.js +++ b/common/script/ops/releaseMounts.js @@ -1,43 +1,32 @@ import content from '../content/index'; import i18n from '../i18n'; -import { - NotAuthorized, -} from '../libs/errors'; - -module.exports = function releaseMounts (user, req = {}, analytics) { - let mount; +module.exports = function(user, req, cb, analytics) { + var analyticsData, mount; if (user.balance < 1) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); - } - - user.balance -= 1; - user.items.currentMount = ''; - - for (mount in content.pets) { - user.items.mounts[mount] = null; - } - - if (!user.achievements.mountMasterCount) { - user.achievements.mountMasterCount = 0; - } - user.achievements.mountMasterCount++; - - if (analytics) { - analytics.track('release mounts', { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return user; + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } else { - return [ - user.items.mounts, - i18n.t('mountsReleased'), - ]; + user.balance -= 1; + user.items.currentMount = ""; + for (mount in content.pets) { + user.items.mounts[mount] = null; + } + if (!user.achievements.mountMasterCount) { + user.achievements.mountMasterCount = 0; + } + user.achievements.mountMasterCount++; } + analyticsData = { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('release mounts', analyticsData); + } + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/releasePets.js b/common/script/ops/releasePets.js index 9466e1ccda..a4b452cd86 100644 --- a/common/script/ops/releasePets.js +++ b/common/script/ops/releasePets.js @@ -1,41 +1,32 @@ import content from '../content/index'; import i18n from '../i18n'; -import { - NotAuthorized, -} from '../libs/errors'; -module.exports = function releasePets (user, req = {}, analytics) { +module.exports = function(user, req, cb, analytics) { + var analyticsData, pet; if (user.balance < 1) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); - } - - user.balance -= 1; - user.items.currentPet = ''; - - for (let pet in content.pets) { - user.items.pets[pet] = 0; - } - - if (!user.achievements.beastMasterCount) { - user.achievements.beastMasterCount = 0; - } - user.achievements.beastMasterCount++; - - if (analytics) { - analytics.track('release pets', { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return user; + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } else { - return [ - user.items.pets, - i18n.t('petsReleased'), - ]; + user.balance -= 1; + for (pet in content.pets) { + user.items.pets[pet] = 0; + } + if (!user.achievements.beastMasterCount) { + user.achievements.beastMasterCount = 0; + } + user.achievements.beastMasterCount++; + user.items.currentPet = ""; } + analyticsData = { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('release pets', analyticsData); + } + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/reroll.js b/common/script/ops/reroll.js index 3087845509..f6a0862f1d 100644 --- a/common/script/ops/reroll.js +++ b/common/script/ops/reroll.js @@ -1,40 +1,29 @@ import i18n from '../i18n'; import _ from 'lodash'; -import { - NotAuthorized, -} from '../libs/errors'; -module.exports = function reroll (user, tasks = [], req = {}, analytics) { +module.exports = function(user, req, cb, analytics) { + var analyticsData; if (user.balance < 1) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } - user.balance--; - user.stats.hp = 50; - - _.each(tasks, function resetTaskValues (task) { - if (!task.challenge || !task.challenge.id || task.challenge.broken) { - if (task.type !== 'reward') { - task.value = 0; - } + _.each(user.tasks, function(task) { + if (task.type !== 'reward') { + return task.value = 0; } }); - - if (analytics) { - analytics.track('Fortify Potion', { - uuid: user._id, - acquireMethod: 'Gems', - gemCost: 4, - category: 'behavior', - }); - } - - if (req.v2 === true) { - return user; - } else { - return [ - {user, tasks}, - i18n.t('fortifyComplete'), - ]; + user.stats.hp = 50; + analyticsData = { + uuid: user._id, + acquireMethod: 'Gems', + gemCost: 4, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('Fortify Potion', analyticsData); } + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/reset.js b/common/script/ops/reset.js index 3e48fa4f2d..fbe87729e3 100644 --- a/common/script/ops/reset.js +++ b/common/script/ops/reset.js @@ -1,29 +1,35 @@ -import resetGear from '../fns/resetGear'; -import i18n from '../i18n'; +import _ from 'lodash'; -module.exports = function reset (user, tasks = [], req = {}) { +module.exports = function(user, req, cb) { + var gear; + user.habits = []; + user.dailys = []; + user.todos = []; + user.rewards = []; user.stats.hp = 50; user.stats.lvl = 1; user.stats.gp = 0; user.stats.exp = 0; - - let tasksToRemove = []; - tasks.forEach(task => { - if (!task.challenge || !task.challenge.id || task.challenge.broken) { - tasksToRemove.push(task._id); - let i = user.tasksOrder[`${task.type}s`].indexOf(task._id); - if (i !== -1) user.tasksOrder[`${task.type}s`].splice(i, 1); - } + gear = user.items.gear; + _.each(['equipped', 'costume'], function(type) { + gear[type].armor = 'armor_base_0'; + gear[type].weapon = 'weapon_base_0'; + gear[type].head = 'head_base_0'; + return gear[type].shield = 'shield_base_0'; }); - - resetGear(user); - - if (req.v2 === true) { - return user; - } else { - return [ - {user, tasksToRemove}, - i18n.t('resetComplete'), - ]; + if (typeof gear.owned === 'undefined') { + gear.owned = {}; } + _.each(gear.owned, function(v, k) { + if (gear.owned[k]) { + gear.owned[k] = false; + } + return true; + }); + gear.owned.weapon_warrior_0 = true; + if (typeof user.markModified === "function") { + user.markModified('items.gear.owned'); + } + user.preferences.costume = false; + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/revive.js b/common/script/ops/revive.js index 30b29a78fc..7b6fe8445f 100644 --- a/common/script/ops/revive.js +++ b/common/script/ops/revive.js @@ -1,107 +1,72 @@ import content from '../content/index'; import i18n from '../i18n'; import _ from 'lodash'; -import { - NotAuthorized, -} from '../libs/errors'; -import randomVal from '../fns/randomVal'; -module.exports = function revive (user, req = {}, analytics) { - if (user.stats.hp > 0) { - throw new NotAuthorized(i18n.t('cannotRevive', req.language)); +module.exports = function(user, req, cb, analytics) { + var analyticsData, base, cl, gearOwned, item, losableItems, lostItem, lostStat; + if (!(user.stats.hp <= 0)) { + return typeof cb === "function" ? cb({ + code: 400, + message: "Cannot revive if not dead" + }) : void 0; } - _.merge(user.stats, { hp: 50, exp: 0, - gp: 0, + gp: 0 }); - if (user.stats.lvl > 1) { user.stats.lvl--; } - - let lostStat = randomVal(user, _.reduce(['str', 'con', 'per', 'int'], function findRandomStat (m, k) { + lostStat = user.fns.randomVal(_.reduce(['str', 'con', 'per', 'int'], (function(m, k) { if (user.stats[k]) { m[k] = k; } return m; - }, {})); - + }), {})); if (lostStat) { user.stats[lostStat]--; } - - let base = user.items.gear.owned; - let gearOwned; - - if (typeof base.toObject === 'function') { - gearOwned = base.toObject(); - } else { - gearOwned = user.items.gear.owned; - } - - let losableItems = {}; - let userClass = user.stats.class; - - _.each(gearOwned, function findLosableItems (value, key) { - let itm; - if (value) { - itm = content.gear.flat[key]; - + cl = user.stats["class"]; + gearOwned = (typeof (base = user.items.gear.owned).toObject === "function" ? base.toObject() : void 0) || user.items.gear.owned; + losableItems = {}; + _.each(gearOwned, function(v, k) { + var itm; + if (v) { + itm = content.gear.flat['' + k]; if (itm) { - let itemHasValueOrWarrior0 = itm.value > 0 || key === 'weapon_warrior_0'; - - let itemClassEqualsUserClass = itm.klass === userClass; - - let itemClassSpecial = itm.klass === 'special'; - let itemNotSpecialOrUserClassIsSpecial = !itm.specialClass || itm.specialClass === userClass; - let itemIsSpecial = itemNotSpecialOrUserClassIsSpecial && itemClassSpecial; - - let itemIsArmoire = itm.klass === 'armoire'; - - if (itemHasValueOrWarrior0 && (itemClassEqualsUserClass || itemIsSpecial || itemIsArmoire)) { - losableItems[key] = key; - return losableItems[key]; + if ((itm.value > 0 || k === 'weapon_warrior_0') && (itm.klass === cl || (itm.klass === 'special' && (!itm.specialClass || itm.specialClass === cl)) || itm.klass === 'armoire')) { + return losableItems['' + k] = '' + k; } } } }); - - let lostItem = randomVal(user, losableItems); - - let message = ''; - let item = content.gear.flat[lostItem]; - - if (item) { + lostItem = user.fns.randomVal(losableItems); + if (item = content.gear.flat[lostItem]) { user.items.gear.owned[lostItem] = false; - if (user.items.gear.equipped[item.type] === lostItem) { - user.items.gear.equipped[item.type] = `${item.type}_base_0`; + user.items.gear.equipped[item.type] = item.type + "_base_0"; } - if (user.items.gear.costume[item.type] === lostItem) { - user.items.gear.costume[item.type] = `${item.type}_base_0`; + user.items.gear.costume[item.type] = item.type + "_base_0"; } - - message = i18n.t('messageLostItem', { itemText: item.text(req.language)}, req.language); } - - if (analytics) { - analytics.track('Death', { - uuid: user._id, - lostItem, - gaLabel: lostItem, - category: 'behavior', - }); + if (typeof user.markModified === "function") { + user.markModified('items.gear'); } - - if (req.v2 === true) { - return user; - } else { - return [ - user.items, - message, - ]; + analyticsData = { + uuid: user._id, + lostItem: lostItem, + gaLabel: lostItem, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('Death', analyticsData); } + return typeof cb === "function" ? cb((item ? { + code: 200, + message: i18n.t('messageLostItem', { + itemText: item.text(req.language) + }, req.language) + } : null), user) : void 0; }; diff --git a/common/script/ops/score.js b/common/script/ops/score.js new file mode 100644 index 0000000000..ec0d2a70e2 --- /dev/null +++ b/common/script/ops/score.js @@ -0,0 +1,221 @@ +import moment from 'moment'; +import _ from 'lodash'; +import i18n from '../i18n'; + +module.exports = function(user, req, cb) { + var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, gainMP, id, multiplier, num, options, ref, stats, subtractPoints, task, th; + ref = req.params, id = ref.id, direction = ref.direction; + task = user.tasks[id]; + options = req.query || {}; + _.defaults(options, { + times: 1, + cron: false + }); + user._tmp = {}; + stats = { + gp: +user.stats.gp, + hp: +user.stats.hp, + exp: +user.stats.exp + }; + task.value = +task.value; + task.streak = ~~task.streak; + if (task.priority == null) { + task.priority = 1; + } + if (task.value > stats.gp && task.type === 'reward') { + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('messageNotEnoughGold', req.language) + }) : void 0; + } + delta = 0; + calculateDelta = function() { + var currVal, nextDelta, ref1; + currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value; + nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); + if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) { + if (direction === 'down' && task.type === 'daily' && options.cron) { + nextDelta *= 1 - _.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 0) / task.checklist.length; + } + if (task.type === 'todo') { + nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 0); + } + } + return nextDelta; + }; + calculateReverseDelta = function() { + var calc, closeEnough, currVal, diff, nextDelta, ref1, testVal; + currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value; + testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); + closeEnough = 0.00001; + while (true) { + calc = testVal + Math.pow(0.9747, testVal); + diff = currVal - calc; + if (Math.abs(diff) < closeEnough) { + break; + } + if (diff > 0) { + testVal -= diff; + } else { + testVal += diff; + } + } + nextDelta = testVal - currVal; + if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) { + if (task.type === 'todo') { + nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 0); + } + } + return nextDelta; + }; + changeTaskValue = function() { + return _.times(options.times, function() { + var nextDelta, ref1; + nextDelta = !options.cron && direction === 'down' ? calculateReverseDelta() : calculateDelta(); + if (task.type !== 'reward') { + if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) { + user.stats.training[task.attribute] += nextDelta; + } + if (direction === 'up') { + user.party.quest.progress.up = user.party.quest.progress.up || 0; + if ((ref1 = task.type) === 'daily' || ref1 === 'todo') { + user.party.quest.progress.up += nextDelta * (1 + (user._statsComputed.str / 200)); + } + if (task.type === 'habit') { + user.party.quest.progress.up += nextDelta * (0.5 + (user._statsComputed.str / 400)); + } + } + task.value += nextDelta; + } + return delta += nextDelta; + }); + }; + addPoints = function() { + var _crit, afterStreak, currStreak, gpMod, intBonus, perBonus, streakBonus; + _crit = (delta > 0 ? user.fns.crit() : 1); + if (_crit > 1) { + user._tmp.crit = _crit; + } + intBonus = 1 + (user._statsComputed.int * .025); + stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6); + perBonus = 1 + user._statsComputed.per * .02; + gpMod = delta * task.priority * _crit * perBonus; + return stats.gp += task.streak ? (currStreak = direction === 'down' ? task.streak - 1 : task.streak, streakBonus = currStreak / 100 + 1, afterStreak = gpMod * streakBonus, currStreak > 0 ? gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0 : void 0, afterStreak) : gpMod; + }; + subtractPoints = function() { + var conBonus, hpMod; + conBonus = 1 - (user._statsComputed.con / 250); + if (conBonus < .1) { + conBonus = 0.1; + } + hpMod = delta * conBonus * task.priority * 2; + return stats.hp += Math.round(hpMod * 10) / 10; + }; + gainMP = function(delta) { + delta *= user._tmp.crit || 1; + user.stats.mp += delta; + if (user.stats.mp >= user._statsComputed.maxMP) { + user.stats.mp = user._statsComputed.maxMP; + } + if (user.stats.mp < 0) { + return user.stats.mp = 0; + } + }; + switch (task.type) { + case 'habit': + changeTaskValue(); + if (delta > 0) { + addPoints(); + } else { + subtractPoints(); + } + gainMP(_.max([0.25, .0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); + th = (task.history != null ? task.history : task.history = []); + if (th[th.length - 1] && moment(th[th.length - 1].date).isSame(new Date, 'day')) { + th[th.length - 1].value = task.value; + } else { + th.push({ + date: +(new Date), + value: task.value + }); + } + if (typeof user.markModified === "function") { + user.markModified("habits." + (_.findIndex(user.habits, { + id: task.id + })) + ".history"); + } + break; + case 'daily': + if (options.cron) { + changeTaskValue(); + subtractPoints(); + if (!user.stats.buffs.streaks) { + task.streak = 0; + } + } else { + changeTaskValue(); + if (direction === 'down') { + delta = calculateDelta(); + } + addPoints(); + gainMP(_.max([1, .01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); + if (direction === 'up') { + task.streak = task.streak ? task.streak + 1 : 1; + if ((task.streak % 21) === 0) { + user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1; + } + } else { + if ((task.streak % 21) === 0) { + user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; + } + task.streak = task.streak ? task.streak - 1 : 0; + } + } + break; + case 'todo': + if (options.cron) { + changeTaskValue(); + } else { + task.dateCompleted = direction === 'up' ? new Date : void 0; + changeTaskValue(); + if (direction === 'down') { + delta = calculateDelta(); + } + addPoints(); + multiplier = _.max([ + _.reduce(task.checklist, (function(m, i) { + return m + (i.completed ? 1 : 0); + }), 1), 1 + ]); + gainMP(_.max([multiplier, .01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1)); + } + break; + case 'reward': + changeTaskValue(); + stats.gp -= Math.abs(task.value); + num = parseFloat(task.value).toFixed(2); + if (stats.gp < 0) { + stats.hp += stats.gp; + stats.gp = 0; + } + } + user.fns.updateStats(stats, req); + if (typeof window === 'undefined') { + if (direction === 'up') { + user.fns.randomDrop({ + task: task, + delta: delta + }, req); + } + } + if (typeof cb === "function") { + cb(null, user); + } + return delta; +}; diff --git a/common/script/ops/scoreTask.js b/common/script/ops/scoreTask.js deleted file mode 100644 index c366b84624..0000000000 --- a/common/script/ops/scoreTask.js +++ /dev/null @@ -1,260 +0,0 @@ -import _ from 'lodash'; -import { - NotAuthorized, -} from '../libs/errors'; -import i18n from '../i18n'; -import updateStats from '../fns/updateStats'; -import crit from '../fns/crit'; - -const MAX_TASK_VALUE = 21.27; -const MIN_TASK_VALUE = -47.27; -const CLOSE_ENOUGH = 0.00001; - -function _getTaskValue (taskValue) { - if (taskValue < MIN_TASK_VALUE) { - return MIN_TASK_VALUE; - } else if (taskValue > MAX_TASK_VALUE) { - return MAX_TASK_VALUE; - } else { - return taskValue; - } -} - -// Calculates the next task.value based on direction -// Uses a capped inverse log y=.95^x, y>= -5 -function _calculateDelta (task, direction, cron) { - // Min/max on task redness - let currVal = _getTaskValue(task.value); - let nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); - - // Checklists - if (task.checklist && task.checklist.length > 0) { - // If the Daily, only dock them a portion based on their checklist completion - if (direction === 'down' && task.type === 'daily' && cron) { - nextDelta *= 1 - _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length; - } - - // If To-Do, point-match the TD per checklist item completed - if (task.type === 'todo') { - nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0); - } - } - - return nextDelta; -} - -// Approximates the reverse delta for the task value -// This is meant to return the task value to its original value when unchecking a task. -// First, calculate the value using the normal way for our first guess although -// it will be a bit off -function _calculateReverseDelta (task, direction) { - let currVal = _getTaskValue(task.value); - let testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); - - // Now keep moving closer to the original value until we get "close enough" - // Check how close we are to the original value by computing the delta off our guess - // and looking at the difference between that and our current value. - while (true) { // eslint-disable-line no-constant-condition - let calc = testVal + Math.pow(0.9747, testVal); - let diff = currVal - calc; - - if (Math.abs(diff) < CLOSE_ENOUGH) break; - - if (diff > 0) { - testVal -= diff; - } else { - testVal += diff; - } - } - - // When we get close enough, return the difference between our approximated value - // and the current value. This will be the delta calculated from the original value - // before the task was checked. - let nextDelta = testVal - currVal; - - // Checklists - If To-Do, point-match the TD per checklist item completed - if (task.checklist && task.checklist.length > 0 && task.type === 'todo') { - nextDelta *= 1 + _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0); - } - - return nextDelta; -} - -function _gainMP (user, val) { - val *= user._tmp.crit || 1; - user.stats.mp += val; - - if (user.stats.mp >= user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP; - if (user.stats.mp < 0) { - user.stats.mp = 0; - } -} - -// HP modifier -// ===== CONSTITUTION ===== -// TODO Decreases HP loss from bad habits / missed dailies by 0.5% per point. -function _subtractPoints (user, task, stats, delta) { - let conBonus = 1 - user._statsComputed.con / 250; - if (conBonus < 0.1) conBonus = 0.1; - - let hpMod = delta * conBonus * task.priority * 2; // constant 2 multiplier for better results - stats.hp += Math.round(hpMod * 10) / 10; // round to 1dp - return stats.hp; -} - -function _addPoints (user, task, stats, direction, delta) { - // ===== CRITICAL HITS ===== - // allow critical hit only when checking off a task, not when unchecking it: - let _crit = delta > 0 ? crit(user) : 1; - // if there was a crit, alert the user via notification - if (_crit > 1) user._tmp.crit = _crit; - - // Exp Modifier - // ===== Intelligence ===== - // TODO Increases Experience gain by .2% per point. - let intBonus = 1 + user._statsComputed.int * 0.025; - stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6); - - // GP modifier - // ===== PERCEPTION ===== - // TODO Increases Gold gained from tasks by .3% per point. - let perBonus = 1 + user._statsComputed.per * 0.02; - let gpMod = delta * task.priority * _crit * perBonus; - - if (task.streak) { - let currStreak = direction === 'down' ? task.streak - 1 : task.streak; - let streakBonus = currStreak / 100 + 1; // eg, 1-day streak is 1.01, 2-day is 1.02, etc - let afterStreak = gpMod * streakBonus; - if (currStreak > 0 && gpMod > 0) { - user._tmp.streakBonus = afterStreak - gpMod; // keep this on-hand for later, so we can notify streak-bonus - } - - stats.gp += afterStreak; - } else { - stats.gp += gpMod; - } -} - -function _changeTaskValue (user, task, direction, times, cron) { - let addToDelta = 0; - - // If multiple days have passed, multiply times days missed - _.times(times, () => { - // Each iteration calculate the nextDelta, which is then accumulated in the total delta. - let nextDelta = !cron && direction === 'down' ? _calculateReverseDelta(task, direction) : _calculateDelta(task, direction, cron); - - if (task.type !== 'reward') { - if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) { - user.stats.training[task.attribute] += nextDelta; - } - - if (direction === 'up') { // Make progress on quest based on STR - user.party.quest.progress.up = user.party.quest.progress.up || 0; - - if (task.type === 'todo' || task.type === 'daily') { - user.party.quest.progress.up += nextDelta * (1 + user._statsComputed.str / 200); - } else if (task.type === 'habit') { - user.party.quest.progress.up += nextDelta * (0.5 + user._statsComputed.str / 400); - } - } - - task.value += nextDelta; - } - - addToDelta += nextDelta; - }); - - return addToDelta; -} - -module.exports = function scoreTask (options = {}, req = {}) { - let {user, task, direction, times = 1, cron = false} = options; - let delta = 0; - let stats = { - gp: user.stats.gp, - hp: user.stats.hp, - exp: user.stats.exp, - }; - - // This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying - // the API consumer, then cleared afterwards - user._tmp = {}; - - // If they're trying to purchase a too-expensive reward, don't allow them to do that. - if (task.value > user.stats.gp && task.type === 'reward') throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language)); - - if (task.type === 'habit') { - delta += _changeTaskValue(user, task, direction, times, cron); - - // Add habit value to habit-history (if different) - if (delta > 0) { - _addPoints(user, task, stats, direction, delta); - } else { - _subtractPoints(user, task, stats, delta); - } - _gainMP(user, _.max([0.25, 0.0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); - - task.history = task.history || []; - // Add history entry, even more than 1 per day - task.history.push({ - date: Number(new Date()), - value: task.value, - }); - } else if (task.type === 'daily') { - if (cron) { - delta += _changeTaskValue(user, task, direction, times, cron); - _subtractPoints(user, task, stats, delta); - if (!user.stats.buffs.streaks) task.streak = 0; - } else { - delta += _changeTaskValue(user, task, direction, times, cron); - if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly - _addPoints(user, task, stats, direction, delta); // obviously for delta>0, but also a trick to undo accidental checkboxes - _gainMP(user, _.max([1, 0.01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); - - if (direction === 'up') { - task.streak += 1; - // Give a streak achievement when the streak is a multiple of 21 - if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1; - task.completed = true; - } else if (direction === 'down') { - // Remove a streak achievement if streak was a multiple of 21 and the daily was undone - if (task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; - task.streak -= 1; - task.completed = false; - } - } - } else if (task.type === 'todo') { - if (cron) { // don't touch stats on cron - delta += _changeTaskValue(user, task, direction, times, cron); - } else { - if (direction === 'up') { - task.dateCompleted = new Date(); - task.completed = true; - } else if (direction === 'down') { - task.completed = false; - task.dateCompleted = undefined; - } - - delta += _changeTaskValue(user, task, direction, times, cron); - if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly - _addPoints(user, task, stats, direction, delta); - - // MP++ per checklist item in ToDo, bonus per CLI - let multiplier = _.max([_.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 1), 1]); - _gainMP(user, _.max([multiplier, 0.01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1)); - } - } else if (task.type === 'reward') { - // Don't adjust values for rewards - delta += _changeTaskValue(user, task, direction, times, cron); - // purchase item - stats.gp -= Math.abs(task.value); - // hp - gp difference - if (stats.gp < 0) { - stats.hp += stats.gp; - stats.gp = 0; - } - } - - updateStats(user, stats, req); - return [delta]; -}; diff --git a/common/script/ops/sell.js b/common/script/ops/sell.js index 10412da222..33e5f7d673 100644 --- a/common/script/ops/sell.js +++ b/common/script/ops/sell.js @@ -1,43 +1,23 @@ import content from '../content/index'; -import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; -import { - NotFound, - NotAuthorized, - BadRequest, -} from '../libs/errors'; -const ACCEPTEDTYPES = ['eggs', 'hatchingPotions', 'food']; - -module.exports = function sell (user, req = {}) { - let key = _.get(req.params, 'key'); - let type = _.get(req.params, 'type'); - - if (!type) { - throw new BadRequest(i18n.t('typeRequired', req.language)); +module.exports = function(user, req, cb) { + var key, ref, type; + ref = req.params, key = ref.key, type = ref.type; + if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') { + return typeof cb === "function" ? cb({ + code: 404, + message: ":type not found. Must bes in [eggs, hatchingPotions, food]" + }) : void 0; } - - if (!key) { - throw new BadRequest(i18n.t('keyRequired', req.language)); - } - - if (ACCEPTEDTYPES.indexOf(type) === -1) { - throw new NotAuthorized(i18n.t('typeNotSellable', {acceptedTypes: ACCEPTEDTYPES.join(', ')}, req.language)); - } - if (!user.items[type][key]) { - throw new NotFound(i18n.t('userItemsKeyNotFound', {type}, req.language)); + return typeof cb === "function" ? cb({ + code: 404, + message: ":key not found for user.items." + type + }) : void 0; } - user.items[type][key]--; user.stats.gp += content[type][key].value; - - if (req.v2 === true) { - return _.pick(user, splitWhitespace('stats items')); - } else { - return [ - _.pick(user, splitWhitespace('stats items')), - ]; - } + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats items'))) : void 0; }; diff --git a/common/script/ops/sleep.js b/common/script/ops/sleep.js index 1a531ecb88..dec8095ad3 100644 --- a/common/script/ops/sleep.js +++ b/common/script/ops/sleep.js @@ -1,9 +1,4 @@ -module.exports = function sleep (user, req = {}) { +module.exports = function(user, req, cb) { user.preferences.sleep = !user.preferences.sleep; - - if (req.v2 === true) { - return {}; - } else { - return [user.preferences.sleep]; - } + return typeof cb === "function" ? cb(null, {}) : void 0; }; diff --git a/common/script/ops/sortTag.js b/common/script/ops/sortTag.js index c1fc42f330..85dcda169f 100644 --- a/common/script/ops/sortTag.js +++ b/common/script/ops/sortTag.js @@ -1,19 +1,9 @@ -import { BadRequest } from '../libs/errors'; -import _ from 'lodash'; - -// TODO used only in client, move there? - -module.exports = function sortTag (user, req = {}) { - let to = _.get(req, 'query.to'); - let fromParam = _.get(req, 'query.from'); - - let invalidTo = !to && to !== 0; - let invalidFrom = !fromParam && fromParam !== 0; - - if (invalidTo || invalidFrom) { - throw new BadRequest('?to=__&from=__ are required'); +module.exports = function(user, req, cb) { + var from, ref, to; + ref = req.query, to = ref.to, from = ref.from; + if (!((to != null) && (from != null))) { + return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0; } - - user.tags.splice(to, 0, user.tags.splice(fromParam, 1)[0]); - return user.tags; + user.tags.splice(to, 0, user.tags.splice(from, 1)[0]); + return typeof cb === "function" ? cb(null, user.tags) : void 0; }; diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js index ce002d8dc0..b903b692dc 100644 --- a/common/script/ops/sortTask.js +++ b/common/script/ops/sortTask.js @@ -1,49 +1,39 @@ import i18n from '../i18n'; import preenTodos from '../libs/preenTodos'; -import { - NotFound, - BadRequest, -} from '../libs/errors'; -import _ from 'lodash'; -// TODO used only in client, move there? - -module.exports = function sortTask (user, req = {}) { - let id = _.get(req, 'params.id'); - let to = _.get(req, 'query.to'); - let fromParam = _.get(req, 'query.from'); - let taskType = _.get(req, 'params.taskType'); - - let index = _.findIndex(user[`${taskType}s`], function findById (task) { - return task._id === id; - }); - - if (index === -1) { - throw new NotFound(i18n.t('messageTaskNotFound', req.language)); +module.exports = function(user, req, cb) { + var from, id, movedTask, preenedTasks, ref, task, tasks, to; + id = req.params.id; + ref = req.query, to = ref.to, from = ref.from; + task = user.tasks[id]; + if (!task) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTaskNotFound', req.language) + }) : void 0; } - if (!to && !fromParam) { - throw new BadRequest('?to=__&from=__ are required'); + if (!((to != null) && (from != null))) { + return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0; } - - let tasks = user[`${taskType}s`]; - - if (taskType === 'todo') { - let preenedTasks = preenTodos(tasks); - + tasks = user[task.type + "s"]; + if (task.type === 'todo' && tasks[from] !== task) { + preenedTasks = preenTodos(tasks); if (to !== -1) { to = tasks.indexOf(preenedTasks[to]); } - - fromParam = tasks.indexOf(preenedTasks[fromParam]); + from = tasks.indexOf(preenedTasks[from]); } - - let movedTask = tasks.splice(fromParam, 1)[0]; - + if (tasks[from] !== task) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTaskNotFound', req.language) + }) : void 0; + } + movedTask = tasks.splice(from, 1)[0]; if (to === -1) { tasks.push(movedTask); } else { tasks.splice(to, 0, movedTask); } - - return tasks; + return typeof cb === "function" ? cb(null, tasks) : void 0; }; diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js index 5e0b118af0..c53a5997ec 100644 --- a/common/script/ops/unlock.js +++ b/common/script/ops/unlock.js @@ -1,113 +1,63 @@ import i18n from '../i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; -import { - NotAuthorized, - BadRequest, -} from '../libs/errors'; -// If item is already purchased -> equip it -// Otherwise unlock it -module.exports = function unlock (user, req = {}, analytics) { - let path = _.get(req.query, 'path'); - - if (!path) { - throw new BadRequest(i18n.t('pathRequired', req.language)); +module.exports = function(user, req, cb, analytics) { + var alreadyOwns, analyticsData, cost, fullSet, k, path, split, v; + path = req.query.path; + fullSet = ~path.indexOf(","); + cost = ~path.indexOf('background.') ? fullSet ? 3.75 : 1.75 : fullSet ? 1.25 : 0.5; + alreadyOwns = !fullSet && user.fns.dotGet("purchased." + path) === true; + if ((user.balance < cost || !user.balance) && !alreadyOwns) { + return typeof cb === "function" ? cb({ + code: 401, + message: i18n.t('notEnoughGems', req.language) + }) : void 0; } + if (fullSet) { + _.each(path.split(","), function(p) { + if (~path.indexOf('gear.')) { + user.fns.dotSet("" + p, true); + true; + } else { - let isFullSet = path.indexOf(',') !== -1; - let isBackground = path.indexOf('background.') !== -1; - - let cost; - if (isBackground && isFullSet) { - cost = 3.75; - } else if (isBackground) { - cost = 1.75; - } else if (isFullSet) { - cost = 1.25; - } else { - cost = 0.5; - } - - let setPaths; - let alreadyOwns; - - if (isFullSet) { - setPaths = path.split(','); - let alreadyOwnedItems = 0; - - _.each(setPaths, singlePath => { - if (_.get(user, `purchased.${singlePath}`) === true) { - alreadyOwnedItems++; } - }); - - if (alreadyOwnedItems === setPaths.length) { - throw new NotAuthorized(i18n.t('alreadyUnlocked', req.language)); - // TODO write math formula to check if buying the full set is cheaper than the items individually - // (item cost * number of remaining items) < setCost` - } /* else if (alreadyOwnedItems > 0) { - throw new NotAuthorized(i18n.t('alreadyUnlockedPart', req.language)); - } */ - } else { - alreadyOwns = _.get(user, `purchased.${path}`) === true; - } - - if ((!user.balance || user.balance < cost) && !alreadyOwns) { - throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); - } - - if (isFullSet) { - _.each(setPaths, function markItemsAsPurchased (pathPart) { - if (path.indexOf('gear.') !== -1) { - _.set(user, pathPart, true); - } - - _.set(user, `purchased.${pathPart}`, true); + user.fns.dotSet("purchased." + p, true); + return true; }); } else { - if (alreadyOwns) { // eslint-disable-line no-lonely-if - let split = path.split('.'); - let value = split.pop(); - let key = split.join('.'); - if (key === 'background' && value === user.preferences.background) { - value = ''; + if (alreadyOwns) { + split = path.split('.'); + v = split.pop(); + k = split.join('.'); + if (k === 'background' && v === user.preferences.background) { + v = ''; } - - _.set(user, `preferences.${key}`, value); - } else { - _.set(user, `purchased.${path}`, true); + user.fns.dotSet("preferences." + k, v); + return typeof cb === "function" ? cb(null, req) : void 0; } + user.fns.dotSet("purchased." + path, true); } - - if (!alreadyOwns) { - if (path.indexOf('gear.') === -1) { + user.balance -= cost; + if (~path.indexOf('gear.')) { + if (typeof user.markModified === "function") { + user.markModified('gear.owned'); + } + } else { + if (typeof user.markModified === "function") { user.markModified('purchased'); } - - user.balance -= cost; - - if (analytics) { - analytics.track('acquire item', { - uuid: user._id, - itemKey: path, - itemType: 'customization', - acquireMethod: 'Gems', - gemCost: cost / 0.25, - category: 'behavior', - }); - } } - - let response = [ - _.pick(user, splitWhitespace('purchased preferences items')), - ]; - - if (!alreadyOwns) response.push(i18n.t('unlocked', req.language)); - - if (req.v2 === true) { - return response[0]; - } else { - return response; + analyticsData = { + uuid: user._id, + itemKey: path, + itemType: 'customization', + acquireMethod: 'Gems', + gemCost: cost / .25, + category: 'behavior' + }; + if (analytics != null) { + analytics.track('acquire item', analyticsData); } + return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('purchased preferences items'))) : void 0; }; diff --git a/common/script/ops/update.js b/common/script/ops/update.js index c41e74180e..12a100e372 100644 --- a/common/script/ops/update.js +++ b/common/script/ops/update.js @@ -1,11 +1,9 @@ import _ from 'lodash'; -// TODO used only in client, move there? - -module.exports = function updateUser (user, req = {}) { - _.each(req.body, (val, key) => { - _.set(user, key, val); +module.exports = function(user, req, cb) { + _.each(req.body, function(v, k) { + user.fns.dotSet(k, v); + return true; }); - - return user; + return typeof cb === "function" ? cb(null, user) : void 0; }; diff --git a/common/script/ops/updateTag.js b/common/script/ops/updateTag.js index ade87f916d..8e4019fa51 100644 --- a/common/script/ops/updateTag.js +++ b/common/script/ops/updateTag.js @@ -1,20 +1,18 @@ import i18n from '../i18n'; import _ from 'lodash'; -import { NotFound } from '../libs/errors'; -// TODO used only in client, move there? - -module.exports = function updateTag (user, req = {}) { - let tid = _.get(req, 'params.id'); - - let index = _.findIndex(user.tags, { - id: tid, +module.exports = function(user, req, cb) { + var i, tid; + tid = req.params.id; + i = _.findIndex(user.tags, { + id: tid }); - - if (index === -1) { - throw new NotFound(i18n.t('messageTagNotFound', req.language)); + if (!~i) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTagNotFound', req.language) + }) : void 0; } - - user.tags[index].name = _.get(req, 'body.name'); - return user.tags[index]; + user.tags[i].name = req.body.name; + return typeof cb === "function" ? cb(null, user.tags[i]) : void 0; }; diff --git a/common/script/ops/updateTask.js b/common/script/ops/updateTask.js index 2a2b1edba2..427104f15e 100644 --- a/common/script/ops/updateTask.js +++ b/common/script/ops/updateTask.js @@ -1,25 +1,23 @@ +import i18n from '../i18n'; import _ from 'lodash'; -// From server pass task.toObject() not the task document directly -module.exports = function updateTask (task, req = {}) { - let body = req.body || {}; - - // If reminders are updated -> replace the original ones - if (body.reminders) { - task.reminders = body.reminders; +module.exports = function(user, req, cb) { + var ref, task; + if (!(task = user.tasks[(ref = req.params) != null ? ref.id : void 0])) { + return typeof cb === "function" ? cb({ + code: 404, + message: i18n.t('messageTaskNotFound', req.language) + }) : void 0; } - - // If checklist is updated -> replace the original one - if (body.checklist) { - task.checklist = body.checklist; + _.merge(task, _.omit(req.body, ['checklist', 'reminders', 'id', 'type'])); + if (req.body.checklist) { + task.checklist = req.body.checklist; } - - // If tags are updated -> replace the original ones - if (body.tags) { - task.tags = body.tags; + if (req.body.reminders) { + task.reminders = req.body.reminders; } - - _.merge(task, _.omit(body, ['_id', 'id', 'type', 'reminders', 'checklist', 'tags'])); - - return [task]; + if (typeof task.markModified === "function") { + task.markModified('tags'); + } + return typeof cb === "function" ? cb(null, task) : void 0; }; diff --git a/common/script/ops/updateWebhook.js b/common/script/ops/updateWebhook.js index 63fed89b17..e2775a40b4 100644 --- a/common/script/ops/updateWebhook.js +++ b/common/script/ops/updateWebhook.js @@ -1,20 +1,9 @@ -import validator from 'validator'; -import i18n from '../i18n'; -import { - BadRequest, -} from '../libs/errors'; +import _ from 'lodash'; -module.exports = function updateWebhook (user, req) { - if (!validator.isURL(req.body.url)) throw new BadRequest(i18n.t('invalidUrl', req.language)); - if (!validator.isBoolean(req.body.enabled)) throw new BadRequest(i18n.t('invalidEnabled', req.language)); - - user.markModified('preferences.webhooks'); - user.preferences.webhooks[req.params.id].url = req.body.url; - user.preferences.webhooks[req.params.id].enabled = req.body.enabled; - - if (req.v2 === true) { - return user.preferences.webhooks; - } else { - return [user.preferences.webhooks[req.params.id]]; +module.exports = function(user, req, cb) { + _.merge(user.preferences.webhooks[req.params.id], req.body); + if (typeof user.markModified === "function") { + user.markModified('preferences.webhooks'); } + return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0; }; diff --git a/common/script/public/config.js b/common/script/public/config.js index 4456909ef4..bb78e244bd 100644 --- a/common/script/public/config.js +++ b/common/script/public/config.js @@ -1,48 +1,8 @@ 'use strict'; - -angular.module('habitrpg') -.config(['$httpProvider', function($httpProvider){ +angular.module('habitrpg').config(['$httpProvider', function($httpProvider){ $httpProvider.interceptors.push(['$q', '$rootScope', function($q, $rootScope){ - var resyncNumber = 0; - var lastResync = 0; - - // Verify that the user was not updated from another browser/app/client - // If it was, sync - function verifyUserUpdated (response) { - var isApiCall = response.config.url.indexOf('api/v3') !== -1; - var isUserAvailable = $rootScope.User && $rootScope.User.user && $rootScope.User.user._wrapped === true; - var hasUserV = response.data && response.data.userV; - var isNotSync = response.config.url.indexOf('/api/v3/user') !== 0; - - if (isApiCall && isUserAvailable && hasUserV) { - var oldUserV = $rootScope.User.user._v; - $rootScope.User.user._v = response.data.userV; - - // Something has changed on the user object that was not tracked here, sync the user - if (isNotSync && ($rootScope.User.user._v - oldUserV) > 1) { - $rootScope.User.sync(); - } - } - } - return { - request: function (config) { - var url = config.url; - - if (url.indexOf('api/v3') !== -1) { - if ($rootScope.User && $rootScope.User.user) { - if (url.indexOf('?') !== -1) { - config.url += '&userV=' + $rootScope.User.user._v; - } else { - config.url += '?userV=' + $rootScope.User.user._v; - } - } - } - - return config; - }, response: function(response) { - verifyUserUpdated(response); return response; }, responseError: function(response) { @@ -61,43 +21,25 @@ angular.module('habitrpg') if (!mobileApp) // skip mobile for now $rootScope.$broadcast('responseError', "The site has been updated and the page needs to refresh. The last action has not been recorded, please refresh and try again."); - } else if (response.data && response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { + } else if (response.data.code && response.data.code === 'ACCOUNT_SUSPENDED') { confirm(response.data.err); localStorage.clear(); window.location.href = mobileApp ? '/app/login' : '/logout'; //location.reload() - // 400 range - } else if (response.status < 400) { - // never triggered because we're in responseError - $rootScope.$broadcast('responseText', response.data && response.data.message); + // 400 range? } else if (response.status < 500) { - if (response.status === 400 && response.data && response.data.errors && _.isArray(response.data.errors)) { // bad requests with more info - response.data.errors.forEach(function (err) { - $rootScope.$broadcast('responseError', err.message); - }); - } else { - $rootScope.$broadcast('responseError', response.data && response.data.message); - } - - if ($rootScope.User && $rootScope.User.sync) { - if (resyncNumber < 100 && (Date.now() - lastResync) > 500) { // avoid thousands of requests when user is not found - $rootScope.User.sync(); - resyncNumber++; - lastResync = Date.now(); - } - } - + $rootScope.$broadcast('responseText', response.data.err || response.data); // Need to reject the prompse so the error is handled correctly - if (response.status === 401) { + if (response.status === 401) return $q.reject(response); - } + // Error } else { - var error = window.env.t('requestError') + '

"' + - window.env.t('error') + ' ' + (response.data.message || response.data.error || response.data || 'something went wrong') + + var error = window.env.t('requestError') + '

"' + + window.env.t('error') + ' ' + (response.data.err || response.data || 'something went wrong') + '"

' + window.env.t('seeConsole'); if (mobileApp) error = 'Error contacting the server. Please try again in a few minutes.'; - $rootScope.$broadcast('responseError500', error); + $rootScope.$broadcast('responseError', error); console.error(response); } @@ -105,4 +47,4 @@ angular.module('habitrpg') } }; }]); -}]); +}]); \ No newline at end of file diff --git a/common/script/public/userServices.js b/common/script/public/userServices.js new file mode 100644 index 0000000000..e5b1790086 --- /dev/null +++ b/common/script/public/userServices.js @@ -0,0 +1,268 @@ +'use strict'; + +angular.module('habitrpg') + .service('ApiUrl', ['API_URL', function(currentApiUrl){ + this.setApiUrl = function(newUrl){ + currentApiUrl = newUrl; + }; + + this.get = function(){ + return currentApiUrl; + }; + }]) + +/** + * Services that persists and retrieves user from localStorage. + */ + .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'MOBILE_APP', 'Notification', 'ApiUrl', + function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, MOBILE_APP, Notification, ApiUrl) { + var authenticated = false; + var defaultSettings = { + auth: { apiId: '', apiToken: ''}, + sync: { + queue: [], //here OT will be queued up, this is NOT call-back queue! + sent: [] //here will be OT which have been sent, but we have not got reply from server yet. + }, + fetching: false, // whether fetch() was called or no. this is to avoid race conditions + online: false + }; + var settings = {}; //habit mobile settings (like auth etc.) to be stored here + var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate + + var userNotifications = { + // "party.order" : env.t("updatedParty"), + // "party.orderAscending" : env.t("updatedParty") + // party.order notifications are not currently needed because the party avatars are resorted immediately now + }; // this is a list of notifications to send to the user when changes are made, along with the message. + + //first we populate user with schema + user.apiToken = user._id = ''; // we use id / apitoken to determine if registered + + //than we try to load localStorage + if (localStorage.getItem(STORAGE_USER_ID)) { + _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); + } + user._wrapped = false; + + var syncQueue = function (cb) { + if (!authenticated) { + $window.alert("Not authenticated, can't sync, go to settings first."); + return; + } + + var queue = settings.sync.queue; + var sent = settings.sync.sent; + if (queue.length === 0) { + // Sync: Queue is empty + return; + } + if (settings.fetching) { + // Sync: Already fetching + return; + } + if (settings.online!==true) { + // Sync: Not online + return; + } + + settings.fetching = true; + // move all actions from queue array to sent array + _.times(queue.length, function () { + sent.push(queue.shift()); + }); + + // Save the current filters + var current_filters = user.filters; + + $http.post(ApiUrl.get() + '/api/v2/user/batch-update', sent, {params: {data:+new Date, _v:user._v, siteVersion: $window.env && $window.env.siteVersion}}) + .success(function (data, status, heacreatingders, config) { + //make sure there are no pending actions to sync. If there are any it is not safe to apply model from server as we may overwrite user data. + if (!queue.length) { + //we can't do user=data as it will not update user references in all other angular controllers. + + // the user has been modified from another application, sync up + if(data && data.wasModified) { + delete data.wasModified; + $rootScope.$emit('userUpdated', user); + } + + // Update user + _.extend(user, data); + // Preserve filter selections between syncs + _.extend(user.filters,current_filters); + if (!user._wrapped){ + + // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, + // they update the user in the browser and then send the request to the server, where the same operation is + // replicated. We need to wrap each op to provide a callback to send that operation + $window.habitrpgShared.wrap(user); + _.each(user.ops, function(op,k){ + user.ops[k] = function(req,cb){ + if (cb) return op(req,cb); + op(req,function(err,response) { + for(var updatedItem in req.body) { + var itemUpdateResponse = userNotifications[updatedItem]; + if(itemUpdateResponse) Notification.text(itemUpdateResponse); + } + if (err) { + var message = err.code ? err.message : err; + if (MOBILE_APP) Notification.push({type:'text',text:message}); + else Notification.text(message); + // In the case of 200s, they're friendly alert messages like "Your pet has hatched!" - still send the op + if ((err.code && err.code >= 400) || !err.code) return; + } + userServices.log({op:k, params: req.params, query:req.query, body:req.body}); + }); + } + }); + } + + // Emit event when user is synced + $rootScope.$emit('userSynced'); + } + sent.length = 0; + settings.fetching = false; + save(); + if (cb) { + cb(false) + } + + syncQueue(); // call syncQueue to check if anyone pushed more actions to the queue while we were talking to server. + }) + .error(function (data, status, headers, config) { + // (Notifications handled in app.js) + + // If we're offline, queue up offline actions so we can send when we're back online + if (status === 0) { + //move sent actions back to queue + _.times(sent.length, function () { + queue.push(sent.shift()) + }); + settings.fetching = false; + // In the case of errors, discard the corrupt queue + } else { + // Clear the queue. Better if we can hunt down the problem op, but this is the easiest solution + settings.sync.queue = settings.sync.sent = []; + save(); + } + }); + } + + + var save = function () { + localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); + }; + var userServices = { + user: user, + set: function(updates) { + user.ops.update({body:updates}); + }, + + online: function (status) { + if (status===true) { + settings.online = true; + syncQueue(); + } else { + settings.online = false; + }; + }, + + authenticate: function (uuid, token, cb) { + if (!!uuid && !!token) { + var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) + $http.defaults.headers.common['x-api-user'] = uuid; + $http.defaults.headers.common['x-api-key'] = token; + $http.defaults.headers.common['x-user-timezoneOffset'] = offset; + authenticated = true; + settings.auth.apiId = uuid; + settings.auth.apiToken = token; + settings.online = true; + if (user && user._v) user._v--; // shortcut to always fetch new updates on page reload + userServices.log({}, function(){ + // If they don't have timezone, set it + if (user.preferences.timezoneOffset !== offset) + userServices.set({'preferences.timezoneOffset': offset}); + cb && cb(); + }); + } else { + alert('Please enter your ID and Token in settings.') + } + }, + + authenticated: function(){ + return this.settings.auth.apiId !== ""; + }, + + getBalanceInGems: function() { + var balance = user.balance || 0; + return balance * 4; + }, + + log: function (action, cb) { + //push by one buy one if an array passed in. + if (_.isArray(action)) { + action.forEach(function (a) { + settings.sync.queue.push(a); + }); + } else { + settings.sync.queue.push(action); + } + + save(); + syncQueue(cb); + }, + + sync: function(){ + user._v--; + userServices.log({}); + }, + + save: save, + + settings: settings + }; + + + //load settings if we have them + if (localStorage.getItem(STORAGE_SETTINGS_ID)) { + //use extend here to make sure we keep object reference in other angular controllers + _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID))); + + //if settings were saved while fetch was in process reset the flag. + settings.fetching = false; + //create and load if not + } else { + localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings)); + _.extend(settings, defaultSettings); + } + + //If user does not have ApiID that forward him to settings. + if (!settings.auth.apiId || !settings.auth.apiToken) { + + if (MOBILE_APP) { + $location.path("/login"); + } else { + //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=... + var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead + if (search.err) return alert(search.err); + if (search._id && search.apiToken) { + userServices.authenticate(search._id, search.apiToken, function(){ + $window.location.href='/'; + }); + } else { + var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); + if (!isStaticOrSocial){ + localStorage.clear(); + $window.location.href = '/logout'; + } + } + } + + } else { + userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) + } + + return userServices; + } +]); diff --git a/config.json.example b/config.json.example index d70dcac18c..6aeb8ac74a 100644 --- a/config.json.example +++ b/config.json.example @@ -1,6 +1,5 @@ { "PORT":3000, - "ENABLE_CONSOLE_LOGS_IN_PROD":"false", "IP":"0.0.0.0", "CORES":1, "BASE_URL":"http://localhost:3000", @@ -9,8 +8,6 @@ "NODE_DB_URI":"mongodb://localhost/habitrpg", "TEST_DB_URI":"mongodb://localhost/habitrpg_test", "NODE_ENV":"development", - "CRON_SAFE_MODE":"false", - "MAINTENANCE_MODE": "false", "SESSION_SECRET":"YOUR SECRET HERE", "ADMIN_EMAIL": "you@example.com", "SMTP_USER":"user@example.com", @@ -22,7 +19,6 @@ "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", @@ -37,7 +33,7 @@ "EMAIL_SERVER": { "url": "http://example.com", "authUser": "user", - "authPassword": "password" + "authPassword": "password" }, "S3":{ "bucket":"bucket", @@ -64,7 +60,7 @@ "subdomain": "subdomain", "token": "token", "username": "username", - "password": "password" + "password": "password" }, "PUSH_CONFIGS": { "GCM_SERVER_API_KEY": "", diff --git a/gulpfile.js b/gulpfile.js index d7cd0d79c2..0c399ce7fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,7 +9,6 @@ require('babel-register'); if (process.env.NODE_ENV === 'production') { - require('./tasks/gulp-apidoc'); require('./tasks/gulp-newstuff'); require('./tasks/gulp-build'); require('./tasks/gulp-babelify'); diff --git a/karma.conf.js b/karma.conf.js index d0ceffd5c6..2ae3a52a9d 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -11,40 +11,41 @@ module.exports = function karmaConfig (config) { // list of files / patterns to load in the browser files: [ - 'website/client/bower_components/jquery/dist/jquery.js', - 'website/client/bower_components/pnotify/jquery.pnotify.js', - 'website/client/bower_components/angular/angular.js', - 'website/client/bower_components/angular-loading-bar/build/loading-bar.min.js', - 'website/client/bower_components/angular-resource/angular-resource.min.js', - 'website/client/bower_components/hello/dist/hello.all.min.js', - 'website/client/bower_components/angular-sanitize/angular-sanitize.js', - 'website/client/bower_components/bootstrap/dist/js/bootstrap.js', - 'website/client/bower_components/angular-bootstrap/ui-bootstrap.js', - 'website/client/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', - 'website/client/bower_components/angular-ui-router/release/angular-ui-router.js', - 'website/client/bower_components/angular-filter/dist/angular-filter.js', - 'website/client/bower_components/angular-ui/build/angular-ui.js', - 'website/client/bower_components/angular-ui-utils/ui-utils.min.js', - 'website/client/bower_components/Angular-At-Directive/src/at.js', - 'website/client/bower_components/Angular-At-Directive/src/caret.js', - 'website/client/bower_components/angular-mocks/angular-mocks.js', - 'website/client/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js', - 'website/client/bower_components/select2/select2.js', - 'website/client/bower_components/angular-ui-select2/src/select2.js', - 'website/client/bower_components/habitica-markdown/dist/habitica-markdown.min.js', + 'website/public/bower_components/jquery/dist/jquery.js', + 'website/public/bower_components/pnotify/jquery.pnotify.js', + 'website/public/bower_components/angular/angular.js', + 'website/public/bower_components/angular-loading-bar/build/loading-bar.min.js', + 'website/public/bower_components/angular-resource/angular-resource.min.js', + 'website/public/bower_components/hello/dist/hello.all.min.js', + 'website/public/bower_components/angular-sanitize/angular-sanitize.js', + 'website/public/bower_components/bootstrap/dist/js/bootstrap.js', + 'website/public/bower_components/angular-bootstrap/ui-bootstrap.js', + 'website/public/bower_components/angular-bootstrap/ui-bootstrap-tpls.js', + 'website/public/bower_components/angular-ui-router/release/angular-ui-router.js', + 'website/public/bower_components/angular-filter/dist/angular-filter.js', + 'website/public/bower_components/angular-ui/build/angular-ui.js', + 'website/public/bower_components/angular-ui-utils/ui-utils.min.js', + 'website/public/bower_components/Angular-At-Directive/src/at.js', + 'website/public/bower_components/Angular-At-Directive/src/caret.js', + 'website/public/bower_components/angular-mocks/angular-mocks.js', + 'website/public/bower_components/ngInfiniteScroll/build/ng-infinite-scroll.js', + 'website/public/bower_components/select2/select2.js', + 'website/public/bower_components/angular-ui-select2/src/select2.js', + 'website/public/bower_components/habitica-markdown/dist/habitica-markdown.min.js', 'common/dist/scripts/habitrpg-shared.js', 'test/spec/mocks/**/*.js', - 'website/client/js/env.js', - 'website/client/js/app.js', + 'website/public/js/env.js', + 'website/public/js/app.js', 'common/script/public/config.js', + 'common/script/public/userServices.js', 'common/script/public/directives.js', - 'website/client/js/services/**/*.js', - 'website/client/js/filters/**/*.js', - 'website/client/js/directives/**/*.js', - 'website/client/js/controllers/**/*.js', + 'website/public/js/services/**/*.js', + 'website/public/js/filters/**/*.js', + 'website/public/js/directives/**/*.js', + 'website/public/js/controllers/**/*.js', 'test/spec/specHelper.js', 'test/spec/**/*.js', @@ -76,7 +77,7 @@ module.exports = function karmaConfig (config) { browsers: ['PhantomJS'], preprocessors: { - 'website/client/js/**/*.js': ['coverage'], + 'website/public/js/**/*.js': ['coverage'], 'test/**/*.js': ['babel'], }, diff --git a/migrations/20160521_veteran_ladder.js b/migrations/20160521_veteran_ladder.js deleted file mode 100644 index cf92ff5375..0000000000 --- a/migrations/20160521_veteran_ladder.js +++ /dev/null @@ -1,76 +0,0 @@ -var migrationName = '20160521_veteran_ladder.js'; -var authorName = 'Sabe'; // in case script author needs to know when their ... -var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done - -/* - * Award Gilded Turkey pet to Turkey mount owners, Turkey Mount if they only have Turkey Pet, - * and Turkey Pet otherwise - */ - -var dbserver = 'localhost:27017'; // FOR TEST DATABASE -// var dbserver = 'username:password@ds031379-a0.mongolab.com:31379'; // FOR PRODUCTION DATABASE -var dbname = 'habitrpg'; - -var mongo = require('mongoskin'); -var _ = require('lodash'); - -var dbUsers = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('users'); - -// specify a query to limit the affected users (empty for all users): -var query = { - 'auth.timestamps.loggedin':{$gt:new Date('2016-05-01')} // remove when running migration a second time -}; - -// specify fields we are interested in to limit retrieved data (empty if we're not reading data): -var fields = { - 'migration': 1, - 'items.pets.Wolf-Veteran': 1, - 'items.pets.Tiger-Veteran': 1 -}; - -console.warn('Updating users...'); -var progressCount = 1000; -var count = 0; -dbUsers.findEach(query, fields, {batchSize:250}, function(err, user) { - if (err) { return exiting(1, 'ERROR! ' + err); } - if (!user) { - console.warn('All appropriate users found and modified.'); - return displayData(); - } - count++; - - // specify user data to change: - var set = {}; - if (user.migration !== migrationName) { - if (user.items.pets['Tiger-Veteran']) { - set = {'migration':migrationName, 'items.pets.Lion-Veteran':5}; - } else if (user.items.pets['Wolf-Veteran']) { - set = {'migration':migrationName, 'items.pets.Tiger-Veteran':5}; - } else { - set = {'migration':migrationName, 'items.pets.Wolf-Veteran':5}; - } - } - - dbUsers.update({_id:user._id}, {$set:set}); - - if (count%progressCount == 0) console.warn(count + ' ' + user._id); - if (user._id == authorUuid) console.warn(authorName + ' processed'); -}); - - -function displayData() { - console.warn('\n' + count + ' users processed\n'); - return exiting(0); -} - - -function exiting(code, msg) { - code = code || 0; // 0 = success - if (code && !msg) { msg = 'ERROR!'; } - if (msg) { - if (code) { console.error(msg); } - else { console.log( msg); } - } - process.exit(code); -} - diff --git a/migrations/api_v3/challenges.js b/migrations/api_v3/challenges.js deleted file mode 100644 index 009c205e33..0000000000 --- a/migrations/api_v3/challenges.js +++ /dev/null @@ -1,212 +0,0 @@ -// Migrate challenges collection to new schema (except for members) - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/challenges.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); -var fs = require('fs'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// Load new models -var NewChallenge = require('../../website/server/models/challenge').model; -var Tasks = require('../../website/server/models/task'); - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldChallengeCollection; - -var mongoDbNewInstance; -var newChallengeCollection; -var newTaskCollection; - -var BATCH_SIZE = 1000; - -var processedChallenges = 0; -var totoalProcessedTasks = 0; - -var newTasksIds = {}; // a map of old id -> [new id, challengeId] - -// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000 -var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID'); -var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID'); - -function processChallenges (afterId) { - var processedTasks = 0; - var lastChallenge = null; - var oldChallenges; - - var query = {}; - - if (BEFORE_CHALLENGE_ID) { - query._id = {$lte: BEFORE_CHALLENGE_ID}; - } - - if ((afterId || AFTER_CHALLENGE_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_CHALLENGE_ID) { - query._id.$gt = AFTER_CHALLENGE_ID; - } - - var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp(); - var batchInsertChallenges = newChallengeCollection.initializeUnorderedBulkOp(); - - console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`); - - return oldChallengeCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldChallengesR) { - oldChallenges = oldChallengesR; - - console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges and ${totoalProcessedTasks} tasks.`); - - if (oldChallenges.length === BATCH_SIZE) { - lastChallenge = oldChallenges[oldChallenges.length - 1]._id; - } - - oldChallenges.forEach(function (oldChallenge) { - var oldTasks = oldChallenge.habits.concat(oldChallenge.dailys).concat(oldChallenge.rewards).concat(oldChallenge.todos); - delete oldChallenge.habits; - delete oldChallenge.dailys; - delete oldChallenge.rewards; - delete oldChallenge.todos; - - var createdAt = oldChallenge.timestamp; - - oldChallenge.memberCount = oldChallenge.members.length; - if (oldChallenge.prize <= 0) oldChallenge.prize = 0; - if (!oldChallenge.name) oldChallenge.name = 'challenge name'; - if (!oldChallenge.shortName) oldChallenge.name = 'challenge-name'; - - if (!oldChallenge.group) throw new Error('challenge.group is required'); - if (!oldChallenge.leader) throw new Error('challenge.leader is required'); - - - if (oldChallenge.leader === '9') { - oldChallenge.leader = '00000000-0000-4000-9000-000000000000'; - } - - if (oldChallenge.group === 'habitrpg') { - oldChallenge.group = '00000000-0000-4000-A000-000000000000'; - } - - delete oldChallenge.id; - - var newChallenge = new NewChallenge(oldChallenge); - - newChallenge.createdAt = createdAt; - - oldTasks.forEach(function (oldTask) { - oldTask._id = uuid.v4(); - oldTask._legacyId = oldTask.id; // store the old task id - delete oldTask.id; - - oldTask.challenge = oldTask.challenge || {}; - oldTask.challenge.id = newChallenge._id; - - if (newTasksIds[oldTask._legacyId + '-' + newChallenge._id]) { - throw new Error('duplicate :('); - } else { - newTasksIds[oldTask._legacyId + '-' + newChallenge._id] = oldTask._id; - } - - oldTask.tags = _.map(oldTask.tags || {}, function (tagPresent, tagId) { - return tagPresent && tagId; - }).filter(function (tag) { - return tag !== false; - }); - - if (!oldTask.text) oldTask.text = 'task text'; // required - - oldTask.createdAt = oldTask.dateCreated; - - newChallenge.tasksOrder[`${oldTask.type}s`].push(oldTask._id); - if (oldTask.completed) oldTask.completed = false; - - var newTask = new Tasks[oldTask.type](oldTask); - - batchInsertTasks.insert(newTask.toObject()); - processedTasks++; - }); - - batchInsertChallenges.insert(newChallenge.toObject()); - }); - - console.log(`Saving ${oldChallenges.length} challenges and ${processedTasks} tasks.`); - - return Bluebird.all([ - batchInsertChallenges.execute(), - batchInsertTasks.execute(), - ]); - }) - .then(function () { - totoalProcessedTasks += processedTasks; - processedChallenges += oldChallenges.length; - - console.log(`Saved ${oldChallenges.length} challenges and their tasks.`); - - if (lastChallenge) { - return processChallenges(lastChallenge); - } else { - console.log('Writing newTasksIds.json...') - fs.writeFileSync('newTasksIds.json', JSON.stringify(newTasksIds, null, 4), 'utf8'); - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldChallengeCollection = mongoDbOldInstance.collection('challenges'); - - mongoDbNewInstance = newInstance; - newChallengeCollection = mongoDbNewInstance.collection('challenges'); - newTaskCollection = mongoDbNewInstance.collection('tasks'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - return processChallenges(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/api_v3/challengesMembers.js b/migrations/api_v3/challengesMembers.js deleted file mode 100644 index 971119831a..0000000000 --- a/migrations/api_v3/challengesMembers.js +++ /dev/null @@ -1,143 +0,0 @@ -// Migrate challenges members -// Run AFTER users migration - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/challengesMembers.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldChallengeCollection; - -var mongoDbNewInstance; -var newUserCollection; - -var BATCH_SIZE = 1000; - -var processedChallenges = 0; - -// Only process challenges that fall in a interval ie -> up to 0000-4000-0000-0000 -var AFTER_CHALLENGE_ID = nconf.get('AFTER_CHALLENGE_ID'); -var BEFORE_CHALLENGE_ID = nconf.get('BEFORE_CHALLENGE_ID'); - -function processChallenges (afterId) { - var processedTasks = 0; - var lastChallenge = null; - var oldChallenges; - - var query = {}; - - if (BEFORE_CHALLENGE_ID) { - query._id = {$lte: BEFORE_CHALLENGE_ID}; - } - - if ((afterId || AFTER_CHALLENGE_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_CHALLENGE_ID) { - query._id.$gt = AFTER_CHALLENGE_ID; - } - - console.log(`Executing challenges query.\nMatching challenges after ${afterId ? afterId : AFTER_CHALLENGE_ID} and before ${BEFORE_CHALLENGE_ID} (included).`); - - return oldChallengeCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldChallengesR) { - oldChallenges = oldChallengesR; - - var promises = []; - - console.log(`Processing ${oldChallenges.length} challenges. Already processed ${processedChallenges} challenges.`); - - if (oldChallenges.length === BATCH_SIZE) { - lastChallenge = oldChallenges[oldChallenges.length - 1]._id; - } - - oldChallenges.forEach(function (oldChallenge) { - // Tyler Renelle - oldChallenge.members.forEach(function (id, index) { - if (id === '9') { - oldChallenge.members[index] = '00000000-0000-4000-9000-000000000000'; - } - }); - - promises.push(newUserCollection.updateMany({ - _id: {$in: oldChallenge.members || []}, - }, { - $push: {challenges: oldChallenge._id}, - }, {multi: true})); - }); - - console.log(`Migrating members of ${oldChallenges.length} challenges.`); - - return Bluebird.all(promises); - }) - .then(function () { - processedChallenges += oldChallenges.length; - - console.log(`Migrated members of ${oldChallenges.length} challenges.`); - - if (lastChallenge) { - return processChallenges(lastChallenge); - } else { - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldChallengeCollection = mongoDbOldInstance.collection('challenges'); - - mongoDbNewInstance = newInstance; - newUserCollection = mongoDbNewInstance.collection('users'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - return processChallenges(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/api_v3/coupons.js b/migrations/api_v3/coupons.js deleted file mode 100644 index 64071faffe..0000000000 --- a/migrations/api_v3/coupons.js +++ /dev/null @@ -1,136 +0,0 @@ -// Migrate coupons collection to new schema - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/coupons.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// Load new models -var Coupon = require('../../website/server/models/coupon').model; - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldCouponCollection; - -var mongoDbNewInstance; -var newCouponCollection; - -var BATCH_SIZE = 1000; - -var processedCoupons = 0; - -// Only process coupons that fall in a interval ie -> up to 0000-4000-0000-0000 -var AFTER_COUPON_ID = nconf.get('AFTER_COUPON_ID'); -var BEFORE_COUPON_ID = nconf.get('BEFORE_COUPON_ID'); - -function processCoupons (afterId) { - var processedTasks = 0; - var lastCoupon = null; - var oldCoupons; - - var query = {}; - - if (BEFORE_COUPON_ID) { - query._id = {$lte: BEFORE_COUPON_ID}; - } - - if ((afterId || AFTER_COUPON_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_COUPON_ID) { - query._id.$gt = AFTER_COUPON_ID; - } - - var batchInsertCoupons = newCouponCollection.initializeUnorderedBulkOp(); - - console.log(`Executing coupons query.\nMatching coupons after ${afterId ? afterId : AFTER_COUPON_ID} and before ${BEFORE_COUPON_ID} (included).`); - - return oldCouponCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldCouponsR) { - oldCoupons = oldCouponsR; - - console.log(`Processing ${oldCoupons.length} coupons. Already processed ${processedCoupons} coupons.`); - - if (oldCoupons.length === BATCH_SIZE) { - lastCoupon = oldCoupons[oldCoupons.length - 1]._id; - } - - oldCoupons.forEach(function (oldCoupon) { - var newCoupon = new Coupon(oldCoupon); - - batchInsertCoupons.insert(newCoupon.toObject()); - }); - - console.log(`Saving ${oldCoupons.length} coupons.`); - - return batchInsertCoupons.execute(); - }) - .then(function () { - processedCoupons += oldCoupons.length; - - console.log(`Saved ${oldCoupons.length} coupons.`); - - if (lastCoupon) { - return processCoupons(lastCoupon); - } else { - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldCouponCollection = mongoDbOldInstance.collection('coupons'); - - mongoDbNewInstance = newInstance; - newCouponCollection = mongoDbNewInstance.collection('coupons'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - return processCoupons(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/api_v3/emailUnsubscriptions.js b/migrations/api_v3/emailUnsubscriptions.js deleted file mode 100644 index d90525db16..0000000000 --- a/migrations/api_v3/emailUnsubscriptions.js +++ /dev/null @@ -1,137 +0,0 @@ -// Migrate unsubscriptions collection to new schema - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/unsubscriptions.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// Load new models -var EmailUnsubscription = require('../../website/server/models/emailUnsubscription').model; - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldUnsubscriptionCollection; - -var mongoDbNewInstance; -var newUnsubscriptionCollection; - -var BATCH_SIZE = 1000; - -var processedUnsubscriptions = 0; - -// Only process unsubscriptions that fall in a interval ie -> up to 0000-4000-0000-0000 -var AFTER_UNSUBSCRIPTION_ID = nconf.get('AFTER_UNSUBSCRIPTION_ID'); -var BEFORE_UNSUBSCRIPTION_ID = nconf.get('BEFORE_UNSUBSCRIPTION_ID'); - -function processUnsubscriptions (afterId) { - var processedTasks = 0; - var lastUnsubscription = null; - var oldUnsubscriptions; - - var query = {}; - - if (BEFORE_UNSUBSCRIPTION_ID) { - query._id = {$lte: BEFORE_UNSUBSCRIPTION_ID}; - } - - if ((afterId || AFTER_UNSUBSCRIPTION_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_UNSUBSCRIPTION_ID) { - query._id.$gt = AFTER_UNSUBSCRIPTION_ID; - } - - var batchInsertUnsubscriptions = newUnsubscriptionCollection.initializeUnorderedBulkOp(); - - console.log(`Executing unsubscriptions query.\nMatching unsubscriptions after ${afterId ? afterId : AFTER_UNSUBSCRIPTION_ID} and before ${BEFORE_UNSUBSCRIPTION_ID} (included).`); - - return oldUnsubscriptionCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldUnsubscriptionsR) { - oldUnsubscriptions = oldUnsubscriptionsR; - - console.log(`Processing ${oldUnsubscriptions.length} unsubscriptions. Already processed ${processedUnsubscriptions} unsubscriptions.`); - - if (oldUnsubscriptions.length === BATCH_SIZE) { - lastUnsubscription = oldUnsubscriptions[oldUnsubscriptions.length - 1]._id; - } - - oldUnsubscriptions.forEach(function (oldUnsubscription) { - oldUnsubscription.email = oldUnsubscription.email.toLowerCase(); - var newUnsubscription = new EmailUnsubscription(oldUnsubscription); - - batchInsertUnsubscriptions.insert(newUnsubscription.toObject()); - }); - - console.log(`Saving ${oldUnsubscriptions.length} unsubscriptions.`); - - return batchInsertUnsubscriptions.execute(); - }) - .then(function () { - processedUnsubscriptions += oldUnsubscriptions.length; - - console.log(`Saved ${oldUnsubscriptions.length} unsubscriptions.`); - - if (lastUnsubscription) { - return processUnsubscriptions(lastUnsubscription); - } else { - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldUnsubscriptionCollection = mongoDbOldInstance.collection('emailunsubscriptions'); - - mongoDbNewInstance = newInstance; - newUnsubscriptionCollection = mongoDbNewInstance.collection('emailunsubscriptions'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - return processUnsubscriptions(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/api_v3/groups.js b/migrations/api_v3/groups.js deleted file mode 100644 index dfd0616e07..0000000000 --- a/migrations/api_v3/groups.js +++ /dev/null @@ -1,211 +0,0 @@ -/* - members are not stored anymore - invites are not stored anymore - - tavern id and leader must be updated -*/ - -// Migrate groups collection to new schema -// Run AFTER users migration - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/groups.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// Load new models -var NewGroup = require('../../website/server/models/group').model; - -var TAVERN_ID = require('../../website/server/models/group').TAVERN_ID; - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldGroupCollection; - -var mongoDbNewInstance; -var newGroupCollection; -var newUserCollection; - -var BATCH_SIZE = 1000; - -var processedGroups = 0; - -// Only process groups that fall in a interval ie -> up to 0000-4000-0000-0000 -var AFTER_GROUP_ID = nconf.get('AFTER_GROUP_ID'); -var BEFORE_GROUP_ID = nconf.get('BEFORE_GROUP_ID'); - -function processGroups (afterId) { - var processedTasks = 0; - var lastGroup = null; - var oldGroups; - - var query = {}; - - if (BEFORE_GROUP_ID) { - query._id = {$lte: BEFORE_GROUP_ID}; - } - - if ((afterId || AFTER_GROUP_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_GROUP_ID) { - query._id.$gt = AFTER_GROUP_ID; - } - - var batchInsertGroups = newGroupCollection.initializeUnorderedBulkOp(); - - console.log(`Executing groups query.\nMatching groups after ${afterId ? afterId : AFTER_GROUP_ID} and before ${BEFORE_GROUP_ID} (included).`); - - return oldGroupCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldGroupsR) { - oldGroups = oldGroupsR; - - var promises = []; - - console.log(`Processing ${oldGroups.length} groups. Already processed ${processedGroups} groups.`); - - if (oldGroups.length === BATCH_SIZE) { - lastGroup = oldGroups[oldGroups.length - 1]._id; - } - - oldGroups.forEach(function (oldGroup) { - if ((!oldGroup.privacy || oldGroup.privacy === 'private') && (!oldGroup.members || oldGroup.members.length === 0)) return; // delete empty private groups TODO must also delete challenges or this won't work - - oldGroup.members = oldGroup.members || []; - oldGroup.memberCount = oldGroup.members ? oldGroup.members.length : 0; - oldGroup.challengeCount = oldGroup.challenges ? oldGroup.challenges.length : 0; - - if (!oldGroup.balance <= 0) oldGroup.balance = 0; - if (!oldGroup.name) oldGroup.name = 'group name'; - if (!oldGroup.leaderOnly) oldGroup.leaderOnly = {}; - if (!oldGroup.leaderOnly.challenges) oldGroup.leaderOnly.challenges = false; - - // Tavern - if (oldGroup._id === 'habitrpg') { - oldGroup._id = TAVERN_ID; - oldGroup.leader = '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0'; // Siena Leslie - } - - if (!oldGroup.type) { - // throw new Error('group.type is required'); - oldGroup.type = 'guild'; - } - - if (!oldGroup.leader) { - if (oldGroup.members && oldGroup.members.length > 0) { - oldGroup.leader = oldGroup.members[0]; - } else { - throw new Error('group.leader is required and no member available!'); - } - } - - if (!oldGroup.privacy) { - // throw new Error('group.privacy is required'); - oldGroup.privacy = 'private'; - } - - var updateMembers = {}; - - if (oldGroup.type === 'guild') { - updateMembers.$push = {guilds: oldGroup._id}; - } else if (oldGroup.type === 'party') { - updateMembers.$set = {'party._id': oldGroup._id}; - } - - if (oldGroup.members) { - // Tyler Renelle - oldGroup.members.forEach(function (id, index) { - if (id === '9') { - oldGroup.members[index] = '00000000-0000-4000-9000-000000000000'; - } - }); - - promises.push(newUserCollection.updateMany({ - _id: {$in: oldGroup.members}, - }, updateMembers, {multi: true})); - } - - var newGroup = new NewGroup(oldGroup); - - batchInsertGroups.insert(newGroup.toObject()); - }); - - console.log(`Saving ${oldGroups.length} groups and migrating members to users collection.`); - - promises.push(batchInsertGroups.execute()); - return Bluebird.all(promises); - }) - .then(function () { - processedGroups += oldGroups.length; - - console.log(`Saved ${oldGroups.length} groups and migrated their members to the user collection.`); - - if (lastGroup) { - return processGroups(lastGroup); - } else { - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldGroupCollection = mongoDbOldInstance.collection('groups'); - - mongoDbNewInstance = newInstance; - newGroupCollection = mongoDbNewInstance.collection('groups'); - newUserCollection = mongoDbNewInstance.collection('users'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - // First delete the tavern group created by having required the group model - return newGroupCollection.deleteOne({_id: TAVERN_ID}); -}) -.then(function () { - return processGroups(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/api_v3/indexes.js b/migrations/api_v3/indexes.js deleted file mode 100644 index 07aaa21db8..0000000000 --- a/migrations/api_v3/indexes.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - DEFINE BEFORE MIGRATING - - tasks: userId OK (sparse?), challenge.id OK (sparse?), challenge.taskId OK (sparse?), type? completed? - users: - id & apiToken, OK - auth.facebook.emails.value OK -> unique and sparse?, - auth.facebook.id - unique and sparse, OK - auth.local.email - unique and sparse, OK - auth.local.lowerCaseUsername, OK - auth.local.username - unique OK - auth.local.username & auth.local.hashed_password?, - auth.timestamps.created?, OK - auth.timestamps.loggedin?, OK - backer.tier -1 OK - { "contributor.admin" : 1 , "contributor.level" : -1 , "backer.npc" : -1 , "profile.name" : 1} - { "contributor.admin" : 1.0} NO, see ^ - { "contributor.level" : 1.0} OK - { "contributor.level" : 1.0 , "purchased.plan.customerId" : 1.0} ? - NO { "flags.lastWeeklyRecap" : 1 , "_id" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1} - { "invitations.guilds.id" : 1} OK - { "invitations.party.id" : 1} OK - OK { "preferences.sleep" : 1 , "_id" : 1 , "flags.lastWeeklyRecap" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "preferences.emailNotifications.weeklyRecaps" : 1} - OK { "preferences.sleep" : 1 , "_id" : 1 , "lastCron" : 1 , "preferences.emailNotifications.importantAnnouncements" : 1 , "preferences.emailNotifications.unsubscribeFromAll" : 1 , "flags.recaptureEmailsPhase" : 1} - profile.name ? OK - { "purchased.plan.customerId" : 1.0} OK - { "purchased.plan.paymentMethod" : 1.0} OK - - guilds OK - party.id OK - challenges OK - challenges: - { "_id" : 1.0 , "__v" : 1.0} ? NO - { "_id" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} - { "group" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK - { "leader" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} OK - { "members" : 1.0 , "official" : -1.0 , "timestamp" : -1.0} ? NO - { "official" : -1 , "timestamp" : -1} ? - { "official" : -1 , "timestamp" : -1, "_id": 1} ? - groups: - { "_id" : 1 , "quest.key" : 1} ? - { "_id" : 1.0 , "__v" : 1.0} ? - { "_id" : 1.0 , "privacy" : 1.0 , "members" : 1.0} ? NO - { "members" : 1.0 , "type" : 1.0 , "memberCount" : -1.0} ? NO - { "members" : 1} ? NO - { "privacy" : 1.0 , "memberCount" : -1.0} ? - { "privacy" : 1.0} OK - { "type" : 1 , "privacy" : 1} ? - { "type" : 1.0 , "members" : 1.0} ? NO - { "type" : 1} ? OK - emailUnsubscriptions: email unique OK -*/ diff --git a/migrations/api_v3/users.js b/migrations/api_v3/users.js deleted file mode 100644 index 656b8f498a..0000000000 --- a/migrations/api_v3/users.js +++ /dev/null @@ -1,262 +0,0 @@ -// Migrate users collection to new schema -// This should run AFTER challenges migration - -// The console-stamp module must be installed (not included in package.json) - -// It requires two environment variables: MONGODB_OLD and MONGODB_NEW - -// Due to some big user profiles it needs more RAM than is allowed by default by v8 (arounf 1.7GB). -// Run the script with --max-old-space-size=4096 to allow up to 4GB of RAM -console.log('Starting migrations/api_v3/users.js.'); - -require('babel-register'); -require('babel-polyfill'); - -var Bluebird = require('bluebird'); -var MongoDB = require('mongodb'); -var nconf = require('nconf'); -var mongoose = require('mongoose'); -var _ = require('lodash'); -var uuid = require('uuid'); -var consoleStamp = require('console-stamp'); -var common = require('../../common'); -var moment = require('moment'); - -// Add timestamps to console messages -consoleStamp(console); - -// Initialize configuration -require('../../website/server/libs/api-v3/setupNconf')(); - -var MONGODB_OLD = nconf.get('MONGODB_OLD'); -var MONGODB_NEW = nconf.get('MONGODB_NEW'); - -var taskDefaults = common.taskDefaults; -var MongoClient = MongoDB.MongoClient; - -mongoose.Promise = Bluebird; // otherwise mongoose models won't work - -// Load new models -var NewUser = require('../../website/server/models/user').model; -var NewTasks = require('../../website/server/models/task'); - -// To be defined later when MongoClient connects -var mongoDbOldInstance; -var oldUserCollection; - -var mongoDbNewInstance; -var newUserCollection; -var newTaskCollection; - -var BATCH_SIZE = 1000; - -var processedUsers = 0; -var totoalProcessedTasks = 0; - -var challengeTaskWithMatchingId = 0; -var challengeTaskNoMatchingId = 0; - -// Load the new tasks ids for challenges tasks -var newTasksIds = require('./newTasksIds.json'); - -// Only process users that fall in a interval ie up to -> 0000-4000-0000-0000 -var AFTER_USER_ID = nconf.get('AFTER_USER_ID'); -var BEFORE_USER_ID = nconf.get('BEFORE_USER_ID'); - -function processUsers (afterId) { - var processedTasks = 0; - var lastUser = null; - var oldUsers; - - var now = new Date(); - - var query = {}; - - if (BEFORE_USER_ID) { - query._id = {$lte: BEFORE_USER_ID}; - } - - if ((afterId || AFTER_USER_ID) && !query._id) { - query._id = {}; - } - - if (afterId) { - query._id.$gt = afterId; - } else if (AFTER_USER_ID) { - query._id.$gt = AFTER_USER_ID; - } - - var batchInsertTasks = newTaskCollection.initializeUnorderedBulkOp(); - var batchInsertUsers = newUserCollection.initializeUnorderedBulkOp(); - - console.log(`Executing users query.\nMatching users after ${afterId ? afterId : AFTER_USER_ID} and before ${BEFORE_USER_ID} (included).`); - - return oldUserCollection - .find(query) - .sort({_id: 1}) - .limit(BATCH_SIZE) - .toArray() - .then(function (oldUsersR) { - oldUsers = oldUsersR; - - console.log(`Processing ${oldUsers.length} users. Already processed ${processedUsers} users and ${totoalProcessedTasks} tasks.`); - - if (oldUsers.length === BATCH_SIZE) { - lastUser = oldUsers[oldUsers.length - 1]._id; - } - - oldUsers.forEach(function (oldUser) { - var oldTasks = oldUser.habits.concat(oldUser.dailys).concat(oldUser.rewards).concat(oldUser.todos); - delete oldUser.habits; - delete oldUser.dailys; - delete oldUser.rewards; - delete oldUser.todos; - - delete oldUser.id; - - // spookDust -> spookySparkles - - if (oldUser.achievements && oldUser.achievements.spookDust) { - oldUser.achievements.spookySparkles = oldUser.achievements.spookDust; - delete oldUser.achievements.spookDust; - } - - if (oldUser.items && oldUser.items.special && oldUser.items.special.spookDust) { - oldUser.items.special.spookySparkles = oldUser.items.special.spookDust; - delete oldUser.items.special.spookDust; - } - - if (oldUser.stats && oldUser.stats.buffs && oldUser.stats.buffs.spookySparkles) { - oldUser.stats.buffs.spookySparkles = oldUser.stats.buffs.spookDust; - delete oldUser.stats.buffs.spookDust; - } - - // end spookDust -> spookySparkles - - oldUser.tags = oldUser.tags.map(function (tag) { - return { - id: tag.id, - name: tag.name || 'tag name', - challenge: tag.challenge, - }; - }); - - if (oldUser._id === '9') { // Tyler Renelle - oldUser._id = '00000000-0000-4000-9000-000000000000'; - } - - var newUser = new NewUser(oldUser); - var isSubscribed = newUser.isSubscribed(); - - oldTasks.forEach(function (oldTask) { - oldTask._id = uuid.v4(); // create a new unique uuid - oldTask.userId = newUser._id; - oldTask._legacyId = oldTask.id; // store the old task id - delete oldTask.id; - - oldTask.challenge = oldTask.challenge || {}; - if (oldTask.challenge.id) { - if (oldTask.challenge.broken) { - oldTask.challenge.taskId = oldTask._legacyId; - } else { - var newId = newTasksIds[oldTask._legacyId + '-' + oldTask.challenge.id]; - - // Challenges' tasks ids changed - if (!newId && !oldTask.challenge.broken) { - challengeTaskNoMatchingId++; - oldTask.challenge.taskId = oldTask._legacyId; - oldTask.challenge.broken = 'CHALLENGE_TASK_NOT_FOUND'; - } else { - challengeTaskWithMatchingId++; - oldTask.challenge.taskId = newId; - } - } - } - - // Delete old completed todos - if (oldTask.type === 'todo' && oldTask.completed && (!oldTask.challenge.id || oldTask.challenge.broken)) { - if (moment(now).subtract(isSubscribed ? 90 : 30, 'days').toDate() > moment(oldTask.dateCompleted).toDate()) { - return; - } - } - - oldTask.createdAt = oldTask.dateCreated; - - if (!oldTask.text) oldTask.text = 'task text'; // required - oldTask.tags = _.map(oldTask.tags, function (tagPresent, tagId) { - return tagPresent && tagId; - }).filter(function (tag) { - return tag !== false; - }); - - if (oldTask.type !== 'todo' || (oldTask.type === 'todo' && !oldTask.completed)) { - newUser.tasksOrder[`${oldTask.type}s`].push(oldTask._id); - } - - var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', '_legacyId', 'createdAt']; - // using mongoose models is too slow - if (oldTask.type === 'habit') { - oldTask = _.pick(oldTask, allTasksFields.concat(['history', 'up', 'down'])); - } else if (oldTask.type === 'daily') { - oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'history', 'frequency', 'everyX', 'startDate', 'repeat', 'streak'])); - } else if (oldTask.type === 'todo') { - oldTask = _.pick(oldTask, allTasksFields.concat(['completed', 'collapseChecklist', 'checklist', 'date', 'dateCompleted'])); - } else if (oldTask.type === 'reward') { - oldTask = _.pick(oldTask, allTasksFields); - } else { - throw new Error('Task with no or invalid type!'); - } - - batchInsertTasks.insert(taskDefaults(oldTask)); - processedTasks++; - }); - - batchInsertUsers.insert(newUser.toObject()); - }); - - console.log(`Saving ${oldUsers.length} users and ${processedTasks} tasks.`); - - return Bluebird.all([ - batchInsertUsers.execute(), - batchInsertTasks.execute(), - ]); - }) - .then(function () { - totoalProcessedTasks += processedTasks; - processedUsers += oldUsers.length; - - console.log(`Saved ${oldUsers.length} users and their tasks.`); - console.log('Challenges\' tasks no matching id: ', challengeTaskNoMatchingId); - console.log('Challenges\' tasks with matching id: ', challengeTaskWithMatchingId); - - if (lastUser) { - return processUsers(lastUser); - } else { - return console.log('Done!'); - } - }); -} - -// Connect to the databases -Bluebird.all([ - MongoClient.connect(MONGODB_OLD), - MongoClient.connect(MONGODB_NEW), -]) -.then(function (result) { - var oldInstance = result[0]; - var newInstance = result[1]; - - mongoDbOldInstance = oldInstance; - oldUserCollection = mongoDbOldInstance.collection('users'); - - mongoDbNewInstance = newInstance; - newUserCollection = mongoDbNewInstance.collection('users'); - newTaskCollection = mongoDbNewInstance.collection('tasks'); - - console.log(`Connected with MongoClient to ${MONGODB_OLD} and ${MONGODB_NEW}.`); - - return processUsers(); -}) -.catch(function (err) { - console.error(err.stack || err); -}); diff --git a/migrations/manual_password_reset.js b/migrations/manual_password_reset.js index 622e16913b..68b69cbbbe 100644 --- a/migrations/manual_password_reset.js +++ b/migrations/manual_password_reset.js @@ -7,7 +7,7 @@ nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../config.jso var Users = require('mongoskin').db(nconf.get("PRODUCTION_DB:URL"), nconf.get("PRODUCTION_DB").CREDS).collection('users'), async = require('async'), - utils = require('../website/server/utils'), + utils = require('../website/src/utils'), salt = utils.makeSalt(), newPassword = utils.makeSalt(), // use a salt as the new password too (they'll change it later) hashed_password = utils.encryptPassword(newPassword, salt); diff --git a/newrelic.js b/newrelic.js new file mode 100644 index 0000000000..0e8a550af7 --- /dev/null +++ b/newrelic.js @@ -0,0 +1,27 @@ +var nconf = require('nconf'); + +/** + * New Relic agent configuration. + * + * See lib/config.defaults.js in the agent distribution for a more complete + * description of configuration variables and their potential values. + */ +exports.config = { + /** + * Array of application names. + */ + app_name: nconf.get('NEW_RELIC_APP_NAME'), + /** + * Your New Relic license key. + */ + license_key: nconf.get('NEW_RELIC_LICENSE_KEY'), + ssl: false, + logging: { + /** + * Level at which to log. 'trace' is most useful to New Relic when diagnosing + * issues with the agent, 'info' and higher will impose the least overhead on + * production applications. + */ + level: 'info' + } +} diff --git a/package.json b/package.json index fa12cb4dbb..931b624c0b 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,32 @@ { - "name": "habitica", + "name": "habitrpg", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.0.0", - "main": "./website/server/index.js", + "version": "0.0.0-152", + "main": "./website/src/server.js", "dependencies": { - "accepts": "^1.3.2", "amazon-payments": "0.0.4", "amplitude": "^2.0.3", - "apidoc": "^0.16.0", "async": "^1.5.0", "aws-sdk": "^2.0.25", - "babel-plugin-transform-async-to-module-method": "^6.8.0", + "babel-plugin-syntax-async-functions": "^6.5.0", + "babel-plugin-transform-regenerator": "^6.6.0", "babel-polyfill": "^6.6.1", "babel-preset-es2015": "^6.6.0", "babel-register": "^6.6.0", "babelify": "^7.2.0", - "bluebird": "^3.3.5", "body-parser": "^1.15.0", "bower": "~1.3.12", "browserify": "~12.0.1", "compression": "^1.6.1", "connect-ratelimit": "0.0.7", "cookie-session": "^1.2.0", - "coupon-code": "^0.4.3", + "coupon-code": "~0.3.0", "csv-stringify": "^1.0.2", - "cwait": "^1.0.0", "domain-middleware": "~0.1.0", - "estraverse": "^4.1.1", - "express": "~4.13.3", - "express-csv": "~0.6.0", - "express-validator": "^2.18.0", + "express": "^4.13.4", "firebase": "^2.2.9", "firebase-token-generator": "^2.0.0", "glob": "^4.3.5", - "got": "^6.1.1", "grunt": "~0.4.1", "grunt-cli": "~0.1.9", "grunt-contrib-clean": "~0.6.0", @@ -62,34 +55,32 @@ "markdown-it": "^6.0.1", "merge-stream": "^1.0.0", "method-override": "^2.3.5", - "moment": "^2.13.0", - "mongoose": "^4.4.16", + "moment": "~2.10.6", + "mongoose": "~3.8.23", "mongoose-id-autoinc": "~2013.7.14-4", "morgan": "^1.7.0", "nconf": "~0.8.2", - "newrelic": "^1.27.2", - "nib": "^1.1.0", - "nodemailer": "^2.3.2", - "object-path": "^0.9.2", + "newrelic": "~1.26.1", + "uuid": "^2.0.1", + "nib": "~1.0.1", + "nodemailer": "^1.9.0", "pageres": "^4.1.1", "passport": "~0.2.1", "passport-facebook": "2.0.0", - "paypal-ipn": "3.0.0", + "paypal-ipn": "2.1.0", "paypal-rest-sdk": "^1.2.1", "pretty-data": "^0.40.0", "ps-tree": "^1.0.0", "push-notify": "^1.1.1", - "request": "~2.72.0", - "rimraf": "^2.4.3", - "run-sequence": "^1.1.4", + "q": "^1.4.1", + "request": "~2.44.0", "s3-upload-stream": "^1.0.6", "serve-favicon": "^2.3.0", "stripe": "^4.2.0", - "superagent": "^1.8.3", + "superagent": "~1.4.0", "swagger-node-express": "lefnire/swagger-node-express#habitrpg", "universal-analytics": "~0.3.2", - "uuid": "^2.0.1", - "validator": "^4.9.0", + "validator": "~4.2.1", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "winston": "^2.1.0" @@ -97,20 +88,16 @@ "private": true, "engines": { "node": "^4.3.1", - "npm": "^3.8.9" + "npm": "^2.14.9" }, "scripts": { "lint": "eslint .", "test": "npm run lint && gulp test", "test:api-v2:unit": "mocha test/server_side", "test:api-v2:integration": "mocha test/api/v2 --recursive", - "test:api-v3": "gulp test:api-v3", - "test:api-v3:unit": "gulp test:api-v3:unit", - "test:api-v3:integration": "gulp test:api-v3:integration", - "test:api-v3:integration:separate-server": "gulp test:api-v3:integration:separate-server", - "test:api-legacy": "istanbul cover -i \"website/server/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy", - "test:common": "mocha test/common --recursive", - "test:content": "mocha test/content --recursive", + "test:api-legacy": "istanbul cover -i \"website/src/**\" --dir coverage/api ./node_modules/mocha/bin/_mocha test/api-legacy", + "test:common": "mocha test/common", + "test:content": "mocha test/content", "test:karma": "karma start --single-run", "test:karma:watch": "karma start", "test:prepare:webdriver": "webdriver-manager update", @@ -129,7 +116,7 @@ "coveralls": "^2.11.2", "csv": "~0.3.6", "deep-diff": "~0.1.4", - "eslint": "^2.10.1", + "eslint": "^2.7.0", "eslint-config-habitrpg": "^1.0.0", "eslint-plugin-babel": "^3.0.0", "eslint-plugin-mocha": "^2.1.0", @@ -147,23 +134,16 @@ "mocha": "^2.3.3", "mongodb": "^2.0.46", "mongoskin": "~0.6.1", - "nock": "^2.17.0", "phantomjs": "^1.9", "protractor": "^3.1.1", - "require-again": "^1.0.1", "rewire": "^2.3.3", - "shelljs": "^0.7.0", + "rimraf": "^2.4.3", + "run-sequence": "^1.1.4", + "shelljs": "^0.4.0", "sinon": "^1.17.2", "sinon-chai": "^2.8.0", "superagent-defaults": "^0.1.13", "vinyl-source-stream": "^1.0.0", - "vinyl-transform": "^1.0.0", - "xml2js": "^0.4.16" - }, - "apidoc": { - "name": "habitica", - "title": "Habitica", - "version": "3.0.0", - "url": "https://habitica.com" + "vinyl-transform": "^1.0.0" } } diff --git a/tasks/gulp-apidoc.js b/tasks/gulp-apidoc.js deleted file mode 100644 index b8f65d2abd..0000000000 --- a/tasks/gulp-apidoc.js +++ /dev/null @@ -1,22 +0,0 @@ -import gulp from 'gulp'; -import clean from 'rimraf'; -import apidoc from 'apidoc'; - -const APIDOC_DEST_PATH = './website/build/apidoc'; -const APIDOC_SRC_PATH = './website/server'; -gulp.task('apidoc:clean', (done) => { - clean(APIDOC_DEST_PATH, done); -}); - -gulp.task('apidoc', ['apidoc:clean'], (done) => { - let result = apidoc.createDoc({ - src: APIDOC_SRC_PATH, - dest: APIDOC_DEST_PATH, - }); - - if (result === false) { - done(new Error('There was a problem generating apiDoc documentation.')) - } else { - done(); - } -}); diff --git a/tasks/gulp-build.js b/tasks/gulp-build.js index e660145d92..2fd34d0121 100644 --- a/tasks/gulp-build.js +++ b/tasks/gulp-build.js @@ -1,5 +1,4 @@ import gulp from 'gulp'; -import runSequence from 'run-sequence'; import babel from 'gulp-babel'; require('gulp-grunt')(gulp); @@ -12,7 +11,7 @@ gulp.task('build', () => { }); gulp.task('build:src', () => { - return gulp.src('website/server/**/*.js') + return gulp.src('website/src/**/*.js') .pipe(babel()) .pipe(gulp.dest('website/transpiled-babel/')); }); @@ -30,13 +29,9 @@ gulp.task('build:dev', ['browserify', 'prepare:staticNewStuff'], (done) => { }); gulp.task('build:dev:watch', ['build:dev'], () => { - gulp.watch(['website/client/**/*.styl', 'common/script/*']); + gulp.watch(['website/public/**/*.styl', 'common/script/*']); }); gulp.task('build:prod', ['browserify', 'build:server', 'prepare:staticNewStuff'], (done) => { - runSequence( - 'grunt-build:prod', - 'apidoc', - done - ); + gulp.start('grunt-build:prod', done); }); diff --git a/tasks/gulp-console.js b/tasks/gulp-console.js index 026d646cee..07512c6357 100644 --- a/tasks/gulp-console.js +++ b/tasks/gulp-console.js @@ -1,7 +1,8 @@ import mongoose from 'mongoose'; import autoinc from 'mongoose-id-autoinc'; -import logger from '../website/server/libs/api-v3/logger'; +import logging from '../website/src/libs/logging'; import nconf from 'nconf'; +import utils from '../website/src/libs/utils'; import repl from 'repl'; import gulp from 'gulp'; @@ -18,9 +19,11 @@ let improveRepl = (context) => { process.stdout.write('\u001B[2J\u001B[0;0f'); }}); - context.Challenge = require('../website/server/models/challenge').model; - context.Group = require('../website/server/models/group').model; - context.User = require('../website/server/models/user').model; + utils.setupConfig(); + + context.Challenge = require('../website/src/models/challenge').model; + context.Group = require('../website/src/models/group').model; + context.User = require('../website/src/models/user').model; var isProd = nconf.get('NODE_ENV') === 'production'; var mongooseOptions = !isProd ? {} : { @@ -33,7 +36,7 @@ let improveRepl = (context) => { mongooseOptions, function(err) { if (err) throw err; - logger.info('Connected with Mongoose'); + logging.info('Connected with Mongoose'); } ) ); diff --git a/tasks/gulp-newstuff.js b/tasks/gulp-newstuff.js index b6d8093ee5..16085e5c1c 100644 --- a/tasks/gulp-newstuff.js +++ b/tasks/gulp-newstuff.js @@ -4,7 +4,7 @@ import {writeFileSync} from 'fs'; gulp.task('prepare:staticNewStuff', () => { writeFileSync( - './website/client/new-stuff.html', + './website/public/new-stuff.html', jade.compileFile('./website/views/shared/new-stuff.jade')() ); }); diff --git a/tasks/gulp-start.js b/tasks/gulp-start.js index 51825f71ea..7cb842af00 100644 --- a/tasks/gulp-start.js +++ b/tasks/gulp-start.js @@ -9,7 +9,7 @@ gulp.task('nodemon', () => { nodemon({ script: pkg.main, ignore: [ - 'website/client/*', + 'website/public/*', 'website/views/*', 'common/dist/script/content/*', ] diff --git a/tasks/gulp-tests.js b/tasks/gulp-tests.js index 7db299a5c4..cd8622f387 100644 --- a/tasks/gulp-tests.js +++ b/tasks/gulp-tests.js @@ -9,20 +9,17 @@ import mongoose from 'mongoose'; import { exec } from 'child_process'; import psTree from 'ps-tree'; import gulp from 'gulp'; -import Bluebird from 'bluebird'; +import Q from 'q'; import runSequence from 'run-sequence'; import os from 'os'; import nconf from 'nconf'; -// TODO rewrite - const TEST_SERVER_PORT = 3003 let server; const TEST_DB_URI = nconf.get('TEST_DB_URI'); const API_V2_TEST_COMMAND = 'npm run test:api-v2:integration'; -const API_V3_TEST_COMMAND = 'npm run test:api-v3'; const LEGACY_API_TEST_COMMAND = 'npm run test:api-legacy'; const COMMON_TEST_COMMAND = 'npm run test:common'; const CONTENT_TEST_COMMAND = 'npm run test:content'; @@ -44,9 +41,9 @@ let testBin = (string, additionalEnvVariables = '') => { additionalEnvVariables = additionalEnvVariables.split(' ').join('&&set '); additionalEnvVariables = 'set ' + additionalEnvVariables + '&&'; } - return `set NODE_ENV=test&&${additionalEnvVariables}${string}`; + return `set NODE_ENV=testing&&${additionalEnvVariables}${string}`; } else { - return `NODE_ENV=test ${additionalEnvVariables} ${string}`; + return `NODE_ENV=testing ${additionalEnvVariables} ${string}`; } }; @@ -68,7 +65,7 @@ gulp.task('test:prepare:mongo', (cb) => { gulp.task('test:prepare:server', ['test:prepare:mongo'], () => { if (!server) { - server = exec(testBin(`node ./website/server/index.js`, `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT}`), (error, stdout, stderr) => { + server = exec(testBin('node ./website/src/server.js', `NODE_DB_URI=${TEST_DB_URI} PORT=${TEST_SERVER_PORT} `), (error, stdout, stderr) => { if (error) { throw `Problem with the server: ${error}`; } if (stderr) { console.error(stderr); } }); @@ -104,7 +101,7 @@ gulp.task('test:common:clean', (cb) => { }); gulp.task('test:common:watch', ['test:common:clean'], () => { - gulp.watch(['common/script/**/*', 'test/common/**/*'], ['test:common:clean']); + gulp.watch(['common/script/**', 'test/common/**'], ['test:common:clean']); }); gulp.task('test:common:safe', ['test:prepare:build'], (cb) => { @@ -219,7 +216,7 @@ gulp.task('test:api-legacy:watch', [ 'test:prepare:mongo', 'test:api-legacy:clean' ], () => { - gulp.watch(['website/server/**', 'test/api-legacy/**'], ['test:api-legacy:clean']); + gulp.watch(['website/src/**', 'test/api-legacy/**'], ['test:api-legacy:clean']); }); gulp.task('test:karma', ['test:prepare:build'], (cb) => { @@ -265,7 +262,7 @@ gulp.task('test:e2e', ['test:prepare', 'test:prepare:server'], (cb) => { ].map(exec); support.push(server); - Bluebird.all([ + Q.all([ awaitPort(TEST_SERVER_PORT), awaitPort(4444) ]).then(() => { @@ -286,7 +283,7 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => { 'npm run test:e2e:webdriver', ].map(exec); - Bluebird.all([ + Q.all([ awaitPort(TEST_SERVER_PORT), awaitPort(4444) ]).then(() => { @@ -309,16 +306,16 @@ gulp.task('test:e2e:safe', ['test:prepare', 'test:prepare:server'], (cb) => { }); }); -/*gulp.task('test:api-v2', ['test:prepare:server'], (done) => { - process.env.API_VERSION = 'v2'; +gulp.task('test:api-v2', ['test:prepare:server'], (done) => { + awaitPort(TEST_SERVER_PORT).then(() => { - runMochaTests('./test/api/v2/**//*.js', server, done) + runMochaTests('./test/api/v2/**/*.js', server, done) }); }); gulp.task('test:api-v2:watch', ['test:prepare:server'], () => { process.env.RUN_INTEGRATION_TEST_FOREVER = true; - gulp.watch(['website/server/**', 'test/api/v2/**'], ['test:api-v2']); + gulp.watch(['website/src/**', 'test/api/v2/**'], ['test:api-v2']); }); gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { @@ -327,118 +324,7 @@ gulp.task('test:api-v2:safe', ['test:prepare:server'], (done) => { testBin(API_V2_TEST_COMMAND), (err, stdout, stderr) => { testResults.push({ - suite: 'API V2 Specs\t', - pass: testCount(stdout, /(\d+) passing/), - fail: testCount(stderr, /(\d+) failing/), - pend: testCount(stdout, /(\d+) pending/) - }); - done(); - } - ); - pipe(runner); - }); -});*/ - -gulp.task('test:api-v2:integration', (done) => { - let runner = exec( - testBin('mocha test/api/v2 --recursive'), - {maxBuffer: 500*1024}, - (err, stdout, stderr) => done(err) - ) - - pipe(runner); -}); - -gulp.task('test:api-v3:unit', (done) => { - let runner = exec( - testBin('mocha test/api/v3/unit --recursive'), - (err, stdout, stderr) => done(err) - ) - - pipe(runner); -}); - -gulp.task('test:api-v3:unit:watch', () => { - gulp.watch(['website/server/libs/api-v3/*', 'test/api/v3/unit/**/*', 'website/server/controllers/**/*'], ['test:api-v3:unit']); -}); - -gulp.task('test:api-v3:integration', (done) => { - let runner = exec( - testBin('mocha test/api/v3/integration --recursive'), - {maxBuffer: 500*1024}, - (err, stdout, stderr) => done(err) - ) - - pipe(runner); -}); - -gulp.task('test:api-v3:integration:watch', () => { - gulp.watch(['website/server/controllers/api-v3/**/*', 'common/script/ops/*', 'website/server/libs/api-v3/*.js', - 'test/api/v3/integration/**/*'], ['test:api-v3:integration']); -}); - -gulp.task('test:api-v3:integration:separate-server', (done) => { - let runner = exec( - testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'), - {maxBuffer: 500*1024}, - (err, stdout, stderr) => done(err) - ) - - pipe(runner); -}); - -gulp.task('test', (done) => { - runSequence( - 'test:common', - 'test:karma', - 'test:api-v3:unit', - 'test:api-v3:integration', - 'test:api-v2:integration', - done - ); -}); - -gulp.task('test:api-v3', (done) => { - runSequence( - 'test:api-v3:unit', - 'test:api-v3:integration', - done - ); -}); - -// Old tests tasks -/* -gulp.task('test:api-v3', ['test:api-v3:unit', 'test:api-v3:integration']); - -gulp.task('test:api-v3:watch', ['test:api-v3:unit:watch', 'test:api-v3:integration:watch']); - -gulp.task('test:api-v3:unit', (done) => {*/ -// runMochaTests('./test/api/v3/unit/**/*.js', null, done) -/*}); - -gulp.task('test:api-v3:unit:watch', () => { - gulp.watch(['website/server/**', 'test/api/v3/unit/**'], ['test:api-v3:unit']); -}); - -gulp.task('test:api-v3:integration', ['test:prepare:server'], (done) => { - process.env.API_VERSION = 'v3'; - awaitPort(TEST_SERVER_PORT).then(() => {*/ -// runMochaTests('./test/api/v3/integration/**/*.js', server, done) -/* }); -}); - -gulp.task('test:api-v3:integration:watch', ['test:prepare:server'], () => { - process.env.RUN_INTEGRATION_TEST_FOREVER = true; - gulp.watch(['website/server/**', 'test/api/v3/integration/**'], ['test:api-v3:integration']); -}); - -gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => { - awaitPort(TEST_SERVER_PORT).then(() => { - let runner = exec( - testBin(API_V3_TEST_COMMAND), - (err, stdout, stderr) => { - testResults.push({ - suite: 'API V3 Specs\t', + suite: 'API Specs\t', pass: testCount(stdout, /(\d+) passing/), fail: testCount(stdout, /(\d+) failing/), pend: testCount(stdout, /(\d+) pending/) @@ -452,14 +338,13 @@ gulp.task('test:api-v3:safe', ['test:prepare:server'], (done) => { gulp.task('test:all', (done) => { runSequence( - //'test:e2e:safe', - //'test:common:safe', - //'test:content:safe', + 'test:e2e:safe', + 'test:common:safe', + 'test:content:safe', // 'test:server_side:safe', - //'test:karma:safe', - //'test:api-legacy:safe', - //'test:api-v2:safe', - 'test:api-v3:safe', + 'test:karma:safe', + 'test:api-legacy:safe', + 'test:api-v2:safe', done); }); @@ -500,4 +385,4 @@ gulp.task('test', ['test:all'], () => { console.log('\n\x1b[36mThanks for helping keep Habitica clean!\x1b[0m'); process.exit(); } -});*/ +}); diff --git a/tasks/taskHelper.js b/tasks/taskHelper.js index b83faf6af2..408978efd4 100644 --- a/tasks/taskHelper.js +++ b/tasks/taskHelper.js @@ -1,9 +1,9 @@ -import { exec } from 'child_process'; -import psTree from 'ps-tree'; -import nconf from 'nconf'; -import net from 'net'; -import Bluebird from 'bluebird'; -import { post } from 'superagent'; +import { exec } from 'child_process'; +import psTree from 'ps-tree'; +import nconf from 'nconf'; +import net from 'net'; +import Q from 'q'; +import { post } from 'superagent'; import { sync as glob } from 'glob'; import Mocha from 'mocha'; import { resolve } from 'path'; @@ -43,24 +43,25 @@ export function kill(proc) { * has fully spun up. Optionally provide a maximum number of seconds to wait * before failing. */ -export function awaitPort (port, max=60) { - return new Bluebird((reject, resolve) => { - let socket, timeout, interval; +export function awaitPort(port, max=60) { + let socket, timeout, interval; + let deferred = Q.defer(); - timeout = setTimeout(() => { + timeout = setTimeout(() => { + clearInterval(interval); + deferred.reject(`Timed out after ${max} seconds`); + }, max * 1000); + + interval = setInterval(() => { + socket = net.connect({port: port}, () => { clearInterval(interval); - reject(`Timed out after ${max} seconds`); - }, max * 1000); + clearTimeout(timeout); + socket.destroy(); + deferred.resolve(); + }).on('error', () => { socket.destroy }); + }, 1000); - interval = setInterval(() => { - socket = net.connect({port: port}, () => { - clearInterval(interval); - clearTimeout(timeout); - socket.destroy(); - resolve(); - }).on('error', () => { socket.destroy }); - }, 1000); - }); + return deferred.promise }; /* diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000000..8115753727 --- /dev/null +++ b/test/README.md @@ -0,0 +1,5 @@ +We need to clean up this directory. The *real* tests are in spec/ mock/ e2e/ and api.mocha.coffee. We want to: + +1. Move all old / deprecated tests from casper, test2, etc into spec, mock, e2e +1. Remove dependency of api.mocha.coffee on Derby, port it to Mongoose +1. Add better test-coverage diff --git a/test/api-legacy/api-helper.js b/test/api-legacy/api-helper.js index 1b994a80a8..9e61773211 100644 --- a/test/api-legacy/api-helper.js +++ b/test/api-legacy/api-helper.js @@ -1,4 +1,3 @@ -require('babel-core/register'); var path, superagentDefaults; superagentDefaults = require("superagent-defaults"); @@ -6,8 +5,6 @@ superagentDefaults = require("superagent-defaults"); global.request = superagentDefaults(); global.mongoose = require("mongoose"); -var Bluebird = require('bluebird'); -mongoose.Promise = Bluebird; global.moment = require("moment"); @@ -17,7 +14,7 @@ global._ = require("lodash"); global.shared = require("../../common"); -global.User = require("../../website/server/models/user").model; +global.User = require("../../website/src/models/user").model; global.chai = require("chai"); diff --git a/test/api-legacy/challenges.js b/test/api-legacy/challenges.js index b09754bc5c..2f264655ef 100644 --- a/test/api-legacy/challenges.js +++ b/test/api-legacy/challenges.js @@ -1,10 +1,10 @@ var Challenge, Group, app; -app = require("../../website/server/server"); +app = require("../../website/src/server"); -Group = require("../../website/server/models/group").model; +Group = require("../../website/src/models/group").model; -Challenge = require("../../website/server/models/challenge").model; +Challenge = require("../../website/src/models/challenge").model; describe("Challenges", function() { var challenge, group, updateTodo; diff --git a/test/api-legacy/chat.js b/test/api-legacy/chat.js index 1f2dbab487..89b32b0c54 100644 --- a/test/api-legacy/chat.js +++ b/test/api-legacy/chat.js @@ -2,9 +2,9 @@ var Group, app, diff; diff = require("deep-diff"); -Group = require("../../website/server/models/group").model; +Group = require("../../website/src/models/group").model; -app = require("../../website/server/server"); +app = require("../../website/src/server"); describe("Chat", function() { var chat, group; diff --git a/test/api-legacy/coupons.js b/test/api-legacy/coupons.js index 4d4e366473..31d840de61 100644 --- a/test/api-legacy/coupons.js +++ b/test/api-legacy/coupons.js @@ -1,8 +1,8 @@ var Coupon, app, makeSudoUser; -app = require("../../website/server/server"); +app = require("../../website/src/server"); -Coupon = require("../../website/server/models/coupon").model; +Coupon = require("../../website/src/models/coupon").model; makeSudoUser = function(usr, cb) { return registerNewUser(function() { diff --git a/test/api-legacy/inAppPurchases.js b/test/api-legacy/inAppPurchases.js index 96809a7717..d4182e437c 100644 --- a/test/api-legacy/inAppPurchases.js +++ b/test/api-legacy/inAppPurchases.js @@ -1,12 +1,12 @@ var app, iapMock, inApp, rewire, sinon; -app = require('../../website/server/server'); +app = require('../../website/src/server'); rewire = require('rewire'); sinon = require('sinon'); -inApp = rewire('../../website/server/controllers/payments/iap'); +inApp = rewire('../../website/src/controllers/payments/iap'); iapMock = {}; diff --git a/test/api-legacy/party.js b/test/api-legacy/party.js index 2f98b67bde..8ea8188713 100644 --- a/test/api-legacy/party.js +++ b/test/api-legacy/party.js @@ -2,9 +2,9 @@ var Group, app, diff; diff = require("deep-diff"); -Group = require("../../website/server/models/group").model; +Group = require("../../website/src/models/group").model; -app = require("../../website/server/server"); +app = require("../../website/src/server"); describe("Party", function() { return context("Quests", function() { diff --git a/test/api-legacy/pushNotifications.js b/test/api-legacy/pushNotifications.js index 7f98ddccfa..ce3d9672f8 100644 --- a/test/api-legacy/pushNotifications.js +++ b/test/api-legacy/pushNotifications.js @@ -1,6 +1,6 @@ var app, rewire, sinon; -app = require("../../website/server/server"); +app = require("../../website/src/server"); rewire = require('rewire'); @@ -21,7 +21,7 @@ describe("Push-Notifications", function() { }); context("Challenges", function() { var challengeMock, challenges, userMock; - challenges = rewire("../../website/server/controllers/api-v2/challenges"); + challenges = rewire("../../website/src/controllers/api-v2/challenges"); challenges.__set__('pushNotify', pushSpy); challengeMock = { findById: function(arg, cb) { @@ -76,7 +76,7 @@ describe("Push-Notifications", function() { context("Groups", function() { var groups, recipient; recipient = null; - groups = rewire("../../website/server/controllers/api-v2/groups"); + groups = rewire("../../website/src/controllers/api-v2/groups"); groups.__set__('pushNotify', pushSpy); before(function(done) { return registerNewUser(function(err, _user) { @@ -304,7 +304,7 @@ describe("Push-Notifications", function() { }); context("sending gems from balance", function() { var members; - members = rewire("../../website/server/controllers/api-v2/members"); + members = rewire("../../website/src/controllers/api-v2/members"); members.sendMessage = function() { return true; }; @@ -342,7 +342,7 @@ describe("Push-Notifications", function() { }); return describe("Purchases", function() { var membersMock, payments; - payments = rewire("../../website/server/controllers/payments"); + payments = rewire("../../website/src/controllers/payments"); payments.__set__('pushNotify', pushSpy); membersMock = { sendMessage: function() { diff --git a/test/api-legacy/score.js b/test/api-legacy/score.js index af31a4f334..8c6906acfb 100644 --- a/test/api-legacy/score.js +++ b/test/api-legacy/score.js @@ -1,4 +1,4 @@ -require("../../website/server/server"); +require("../../website/src/server"); describe("Score", function() { before(function(done) { diff --git a/test/api-legacy/subscriptions.js b/test/api-legacy/subscriptions.js index 73b55039df..9d8624cb73 100644 --- a/test/api-legacy/subscriptions.js +++ b/test/api-legacy/subscriptions.js @@ -1,8 +1,8 @@ var app, payments; -payments = require("../../website/server/controllers/payments"); +payments = require("../../website/src/controllers/payments"); -app = require("../../website/server/server"); +app = require("../../website/src/server"); describe("Subscriptions", function() { before(function(done) { diff --git a/test/api-legacy/todos.js b/test/api-legacy/todos.js index b72ea57223..5285847fd1 100644 --- a/test/api-legacy/todos.js +++ b/test/api-legacy/todos.js @@ -1,4 +1,4 @@ -require("../../website/server/server"); +require("../../website/src/server"); describe("Todos", function() { before(function(done) { diff --git a/test/api/README.md b/test/api/README.md index 1f5c98abb3..81e7c2d54b 100644 --- a/test/api/README.md +++ b/test/api/README.md @@ -1,7 +1,5 @@ # So you want to write API integration tests? -@TODO rewrite - That's great! This README will serve as a quick primer for style conventions and practices for these tests. ## What is this? @@ -75,7 +73,7 @@ POST-groups_id_leave.test.js To mitigate [callback hell](http://callbackhell.com/) :imp:, we've written a helper method to generate a user object that can make http requests that [return promises](https://babeljs.io/docs/learn-es2015/#promises). This makes it very easy to chain together commands. All you need to do to make a subsequent request is return another promise and then call `.then((result) => {})` on the surrounding block, like so: ```js -it('does something', () => { +it('does something', () => { let user; return generateUser().then((_user) => { // We return the initial promise so this test can be run asyncronously @@ -99,7 +97,7 @@ it('does something', () => { If the test is simple, you can use the [chai-as-promised](http://chaijs.com/plugins/chai-as-promised) `return expect(somePromise).to.eventually` syntax to make your assertion. ```js -it('makes the party creator the leader automatically', () => { +it('makes the party creator the leader automatically', () => { return expect(user.post('/groups', { type: 'party', })).to.eventually.have.deep.property('leader._id', user._id); @@ -109,7 +107,7 @@ it('makes the party creator the leader automatically', () => { If the test is checking that the request returns an error, use the `.eventually.be.rejected.and.eql` syntax. ```js -it('returns an error', () => { +it('returns an error', () => { return expect(user.get('/groups/id-of-a-party-that-user-does-not-belong-to')) .to.eventually.be.rejected.and.eql({ code: 404, diff --git a/test/api/v2/groups/GET-groups.test.js b/test/api/v2/groups/GET-groups.test.js index d941b2533e..203fdd0acc 100644 --- a/test/api/v2/groups/GET-groups.test.js +++ b/test/api/v2/groups/GET-groups.test.js @@ -3,15 +3,12 @@ import { generateUser, resetHabiticaDB, } from '../../../helpers/api-integration/v2'; -import { - TAVERN_ID, -} from '../../../../website/server/models/group'; describe('GET /groups', () => { const NUMBER_OF_PUBLIC_GUILDS = 3; + const NUMBER_OF_USERS_GUILDS = 2; let user; - let leader; before(async () => { // Set up a world with a mixture of public and private guilds @@ -19,7 +16,7 @@ describe('GET /groups', () => { await resetHabiticaDB(); user = await generateUser(); - leader = await generateUser({ balance: 10 }); + let leader = await generateUser({ balance: 10 }); await generateGroup(leader, { name: 'public guild - is member', @@ -71,7 +68,7 @@ describe('GET /groups', () => { await expect(user.get('/groups', null, {type: 'tavern'})) .to.eventually.have.a.lengthOf(1) .and.to.have.deep.property('[0]') - .and.to.have.property('_id', TAVERN_ID); + .and.to.have.property('_id', 'habitrpg'); }); }); @@ -93,8 +90,8 @@ describe('GET /groups', () => { context('guilds passed in as query', () => { it('returns all guilds user is a part of ', async () => { - await expect(leader.get('/groups', null, {type: 'guilds'})) - .to.eventually.have.a.lengthOf(4); + await expect(user.get('/groups', null, {type: 'guilds'})) + .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_GUILDS); }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_invite.test.js b/test/api/v2/groups/POST-groups_id_invite.test.js index ef768dbf7e..5acf6e637a 100644 --- a/test/api/v2/groups/POST-groups_id_invite.test.js +++ b/test/api/v2/groups/POST-groups_id_invite.test.js @@ -27,8 +27,8 @@ describe('POST /groups/:id/invite', () => { await inviter.post(`/groups/${group._id}/invite`, { uuids: [invitee._id], }); - group = await inviter.get(`/groups/${group._id}`); - expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; + await group.sync(); + expect(group.invites).to.include(invitee._id); }); }); }); @@ -53,8 +53,8 @@ describe('POST /groups/:id/invite', () => { await inviter.post(`/groups/${group._id}/invite`, { uuids: [invitee._id], }); - group = await inviter.get(`/groups/${group._id}`); - expect(_.find(group.invites, {_id: invitee._id})._id).to.exists; + await group.sync(); + expect(group.invites).to.include(invitee._id); }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_join.test.js b/test/api/v2/groups/POST-groups_id_join.test.js index cf216354a0..250fd4bbf2 100644 --- a/test/api/v2/groups/POST-groups_id_join.test.js +++ b/test/api/v2/groups/POST-groups_id_join.test.js @@ -30,8 +30,9 @@ describe('POST /groups/:id/join', () => { it(`allows user to join a ${groupType}`, async () => { await invitee.post(`/groups/${group._id}/join`); - group = await invitee.get(`/groups/${group._id}`); - expect(_.find(group.members, {_id: invitee._id})._id).to.exists; + await group.sync(); + + expect(group.members).to.include(invitee._id); }); }); }); @@ -77,9 +78,9 @@ describe('POST /groups/:id/join', () => { it('allows user to join a public guild', async () => { await user.post(`/groups/${group._id}/join`); - group = await user.get(`/groups/${group._id}`); + await group.sync(); - expect(_.find(group.members, {_id: user._id})._id).to.exists; + expect(group.members).to.include(user._id); }); }); @@ -102,9 +103,9 @@ describe('POST /groups/:id/join', () => { it('makes the joining user the leader', async () => { await user.post(`/groups/${group._id}/join`); - group = await user.get(`/groups/${group._id}`); + await group.sync(); - expect(group.leader._id).to.eql(user._id); + await expect(group.leader).to.eql(user._id); }); }); }); diff --git a/test/api/v2/groups/POST-groups_id_leave.test.js b/test/api/v2/groups/POST-groups_id_leave.test.js index f6511e941c..ef5c4a8ac2 100644 --- a/test/api/v2/groups/POST-groups_id_leave.test.js +++ b/test/api/v2/groups/POST-groups_id_leave.test.js @@ -28,9 +28,9 @@ describe('POST /groups/:id/leave', () => { it('leaves the group', async () => { await user.post(`/groups/${group._id}/leave`); - await user.sync(); + await group.sync(); - expect(user.guilds).to.not.include(group._id); + expect(group.members).to.not.include(user._id); }); }); diff --git a/test/api/v2/user/DELETE-user.test.js b/test/api/v2/user/DELETE-user.test.js index 8d28a07b78..db0eeca1c8 100644 --- a/test/api/v2/user/DELETE-user.test.js +++ b/test/api/v2/user/DELETE-user.test.js @@ -4,11 +4,7 @@ import { generateGroup, generateUser, } from '../../../helpers/api-integration/v2'; -import { - find, - map, -} from 'lodash'; -import Bluebird from 'bluebird'; +import { find } from 'lodash'; describe('DELETE /user', () => { let user; @@ -23,18 +19,6 @@ describe('DELETE /user', () => { })).to.eventually.eql(false); }); - it('deletes the user\'s tasks', async () => { - // gets the user's todos ids - let ids = user.todos.map(todo => todo._id); - expect(ids.length).to.be.above(0); // make sure the user has some task to delete - - await user.del('/user'); - - await Bluebird.all(map(ids, id => { - return expect(checkExistence('tasks', id)).to.eventually.eql(false); - })); - }); - context('user has active subscription', () => { it('does not delete account'); }); diff --git a/test/api/v2/user/batch-update/POST-user_batch-update.test.js b/test/api/v2/user/batch-update/POST-user_batch-update.test.js index f64cbc7f7b..0b1f244ff7 100644 --- a/test/api/v2/user/batch-update/POST-user_batch-update.test.js +++ b/test/api/v2/user/batch-update/POST-user_batch-update.test.js @@ -31,7 +31,7 @@ describe('POST /user/batch-update', () => { }); }); - xcontext('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing' + context('development only operations', () => { // These tests will fail if your NODE_ENV is set to 'development' instead of 'testing' let protectedOperations = { 'Add Ten Gems': 'addTenGems', 'Add Hourglass': 'addHourglass', diff --git a/test/api/v2/user/pushDevice/POST-pushDevice.test.js b/test/api/v2/user/pushDevice/POST-pushDevice.test.js index c0b5e9be72..97cfc4dbb9 100644 --- a/test/api/v2/user/pushDevice/POST-pushDevice.test.js +++ b/test/api/v2/user/pushDevice/POST-pushDevice.test.js @@ -2,7 +2,7 @@ import { generateUser, } from '../../../../helpers/api-integration/v2'; -xdescribe('POST /user/pushDevice', () => { +describe('POST /user/pushDevice', () => { let user; beforeEach(async () => { diff --git a/test/api/v2/user/tasks/GET-tasks.test.js b/test/api/v2/user/tasks/GET-tasks.test.js index 1506f4c2f0..7e6f847cbb 100644 --- a/test/api/v2/user/tasks/GET-tasks.test.js +++ b/test/api/v2/user/tasks/GET-tasks.test.js @@ -6,7 +6,14 @@ describe('GET /user/tasks/', () => { let user; beforeEach(async () => { - return generateUser().then((_user) => { + return generateUser({ + dailys: [ + {text: 'daily', type: 'daily'}, + {text: 'daily', type: 'daily'}, + {text: 'daily', type: 'daily'}, + {text: 'daily', type: 'daily'}, + ], + }).then((_user) => { user = _user; }); }); @@ -14,7 +21,7 @@ describe('GET /user/tasks/', () => { it('gets all tasks', async () => { return user.get('/user/tasks/').then((tasks) => { expect(tasks).to.be.an('array'); - expect(tasks.length).to.equal(1); + expect(tasks.length).to.be.greaterThan(3); let task = tasks[0]; expect(task.id).to.exist; diff --git a/test/api/v2/user/tasks/POST-clear-completed.test.js b/test/api/v2/user/tasks/POST-clear-completed.test.js deleted file mode 100644 index 40e6cbd5cf..0000000000 --- a/test/api/v2/user/tasks/POST-clear-completed.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v2'; - -describe('POST /user/tasks/clear-completed', () => { - let user; - - beforeEach(async () => { - return generateUser().then((_user) => { - user = _user; - }); - }); - - it('removes all completed todos', async () => { - let toComplete = await user.post('/user/tasks', { - type: 'todo', - text: 'done', - }); - - await user.post(`/user/tasks/${toComplete._id}/up`); - - let todos = await user.get('/user/tasks?type=todo'); - let uncomplete = await user.post('/user/tasks/clear-completed'); - expect(todos.length).to.equal(uncomplete.length + 1); - }); -}); diff --git a/test/api/v2/user/tasks/POST-tasks.test.js b/test/api/v2/user/tasks/POST-tasks.test.js index 4fe4c9f5ea..5f43224803 100644 --- a/test/api/v2/user/tasks/POST-tasks.test.js +++ b/test/api/v2/user/tasks/POST-tasks.test.js @@ -35,7 +35,7 @@ describe('POST /user/tasks', () => { }); }); - xit('does not create a task with an id that already exists', async () => { + it('does not create a task with an id that already exists', async () => { let todo = user.todos[0]; return expect(user.post('/user/tasks', { diff --git a/test/api/v2/user/tasks/PUT-tasks_id.test.js b/test/api/v2/user/tasks/PUT-tasks_id.test.js index 3bb9d8e9c6..037322a6b7 100644 --- a/test/api/v2/user/tasks/PUT-tasks_id.test.js +++ b/test/api/v2/user/tasks/PUT-tasks_id.test.js @@ -33,13 +33,13 @@ describe('PUT /user/tasks/:id', () => { text: 'new text', notes: 'new notes', value: 10000, - priority: 0.1, + priority: 0.5, attribute: 'str', }).then((updatedTask) => { expect(updatedTask.text).to.eql('new text'); expect(updatedTask.notes).to.eql('new notes'); expect(updatedTask.value).to.eql(10000); - expect(updatedTask.priority).to.eql(0.1); + expect(updatedTask.priority).to.eql(0.5); expect(updatedTask.attribute).to.eql('str'); }); }); diff --git a/test/api/v3/README.md b/test/api/v3/README.md deleted file mode 100644 index ad9b55a32c..0000000000 --- a/test/api/v3/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# How to run tests: - -1. `npm test` is equivalent to `gulp test:api-v3` which will run, in order, `gulp lint`, `gulp test:api-v3:unit` and `gulp test:api-v3:integration`. If one of these fails, the whole `npm test` command blocks and fails. Each of these commands can also be run as a standalone command. -2. To run the server and the integrations tests in two different terminals (to better inspect the output in the server) run `npm start` in one and `npm test:api-v3:integration:separate-server` in the other diff --git a/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js b/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js deleted file mode 100644 index 45dc91758a..0000000000 --- a/test/api/v3/integration/challenges/DELETE-challenges_challengeId.test.js +++ /dev/null @@ -1,94 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - sleep, - checkExistence, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /challenges/:challengeId', () => { - it('returns error when challengeId is not a valid UUID', async () => { - let user = await generateUser(); - await expect(user.del('/challenges/test')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when challengeId is not for a valid challenge', async () => { - let user = await generateUser(); - - await expect(user.del(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - context('Deleting a valid challenge', () => { - let groupLeader; - let group; - let challenge; - let taskText = 'A challenge task text'; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup(); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - - challenge = await generateChallenge(groupLeader, group); - - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: taskText}, - ]); - - await challenge.sync(); - }); - - it('returns an error when user doesn\'t have permissions to delete the challenge', async () => { - let user = await generateUser(); - - await expect(user.del(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyLeaderDeleteChal'), - }); - }); - - it('deletes challenge', async () => { - await groupLeader.del(`/challenges/${challenge._id}`); - - await sleep(0.5); - - await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false); - }); - - it('refunds gems to group leader', async () => { - let oldBalance = (await groupLeader.sync()).balance; - - await groupLeader.del(`/challenges/${challenge._id}`); - - await sleep(0.5); - - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4); - }); - - it('sets broken and doesn\'t set winner flags for user\'s challenge tasks', async () => { - await groupLeader.del(`/challenges/${challenge._id}`); - - await sleep(0.5); - - let tasks = await groupLeader.get('/tasks/user'); - let testTask = _.find(tasks, (task) => { - return task.text === taskText; - }); - - expect(testTask.challenge.broken).to.eql('CHALLENGE_DELETED'); - expect(testTask.challenge.winner).to.be.null; - }); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js deleted file mode 100644 index 33b329300a..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId.test.js +++ /dev/null @@ -1,142 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - generateChallenge, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /challenges/:challengeId', () => { - it('fails if challenge doesn\'t exists', async () => { - let user = await generateUser(); - await expect(user.get(`/challenges/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - context('public guild', () => { - let groupLeader; - let group; - let challenge; - let user; - - beforeEach(async () => { - user = await generateUser(); - - let populatedGroup = await createAndPopulateGroup({ - groupDetails: {type: 'guild', privacy: 'public'}, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - - challenge = await generateChallenge(groupLeader, group); - }); - - it('should return challenge data', async () => { - let chal = await user.get(`/challenges/${challenge._id}`); - expect(chal.memberCount).to.equal(challenge.memberCount); - expect(chal.name).to.equal(challenge.name); - expect(chal._id).to.equal(challenge._id); - - expect(chal.leader).to.eql({ - _id: groupLeader._id, - id: groupLeader._id, - profile: {name: groupLeader.profile.name}, - }); - expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); - }); - }); - - context('private guild', () => { - let groupLeader; - let group; - let challenge; - let members; - let user; - - beforeEach(async () => { - user = await generateUser(); - - let populatedGroup = await createAndPopulateGroup({ - groupDetails: {type: 'guild', privacy: 'private'}, - members: 1, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - members = populatedGroup.members; - - challenge = await generateChallenge(groupLeader, group); - await members[0].post(`/challenges/${challenge._id}/join`); - }); - - it('fails if user doesn\'t have access to the challenge', async () => { - await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('should return challenge data', async () => { - let chal = await members[0].get(`/challenges/${challenge._id}`); - expect(chal.name).to.equal(challenge.name); - expect(chal._id).to.equal(challenge._id); - - expect(chal.leader).to.eql({ - _id: groupLeader._id, - id: groupLeader._id, - profile: {name: groupLeader.profile.name}, - }); - expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); - }); - }); - - context('party', () => { - let groupLeader; - let group; - let challenge; - let members; - let user; - - beforeEach(async () => { - user = await generateUser(); - - let populatedGroup = await createAndPopulateGroup({ - groupDetails: {type: 'party'}, - members: 1, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - members = populatedGroup.members; - - challenge = await generateChallenge(groupLeader, group); - await members[0].post(`/challenges/${challenge._id}/join`); - }); - - it('fails if user doesn\'t have access to the challenge', async () => { - await expect(user.get(`/challenges/${challenge._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('should return challenge data', async () => { - let chal = await members[0].get(`/challenges/${challenge._id}`); - expect(chal.name).to.equal(challenge.name); - expect(chal._id).to.equal(challenge._id); - - expect(chal.leader).to.eql({ - _id: groupLeader._id, - id: groupLeader.id, - profile: {name: groupLeader.profile.name}, - }); - expect(chal.group).to.eql(_.pick(group, ['_id', 'id', 'name', 'type', 'privacy'])); - }); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js deleted file mode 100644 index 2b98af9579..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_export_csv.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - generateChallenge, - translate as t, - sleep, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /challenges/:challengeId/export/csv', () => { - let groupLeader; - let group; - let challenge; - let members; - let user; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 3, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - members = populatedGroup.members; - - challenge = await generateChallenge(groupLeader, group); - await members[0].post(`/challenges/${challenge._id}/join`); - await members[1].post(`/challenges/${challenge._id}/join`); - await members[2].post(`/challenges/${challenge._id}/join`); - - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: 'Task 1'}, - {type: 'todo', text: 'Task 2'}, - ]); - await sleep(0.5); // Make sure tasks are synced to the users - await members[0].sync(); - await members[1].sync(); - await members[2].sync(); - }); - - it('fails if challenge doesn\'t exists', async () => { - user = await generateUser(); - user.get('/user'); - await expect(user.get(`/challenges/${generateUUID()}/export/csv`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('fails if user doesn\'t have access to the challenge', async () => { - user = await generateUser(); - user.get('/user'); - - await expect(user.get(`/challenges/${challenge._id}/export/csv`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('should return a valid CSV file with export data', async () => { - let res = await members[0].get(`/challenges/${challenge._id}/export/csv`); - let sortedMembers = _.sortBy([members[0], members[1], members[2], groupLeader], '_id'); - let splitRes = res.split('\n'); - - expect(splitRes[0]).to.equal('UUID,name,Task,Value,Notes,Task,Value,Notes'); - expect(splitRes[1]).to.equal(`${sortedMembers[0]._id},${sortedMembers[0].profile.name},habit:Task 1,0,,todo:Task 2,0,`); - expect(splitRes[2]).to.equal(`${sortedMembers[1]._id},${sortedMembers[1].profile.name},habit:Task 1,0,,todo:Task 2,0,`); - expect(splitRes[3]).to.equal(`${sortedMembers[2]._id},${sortedMembers[2].profile.name},habit:Task 1,0,,todo:Task 2,0,`); - expect(splitRes[4]).to.equal(`${sortedMembers[3]._id},${sortedMembers[3].profile.name},habit:Task 1,0,,todo:Task 2,0,`); - expect(splitRes[5]).to.equal(''); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js deleted file mode 100644 index 5117dcd556..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js +++ /dev/null @@ -1,109 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /challenges/:challengeId/members', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('validates optional req.query.lastId to be an UUID', async () => { - await expect(user.get(`/challenges/${generateUUID()}/members?lastId=invalidUUID`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('fails if challenge doesn\'t exists', async () => { - await expect(user.get(`/challenges/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('fails if user doesn\'t have access to the challenge', async () => { - let group = await generateGroup(user); - let challenge = await generateChallenge(user, group); - let anotherUser = await generateUser(); - - await expect(anotherUser.get(`/challenges/${challenge._id}/members`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('works with challenges belonging to public guild', async () => { - let leader = await generateUser({balance: 4}); - let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); - let challenge = await generateChallenge(leader, group); - let res = await user.get(`/challenges/${challenge._id}/members`); - expect(res[0]).to.eql({ - _id: leader._id, - id: leader._id, - profile: {name: leader.profile.name}, - }); - expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); - expect(res[0].profile).to.have.all.keys(['name']); - }); - - it('populates only some fields', async () => { - let anotherUser = await generateUser({balance: 3}); - let group = await generateGroup(anotherUser, {type: 'guild', privacy: 'public', name: generateUUID()}); - let challenge = await generateChallenge(anotherUser, group); - let res = await user.get(`/challenges/${challenge._id}/members`); - expect(res[0]).to.eql({ - _id: anotherUser._id, - id: anotherUser._id, - profile: {name: anotherUser.profile.name}, - }); - expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); - expect(res[0].profile).to.have.all.keys(['name']); - }); - - it('returns only first 30 members', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - - let usersToGenerate = []; - for (let i = 0; i < 31; i++) { - usersToGenerate.push(generateUser({challenges: [challenge._id]})); - } - await Promise.all(usersToGenerate); - - let res = await user.get(`/challenges/${challenge._id}/members`); - expect(res.length).to.equal(30); - res.forEach(member => { - expect(member).to.have.all.keys(['_id', 'id', 'profile']); - expect(member.profile).to.have.all.keys(['name']); - }); - }); - - it('supports using req.query.lastId to get more members', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - - let usersToGenerate = []; - for (let i = 0; i < 57; i++) { - usersToGenerate.push(generateUser({challenges: [challenge._id]})); - } - let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader) - let expectedIds = [user._id].concat(generatedUsers.map(generatedUser => generatedUser._id)); - - let res = await user.get(`/challenges/${challenge._id}/members`); - expect(res.length).to.equal(30); - let res2 = await user.get(`/challenges/${challenge._id}/members?lastId=${res[res.length - 1]._id}`); - expect(res2.length).to.equal(28); - - let resIds = res.concat(res2).map(member => member._id); - expect(resIds).to.eql(expectedIds.sort()); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js deleted file mode 100644 index c47e1d4ad8..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_members_memberId.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import { - generateUser, - generateChallenge, - generateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /challenges/:challengeId/members/:memberId', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('validates req.params.memberId to be an UUID', async () => { - await expect(user.get(`/challenges/invalidUUID/members/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('validates req.params.memberId to be an UUID', async () => { - await expect(user.get(`/challenges/${generateUUID()}/members/invalidUUID`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('fails if member doesn\'t exists', async () => { - let userId = generateUUID(); - await expect(user.get(`/challenges/${generateUUID()}/members/${userId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId}), - }); - }); - - it('fails if challenge doesn\'t exists', async () => { - let member = await generateUser(); - await expect(user.get(`/challenges/${generateUUID()}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('fails if user doesn\'t have access to the challenge', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - let anotherUser = await generateUser(); - let member = await generateUser(); - await expect(anotherUser.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('fails if member is not part of the challenge', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - let member = await generateUser(); - await expect(user.get(`/challenges/${challenge._id}/members/${member._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeMemberNotFound'), - }); - }); - - it('works with challenges belonging to a public guild', async () => { - let groupLeader = await generateUser({balance: 4}); - let group = await generateGroup(groupLeader, {type: 'guild', privacy: 'public', name: generateUUID()}); - let challenge = await generateChallenge(groupLeader, group); - let taskText = 'Test Text'; - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]); - - let memberProgress = await user.get(`/challenges/${challenge._id}/members/${groupLeader._id}`); - expect(memberProgress).to.have.all.keys(['_id', 'id', 'profile', 'tasks']); - expect(memberProgress.profile).to.have.all.keys(['name']); - expect(memberProgress.tasks.length).to.equal(1); - }); - - it('returns the member tasks for the challenges', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: 'Test Text'}]); - - let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`); - let chalTasks = await user.get(`/tasks/challenge/${challenge._id}`); - expect(memberProgress.tasks.length).to.equal(chalTasks.length); - expect(memberProgress.tasks[0].challenge.id).to.equal(challenge._id); - expect(memberProgress.tasks[0].challenge.taskId).to.equal(chalTasks[0]._id); - }); - - it('returns the tasks without the tags', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let challenge = await generateChallenge(user, group); - let taskText = 'Test Text'; - await user.post(`/tasks/challenge/${challenge._id}`, [{type: 'habit', text: taskText}]); - - let memberProgress = await user.get(`/challenges/${challenge._id}/members/${user._id}`); - expect(memberProgress.tasks[0]).not.to.have.key('tags'); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js deleted file mode 100644 index 275fe798f5..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js +++ /dev/null @@ -1,118 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET challenges/group/:groupId', () => { - context('Public Guild', () => { - let publicGuild, user, nonMember, challenge, challenge2; - - before(async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestGuild', - type: 'guild', - privacy: 'public', - }, - }); - - publicGuild = group; - user = groupLeader; - - nonMember = await generateUser(); - - challenge = await generateChallenge(user, group); - challenge2 = await generateChallenge(user, group); - }); - - it('should return group challenges for non member with populated leader', async () => { - let challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`); - - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - }); - - it('should return group challenges for member with populated leader', async () => { - let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); - - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - }); - }); - - context('Private Guild', () => { - let privateGuild, user, nonMember, challenge, challenge2; - - before(async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, - }); - - privateGuild = group; - user = groupLeader; - - nonMember = await generateUser(); - - challenge = await generateChallenge(user, group); - challenge2 = await generateChallenge(user, group); - }); - - it('should prevent non-member from seeing challenges', async () => { - await expect(nonMember.get(`/challenges/groups/${privateGuild._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('should return group challenges for member with populated leader', async () => { - let challenges = await user.get(`/challenges/groups/${privateGuild._id}`); - - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: privateGuild.leader._id, - id: privateGuild.leader._id, - profile: {name: user.profile.name}, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: privateGuild.leader._id, - id: privateGuild.leader._id, - profile: {name: user.profile.name}, - }); - }); - }); -}); diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js deleted file mode 100644 index ecb9f55383..0000000000 --- a/test/api/v3/integration/challenges/GET-challenges_user.test.js +++ /dev/null @@ -1,132 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET challenges/user', () => { - let user, member, nonMember, challenge, challenge2, publicGuild; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestGuild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - user = groupLeader; - publicGuild = group; - member = members[0]; - nonMember = await generateUser(); - - challenge = await generateChallenge(user, group); - challenge2 = await generateChallenge(user, group); - }); - - it('should return challenges user has joined', async () => { - await nonMember.post(`/challenges/${challenge._id}/join`); - - let challenges = await nonMember.get('/challenges/user'); - - let foundChallenge = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge).to.exist; - expect(foundChallenge.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - }); - - it('should return challenges user has created', async () => { - let challenges = await user.get('/challenges/user'); - - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge1.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge2.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - }); - - it('should return challenges in user\'s group', async () => { - let challenges = await member.get('/challenges/user'); - - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge1.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge2.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - }); - - it('should not return challenges user doesn\'t have access to', async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, - }); - - let privateChallenge = await generateChallenge(groupLeader, group); - - let challenges = await nonMember.get('/challenges/user'); - - let foundChallenge = _.find(challenges, { _id: privateChallenge._id }); - expect(foundChallenge).to.not.exist; - }); -}); diff --git a/test/api/v3/integration/challenges/POST-challenges.test.js b/test/api/v3/integration/challenges/POST-challenges.test.js deleted file mode 100644 index a97578eef5..0000000000 --- a/test/api/v3/integration/challenges/POST-challenges.test.js +++ /dev/null @@ -1,308 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /challenges', () => { - it('returns error when group is empty', async () => { - let user = await generateUser(); - - await expect(user.post('/challenges')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when groupId is not for a valid group', async () => { - let user = await generateUser(); - - await expect(user.post('/challenges', { - group: generateUUID(), - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns error when creating a challenge in the tavern with no prize', async () => { - let user = await generateUser(); - - await expect(user.post('/challenges', { - group: 'habitrpg', - prize: 0, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('tavChalsMinPrize'), - }); - }); - - it('returns error when creating a challenge in a public guild and you are not a member of it', async () => { - let user = await generateUser(); - let { group } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }); - - await expect(user.post('/challenges', { - group: group._id, - prize: 4, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('mustBeGroupMember'), - }); - }); - - context('Creating a challenge for a valid group', () => { - let groupLeader; - let group; - let groupMember; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 1, - leaderDetails: { - balance: 3, - }, - groupDetails: { - type: 'guild', - leaderOnly: { - challenges: true, - }, - }, - }); - - groupLeader = await populatedGroup.groupLeader.sync(); - group = populatedGroup.group; - groupMember = populatedGroup.members[0]; - }); - - it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => { - await expect(groupMember.post('/challenges', { - group: group._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyGroupLeaderChal'), - }); - }); - - it('returns an error when non-leader member creates a challenge in leaderOnly group', async () => { - await expect(groupMember.post('/challenges', { - group: group._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyGroupLeaderChal'), - }); - }); - - it('allows non-leader member to create a challenge', async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 1, - }); - - group = populatedGroup.group; - groupMember = populatedGroup.members[0]; - - let chal = await groupMember.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - }); - - expect(chal.leader).to.eql({ - _id: groupMember._id, - profile: {name: groupMember.profile.name}, - }); - }); - - it('doesn\'t take gems from user or group when challenge has no prize', async () => { - let oldUserBalance = groupLeader.balance; - let oldGroupBalance = group.balance; - - await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - prize: 0, - }); - - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance); - await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance); - }); - - it('returns error when user and group can\'t pay prize', async () => { - await expect(groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - prize: 20, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantAfford'), - }); - }); - - it('takes prize out of group if it has sufficient funds', async () => { - let oldUserBalance = groupLeader.balance; - let oldGroupBalance = group.balance; - let prize = 4; - - await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - prize, - }); - - await expect(group.sync()).to.eventually.have.property('balance', oldGroupBalance - prize / 4); - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance); - }); - - it('takes prize out of both group and user if group doesn\'t have enough', async () => { - let oldUserBalance = groupLeader.balance; - let prize = 8; - - await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - prize, - }); - - await expect(group.sync()).to.eventually.have.property('balance', 0); - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - (prize / 4 - 1)); - }); - - it('takes prize out of user if group has no balance', async () => { - let oldUserBalance = groupLeader.balance; - let prize = 8; - - await group.update({ balance: 0}); - await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - prize, - }); - - await expect(group.sync()).to.eventually.have.property('balance', 0); - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldUserBalance - prize / 4); - }); - - it('increases challenge count of group', async () => { - let oldChallengeCount = group.challengeCount; - - await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - }); - - await expect(group.sync()).to.eventually.have.property('challengeCount', oldChallengeCount + 1); - }); - - it('sets challenge as official if created by admin and official flag is set', async () => { - await groupLeader.update({ - contributor: { - admin: true, - }, - }); - - let challenge = await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - official: true, - }); - - expect(challenge.official).to.eql(true); - }); - - it('doesn\'t set challenge as official if official flag is set by non-admin', async () => { - let challenge = await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - official: true, - }); - - expect(challenge.official).to.eql(false); - }); - - it('returns an error when challenge validation fails; doesn\'s save user or group', async () => { - let oldChallengeCount = group.challengeCount; - let oldUserBalance = groupLeader.balance; - let oldUserChallenges = groupLeader.challenges; - let oldGroupBalance = group.balance; - - await expect(groupLeader.post('/challenges', { - group: group._id, - prize: 8, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Challenge validation failed', - }); - - group = await group.sync(); - groupLeader = await groupLeader.sync(); - - expect(group.challengeCount).to.eql(oldChallengeCount); - expect(group.balance).to.eql(oldGroupBalance); - expect(groupLeader.balance).to.eql(oldUserBalance); - expect(groupLeader.challenges).to.eql(oldUserChallenges); - }); - - it('sets all properites of the challenge as passed', async () => { - let name = 'Test Challenge'; - let shortName = 'TC Label'; - let description = 'Test Description'; - let prize = 4; - - let challenge = await groupLeader.post('/challenges', { - group: group._id, - name, - shortName, - description, - prize, - }); - - expect(challenge.leader).to.eql({ - _id: groupLeader._id, - profile: {name: groupLeader.profile.name}, - }); - expect(challenge.name).to.eql(name); - expect(challenge.shortName).to.eql(shortName); - expect(challenge.description).to.eql(description); - expect(challenge.official).to.eql(false); - expect(challenge.group).to.eql({ - _id: group._id, - privacy: group.privacy, - name: group.name, - type: group.type, - }); - expect(challenge.memberCount).to.eql(1); - expect(challenge.prize).to.eql(prize); - }); - - it('adds challenge to creator\'s challenges', async () => { - let challenge = await groupLeader.post('/challenges', { - group: group._id, - name: 'Test Challenge', - shortName: 'TC Label', - }); - - await expect(groupLeader.sync()).to.eventually.have.property('challenges').to.include(challenge._id); - }); - }); -}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js deleted file mode 100644 index c55c0e0b49..0000000000 --- a/test/api/v3/integration/challenges/POST-challenges_challengeId_join.test.js +++ /dev/null @@ -1,127 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /challenges/:challengeId/join', () => { - it('returns error when challengeId is not a valid UUID', async () => { - let user = await generateUser({ balance: 1}); - - await expect(user.post('/challenges/test/join')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when challengeId is not for a valid challenge', async () => { - let user = await generateUser({ balance: 1}); - - await expect(user.post(`/challenges/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - context('Joining a valid challenge', () => { - let groupLeader; - let group; - let challenge; - let authorizedUser; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 1, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - authorizedUser = populatedGroup.members[0]; - - challenge = await generateChallenge(groupLeader, group); - }); - - it('returns an error when user doesn\'t have permissions to access the challenge', async () => { - let unauthorizedUser = await generateUser(); - - await expect(unauthorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('returns challenge data', async () => { - let res = await authorizedUser.post(`/challenges/${challenge._id}/join`); - - expect(res.group).to.eql({ - _id: group._id, - privacy: group.privacy, - name: group.name, - type: group.type, - }); - expect(res.leader).to.eql({ - _id: groupLeader._id, - id: groupLeader._id, - profile: {name: groupLeader.profile.name}, - }); - expect(res.name).to.equal(challenge.name); - }); - - it('adds challenge to user challenges', async () => { - await authorizedUser.post(`/challenges/${challenge._id}/join`); - - await authorizedUser.sync(); - - expect(authorizedUser).to.have.property('challenges').to.include(challenge._id); - }); - - it('returns error when user has already joined the challenge', async () => { - await authorizedUser.post(`/challenges/${challenge._id}/join`); - - await expect(authorizedUser.post(`/challenges/${challenge._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInChallenge'), - }); - }); - - it('increases memberCount of challenge', async () => { - let oldMemberCount = challenge.memberCount; - - await authorizedUser.post(`/challenges/${challenge._id}/join`); - - await challenge.sync(); - - expect(challenge).to.have.property('memberCount', oldMemberCount + 1); - }); - - it('syncs challenge tasks to joining user', async () => { - let taskText = 'A challenge task text'; - - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: taskText}, - ]); - - await authorizedUser.post(`/challenges/${challenge._id}/join`); - let tasks = await authorizedUser.get('/tasks/user'); - let tasksTexts = tasks.map((task) => { - return task.text; - }); - - expect(tasksTexts).to.include(taskText); - }); - - it('adds challenge tag to user tags', async () => { - let userTagsLength = (await authorizedUser.get('/tags')).length; - - await authorizedUser.post(`/challenges/${challenge._id}/join`); - - await expect(authorizedUser.get('/tags')).to.eventually.have.length(userTagsLength + 1); - }); - }); -}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js deleted file mode 100644 index 9694b263a9..0000000000 --- a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js +++ /dev/null @@ -1,123 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /challenges/:challengeId/leave', () => { - it('returns error when challengeId is not a valid UUID', async () => { - let user = await generateUser(); - - await expect(user.post('/challenges/test/leave')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when challengeId is not for a valid challenge', async () => { - let user = await generateUser(); - - await expect(user.post(`/challenges/${generateUUID()}/leave`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - context('Leaving a valid challenge', () => { - let groupLeader; - let group; - let challenge; - let notInChallengeUser; - let leavingUser; - let taskText; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 2, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - leavingUser = populatedGroup.members[0]; - notInChallengeUser = populatedGroup.members[1]; - - challenge = await generateChallenge(groupLeader, group); - - taskText = 'A challenge task text'; - - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: taskText}, - ]); - - await leavingUser.post(`/challenges/${challenge._id}/join`); - - await challenge.sync(); - }); - - it('returns an error when user doesn\'t have permissions to view the challenge', async () => { - let unauthorizedUser = await generateUser(); - - await expect(unauthorizedUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('returns an error when user isn\'t a member of the challenge', async () => { - await expect(notInChallengeUser.post(`/challenges/${challenge._id}/leave`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('challengeMemberNotFound'), - }); - }); - - it('removes challenge from user challenges', async () => { - await leavingUser.post(`/challenges/${challenge._id}/leave`); - - await leavingUser.sync(); - - expect(leavingUser).to.have.property('challenges').to.not.include(challenge._id); - }); - - it('decreases memberCount of challenge', async () => { - let oldMemberCount = challenge.memberCount; - - await leavingUser.post(`/challenges/${challenge._id}/leave`); - - await challenge.sync(); - - expect(challenge).to.have.property('memberCount', oldMemberCount - 1); - }); - - it('unlinks challenge tasks from leaving user when remove-all is passed', async () => { - await leavingUser.post(`/challenges/${challenge._id}/leave`, { - keep: 'remove-all', - }); - let tasks = await leavingUser.get('/tasks/user'); - let tasksTexts = tasks.map((task) => { - return task.text; - }); - - expect(tasksTexts).to.not.include(taskText); - }); - - it('doesn\'t unlink challenge tasks from leaving user when remove-all isn\'t passed', async () => { - await leavingUser.post(`/challenges/${challenge._id}/leave`, { - keep: 'test', - }); - - let tasks = await leavingUser.get('/tasks/user'); - let testTask = _.find(tasks, (task) => { - return task.text === taskText; - }); - - expect(testTask).to.not.be.undefined; - expect(testTask.challenge).to.be.undefined; - }); - }); -}); diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js deleted file mode 100644 index 98dfc20926..0000000000 --- a/test/api/v3/integration/challenges/POST-challenges_challengeId_winner_winnerId.test.js +++ /dev/null @@ -1,139 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - sleep, - checkExistence, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /challenges/:challengeId/winner/:winnerId', () => { - it('returns error when challengeId is not a valid UUID', async () => { - let user = await generateUser(); - - await expect(user.post(`/challenges/test/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when winnerId is not a valid UUID', async () => { - let user = await generateUser(); - - await expect(user.post(`/challenges/${generateUUID()}/selectWinner/test`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns error when challengeId is not for a valid challenge', async () => { - let user = await generateUser(); - - await expect(user.post(`/challenges/${generateUUID()}/selectWinner/${user._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - context('Selecting winner for a valid challenge', () => { - let groupLeader; - let group; - let challenge; - let winningUser; - let taskText = 'A challenge task text'; - - beforeEach(async () => { - let populatedGroup = await createAndPopulateGroup({ - members: 1, - }); - - groupLeader = populatedGroup.groupLeader; - group = populatedGroup.group; - winningUser = populatedGroup.members[0]; - - challenge = await generateChallenge(groupLeader, group, { - prize: 1, - }); - - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: taskText}, - ]); - - await winningUser.post(`/challenges/${challenge._id}/join`); - - await challenge.sync(); - }); - - it('returns an error when user doesn\'t have permissions to select winner', async () => { - await expect(winningUser.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyLeaderDeleteChal'), - }); - }); - - it('returns an error when winning user isn\'t part of the challenge', async () => { - let notInChallengeUser = await generateUser(); - - await expect(groupLeader.post(`/challenges/${challenge._id}/selectWinner/${notInChallengeUser._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('winnerNotFound', {userId: notInChallengeUser._id}), - }); - }); - - it('deletes challenge after winner is selected', async () => { - await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); - - await sleep(0.5); - - await expect(checkExistence('challenges', challenge._id)).to.eventually.equal(false); - }); - - it('adds challenge to winner\'s achievements', async () => { - await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); - - await sleep(0.5); - - await expect(winningUser.sync()).to.eventually.have.deep.property('achievements.challenges').to.include(challenge.name); - }); - - it('gives winner gems as reward', async () => { - let oldBalance = winningUser.balance; - - await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); - - await sleep(0.5); - - await expect(winningUser.sync()).to.eventually.have.property('balance', oldBalance + challenge.prize / 4); - }); - - it('doesn\'t refund gems to group leader', async () => { - let oldBalance = (await groupLeader.sync()).balance; - - await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); - - await sleep(0.5); - - await expect(groupLeader.sync()).to.eventually.have.property('balance', oldBalance); - }); - - it('sets broken and winner flags for user\'s challenge tasks', async () => { - await groupLeader.post(`/challenges/${challenge._id}/selectWinner/${winningUser._id}`); - - await sleep(0.5); - - let tasks = await winningUser.get('/tasks/user'); - let testTask = _.find(tasks, (task) => { - return task.text === taskText; - }); - - expect(testTask.challenge.broken).to.eql('CHALLENGE_CLOSED'); - expect(testTask.challenge.winner).to.eql(winningUser.profile.name); - }); - }); -}); diff --git a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js b/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js deleted file mode 100644 index 1fdcc7a5a2..0000000000 --- a/test/api/v3/integration/challenges/PUT-challenges_challengeId.test.js +++ /dev/null @@ -1,85 +0,0 @@ -import { - generateUser, - generateChallenge, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('PUT /challenges/:challengeId', () => { - let privateGuild, user, nonMember, challenge, member; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, - members: 1, - }); - - privateGuild = group; - user = groupLeader; - - nonMember = await generateUser(); - member = members[0]; - - challenge = await generateChallenge(user, group); - await member.post(`/challenges/${challenge._id}/join`); - }); - - it('fails if the user can\'t view the challenge', async () => { - await expect(nonMember.put(`/challenges/${challenge._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('should only allow the leader or an admin to update the challenge', async () => { - await expect(member.put(`/challenges/${challenge._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyLeaderUpdateChal'), - }); - }); - - it('only updates allowed fields', async () => { - let res = await user.put(`/challenges/${challenge._id}`, { - // ignored - prize: 33, - group: 'blabla', - memberCount: 33, - tasksOrder: 'new order', - official: true, - shortName: 'new short name', - - // applied - name: 'New Challenge Name', - description: 'New challenge description.', - leader: member._id, - }); - - expect(res.prize).to.equal(0); - expect(res.group).to.eql({ - _id: privateGuild._id, - privacy: privateGuild.privacy, - name: privateGuild.name, - type: privateGuild.type, - }); - expect(res.memberCount).to.equal(2); - expect(res.tasksOrder).not.to.equal('new order'); - expect(res.official).to.equal(false); - expect(res.shortName).not.to.equal('new short name'); - - expect(res.leader).to.eql({ - _id: member._id, - id: member._id, - profile: {name: member.profile.name}, - }); - expect(res.name).to.equal('New Challenge Name'); - expect(res.description).to.equal('New challenge description.'); - }); -}); diff --git a/test/api/v3/integration/chat/DELETE-chat_id.test.js b/test/api/v3/integration/chat/DELETE-chat_id.test.js deleted file mode 100644 index 5d1f73f867..0000000000 --- a/test/api/v3/integration/chat/DELETE-chat_id.test.js +++ /dev/null @@ -1,81 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /groups/:groupId/chat/:chatId', () => { - let groupWithChat, message, user, userThatDidNotCreateChat, admin; - - before(async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - }); - - groupWithChat = group; - user = groupLeader; - message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); - message = message.message; - userThatDidNotCreateChat = await generateUser(); - admin = await generateUser({'contributor.admin': true}); - }); - - context('Chat errors', () => { - it('returns an error is message does not exist', async () => { - let fakeChatId = generateUUID(); - await expect(user.del(`/groups/${groupWithChat._id}/chat/${fakeChatId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatNotFound'), - }); - }); - - it('returns an error when user does not have permission to delete', async () => { - await expect(userThatDidNotCreateChat.del(`/groups/${groupWithChat._id}/chat/${message.id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyCreatorOrAdminCanDeleteChat'), - }); - }); - }); - - context('Chat success', () => { - let nextMessage; - - beforeEach(async () => { - nextMessage = await user.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some new message' }); - nextMessage = nextMessage.message; - }); - - it('allows creator to delete a their message', async () => { - await user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); - let messages = await user.get(`/groups/${groupWithChat._id}/chat/`); - expect(messages).is.an('array'); - expect(messages).to.not.include(nextMessage); - }); - - it('allows admin to delete another user\'s message', async () => { - await admin.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}`); - let messages = await user.get(`/groups/${groupWithChat._id}/chat/`); - expect(messages).is.an('array'); - expect(messages).to.not.include(nextMessage); - }); - - it('returns empty when previous message parameter is passed and the last message was deleted', async () => { - await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${nextMessage.id}`)) - .to.eventually.be.empty; - }); - - it('returns the update chat when previous message parameter is passed and the chat is updated', async () => { - await expect(user.del(`/groups/${groupWithChat._id}/chat/${nextMessage.id}?previousMsg=${message.id}`)) - .eventually - .is.an('array') - .to.include(message) - .to.be.lengthOf(1); - }); - }); -}); diff --git a/test/api/v3/integration/chat/GET-chat.test.js b/test/api/v3/integration/chat/GET-chat.test.js deleted file mode 100644 index 91424d655e..0000000000 --- a/test/api/v3/integration/chat/GET-chat.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import { - generateUser, - generateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET /groups/:groupId/chat', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - context('public Guild', () => { - let group; - - before(async () => { - let leader = await generateUser({balance: 2}); - - group = await generateGroup(leader, { - name: 'test group', - type: 'guild', - privacy: 'public', - }, { - chat: [ - {text: 'Hello', flags: {}}, - {text: 'Welcome to the Guild', flags: {}}, - ], - }); - }); - - it('returns Guild chat', async () => { - let chat = await user.get(`/groups/${group._id}/chat`); - - expect(chat).to.eql(group.chat); - }); - }); - - context('private Guild', () => { - let group; - - before(async () => { - let leader = await generateUser({balance: 2}); - - group = await generateGroup(leader, { - name: 'test group', - type: 'guild', - privacy: 'private', - }, { - chat: [ - 'Hello', - 'Welcome to the Guild', - ], - }); - }); - - it('returns error if user is not member of requested private group', async () => { - await expect(user.get(`/groups/${group._id}/chat`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - }); -}); diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js deleted file mode 100644 index 51a3abb164..0000000000 --- a/test/api/v3/integration/chat/POST-chat.flag.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { find } from 'lodash'; - -describe('POST /chat/:chatId/flag', () => { - let user, admin, anotherUser, group; - const TEST_MESSAGE = 'Test Message'; - - before(async () => { - user = await generateUser({balance: 1}); - admin = await generateUser({balance: 1, 'contributor.admin': true}); - anotherUser = await generateUser(); - - group = await user.post('/groups', { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }); - }); - - it('Returns an error when chat message is not found', async () => { - await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatNotFound'), - }); - }); - - it('Returns an error when user tries to flag their own message', async () => { - let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE }); - await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatFlagOwnMessage'), - }); - }); - - it('Flags a chat', async () => { - let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); - message = message.message; - - let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`); - expect(flagResult.flags[user._id]).to.equal(true); - expect(flagResult.flagCount).to.equal(1); - - let groupWithFlags = await admin.get(`/groups/${group._id}`); - - let messageToCheck = find(groupWithFlags.chat, {id: message.id}); - expect(messageToCheck.flags[user._id]).to.equal(true); - }); - - it('Flags a chat with a higher flag acount when an admin flags the message', async () => { - let message = await user.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); - message = message.message; - - let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`); - expect(flagResult.flags[admin._id]).to.equal(true); - expect(flagResult.flagCount).to.equal(5); - - let groupWithFlags = await admin.get(`/groups/${group._id}`); - - let messageToCheck = find(groupWithFlags.chat, {id: message.id}); - expect(messageToCheck.flags[admin._id]).to.equal(true); - expect(messageToCheck.flagCount).to.equal(5); - }); - - it('Returns an error when user tries to flag a message that is already flagged', async () => { - let message = await anotherUser.post(`/groups/${group._id}/chat`, { message: TEST_MESSAGE}); - message = message.message; - - await user.post(`/groups/${group._id}/chat/${message.id}/flag`); - - await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatFlagAlreadyReported'), - }); - }); -}); diff --git a/test/api/v3/integration/chat/POST-chat.like.test.js b/test/api/v3/integration/chat/POST-chat.like.test.js deleted file mode 100644 index d7ae6047df..0000000000 --- a/test/api/v3/integration/chat/POST-chat.like.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { find } from 'lodash'; - -describe('POST /chat/:chatId/like', () => { - let user; - let groupWithChat; - let testMessage = 'Test Message'; - let anotherUser; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - user = groupLeader; - groupWithChat = group; - anotherUser = members[0]; - }); - - it('Returns an error when chat message is not found', async () => { - await expect(user.post(`/groups/${groupWithChat._id}/chat/incorrectMessage/like`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatNotFound'), - }); - }); - - it('Returns an error when user tries to like their own message', async () => { - let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); - - await expect(user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatLikeOwnMessage'), - }); - }); - - it('Likes a chat', async () => { - let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); - - let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); - - expect(likeResult.likes[user._id]).to.equal(true); - - let groupWithChatLikes = await user.get(`/groups/${groupWithChat._id}`); - - let messageToCheck = find(groupWithChatLikes.chat, {id: message.message.id}); - expect(messageToCheck.likes[user._id]).to.equal(true); - }); - - it('Unlikes a chat', async () => { - let message = await anotherUser.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); - - let likeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); - expect(likeResult.likes[user._id]).to.equal(true); - - let unlikeResult = await user.post(`/groups/${groupWithChat._id}/chat/${message.message.id}/like`); - expect(unlikeResult.likes[user._id]).to.equal(false); - - let groupWithoutChatLikes = await user.get(`/groups/${groupWithChat._id}`); - - let messageToCheck = find(groupWithoutChatLikes.chat, {id: message.message.id}); - expect(messageToCheck.likes[user._id]).to.equal(false); - }); -}); diff --git a/test/api/v3/integration/chat/POST-chat.test.js b/test/api/v3/integration/chat/POST-chat.test.js deleted file mode 100644 index 99d1469af8..0000000000 --- a/test/api/v3/integration/chat/POST-chat.test.js +++ /dev/null @@ -1,81 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /chat', () => { - let user, groupWithChat, userWithChatRevoked, member; - let testMessage = 'Test Message'; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - members: 2, - }); - - user = groupLeader; - groupWithChat = group; - userWithChatRevoked = await members[0].update({'flags.chatRevoked': true}); - member = members[0]; - }); - - it('Returns an error when no message is provided', async () => { - await expect(user.post(`/groups/${groupWithChat._id}/chat`, { message: ''})) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('Returns an error when group is not found', async () => { - await expect(user.post('/groups/invalidID/chat', { message: testMessage})).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('Returns an error when chat privileges are revoked', async () => { - await expect(userWithChatRevoked.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage})).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Your chat privileges have been revoked.', - }); - }); - - it('creates a chat', async () => { - let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); - - expect(message.message.id).to.exist; - }); - - it('notifies other users of new messages for a guild', async () => { - let message = await user.post(`/groups/${groupWithChat._id}/chat`, { message: testMessage}); - let memberWithNotification = await member.get('/user'); - - expect(message.message.id).to.exist; - expect(memberWithNotification.newMessages[`${groupWithChat._id}`]).to.exist; - }); - - it('notifies other users of new messages for a party', async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - privacy: 'private', - }, - members: 1, - }); - - let message = await groupLeader.post(`/groups/${group._id}/chat`, { message: testMessage}); - let memberWithNotification = await members[0].get('/user'); - - expect(message.message.id).to.exist; - expect(memberWithNotification.newMessages[`${group._id}`]).to.exist; - }); -}); diff --git a/test/api/v3/integration/chat/POST-chat_seen.test.js b/test/api/v3/integration/chat/POST-chat_seen.test.js deleted file mode 100644 index 8b22461a04..0000000000 --- a/test/api/v3/integration/chat/POST-chat_seen.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import { - createAndPopulateGroup, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /groups/:id/chat/seen', () => { - context('Guild', () => { - let guild, guildLeader, guildMember, guildMessage; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - guild = group; - guildLeader = groupLeader; - guildMember = members[0]; - - guildMessage = await guildLeader.post(`/groups/${guild._id}/chat`, { message: 'Some guild message' }); - guildMessage = guildMessage.message; - }); - - it('clears new messages for a guild', async () => { - await guildMember.post(`/groups/${guild._id}/chat/seen`); - - let guildThatHasSeenChat = await guildMember.get('/user'); - - expect(guildThatHasSeenChat.newMessages).to.be.empty; - }); - }); - - context('Party', () => { - let party, partyLeader, partyMember, partyMessage; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'party', - privacy: 'private', - }, - members: 1, - }); - - party = group; - partyLeader = groupLeader; - partyMember = members[0]; - - partyMessage = await partyLeader.post(`/groups/${party._id}/chat`, { message: 'Some party message' }); - partyMessage = partyMessage.message; - }); - - it('clears new messages for a party', async () => { - await partyMember.post(`/groups/${party._id}/chat/seen`); - - let partyMemberThatHasSeenChat = await partyMember.get('/user'); - - expect(partyMemberThatHasSeenChat.newMessages).to.be.empty; - }); - }); -}); diff --git a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js b/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js deleted file mode 100644 index 87cad5d6f0..0000000000 --- a/test/api/v3/integration/chat/POST-groups_id_chat_id_clear_flags.test.js +++ /dev/null @@ -1,101 +0,0 @@ -import { - createAndPopulateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /groups/:id/chat/:id/clearflags', () => { - let groupWithChat, message, author, nonAdmin, admin; - - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - groupWithChat = group; - author = groupLeader; - nonAdmin = members[0]; - admin = await generateUser({'contributor.admin': true}); - - message = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message' }); - message = message.message; - admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/flag`); - }); - - context('Single Message', () => { - it('returns error when non-admin attempts to clear flags', async () => { - return expect(nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupChatAdminClearFlagCount'), - }); - }); - - it('returns error if message does not exist', async () => { - let fakeMessageID = generateUUID(); - - await expect(admin.post(`/groups/${groupWithChat._id}/chat/${fakeMessageID}/clearflags`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('messageGroupChatNotFound'), - }); - }); - - it('clears flags and leaves old flags on the flag object', async () => { - await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`); - let messages = await admin.get(`/groups/${groupWithChat._id}/chat`); - expect(messages[0].flagCount).to.eql(0); - expect(messages[0].flags).to.have.property(admin._id, true); - }); - }); - - context('admin user, group with multiple messages', () => { - let message2, message3, message4; - - before(async () => { - message2 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 2' }); - message2 = message2.message; - await admin.post(`/groups/${groupWithChat._id}/chat/${message2.id}/flag`); - - message3 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 3' }); - message3 = message3.message; - await admin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`); - await nonAdmin.post(`/groups/${groupWithChat._id}/chat/${message3.id}/flag`); - - message4 = await author.post(`/groups/${groupWithChat._id}/chat`, { message: 'Some message 4' }); - message4 = message4.message; - }); - - it('changes only the message that is flagged', async () => { - await admin.post(`/groups/${groupWithChat._id}/chat/${message.id}/clearflags`); - let messages = await admin.get(`/groups/${groupWithChat._id}/chat`); - - expect(messages).to.have.lengthOf(4); - - let messageThatWasUnflagged = messages[3]; - let messageWith1Flag = messages[2]; - let messageWith2Flag = messages[1]; - let messageWithoutFlags = messages[0]; - - expect(messageThatWasUnflagged.flagCount).to.eql(0); - expect(messageThatWasUnflagged.flags).to.have.property(admin._id, true); - - expect(messageWith1Flag.flagCount).to.eql(5); - expect(messageWith1Flag.flags).to.have.property(admin._id, true); - - expect(messageWith2Flag.flagCount).to.eql(6); - expect(messageWith2Flag.flags).to.have.property(admin._id, true); - expect(messageWith2Flag.flags).to.have.property(nonAdmin._id, true); - - expect(messageWithoutFlags.flagCount).to.eql(0); - expect(messageWithoutFlags.flags).to.eql({}); - }); - }); -}); diff --git a/test/api/v3/integration/content/GET-content.test.js b/test/api/v3/integration/content/GET-content.test.js deleted file mode 100644 index 9324fce398..0000000000 --- a/test/api/v3/integration/content/GET-content.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - requester, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import i18n from '../../../../../common/script/i18n'; - -describe('GET /content', () => { - it('returns content (and does not require authentication)', async () => { - let res = await requester().get('/content'); - expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); - expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText')); - }); - - it('returns content not in English', async () => { - let res = await requester().get('/content?language=de'); - expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); - expect(res.backgrounds.backgrounds062014.beach.text).to.equal(i18n.t('backgroundBeachText', 'de')); - }); - - it('falls back to English if the desired language is not found', async () => { - let res = await requester().get('/content?language=wrong'); - expect(res).to.have.deep.property('backgrounds.backgrounds062014.beach'); - expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText')); - }); -}); diff --git a/test/api/v3/integration/coupons/GET-coupons.test.js b/test/api/v3/integration/coupons/GET-coupons.test.js deleted file mode 100644 index 6008d2b1df..0000000000 --- a/test/api/v3/integration/coupons/GET-coupons.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { - generateUser, - translate as t, - resetHabiticaDB, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET /coupons/', () => { - let user; - before(async () => { - await resetHabiticaDB(); - }); - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error if user has no sudo permission', async () => { - await user.get('/user'); // needed so the request after this will authenticate with the correct cookie session - await expect(user.get('/coupons')).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('noSudoAccess'), - }); - }); - - it('should return the coupons in CSV format ordered by creation date', async () => { - await user.update({ - 'contributor.sudo': true, - }); - - let coupons = await user.post('/coupons/generate/wondercon?count=11'); - let res = await user.get('/coupons'); - let splitRes = res.split('\n'); - - expect(splitRes.length).to.equal(13); - expect(splitRes[0]).to.equal('code,event,date,user'); - expect(splitRes[6].split(',')[1]).to.equal(coupons[5].event); - }); -}); diff --git a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js b/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js deleted file mode 100644 index e1f9db3583..0000000000 --- a/test/api/v3/integration/coupons/POST-coupons_enter_code.test.js +++ /dev/null @@ -1,62 +0,0 @@ -import { - generateUser, - translate as t, - resetHabiticaDB, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /coupons/enter/:code', () => { - let user; - let sudoUser; - - before(async () => { - await resetHabiticaDB(); - }); - - beforeEach(async () => { - user = await generateUser(); - sudoUser = await generateUser({ - 'contributor.sudo': true, - }); - }); - - it('returns an error if code is missing', async () => { - await expect(user.post('/coupons/enter')).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); - - it('returns an error if code is invalid', async () => { - await expect(user.post('/coupons/enter/notValid')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidCoupon'), - }); - }); - - it('returns an error if coupon has been used', async () => { - let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); - await user.post(`/coupons/enter/${coupon._id}`); // use coupon - - await expect(user.post(`/coupons/enter/${coupon._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('couponUsed'), - }); - }); - - it('should apply the coupon to the user', async () => { - let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); - let userRes = await user.post(`/coupons/enter/${coupon._id}`); - expect(userRes._id).to.equal(user._id); - expect(userRes.items.gear.owned.eyewear_special_wondercon_red).to.be.true; - expect(userRes.items.gear.owned.eyewear_special_wondercon_black).to.be.true; - expect(userRes.items.gear.owned.back_special_wondercon_black).to.be.true; - expect(userRes.items.gear.owned.back_special_wondercon_red).to.be.true; - expect(userRes.items.gear.owned.body_special_wondercon_red).to.be.true; - expect(userRes.items.gear.owned.body_special_wondercon_black).to.be.true; - expect(userRes.items.gear.owned.body_special_wondercon_gold).to.be.true; - expect(userRes.extra).to.eql({signupEvent: 'wondercon'}); - }); -}); diff --git a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js b/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js deleted file mode 100644 index 27bbc5c4f7..0000000000 --- a/test/api/v3/integration/coupons/POST-coupons_generate_event.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import { - generateUser, - translate as t, - resetHabiticaDB, -} from '../../../../helpers/api-v3-integration.helper'; -import couponCode from 'coupon-code'; - -describe('POST /coupons/generate/:event', () => { - let user; - before(async () => { - await resetHabiticaDB(); - }); - - beforeEach(async () => { - user = await generateUser({ - 'contributor.sudo': true, - }); - }); - - it('returns an error if user has no sudo permission', async () => { - await user.update({ - 'contributor.sudo': false, - }); - - await expect(user.post('/coupons/generate/aaa')).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('noSudoAccess'), - }); - }); - - it('returns an error if event is missing', async () => { - await expect(user.post('/coupons/generate')).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); - - it('returns an error if event is invalid', async () => { - await expect(user.post('/coupons/generate/notValid?count=1')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Coupon validation failed', - }); - }); - - it('returns an error if count is missing', async () => { - await expect(user.post('/coupons/generate/notValid')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('should generate coupons', async () => { - await user.update({ - 'contributor.sudo': true, - }); - - let coupons = await user.post('/coupons/generate/wondercon?count=2'); - expect(coupons.length).to.equal(2); - expect(coupons[0].event).to.equal('wondercon'); - expect(couponCode.validate(coupons[1]._id)).to.not.equal(''); // '' means invalid - }); -}); diff --git a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js b/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js deleted file mode 100644 index 9d433813ea..0000000000 --- a/test/api/v3/integration/coupons/POST-coupons_validate_code.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import { - generateUser, - requester, - resetHabiticaDB, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /coupons/validate/:code', () => { - let api = requester(); - - before(async () => { - await resetHabiticaDB(); - }); - - it('returns an error if code is missing', async () => { - await expect(api.post('/coupons/validate')).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); - - it('returns true if coupon code is valid', async () => { - let sudoUser = await generateUser({ - 'contributor.sudo': true, - }); - - let [coupon] = await sudoUser.post('/coupons/generate/wondercon?count=1'); - let res = await api.post(`/coupons/validate/${coupon._id}`); - expect(res).to.eql({valid: true}); - }); - - it('returns false if coupon code is valid', async () => { - let res = await api.post('/coupons/validate/notValid'); - expect(res).to.eql({valid: false}); - }); -}); diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js deleted file mode 100644 index a7b97390b6..0000000000 --- a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.html.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /export/avatar-:memberId.html', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('validates req.params.memberId', async () => { - await expect(user.get('/export/avatar-:memberId.html')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('handles non-existing members', async () => { - let dummyId = generateUUID(); - await expect(user.get(`/export/avatar-${dummyId}.html`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: dummyId}), - }); - }); - - it('returns an html page', async () => { - let res = await user.get(`/export/avatar-${user._id}.html`); - expect(res.substring(0, 100).indexOf('')).to.equal(0); - }); -}); diff --git a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js b/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js deleted file mode 100644 index a46ed695c6..0000000000 --- a/test/api/v3/integration/dataexport/GET-export_avatar-memberId.png.test.js +++ /dev/null @@ -1,3 +0,0 @@ -// TODO how to test this route since it points to a file on AWS s3? - -describe('GET /export/avatar-:memberId.png', () => {}); diff --git a/test/api/v3/integration/dataexport/GET-export_history.csv.test.js b/test/api/v3/integration/dataexport/GET-export_history.csv.test.js deleted file mode 100644 index 2dbc80c49d..0000000000 --- a/test/api/v3/integration/dataexport/GET-export_history.csv.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import { - updateDocument, -} from '../../../../helpers/mongo'; -import moment from 'moment'; - -describe('GET /export/history.csv', () => { - it('should return a valid CSV file with tasks history data', async () => { - let user = await generateUser(); - let tasks = await user.post('/tasks/user', [ - {type: 'habit', text: 'habit 1'}, - {type: 'daily', text: 'daily 1'}, - {type: 'habit', text: 'habit 2'}, - {type: 'todo', text: 'todo 1'}, - ]); - - // score all the tasks twice - await Promise.all(tasks.map(task => { - return user.post(`/tasks/${task._id}/score/up`); - })); - await Promise.all(tasks.map(task => { - return user.post(`/tasks/${task._id}/score/up`); - })); - - // adding an history entry to daily 1 manually because cron didn't run yet - await updateDocument('tasks', tasks[1], { - history: {value: 3.2, date: Number(new Date())}, - }); - - // get updated tasks - tasks = await Promise.all(tasks.map(task => { - return user.get(`/tasks/${task._id}`); - })); - - let res = await user.get('/export/history.csv'); - let splitRes = res.split('\n'); - expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value'); - expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`); - expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`); - expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`); - expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`); - expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`); - expect(splitRes[6]).to.equal(''); - }); -}); diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js deleted file mode 100644 index d8152f1209..0000000000 --- a/test/api/v3/integration/dataexport/GET-export_userdata.json.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET /export/userdata.json', () => { - it('should return a valid JSON file with user data', async () => { - let user = await generateUser(); - let tasks = await user.post('/tasks/user', [ - {type: 'habit', text: 'habit 1'}, - {type: 'daily', text: 'daily 1'}, - {type: 'reward', text: 'reward 1'}, - {type: 'todo', text: 'todo 1'}, - ]); - - let res = await user.get('/export/userdata.json'); - expect(res._id).to.equal(user._id); - expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']); - expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']); - expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']); - expect(res.tasks.habits.length).to.equal(1); - expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id); - expect(res.tasks.dailys.length).to.equal(1); - expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id); - expect(res.tasks.rewards.length).to.equal(1); - expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id); - expect(res.tasks.todos.length).to.equal(2); - expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id); - }); -}); diff --git a/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js b/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js deleted file mode 100644 index 58bf4e6135..0000000000 --- a/test/api/v3/integration/dataexport/GET-export_userdata.xml.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import xml2js from 'xml2js'; -import Bluebird from 'bluebird'; - -let parseStringAsync = Bluebird.promisify(xml2js.parseString, {context: xml2js}); - -describe('GET /export/userdata.xml', () => { - it('should return a valid XML file with user data', async () => { - let user = await generateUser(); - let tasks = await user.post('/tasks/user', [ - {type: 'habit', text: 'habit 1'}, - {type: 'daily', text: 'daily 1'}, - {type: 'reward', text: 'reward 1'}, - {type: 'todo', text: 'todo 1'}, - // due to how the xml parser works an array is returned only if there's more than one children - // so we create two tasks for each type - {type: 'habit', text: 'habit 2'}, - {type: 'daily', text: 'daily 2'}, - {type: 'reward', text: 'reward 2'}, - {type: 'todo', text: 'todo 2'}, - - ]); - - let response = await user.get('/export/userdata.xml'); - let {user: res} = await parseStringAsync(response, {explicitArray: false}); - - expect(res._id).to.equal(user._id); - expect(res).to.contain.all.keys(['tasks', 'flags', 'tasksOrder', 'auth']); - expect(res.auth.local).not.to.have.keys(['salt', 'hashed_password']); - expect(res.tasks).to.have.all.keys(['dailys', 'habits', 'todos', 'rewards']); - expect(res.tasks.habits.length).to.equal(2); - expect(res.tasks.habits[0]._id).to.equal(tasks[0]._id); - expect(res.tasks.dailys.length).to.equal(2); - expect(res.tasks.dailys[0]._id).to.equal(tasks[1]._id); - expect(res.tasks.rewards.length).to.equal(2); - expect(res.tasks.rewards[0]._id).to.equal(tasks[2]._id); - expect(res.tasks.todos.length).to.equal(3); - expect(res.tasks.todos[1]._id).to.equal(tasks[3]._id); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js b/test/api/v3/integration/debug/POST-debug_addHourglass.test.js deleted file mode 100644 index 767fa840f2..0000000000 --- a/test/api/v3/integration/debug/POST-debug_addHourglass.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /debug/add-hourglass', () => { - let userToGetHourGlass; - - before(async () => { - userToGetHourGlass = await generateUser(); - }); - - after(() => { - nconf.set('IS_PROD', false); - }); - - it('adds Hourglass to the current user', async () => { - await userToGetHourGlass.post('/debug/add-hourglass'); - - let userWithHourGlass = await userToGetHourGlass.get('/user'); - - expect(userWithHourGlass.purchased.plan.consecutive.trinkets).to.equal(1); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(userToGetHourGlass.post('/debug/add-hourglass')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js b/test/api/v3/integration/debug/POST-debug_addTenGems.test.js deleted file mode 100644 index fd01aea5d3..0000000000 --- a/test/api/v3/integration/debug/POST-debug_addTenGems.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /debug/add-ten-gems', () => { - let userToGainTenGems; - - before(async () => { - userToGainTenGems = await generateUser(); - }); - - after(() => { - nconf.set('IS_PROD', false); - }); - - it('adds ten gems to the current user', async () => { - await userToGainTenGems.post('/debug/add-ten-gems'); - - let userWithTenGems = await userToGainTenGems.get('/user'); - - expect(userWithTenGems.balance).to.equal(2.5); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(userToGainTenGems.post('/debug/add-ten-gems')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js deleted file mode 100644 index 69628aa8bc..0000000000 --- a/test/api/v3/integration/debug/POST-debug_make-admin.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -xdescribe('POST /debug/make-admin (pended for v3 prod testing)', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - afterEach(() => { - nconf.set('IS_PROD', false); - }); - - it('makes user an admine', async () => { - await user.post('/debug/make-admin'); - - await user.sync(); - - expect(user.contributor.admin).to.eql(true); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(user.post('/debug/make-admin')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js b/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js deleted file mode 100644 index 93f9081492..0000000000 --- a/test/api/v3/integration/debug/POST-debug_modify-inventory.test.js +++ /dev/null @@ -1,160 +0,0 @@ -/* eslint-disable camelcase */ - -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /debug/modify-inventory', () => { - let user, originalItems; - - before(async () => { - originalItems = { - gear: { owned: { armor_base_0: true } }, - special: { - snowball: 1, - }, - pets: { - 'Wolf-Desert': 5, - }, - mounts: { - 'Wolf-Desert': true, - }, - eggs: { - Wolf: 5, - }, - hatchingPotions: { - Desert: 5, - }, - food: { - Watermelon: 5, - }, - quests: { - gryphon: 5, - }, - }; - user = await generateUser({ - items: originalItems, - }); - }); - - afterEach(() => { - nconf.set('IS_PROD', false); - }); - - it('sets equipment', async () => { - let gear = { - weapon_healer_2: true, - weapon_wizard_1: true, - weapon_special_critical: true, - }; - - await user.post('/debug/modify-inventory', { - gear, - }); - - await user.sync(); - - expect(user.items.gear.owned).to.eql(gear); - }); - - it('sets special spells', async () => { - let special = { - shinySeed: 3, - }; - - await user.post('/debug/modify-inventory', { - special, - }); - - await user.sync(); - - expect(user.items.special).to.eql(special); - }); - - it('sets mounts', async () => { - let mounts = { - 'Orca-Base': true, - 'Mammoth-Base': true, - }; - - await user.post('/debug/modify-inventory', { - mounts, - }); - - await user.sync(); - - expect(user.items.mounts).to.eql(mounts); - }); - - it('sets eggs', async () => { - let eggs = { - Gryphon: 3, - Hedgehog: 7, - }; - - await user.post('/debug/modify-inventory', { - eggs, - }); - - await user.sync(); - - expect(user.items.eggs).to.eql(eggs); - }); - - it('sets hatching potions', async () => { - let hatchingPotions = { - White: 7, - Spooky: 2, - }; - - await user.post('/debug/modify-inventory', { - hatchingPotions, - }); - - await user.sync(); - - expect(user.items.hatchingPotions).to.eql(hatchingPotions); - }); - - it('sets food', async () => { - let food = { - Meat: 5, - Candy_Red: 7, - }; - - await user.post('/debug/modify-inventory', { - food, - }); - - await user.sync(); - - expect(user.items.food).to.eql(food); - }); - - it('sets quests', async () => { - let quests = { - whale: 5, - cheetah: 10, - }; - - await user.post('/debug/modify-inventory', { - quests, - }); - - await user.sync(); - - expect(user.items.quests).to.eql(quests); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(user.post('/debug/modify-inventory')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js b/test/api/v3/integration/debug/POST-debug_quest-progress.test.js deleted file mode 100644 index 3ae3d48882..0000000000 --- a/test/api/v3/integration/debug/POST-debug_quest-progress.test.js +++ /dev/null @@ -1,63 +0,0 @@ -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /debug/quest-progress', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - afterEach(() => { - nconf.set('IS_PROD', false); - }); - - it('errors if user is not on a quest', async () => { - await expect(user.post('/debug/quest-progress')) - .to.eventually.be.rejected.and.to.deep.equal({ - code: 400, - error: 'BadRequest', - message: 'User is not on a valid quest.', - }); - }); - - it('increases boss quest progress by 1000', async () => { - await user.update({ - 'party.quest.key': 'whale', - }); - - await user.post('/debug/quest-progress'); - - await user.sync(); - - expect(user.party.quest.progress.up).to.eql(1000); - }); - - it('increases collection quest progress by 300 items', async () => { - await user.update({ - 'party.quest.key': 'evilsanta2', - }); - - await user.post('/debug/quest-progress'); - - await user.sync(); - - expect(user.party.quest.progress.collect).to.eql({ - tracks: 300, - branches: 300, - }); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(user.post('/debug/quest-progress')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/debug/POST-debug_set-cron.test.js b/test/api/v3/integration/debug/POST-debug_set-cron.test.js deleted file mode 100644 index c737831d95..0000000000 --- a/test/api/v3/integration/debug/POST-debug_set-cron.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import nconf from 'nconf'; -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /debug/set-cron', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - afterEach(() => { - nconf.set('IS_PROD', false); - }); - - it('sets last cron', async () => { - let newCron = new Date(2015, 11, 20); - - await user.post('/debug/set-cron', { - lastCron: newCron, - }); - - await user.sync(); - - expect(user.lastCron).to.eql(newCron); - }); - - it('returns error when not in production mode', async () => { - nconf.set('IS_PROD', true); - - await expect(user.post('/debug/set-cron')) - .eventually.be.rejected.and.to.deep.equal({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js deleted file mode 100644 index 1bd3a532fa..0000000000 --- a/test/api/v3/integration/emails/GET-email-unsubscribe.test.js +++ /dev/null @@ -1,68 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { encrypt } from '../../../../../website/server/libs/api-v3/encryption'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /email/unsubscribe', () => { - let user; - let testEmail = 'test@habitica.com'; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('return error when code is not provided', async () => { - await expect(user.get('/email/unsubscribe')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('return error when user is not found', async () => { - let code = encrypt(JSON.stringify({ - _id: generateUUID(), - })); - - await expect(user.get(`/email/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userNotFound'), - }); - }); - - it('unsubscribes a user from email notifications', async () => { - let code = encrypt(JSON.stringify({ - _id: user._id, - email: user.email, - })); - - await user.get(`/email/unsubscribe?code=${code}`); - - let unsubscribedUser = await user.get('/user'); - - expect(unsubscribedUser.preferences.emailNotifications.unsubscribeFromAll).to.be.true; - }); - - it('unsubscribes an email from notifications', async () => { - let code = encrypt(JSON.stringify({ - email: testEmail, - })); - - let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); - - expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); - }); - - it('returns okay when email is already unsubscribed', async () => { - let code = encrypt(JSON.stringify({ - email: testEmail, - })); - - let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); - - expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); - }); -}); diff --git a/test/api/v3/integration/groups/GET-groups.test.js b/test/api/v3/integration/groups/GET-groups.test.js deleted file mode 100644 index 8e1e9dfbfc..0000000000 --- a/test/api/v3/integration/groups/GET-groups.test.js +++ /dev/null @@ -1,115 +0,0 @@ -import { - generateUser, - resetHabiticaDB, - generateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { - TAVERN_ID, -} from '../../../../../website/server/models/group'; - -describe('GET /groups', () => { - let user; - const NUMBER_OF_PUBLIC_GUILDS = 3; // 2 + the tavern - const NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER = 1; - const NUMBER_OF_USERS_PRIVATE_GUILDS = 1; - const NUMBER_OF_GROUPS_USER_CAN_VIEW = 5; - - before(async () => { - await resetHabiticaDB(); - - let leader = await generateUser({ balance: 10 }); - user = await generateUser({balance: 4}); - - let publicGuildUserIsMemberOf = await generateGroup(leader, { - name: 'public guild - is member', - type: 'guild', - privacy: 'public', - }); - await leader.post(`/groups/${publicGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]}); - await user.post(`/groups/${publicGuildUserIsMemberOf._id}/join`); - - await generateGroup(leader, { - name: 'public guild - is not member', - type: 'guild', - privacy: 'public', - }); - - let privateGuildUserIsMemberOf = await generateGroup(leader, { - name: 'private guild - is member', - type: 'guild', - privacy: 'private', - }); - await leader.post(`/groups/${privateGuildUserIsMemberOf._id}/invite`, { uuids: [user._id]}); - await user.post(`/groups/${privateGuildUserIsMemberOf._id}/join`); - - await generateGroup(leader, { - name: 'private guild - is not member', - type: 'guild', - privacy: 'private', - }); - - await generateGroup(leader, { - name: 'party - is not member', - type: 'party', - privacy: 'private', - }); - - await user.post('/groups', { - name: 'party - is member', - type: 'party', - privacy: 'private', - }); - }); - - it('returns error when no query passed in', async () => { - await expect(user.get('/groups')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when an invalid ?type query is passed', async () => { - await expect(user.get('/groups?type=invalid')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('groupTypesRequired'), - }); - }); - - it('returns only the tavern when tavern passed in as query', async () => { - await expect(user.get('/groups?type=tavern')) - .to.eventually.have.a.lengthOf(1) - .and.to.have.deep.property('[0]') - .and.to.have.property('_id', TAVERN_ID); - }); - - it('returns only the user\'s party when party passed in as query', async () => { - await expect(user.get('/groups?type=party')) - .to.eventually.have.a.lengthOf(1) - .and.to.have.deep.property('[0]'); - }); - - it('returns all public guilds when publicGuilds passed in as query', async () => { - await expect(user.get('/groups?type=publicGuilds')) - .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS); - }); - - it('returns all the user\'s guilds when guilds passed in as query', async () => { - await expect(user.get('/groups?type=guilds')) - .to.eventually.have.a.lengthOf(NUMBER_OF_PUBLIC_GUILDS_USER_IS_MEMBER + NUMBER_OF_USERS_PRIVATE_GUILDS); - }); - - it('returns all private guilds user is a part of when privateGuilds passed in as query', async () => { - await expect(user.get('/groups?type=privateGuilds')) - .to.eventually.have.a.lengthOf(NUMBER_OF_USERS_PRIVATE_GUILDS); - }); - - it('returns a list of groups user has access to', async () => { - await expect(user.get('/groups?type=privateGuilds,publicGuilds,party,tavern')) - .to.eventually.have.lengthOf(NUMBER_OF_GROUPS_USER_CAN_VIEW); - }); -}); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js b/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js deleted file mode 100644 index 1b7c172d94..0000000000 --- a/test/api/v3/integration/groups/GET-groups_groupId_invites.test.js +++ /dev/null @@ -1,102 +0,0 @@ -import { - generateUser, - generateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /groups/:groupId/invites', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('validates optional req.query.lastId to be an UUID', async () => { - await expect(user.get('/groups/groupId/invites?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('fails if group doesn\'t exists', async () => { - await expect(user.get(`/groups/${generateUUID()}/invites`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('fails if user doesn\'t have access to the group', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let anotherUser = await generateUser(); - await expect(anotherUser.get(`/groups/${group._id}/invites`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('works when passing party as req.params.groupId', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let invited = await generateUser(); - await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]}); - let res = await user.get('/groups/party/invites'); - - expect(res).to.be.an('array'); - expect(res.length).to.equal(1); - expect(res[0]).to.eql({ - _id: invited._id, - id: invited._id, - profile: {name: invited.profile.name}, - }); - }); - - it('populates only some fields', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let invited = await generateUser(); - await user.post(`/groups/${group._id}/invite`, {uuids: [invited._id]}); - let res = await user.get('/groups/party/invites'); - expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); - expect(res[0].profile).to.have.all.keys(['name']); - }); - - it('returns only first 30 invites', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let invitesToGenerate = []; - for (let i = 0; i < 31; i++) { - invitesToGenerate.push(generateUser()); - } - let generatedInvites = await Promise.all(invitesToGenerate); - await user.post(`/groups/${group._id}/invite`, {uuids: generatedInvites.map(invite => invite._id)}); - - let res = await user.get('/groups/party/invites'); - expect(res.length).to.equal(30); - res.forEach(member => { - expect(member).to.have.all.keys(['_id', 'id', 'profile']); - expect(member.profile).to.have.all.keys(['name']); - }); - }); - - it('supports using req.query.lastId to get more invites', async () => { - let leader = await generateUser({balance: 4}); - let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); - - let invitesToGenerate = []; - for (let i = 0; i < 32; i++) { - invitesToGenerate.push(generateUser()); - } - let generatedInvites = await Promise.all(invitesToGenerate); // Group has 32 invites - let expectedIds = generatedInvites.map(generatedInvite => generatedInvite._id); - await user.post(`/groups/${group._id}/invite`, {uuids: expectedIds}); - - let res = await user.get(`/groups/${group._id}/invites`); - expect(res.length).to.equal(30); - let res2 = await user.get(`/groups/${group._id}/invites?lastId=${res[res.length - 1]._id}`); - expect(res2.length).to.equal(2); - - let resIds = res.concat(res2).map(invite => invite._id); - expect(resIds).to.eql(expectedIds.sort()); - }); -}); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js deleted file mode 100644 index 857f6fb863..0000000000 --- a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js +++ /dev/null @@ -1,96 +0,0 @@ -import { - generateUser, - generateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /groups/:groupId/members', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('validates optional req.query.lastId to be an UUID', async () => { - await expect(user.get('/groups/groupId/members?lastId=invalidUUID')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('fails if group doesn\'t exists', async () => { - await expect(user.get(`/groups/${generateUUID()}/members`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('fails if user doesn\'t have access to the group', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - let anotherUser = await generateUser(); - await expect(anotherUser.get(`/groups/${group._id}/members`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('works when passing party as req.params.groupId', async () => { - await generateGroup(user, {type: 'party', name: generateUUID()}); - let res = await user.get('/groups/party/members'); - expect(res).to.be.an('array'); - expect(res.length).to.equal(1); - expect(res[0]).to.eql({ - _id: user._id, - id: user._id, - profile: {name: user.profile.name}, - }); - }); - - it('populates only some fields', async () => { - await generateGroup(user, {type: 'party', name: generateUUID()}); - let res = await user.get('/groups/party/members'); - expect(res[0]).to.have.all.keys(['_id', 'id', 'profile']); - expect(res[0].profile).to.have.all.keys(['name']); - }); - - it('returns only first 30 members', async () => { - let group = await generateGroup(user, {type: 'party', name: generateUUID()}); - - let usersToGenerate = []; - for (let i = 0; i < 31; i++) { - usersToGenerate.push(generateUser({party: {_id: group._id}})); - } - await Promise.all(usersToGenerate); - - let res = await user.get('/groups/party/members'); - expect(res.length).to.equal(30); - res.forEach(member => { - expect(member).to.have.all.keys(['_id', 'id', 'profile']); - expect(member.profile).to.have.all.keys(['name']); - }); - }); - - it('supports using req.query.lastId to get more members', async () => { - let leader = await generateUser({balance: 4}); - let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); - - let usersToGenerate = []; - for (let i = 0; i < 57; i++) { - usersToGenerate.push(generateUser({guilds: [group._id]})); - } - let generatedUsers = await Promise.all(usersToGenerate); // Group has 59 members (1 is the leader) - let expectedIds = [leader._id].concat(generatedUsers.map(generatedUser => generatedUser._id)); - - let res = await user.get(`/groups/${group._id}/members`); - expect(res.length).to.equal(30); - let res2 = await user.get(`/groups/${group._id}/members?lastId=${res[res.length - 1]._id}`); - expect(res2.length).to.equal(28); - - let resIds = res.concat(res2).map(member => member._id); - expect(resIds).to.eql(expectedIds.sort()); - }); -}); diff --git a/test/api/v3/integration/groups/GET-groups_id.test.js b/test/api/v3/integration/groups/GET-groups_id.test.js deleted file mode 100644 index 2ecb53a33f..0000000000 --- a/test/api/v3/integration/groups/GET-groups_id.test.js +++ /dev/null @@ -1,298 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -import { - each, -} from 'lodash'; - -describe('GET /groups/:id', () => { - let typesOfGroups = {}; - typesOfGroups['public guild'] = { type: 'guild', privacy: 'public' }; - typesOfGroups['private guild'] = { type: 'guild', privacy: 'private' }; - typesOfGroups.party = { type: 'party', privacy: 'private' }; - - each(typesOfGroups, (groupDetails, groupType) => { - context(`Member of a ${groupType}`, () => { - let leader, member, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 30, - groupDetails, - }); - - leader = groupData.groupLeader; - member = groupData.members[0]; - createdGroup = groupData.group; - }); - - it('returns the group object', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - - it('transforms leader id to leader object', async () => { - let group = await member.get(`/groups/${createdGroup._id}`); - - expect(group.leader._id).to.eql(leader._id); - expect(group.leader.profile.name).to.eql(leader.profile.name); - }); - }); - }); - - context('Non-member of a public guild', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('returns the group object for a non-member', async () => { - let group = await nonMember.get(`/groups/${createdGroup._id}`); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - }); - - context('Non-member of a private guild', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('does not return the group object for a non-member', async () => { - await expect(nonMember.get(`/groups/${createdGroup._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - }); - - context('Non-member of a party', () => { - let nonMember, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test party', - type: 'party', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - nonMember = await generateUser(); - }); - - it('does not return the group object for a non-member', async () => { - await expect(nonMember.get(`/groups/${createdGroup._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - }); - - context('Member of a party', () => { - let member, createdGroup; - - before(async () => { - let groupData = await createAndPopulateGroup({ - members: 1, - groupDetails: { - name: 'test party', - type: 'party', - privacy: 'private', - }, - }); - - createdGroup = groupData.group; - member = groupData.members[0]; - }); - - it('returns the user\'s party if an id of "party" is passed in', async () => { - let group = await member.get('/groups/party'); - - expect(group._id).to.eql(createdGroup._id); - expect(group.name).to.eql(createdGroup.name); - expect(group.type).to.eql(createdGroup.type); - expect(group.privacy).to.eql(createdGroup.privacy); - }); - }); - - context('Non-existent group', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns error if group does not exist', async () => { - await expect(user.get('/groups/group-that-does-not-exist')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - }); - - context('Flagged messages', () => { - let group; - - let chat1 = { - id: 'chat1', - text: 'chat 1', - flags: {}, - }; - - let chat2 = { - id: 'chat2', - text: 'chat 2', - flags: {}, - flagCount: 0, - }; - - let chat3 = { - id: 'chat3', - text: 'chat 3', - flags: { - 'user-id': true, - }, - flagCount: 1, - }; - - let chat4 = { - id: 'chat4', - text: 'chat 4', - flags: { - 'user-id': true, - 'other-user-id': true, - }, - flagCount: 2, - }; - - let chat5 = { - id: 'chat5', - text: 'chat 5', - flags: { - 'user-id': true, - 'other-user-id': true, - 'yet-another-user-id': true, - }, - flagCount: 3, - }; - - beforeEach(async () => { - let groupData = await createAndPopulateGroup({ - groupDetails: { - name: 'test guild', - type: 'guild', - privacy: 'public', - chat: [ - chat1, - chat2, - chat3, - chat4, - chat5, - ], - }, - }); - - group = groupData.group; - - await group.addChat([chat1, chat2, chat3, chat4, chat5]); - }); - - context('non-admin', () => { - let nonAdmin; - - beforeEach(async () => { - nonAdmin = await generateUser(); - }); - - it('does not include messages with a flag count of 2 or greater', async () => { - let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); - - expect(fetchedGroup.chat).to.have.lengthOf(3); - expect(fetchedGroup.chat[0].id).to.eql(chat1.id); - expect(fetchedGroup.chat[1].id).to.eql(chat2.id); - expect(fetchedGroup.chat[2].id).to.eql(chat3.id); - }); - - it('does not include user ids in flags object', async () => { - let fetchedGroup = await nonAdmin.get(`/groups/${group._id}`); - let chatWithOneFlag = fetchedGroup.chat[2]; - - expect(chatWithOneFlag.id).to.eql(chat3.id); - expect(chat3.flags).to.eql({ 'user-id': true }); - expect(chatWithOneFlag.flags).to.eql({}); - }); - }); - - context('admin', () => { - let admin; - - beforeEach(async () => { - admin = await generateUser({ - 'contributor.admin': true, - }); - }); - - it('includes all messages', async () => { - let fetchedGroup = await admin.get(`/groups/${group._id}`); - - expect(fetchedGroup.chat).to.have.lengthOf(5); - expect(fetchedGroup.chat[0].id).to.eql(chat1.id); - expect(fetchedGroup.chat[1].id).to.eql(chat2.id); - expect(fetchedGroup.chat[2].id).to.eql(chat3.id); - expect(fetchedGroup.chat[3].id).to.eql(chat4.id); - expect(fetchedGroup.chat[4].id).to.eql(chat5.id); - }); - - it('includes user ids in flags object', async () => { - let fetchedGroup = await admin.get(`/groups/${group._id}`); - let chatWithOneFlag = fetchedGroup.chat[2]; - - expect(chatWithOneFlag.id).to.eql(chat3.id); - expect(chat3.flags).to.eql({ 'user-id': true }); - expect(chatWithOneFlag.flags).to.eql(chat3.flags); - }); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups.test.js b/test/api/v3/integration/groups/POST-groups.test.js deleted file mode 100644 index ad8f7a6c92..0000000000 --- a/test/api/v3/integration/groups/POST-groups.test.js +++ /dev/null @@ -1,228 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /group', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ balance: 10 }); - }); - - context('All Groups', () => { - it('it returns validation error when type is not provided', async () => { - await expect( - user.post('/groups', { name: 'Test Group Without Type' }) - ).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Group validation failed', - }); - }); - - it('it returns validation error when type is not supported', async () => { - await expect( - user.post('/groups', { name: 'Group with unsupported type', type: 'foo' }) - ).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Group validation failed', - }); - }); - - it('sets the group leader to the user who created the group', async () => { - let group = await user.post('/groups', { - name: 'Test Public Guild', - type: 'guild', - }); - - expect(group.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - }); - - context('Guilds', () => { - it('returns an error when a user with insufficient funds attempts to create a guild', async () => { - await user.update({ balance: 0 }); - - await expect( - user.post('/groups', { - name: 'Test Public Guild', - type: 'guild', - }) - ).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageInsufficientGems'), - }); - }); - - it('adds guild to user\'s list of guilds', async () => { - let guild = await user.post('/groups', { - name: 'some guild', - type: 'guild', - privacy: 'public', - }); - - let updatedUser = await user.get('/user'); - - expect(updatedUser.guilds).to.include(guild._id); - }); - - context('public guild', () => { - it('creates a group', async () => { - let groupName = 'Test Public Guild'; - let groupType = 'guild'; - let groupPrivacy = 'public'; - - let publicGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(publicGuild._id).to.exist; - expect(publicGuild.name).to.equal(groupName); - expect(publicGuild.type).to.equal(groupType); - expect(publicGuild.memberCount).to.equal(1); - expect(publicGuild.privacy).to.equal(groupPrivacy); - expect(publicGuild.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - }); - - context('private guild', () => { - let groupName = 'Test Private Guild'; - let groupType = 'guild'; - let groupPrivacy = 'private'; - - it('creates a group', async () => { - let privateGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(privateGuild._id).to.exist; - expect(privateGuild.name).to.equal(groupName); - expect(privateGuild.type).to.equal(groupType); - expect(privateGuild.memberCount).to.equal(1); - expect(privateGuild.privacy).to.equal(groupPrivacy); - expect(privateGuild.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - - it('deducts gems from user and adds them to guild bank', async () => { - let privateGuild = await user.post('/groups', { - name: groupName, - type: groupType, - privacy: groupPrivacy, - }); - - expect(privateGuild.balance).to.eql(1); - - let updatedUser = await user.get('/user'); - - expect(updatedUser.balance).to.eql(user.balance - 1); - }); - }); - }); - - context('Parties', () => { - let partyName = 'Test Party'; - let partyType = 'party'; - - it('creates a party', async () => { - let party = await user.post('/groups', { - name: partyName, - type: partyType, - }); - - expect(party._id).to.exist; - expect(party.name).to.equal(partyName); - expect(party.type).to.equal(partyType); - expect(party.memberCount).to.equal(1); - expect(party.leader).to.eql({ - _id: user._id, - profile: { - name: user.profile.name, - }, - }); - }); - - it('does not require gems to create a party', async () => { - await user.update({ balance: 0 }); - - let party = await user.post('/groups', { - name: partyName, - type: partyType, - }); - - expect(party._id).to.exist; - - let updatedUser = await user.get('/user'); - - expect(updatedUser.balance).to.eql(user.balance); - }); - - it('sets party id on user object', async () => { - let party = await user.post('/groups', { - name: partyName, - type: partyType, - }); - - let updatedUser = await user.get('/user'); - - expect(updatedUser.party._id).to.eql(party._id); - }); - - it('does not award Party Up achievement to solo partier', async () => { - await user.post('/groups', { - name: partyName, - type: partyType, - }); - - let updatedUser = await user.get('/user'); - - expect(updatedUser.achievements.partyUp).to.not.eql(true); - }); - - it('prevents user in a party from creating another party', async () => { - await user.post('/groups', { - name: partyName, - type: partyType, - }); - - await expect(user.post('/groups')).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupAlreadyInParty'), - }); - }); - - it('prevents creating a public party', async () => { - await expect(user.post('/groups', { - name: partyName, - type: partyType, - privacy: 'public', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('partyMustbePrivate'), - }); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js deleted file mode 100644 index f47a734e89..0000000000 --- a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js +++ /dev/null @@ -1,286 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - checkExistence, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /group/:groupId/join', () => { - const PET_QUEST = 'whale'; - - it('returns error when groupId is not for a valid group', async () => { - let joiningUser = await generateUser(); - - await expect(joiningUser.post(`/groups/${generateUUID()}/join`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - context('Joining a public guild', () => { - let user, joiningUser, publicGuild; - - beforeEach(async () => { - let {group, groupLeader} = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - }); - - publicGuild = group; - user = groupLeader; - joiningUser = await generateUser(); - }); - - it('allows non-invited users to join public guilds', async () => { - let res = await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get('/user')).to.eventually.have.property('guilds').to.include(publicGuild._id); - expect(res.leader._id).to.eql(user._id); - expect(res.leader.profile.name).to.eql(user.profile.name); - }); - - it('returns an error is user was already a member', async () => { - await joiningUser.post(`/groups/${publicGuild._id}/join`); - await expect(joiningUser.post(`/groups/${publicGuild._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInGroup'), - }); - }); - - it('promotes joining member in a public empty guild to leader', async () => { - await user.post(`/groups/${publicGuild._id}/leave`); - - await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.deep.property('leader._id', joiningUser._id); - }); - - it('increments memberCount when joining guilds', async () => { - let oldMemberCount = publicGuild.memberCount; - - await joiningUser.post(`/groups/${publicGuild._id}/join`); - - await expect(joiningUser.get(`/groups/${publicGuild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); - }); - }); - - context('Joining a private guild', () => { - let user, invitedUser, guild; - - beforeEach(async () => { - let { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'private', - }, - invites: 1, - }); - - guild = group; - user = groupLeader; - invitedUser = invitees[0]; - }); - - it('returns error when user is not invited to private guild', async () => { - let userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${guild._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - context('User is invited', () => { - it('allows invited user to join private guilds', async () => { - await invitedUser.post(`/groups/${guild._id}/join`); - - await expect(invitedUser.get('/user')).to.eventually.have.property('guilds').to.include(guild._id); - }); - - it('clears invitation from user when joining guilds', async () => { - await invitedUser.post(`/groups/${guild._id}/join`); - - await expect(invitedUser.get('/user')) - .to.eventually.have.deep.property('invitations.guilds') - .to.not.include({id: guild._id}); - }); - - it('increments memberCount when joining guilds', async () => { - let oldMemberCount = guild.memberCount; - - await invitedUser.post(`/groups/${guild._id}/join`); - - await expect(invitedUser.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); - }); - - it('does not give basilist quest to inviter when joining a guild', async () => { - await invitedUser.post(`/groups/${guild._id}/join`); - - await expect(user.get('/user')).to.eventually.not.have.deep.property('items.quests.basilist'); - }); - - it('does not increment basilist quest count to inviter with basilist when joining a guild', async () => { - await user.update({ 'items.quests.basilist': 1 }); - - await invitedUser.post(`/groups/${guild._id}/join`); - - await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1); - }); - }); - }); - - context('Joining a party', () => { - let user, invitedUser, party; - - beforeEach(async () => { - let { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - }, - members: 2, - invites: 1, - }); - - party = group; - user = groupLeader; - invitedUser = invitees[0]; - }); - - it('returns error when user is not invited to party', async () => { - let userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - context('User is invited', () => { - it('allows invited user to join party', async () => { - await invitedUser.post(`/groups/${party._id}/join`); - - await expect(invitedUser.get('/user')).to.eventually.have.deep.property('party._id', party._id); - }); - - it('clears invitation from user when joining party', async () => { - await invitedUser.post(`/groups/${party._id}/join`); - - await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id'); - }); - - it('increments memberCount when joining party', async () => { - let oldMemberCount = party.memberCount; - - await invitedUser.post(`/groups/${party._id}/join`); - - await expect(invitedUser.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount + 1); - }); - - it('gives basilist quest item to the inviter when joining a party', async () => { - await invitedUser.post(`/groups/${party._id}/join`); - - await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 1); - }); - - it('increments basilist quest item count to inviter when joining a party', async () => { - await user.update({'items.quests.basilist': 1 }); - - await invitedUser.post(`/groups/${party._id}/join`); - - await expect(user.get('/user')).to.eventually.have.deep.property('items.quests.basilist', 2); - }); - - it('deletes previous party where the user was the only member', async () => { - let userToInvite = await generateUser(); - let oldParty = await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); - await user.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - await userToInvite.post(`/groups/${party._id}/join`); - - await expect(user.get('/user')).to.eventually.have.deep.property('party._id', party._id); - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false); - }); - - it('invites joining member to active quest', async () => { - await user.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - await user.post(`/groups/${party._id}/quests/invite/${PET_QUEST}`); - - await invitedUser.post(`/groups/${party._id}/join`); - - await invitedUser.sync(); - await party.sync(); - - expect(invitedUser).to.have.deep.property('party.quest.RSVPNeeded', true); - expect(invitedUser).to.have.deep.property('party.quest.key', party.quest.key); - expect(party.quest.members[invitedUser._id]).to.be.null; - }); - }); - }); - - context('Party incentive achievements', () => { - let leader, member, party; - - beforeEach(async () => { - leader = await generateUser(); - member = await generateUser(); - party = await leader.post('/groups', { - name: 'Testing Party', - type: 'party', - }); - await leader.post(`/groups/${party._id}/invite`, { - uuids: [member._id], - }); - await member.post(`/groups/${party._id}/join`); - }); - - it('awards Party Up achievement to party of size 2', async () => { - await member.sync(); - await leader.sync(); - - expect(member).to.have.deep.property('achievements.partyUp', true); - expect(leader).to.have.deep.property('achievements.partyUp', true); - }); - - it('does not award Party On achievement to party of size 2', async () => { - await member.sync(); - await leader.sync(); - - expect(member).to.not.have.deep.property('achievements.partyOn'); - expect(leader).to.not.have.deep.property('achievements.partyOn'); - }); - - it('awards Party On achievement to party of size 4', async () => { - let addlMemberOne = await generateUser(); - let addlMemberTwo = await generateUser(); - await leader.post(`/groups/${party._id}/invite`, { - uuids: [addlMemberOne._id, addlMemberTwo._id], - }); - await addlMemberOne.post(`/groups/${party._id}/join`); - await addlMemberTwo.post(`/groups/${party._id}/join`); - - await member.sync(); - await leader.sync(); - - expect(member).to.have.deep.property('achievements.partyOn', true); - expect(leader).to.have.deep.property('achievements.partyOn', true); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_leave.js b/test/api/v3/integration/groups/POST-groups_groupId_leave.js deleted file mode 100644 index 3e13634bd8..0000000000 --- a/test/api/v3/integration/groups/POST-groups_groupId_leave.js +++ /dev/null @@ -1,210 +0,0 @@ -import { - generateChallenge, - checkExistence, - createAndPopulateGroup, - sleep, - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { - each, -} from 'lodash'; - -describe('POST /groups/:groupId/leave', () => { - let typesOfGroups = { - 'public guild': { type: 'guild', privacy: 'public' }, - 'private guild': { type: 'guild', privacy: 'private' }, - party: { type: 'party', privacy: 'private' }, - }; - - each(typesOfGroups, (groupDetails, groupType) => { - context(`Leaving a ${groupType}`, () => { - let groupToLeave; - let leader; - let member; - let memberCount; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails, - members: 1, - }); - - groupToLeave = group; - leader = groupLeader; - member = members[0]; - memberCount = group.memberCount; - }); - - it('prevents non members from leaving', async () => { - let user = await generateUser(); - await expect(user.post(`/groups/${groupToLeave._id}/leave`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it(`lets user leave a ${groupType}`, async () => { - await member.post(`/groups/${groupToLeave._id}/leave`); - - let userThatLeftGroup = await member.get('/user'); - - expect(userThatLeftGroup.guilds).to.be.empty; - expect(userThatLeftGroup.party._id).to.not.exist; - await groupToLeave.sync(); - expect(groupToLeave.memberCount).to.equal(memberCount - 1); - }); - - it(`sets a new group leader when leader leaves a ${groupType}`, async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`); - - await groupToLeave.sync(); - expect(groupToLeave.memberCount).to.equal(memberCount - 1); - expect(groupToLeave.leader).to.equal(member._id); - }); - - context('With challenges', () => { - let challenge; - - beforeEach(async () => { - challenge = await generateChallenge(leader, groupToLeave); - - await leader.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - - await sleep(0.5); - }); - - it('removes all challenge tasks when keep parameter is set to remove', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave?keep=remove-all`); - - let userWithoutChallengeTasks = await leader.get('/user'); - - expect(userWithoutChallengeTasks.challenges).to.not.include(challenge._id); - expect(userWithoutChallengeTasks.tasksOrder.habits).to.be.empty; - }); - - it('keeps all challenge tasks when keep parameter is not set', async () => { - await leader.post(`/groups/${groupToLeave._id}/leave`); - - let userWithChallengeTasks = await leader.get('/user'); - - expect(userWithChallengeTasks.challenges).to.not.include(challenge._id); - // @TODO find elegant way to assert against the task existing - expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty; - }); - }); - - it('prevents quest leader from leaving a groupToLeave'); - it('prevents a user from leaving during an active quest'); - }); - }); - - context('Leaving a group as the last member', () => { - context('private guild', () => { - let privateGuild; - let leader; - let invitedUser; - - beforeEach(async () => { - let { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Private Guild', - type: 'guild', - }, - invites: 1, - }); - - privateGuild = group; - leader = groupLeader; - invitedUser = invitees[0]; - }); - - it('removes a group when the last member leaves', async () => { - await leader.post(`/groups/${privateGuild._id}/leave`); - - await expect(checkExistence('groups', privateGuild._id)).to.eventually.equal(false); - }); - - it('removes invitations when the last member leaves', async () => { - await leader.post(`/groups/${privateGuild._id}/leave`); - - let userWithoutInvitation = await invitedUser.get('/user'); - - expect(userWithoutInvitation.invitations.guilds).to.be.empty; - }); - }); - - context('public guild', () => { - let publicGuild; - let leader; - let invitedUser; - - beforeEach(async () => { - let { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Public Guild', - type: 'guild', - privacy: 'public', - }, - invites: 1, - }); - - publicGuild = group; - leader = groupLeader; - invitedUser = invitees[0]; - }); - - it('keeps the group when the last member leaves', async () => { - await leader.post(`/groups/${publicGuild._id}/leave`); - - await expect(checkExistence('groups', publicGuild._id)).to.eventually.equal(true); - }); - - it('keeps the invitations when the last member leaves a public guild', async () => { - await leader.post(`/groups/${publicGuild._id}/leave`); - - let userWithoutInvitation = await invitedUser.get('/user'); - - expect(userWithoutInvitation.invitations.guilds).to.not.be.empty; - }); - }); - - context('party', () => { - let party; - let leader; - let invitedUser; - - beforeEach(async () => { - let { group, groupLeader, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - }, - invites: 1, - }); - - party = group; - leader = groupLeader; - invitedUser = invitees[0]; - }); - - it('removes a group when the last member leaves a party', async () => { - await leader.post(`/groups/${party._id}/leave`); - - await expect(checkExistence('party', party._id)).to.eventually.equal(false); - }); - - it('removes invitations when the last member leaves a party', async () => { - await leader.post(`/groups/${party._id}/leave`); - - let userWithoutInvitation = await invitedUser.get('/user'); - - expect(userWithoutInvitation.invitations.party).to.be.empty; - }); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js b/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js deleted file mode 100644 index 18f83fe9c9..0000000000 --- a/test/api/v3/integration/groups/POST-groups_groupId_reject.test.js +++ /dev/null @@ -1,113 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /group/:groupId/reject-invite', () => { - context('Rejecting a public guild invite', () => { - let publicGuild, invitedUser; - - beforeEach(async () => { - let {group, invitees} = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'public', - }, - invites: 1, - }); - - publicGuild = group; - invitedUser = invitees[0]; - }); - - it('returns error when user is not invited', async () => { - let userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${publicGuild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - it('clears invitation from user', async () => { - await invitedUser.post(`/groups/${publicGuild._id}/reject-invite`); - - await expect(invitedUser.get('/user')) - .to.eventually.have.deep.property('invitations.guilds') - .to.not.include({id: publicGuild._id}); - }); - }); - - context('Rejecting a private guild invite', () => { - let invitedUser, guild; - - beforeEach(async () => { - let { group, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'private', - }, - invites: 1, - }); - - guild = group; - invitedUser = invitees[0]; - }); - - it('returns error when user is not invited', async () => { - let userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${guild._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - it('clears invitation from user', async () => { - await invitedUser.post(`/groups/${guild._id}/reject-invite`); - - await expect(invitedUser.get('/user')) - .to.eventually.have.deep.property('invitations.guilds') - .to.not.include({id: guild._id}); - }); - }); - - context('Rejecting a party invite', () => { - let invitedUser, party; - - beforeEach(async () => { - let { group, invitees } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - }, - members: 2, - invites: 1, - }); - - party = group; - invitedUser = invitees[0]; - }); - - it('returns error when user is not invited', async () => { - let userWithoutInvite = await generateUser(); - - await expect(userWithoutInvite.post(`/groups/${party._id}/reject-invite`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupRequiresInvite'), - }); - }); - - it('clears invitation from user', async () => { - await invitedUser.post(`/groups/${party._id}/reject-invite`); - - await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id'); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js b/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js deleted file mode 100644 index 0ebde39361..0000000000 --- a/test/api/v3/integration/groups/POST-groups_id_removeMember.test.js +++ /dev/null @@ -1,130 +0,0 @@ -import { - generateUser, - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /groups/:groupId/removeMember/:memberId', () => { - let leader; - let invitedUser; - let guild; - let member; - let member2; - - beforeEach(async () => { - let { group, groupLeader, invitees, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Guild', - type: 'guild', - privacy: 'private', - }, - invites: 1, - members: 2, - }); - - guild = group; - leader = groupLeader; - invitedUser = invitees[0]; - member = members[0]; - member2 = members[1]; - }); - - context('All Groups', () => { - it('returns an error when user is not member of the group', async () => { - let nonMember = await generateUser(); - - expect(nonMember.post(`/groups/${guild._id}/removeMember/${member._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - type: 'NotAuthorized', - message: t('onlyLeaderCanRemoveMember'), - }); - }); - - it('returns an error when user is a non-leader member of a group', async () => { - expect(member2.post(`/groups/${guild._id}/removeMember/${member._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - type: 'NotAuthorized', - message: t('onlyLeaderCanRemoveMember'), - }); - }); - - it('does not allow leader to remove themselves', async () => { - expect(leader.post(`/groups/${guild._id}/removeMember/${leader._id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - text: t('messageGroupCannotRemoveSelf'), - }); - }); - }); - - context('Guilds', () => { - it('can remove other members', async () => { - await leader.post(`/groups/${guild._id}/removeMember/${member._id}`); - let memberRemoved = await member.get('/user'); - - expect(memberRemoved.guilds.indexOf(guild._id)).eql(-1); - }); - - it('updates memberCount', async () => { - let oldMemberCount = guild.memberCount; - await leader.post(`/groups/${guild._id}/removeMember/${member._id}`); - await expect(leader.get(`/groups/${guild._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1); - }); - - it('can remove other invites', async () => { - await leader.post(`/groups/${guild._id}/removeMember/${invitedUser._id}`); - - let invitedUserWithoutInvite = await invitedUser.get('/user'); - - expect(_.findIndex(invitedUserWithoutInvite.invitations.guilds, {id: guild._id})).eql(-1); - }); - }); - - context('Party', () => { - let party; - let partyleader; - let partyInvitedUser; - let partyMember; - - beforeEach(async () => { - let { group, groupLeader, invitees, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'Test Party', - type: 'party', - privacy: 'private', - }, - invites: 1, - members: 1, - }); - - party = group; - partyleader = groupLeader; - partyInvitedUser = invitees[0]; - partyMember = members[0]; - }); - - it('can remove other members', async () => { - await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`); - - let memberRemoved = await partyMember.get('/user'); - - expect(memberRemoved.party._id).eql(undefined); - }); - - it('updates memberCount', async () => { - let oldMemberCount = party.memberCount; - await partyleader.post(`/groups/${party._id}/removeMember/${partyMember._id}`); - await expect(partyleader.get(`/groups/${party._id}`)).to.eventually.have.property('memberCount', oldMemberCount - 1); - }); - - it('can remove other invites', async () => { - await partyleader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`); - - let invitedUserWithoutInvite = await partyInvitedUser.get('/user'); - - expect(_.findIndex(invitedUserWithoutInvite.invitations.party, {id: party._id})).eql(-1); - }); - }); -}); diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js deleted file mode 100644 index 92328463b2..0000000000 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ /dev/null @@ -1,332 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -const INVITES_LIMIT = 100; - -describe('Post /groups/:groupId/invite', () => { - let inviter; - let group; - let groupName = 'Test Public Guild'; - - beforeEach(async () => { - inviter = await generateUser({balance: 1}); - group = await inviter.post('/groups', { - name: groupName, - type: 'guild', - }); - }); - - describe('user id invites', () => { - it('returns an error when invited user is not found', async () => { - let fakeID = generateUUID(); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [fakeID], - })) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: fakeID}), - }); - }); - - it('returns an error when uuids is not an array', async () => { - let fakeID = generateUUID(); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: {fakeID}, - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('uuidsMustBeAnArray'), - }); - }); - - it('returns empty when uuids is empty', async () => { - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [], - })) - .to.eventually.be.empty; - }); - - it('returns an error when there are more than INVITES_LIMIT uuids', async () => { - let uuids = []; - - for (let i = 0; i < 101; i += 1) { - uuids.push(generateUUID()); - } - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids, - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), - }); - }); - - it('invites a user to a group by uuid', async () => { - let userToInvite = await generateUser(); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - })).to.eventually.deep.equal([{ - id: group._id, - name: groupName, - inviter: inviter._id, - }]); - - await expect(userToInvite.get('/user')) - .to.eventually.have.deep.property('invitations.guilds[0].id', group._id); - }); - - it('invites multiple users to a group by uuid', async () => { - let userToInvite = await generateUser(); - let userToInvite2 = await generateUser(); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id, userToInvite2._id], - })).to.eventually.deep.equal([ - { - id: group._id, - name: groupName, - inviter: inviter._id, - }, - { - id: group._id, - name: groupName, - inviter: inviter._id, - }, - ]); - - await expect(userToInvite.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id); - await expect(userToInvite2.get('/user')).to.eventually.have.deep.property('invitations.guilds[0].id', group._id); - }); - - it('returns an error when inviting multiple users and a user is not found', async () => { - let userToInvite = await generateUser(); - let fakeID = generateUUID(); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id, fakeID], - })) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: fakeID}), - }); - }); - }); - - describe('email invites', () => { - let testInvite = {name: 'test', email: 'test@habitica.com'}; - - it('returns an error when invite is missing an email', async () => { - await expect(inviter.post(`/groups/${group._id}/invite`, { - emails: [{name: 'test'}], - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('inviteMissingEmail'), - }); - }); - - it('returns an error when emails is not an array', async () => { - await expect(inviter.post(`/groups/${group._id}/invite`, { - emails: {testInvite}, - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('emailsMustBeAnArray'), - }); - }); - - it('returns empty when emails is an empty array', async () => { - await expect(inviter.post(`/groups/${group._id}/invite`, { - emails: [], - })) - .to.eventually.be.empty; - }); - - it('returns an error when there are more than INVITES_LIMIT emails', async () => { - let emails = []; - - for (let i = 0; i < 101; i += 1) { - emails.push(`${generateUUID()}@habitica.com`); - } - - await expect(inviter.post(`/groups/${group._id}/invite`, { - emails, - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), - }); - }); - - it('invites a user to a group by email', async () => { - let res = await inviter.post(`/groups/${group._id}/invite`, { - emails: [testInvite], - inviter: 'inviter name', - }); - - expect(res).to.exist; - }); - - it('invites multiple users to a group by email', async () => { - let res = await inviter.post(`/groups/${group._id}/invite`, { - emails: [testInvite, {name: 'test2', email: 'test2@habitica.com'}], - }); - - expect(res).to.exist; - }); - }); - - describe('user and email invites', () => { - it('returns an error when emails and uuids are not provided', async () => { - await expect(inviter.post(`/groups/${group._id}/invite`)) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('canOnlyInviteEmailUuid'), - }); - }); - - it('returns an error when there are more than INVITES_LIMIT uuids and emails', async () => { - let emails = []; - let uuids = []; - - for (let i = 0; i < 50; i += 1) { - emails.push(`${generateUUID()}@habitica.com`); - } - - for (let i = 0; i < 51; i += 1) { - uuids.push(generateUUID()); - } - - await expect(inviter.post(`/groups/${group._id}/invite`, { - emails, - uuids, - })) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT}), - }); - }); - - it('invites users to a group by uuid and email', async () => { - let newUser = await generateUser(); - let invite = await inviter.post(`/groups/${group._id}/invite`, { - uuids: [newUser._id], - emails: [{name: 'test', email: 'test@habitica.com'}], - }); - let invitedUser = await newUser.get('/user'); - - expect(invitedUser.invitations.guilds[0].id).to.equal(group._id); - expect(invite).to.exist; - }); - }); - - describe('guild invites', () => { - it('returns an error when invited user is already invited to the group', async () => { - let userToInivite = await generateUser(); - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInivite._id], - }); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInivite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInvitedToGroup'), - }); - }); - - it('returns an error when invited user is already in the group', async () => { - let userToInvite = await generateUser(); - await inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - }); - await userToInvite.post(`/groups/${group._id}/join`); - - await expect(inviter.post(`/groups/${group._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInGroup'), - }); - }); - }); - - describe('party invites', () => { - let party; - - beforeEach(async () => { - party = await inviter.post('/groups', { - name: 'Test Party', - type: 'party', - }); - }); - - it('returns an error when invited user has a pending invitation to the party', async () => { - let userToInvite = await generateUser(); - await inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - - await expect(inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyPendingInvitation'), - }); - }); - - it('returns an error when invited user is already in a party of more than 1 member', async () => { - let userToInvite = await generateUser(); - let userToInvite2 = await generateUser(); - await inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id, userToInvite2._id], - }); - await userToInvite.post(`/groups/${party._id}/join`); - await userToInvite2.post(`/groups/${party._id}/join`); - - await expect(inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('userAlreadyInAParty'), - }); - }); - - it('allow inviting a user to a party if he\'s partying solo', async () => { - let userToInvite = await generateUser(); - await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id); - }); - }); -}); diff --git a/test/api/v3/integration/groups/PUT-groups.test.js b/test/api/v3/integration/groups/PUT-groups.test.js deleted file mode 100644 index 8d581d56ca..0000000000 --- a/test/api/v3/integration/groups/PUT-groups.test.js +++ /dev/null @@ -1,46 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('PUT /group', () => { - let leader, nonLeader, groupToUpdate; - let groupName = 'Test Public Guild'; - let groupType = 'guild'; - let groupUpdatedName = 'Test Public Guild Updated'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: groupName, - type: groupType, - privacy: 'public', - }, - members: 1, - }); - - groupToUpdate = group; - leader = groupLeader; - nonLeader = members[0]; - }); - - it('returns an error when a non group leader tries to update', async () => { - await expect(nonLeader.put(`/groups/${groupToUpdate._id}`, { - name: groupUpdatedName, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageGroupOnlyLeaderCanUpdate'), - }); - }); - - it('updates a group', async () => { - let updatedGroup = await leader.put(`/groups/${groupToUpdate._id}`, { - name: groupUpdatedName, - }); - - expect(updatedGroup.leader._id).to.eql(leader._id); - expect(updatedGroup.leader.profile.name).to.eql(leader.profile.name); - expect(updatedGroup.name).to.equal(groupUpdatedName); - }); -}); diff --git a/test/api/v3/integration/hall/GET-hall_heroes.test.js b/test/api/v3/integration/hall/GET-hall_heroes.test.js deleted file mode 100644 index 745bc7739c..0000000000 --- a/test/api/v3/integration/hall/GET-hall_heroes.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET /hall/heroes', () => { - it('returns all heroes sorted by -contributor.level and with correct fields', async () => { - let nonHero = await generateUser(); - let hero1 = await generateUser({ - contributor: {level: 1}, - }); - let hero2 = await generateUser({ - contributor: {level: 3}, - }); - - let heroes = await nonHero.get('/hall/heroes'); - expect(heroes.length).to.equal(2); - expect(heroes[0]._id).to.equal(hero2._id); - expect(heroes[1]._id).to.equal(hero1._id); - - expect(heroes[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); - expect(heroes[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); - - expect(heroes[0].profile).to.have.all.keys(['name']); - expect(heroes[1].profile).to.have.all.keys(['name']); - - expect(heroes[0].profile.name).to.equal(hero2.profile.name); - expect(heroes[1].profile.name).to.equal(hero1.profile.name); - }); -}); diff --git a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js b/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js deleted file mode 100644 index 2bf1a0a017..0000000000 --- a/test/api/v3/integration/hall/GET-hall_heroes_heroId.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /heroes/:heroId', () => { - let user; - - before(async () => { - user = await generateUser({ - contributor: {admin: true}, - }); - }); - - it('requires the caller to be an admin', async () => { - let nonAdmin = await generateUser(); - - await expect(nonAdmin.get(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('noAdminAccess'), - }); - }); - - 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'), - }); - }); - - it('handles non-existing heroes', async () => { - let dummyId = generateUUID(); - await expect(user.get(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: dummyId}), - }); - }); - - it('returns only necessary hero data', async () => { - let hero = await generateUser({ - contributor: {tier: 23}, - }); - let heroRes = await user.get(`/hall/heroes/${hero._id}`); - - 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']); - }); -}); diff --git a/test/api/v3/integration/hall/GET-hall_patrons.test.js b/test/api/v3/integration/hall/GET-hall_patrons.test.js deleted file mode 100644 index 9599daef89..0000000000 --- a/test/api/v3/integration/hall/GET-hall_patrons.test.js +++ /dev/null @@ -1,60 +0,0 @@ -import { - generateUser, - translate as t, - resetHabiticaDB, -} from '../../../../helpers/api-v3-integration.helper'; -import { times } from 'lodash'; - -describe('GET /hall/patrons', () => { - let user; - - beforeEach(async () => { - await resetHabiticaDB(); - user = await generateUser(); - }); - - it('fails if req.query.page is not numeric', async () => { - await expect(user.get('/hall/patrons?page=notNumber')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns all patrons sorted by -backer.tier and with correct fields', async () => { - let patron1 = await generateUser({ - backer: {tier: 1}, - }); - let patron2 = await generateUser({ - backer: {tier: 3}, - }); - - let patrons = await user.get('/hall/patrons'); - expect(patrons.length).to.equal(2); - expect(patrons[0]._id).to.equal(patron2._id); - expect(patrons[1]._id).to.equal(patron1._id); - - expect(patrons[0]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); - expect(patrons[1]).to.have.all.keys(['_id', 'contributor', 'backer', 'profile']); - - expect(patrons[0].profile).to.have.all.keys(['name']); - expect(patrons[1].profile).to.have.all.keys(['name']); - - expect(patrons[0].profile.name).to.equal(patron2.profile.name); - expect(patrons[1].profile.name).to.equal(patron1.profile.name); - }); - - it('returns only first 50 patrons per request, more if req.query.page is passed', async () => { - await Promise.all(times(53, n => { - return generateUser({backer: {tier: n}}); - })); - - let patrons = await user.get('/hall/patrons'); - expect(patrons.length).to.equal(50); - - let morePatrons = await user.get('/hall/patrons?page=1'); - expect(morePatrons.length).to.equal(2); - expect(morePatrons[0].backer.tier).to.equal(2); - expect(morePatrons[1].backer.tier).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js deleted file mode 100644 index 9948cbe3c7..0000000000 --- a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js +++ /dev/null @@ -1,148 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('PUT /heroes/:heroId', () => { - let user; - - before(async () => { - user = await generateUser({ - contributor: {admin: true}, - }); - }); - - it('requires the caller to be an admin', async () => { - let nonAdmin = await generateUser(); - - await expect(nonAdmin.put(`/hall/heroes/${user._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('noAdminAccess'), - }); - }); - - it('validates req.params.heroId', async () => { - await expect(user.put('/hall/heroes/invalidUUID')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('handles non-existing heroes', async () => { - let dummyId = generateUUID(); - await expect(user.put(`/hall/heroes/${dummyId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: dummyId}), - }); - }); - - it('updates contributor level, balance, ads, blocked', async () => { - let hero = await generateUser(); - let heroRes = await user.put(`/hall/heroes/${hero._id}`, { - balance: 3, - contributor: {level: 1}, - purchased: {ads: true}, - auth: {blocked: true}, - }); - - // test response - expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys - '_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']); - - // test response values - expect(heroRes.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level - expect(heroRes.contributor.level).to.equal(1); - expect(heroRes.purchased.ads).to.equal(true); - expect(heroRes.auth.blocked).to.equal(true); - // test hero values - await hero.sync(); - expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level - expect(hero.contributor.level).to.equal(1); - expect(hero.flags.contributor).to.equal(true); - expect(hero.purchased.ads).to.equal(true); - expect(hero.auth.blocked).to.equal(true); - }); - - it('updates contributor level', async () => { - let hero = await generateUser({ - contributor: {level: 5}, - }); - let heroRes = await user.put(`/hall/heroes/${hero._id}`, { - contributor: {level: 6}, - }); - - // test response - expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys - '_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']); - - // test response values - expect(heroRes.balance).to.equal(1); // 0+1 for sixth contrib level - expect(heroRes.contributor.level).to.equal(6); - expect(heroRes.items.pets['Dragon-Hydra']).to.equal(5); - // test hero values - await hero.sync(); - expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level - expect(hero.contributor.level).to.equal(6); - expect(hero.flags.contributor).to.equal(true); - expect(hero.items.pets['Dragon-Hydra']).to.equal(5); - }); - - it('updates contributor data', async () => { - let hero = await generateUser({ - contributor: {level: 5}, - }); - let heroRes = await user.put(`/hall/heroes/${hero._id}`, { - contributor: {text: 'Astronaut'}, - }); - - // test response - expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys - '_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']); - - // test response values - expect(heroRes.contributor.level).to.equal(5); // doesn't modify previous values - expect(heroRes.contributor.text).to.equal('Astronaut'); - // test hero values - await hero.sync(); - expect(hero.contributor.level).to.equal(5); // doesn't modify previous values - expect(hero.contributor.text).to.equal('Astronaut'); - }); - - it('updates items', async () => { - let hero = await generateUser(); - let heroRes = await user.put(`/hall/heroes/${hero._id}`, { - itemPath: 'items.special.snowball', - itemVal: 5, - }); - - // test response - expect(heroRes).to.have.all.keys([ // works as: object has all and only these keys - '_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']); - - // test response values - expect(heroRes.items.special.snowball).to.equal(5); - // test hero values - await hero.sync(); - expect(hero.items.special.snowball).to.equal(5); - }); -}); diff --git a/test/api/v3/integration/members/GET-members_id.test.js b/test/api/v3/integration/members/GET-members_id.test.js deleted file mode 100644 index d8ac3c4119..0000000000 --- a/test/api/v3/integration/members/GET-members_id.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /members/:memberId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('validates req.params.memberId', async () => { - await expect(user.get('/members/invalidUUID')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns a member public data only', async () => { - let member = await generateUser({ // make sure user has all the fields that can be returned by the getMember call - contributor: {level: 1}, - backer: {tier: 3}, - preferences: { - costume: false, - background: 'volcano', - }, - }); - let memberRes = await user.get(`/members/${member._id}`); - expect(memberRes).to.have.all.keys([ // works as: object has all and only these keys - '_id', 'id', 'preferences', 'profile', 'stats', 'achievements', 'party', - 'backer', 'contributor', 'auth', 'items', - ]); - expect(Object.keys(memberRes.auth)).to.eql(['timestamps']); - expect(Object.keys(memberRes.preferences).sort()).to.eql(['size', 'hair', 'skin', 'shirt', - 'costume', 'sleep', 'background'].sort()); - }); - - it('handles non-existing members', async () => { - let dummyId = generateUUID(); - await expect(user.get(`/members/${dummyId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: dummyId}), - }); - }); -}); diff --git a/test/api/v3/integration/members/POST-send_private_message.test.js b/test/api/v3/integration/members/POST-send_private_message.test.js deleted file mode 100644 index 3bd3380437..0000000000 --- a/test/api/v3/integration/members/POST-send_private_message.test.js +++ /dev/null @@ -1,107 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /members/send-private-message', () => { - let userToSendMessage; - let messageToSend = 'Test Private Message'; - - beforeEach(async () => { - userToSendMessage = await generateUser(); - }); - - it('returns error when message is not provided', async () => { - await expect(userToSendMessage.post('/members/send-private-message')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when toUserId is not provided', async () => { - await expect(userToSendMessage.post('/members/send-private-message', { - message: messageToSend, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when to user is not found', async () => { - await expect(userToSendMessage.post('/members/send-private-message', { - message: messageToSend, - toUserId: generateUUID(), - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userNotFound'), - }); - }); - - it('returns error when to user has blocked the sender', async () => { - let receiver = await generateUser({'inbox.blocks': [userToSendMessage._id]}); - - await expect(userToSendMessage.post('/members/send-private-message', { - message: messageToSend, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notAuthorizedToSendMessageToThisUser'), - }); - }); - - it('returns error when sender has blocked to user', async () => { - let receiver = await generateUser(); - let sender = await generateUser({'inbox.blocks': [receiver._id]}); - - await expect(sender.post('/members/send-private-message', { - message: messageToSend, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notAuthorizedToSendMessageToThisUser'), - }); - }); - - it('returns error when to user has opted out of messaging', async () => { - let receiver = await generateUser({'inbox.optOut': true}); - - await expect(userToSendMessage.post('/members/send-private-message', { - message: messageToSend, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notAuthorizedToSendMessageToThisUser'), - }); - }); - - it('sends a private message to a user', async () => { - let receiver = await generateUser(); - - await userToSendMessage.post('/members/send-private-message', { - message: messageToSend, - toUserId: receiver._id, - }); - - let updatedReceiver = await receiver.get('/user'); - let updatedSender = await userToSendMessage.get('/user'); - - let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (message) => { - return message.uuid === userToSendMessage._id && message.text === messageToSend; - }); - - let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (message) => { - return message.uuid === receiver._id && message.text === messageToSend; - }); - - expect(sendersMessageInReceiversInbox).to.exist; - expect(sendersMessageInSendersInbox).to.exist; - }); -}); diff --git a/test/api/v3/integration/members/POST-transfer_gems.test.js b/test/api/v3/integration/members/POST-transfer_gems.test.js deleted file mode 100644 index 96644a3e88..0000000000 --- a/test/api/v3/integration/members/POST-transfer_gems.test.js +++ /dev/null @@ -1,174 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /members/transfer-gems', () => { - let userToSendMessage; - let receiver; - let message = 'Test Private Message'; - let gemAmount = 20; - - beforeEach(async () => { - userToSendMessage = await generateUser({balance: 5}); - receiver = await generateUser(); - }); - - it('returns error when no parameters are provided', async () => { - await expect(userToSendMessage.post('/members/transfer-gems')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when toUserId is not provided', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when to user is not found', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount, - toUserId: generateUUID(), - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userNotFound'), - }); - }); - - it('returns error when to user attempts to send gems to themselves', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount, - toUserId: userToSendMessage._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cannotSendGemsToYourself'), - }); - }); - - it('returns error when there is no gemAmount', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when gemAmount is not an integer', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount: 1.5, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when gemAmount is negative', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount: -5, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('badAmountOfGemsToSend'), - }); - }); - - it('returns error when gemAmount is more than the sender\'s balance', async () => { - await expect(userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount: gemAmount + 4, - toUserId: receiver._id, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('badAmountOfGemsToSend'), - }); - }); - - it('sends a private message about gems to a user', async () => { - await userToSendMessage.post('/members/transfer-gems', { - message, - gemAmount, - toUserId: receiver._id, - }); - - let updatedReceiver = await receiver.get('/user'); - let updatedSender = await userToSendMessage.get('/user'); - - let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === userToSendMessage._id; - }); - - let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === receiver._id; - }); - - let messageSentContent = t('privateMessageGiftIntro', { - receiverName: receiver.profile.name, - senderName: userToSendMessage.profile.name, - }); - messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount}); - messageSentContent += message; - - expect(sendersMessageInReceiversInbox).to.exist; - expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent); - expect(updatedReceiver.balance).to.equal(gemAmount / 4); - - expect(sendersMessageInSendersInbox).to.exist; - expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent); - expect(updatedSender.balance).to.equal(0); - }); - - it('does not requrie a message', async () => { - await userToSendMessage.post('/members/transfer-gems', { - gemAmount, - toUserId: receiver._id, - }); - - let updatedReceiver = await receiver.get('/user'); - let updatedSender = await userToSendMessage.get('/user'); - - let sendersMessageInReceiversInbox = _.find(updatedReceiver.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === userToSendMessage._id; - }); - - let sendersMessageInSendersInbox = _.find(updatedSender.inbox.messages, (inboxMessage) => { - return inboxMessage.uuid === receiver._id; - }); - - let messageSentContent = t('privateMessageGiftIntro', { - receiverName: receiver.profile.name, - senderName: userToSendMessage.profile.name, - }); - messageSentContent += t('privateMessageGiftGemsMessage', {gemAmount}); - - expect(sendersMessageInReceiversInbox).to.exist; - expect(sendersMessageInReceiversInbox.text).to.equal(messageSentContent); - expect(updatedReceiver.balance).to.equal(gemAmount / 4); - - expect(sendersMessageInSendersInbox).to.exist; - expect(sendersMessageInSendersInbox.text).to.equal(messageSentContent); - expect(updatedSender.balance).to.equal(0); - }); -}); diff --git a/test/api/v3/integration/models/GET-model_paths.test.js b/test/api/v3/integration/models/GET-model_paths.test.js deleted file mode 100644 index 0a9a94451a..0000000000 --- a/test/api/v3/integration/models/GET-model_paths.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /models/:model/paths', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns an error when model is not accessible or doesn\'t exists', async () => { - await expect(user.get('/models/1234/paths')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - let models = ['habit', 'daily', 'todo', 'reward', 'user', 'tag', 'challenge', 'group']; - models.forEach(model => { - it(`returns the model paths for ${model}`, async () => { - let res = await user.get(`/models/${model}/paths`); - - if (model !== 'tag') expect(res._id).to.equal('String'); - if (model === 'tag') expect(res.id).to.equal('String'); - - expect(res).to.not.have.keys('__v'); - }); - }); -}); diff --git a/test/api/v3/integration/notFound.test.js b/test/api/v3/integration/notFound.test.js deleted file mode 100644 index 747b370af9..0000000000 --- a/test/api/v3/integration/notFound.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import { requester } from '../../../helpers/api-integration/v3'; - -describe('notFound Middleware', () => { - it('returns a 404 error when the resource is not found', async () => { - let request = requester().get('/api/v3/dummy-url'); - - await expect(request).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: 'Not found.', - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js deleted file mode 100644 index 37588d1f18..0000000000 --- a/test/api/v3/integration/payments/GET-payments_amazon_subscribe_cancel.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('payments : amazon #subscribeCancel', () => { - let endpoint = '/amazon/subscribe/cancel'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies subscription', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js deleted file mode 100644 index 7c692f31d1..0000000000 --- a/test/api/v3/integration/payments/GET-payments_paypal_checkout.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -xdescribe('payments : paypal #checkout', () => { - let endpoint = '/paypal/checkout'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies subscription', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js deleted file mode 100644 index 6de04c8848..0000000000 --- a/test/api/v3/integration/payments/GET-payments_paypal_checkout_success.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -xdescribe('payments : paypal #checkoutSuccess', () => { - let endpoint = '/paypal/checkout/success'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies subscription', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js deleted file mode 100644 index 54c540ee39..0000000000 --- a/test/api/v3/integration/payments/GET-payments_paypal_subscribe.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -xdescribe('payments : paypal #subscribe', () => { - let endpoint = '/paypal/subscribe'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js deleted file mode 100644 index 1ba8b7af16..0000000000 --- a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_cancel.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('payments : paypal #subscribeCancel', () => { - let endpoint = '/paypal/subscribe/cancel'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js b/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js deleted file mode 100644 index 1a38342e9c..0000000000 --- a/test/api/v3/integration/payments/GET-payments_paypal_subscribe_success.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -xdescribe('payments : paypal #subscribeSuccess', () => { - let endpoint = '/paypal/subscribe/success'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js b/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js deleted file mode 100644 index 6d7ac87d0f..0000000000 --- a/test/api/v3/integration/payments/GET-payments_stripe_subscribe_cancel.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - stripe - #subscribeCancel', () => { - let endpoint = '/stripe/subscribe/cancel'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.get(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js b/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js deleted file mode 100644 index 8745a74e85..0000000000 --- a/test/api/v3/integration/payments/POST-payments_amazon_checkout.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - amazon - #checkout', () => { - let endpoint = '/amazon/checkout'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Missing req.body.orderReferenceId', - }); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js b/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js deleted file mode 100644 index 17a50520eb..0000000000 --- a/test/api/v3/integration/payments/POST-payments_amazon_createOrderReferenceId.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - amazon - #createOrderReferenceId', () => { - let endpoint = '/amazon/createOrderReferenceId'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies billingAgreementId', async (done) => { - try { - await user.post(endpoint); - } catch (e) { - // Parameter AWSAccessKeyId cannot be empty. - expect(e.error).to.eql('BadRequest'); - done(); - } - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js b/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js deleted file mode 100644 index 5c3b98ad87..0000000000 --- a/test/api/v3/integration/payments/POST-payments_amazon_subscribe.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - amazon - #subscribe', () => { - let endpoint = '/amazon/subscribe'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies subscription code', async () => { - await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('missingSubscriptionCode'), - }); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js b/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js deleted file mode 100644 index 51ccf8c41c..0000000000 --- a/test/api/v3/integration/payments/POST-payments_amazon_verifyAccessToken.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('payments : amazon', () => { - let endpoint = '/amazon/verifyAccessToken'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies access token', async () => { - await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Missing req.body.access_token', - }); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js b/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js deleted file mode 100644 index 219e9ce35b..0000000000 --- a/test/api/v3/integration/payments/POST-payments_paypal_ipn.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - paypal - #ipn', () => { - let endpoint = '/paypal/ipn'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - let result = await user.post(endpoint); - expect(result).to.eql('OK'); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js b/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js deleted file mode 100644 index 1443a3af74..0000000000 --- a/test/api/v3/integration/payments/POST-payments_stripe_checkout.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - stripe - #checkout', () => { - let endpoint = '/stripe/checkout'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.post(endpoint, {id: 123})).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'Error', - message: 'Invalid API Key provided: ****************************1111', - }); - }); -}); diff --git a/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js b/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js deleted file mode 100644 index d6d568ace4..0000000000 --- a/test/api/v3/integration/payments/POST-payments_stripe_subscribe_edit.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('payments - stripe - #subscribeEdit', () => { - let endpoint = '/stripe/subscribe/edit'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('verifies credentials', async () => { - await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('missingSubscription'), - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js deleted file mode 100644 index 665a185279..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_accept.test.js +++ /dev/null @@ -1,119 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /groups/:groupId/quests/accept', () => { - const PET_QUEST = 'whale'; - - let questingGroup; - let leader; - let partyMembers; - let user; - - beforeEach(async () => { - user = await generateUser(); - - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - }); - - context('failure conditions', () => { - it('does not accept quest without an invite', async () => { - await expect(leader.post(`/groups/${questingGroup._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questInviteNotFound'), - }); - }); - - it('does not accept quest for a group in which user is not a member', async () => { - await expect(user.post(`/groups/${questingGroup._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not accept quest for a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('does not accept invite twice', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('questAlreadyAccepted'), - }); - }); - - it('does not accept invite for a quest already underway', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - // quest will start after everyone has accepted - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questAlreadyUnderway'), - }); - }); - }); - - context('successfully accepting a quest invitation', () => { - it('joins a quest from an invitation', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - - await Promise.all([partyMembers[0].sync(), questingGroup.sync()]); - expect(leader.party.quest.RSVPNeeded).to.equal(false); - expect(questingGroup.quest.members[partyMembers[0]._id]); - }); - - it('does not begin the quest if pending invitations remain', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - - await questingGroup.sync(); - expect(questingGroup.quest.active).to.equal(false); - }); - - it('begins the quest if accepting the last pending invite', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - // quest will start after everyone has accepted - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await questingGroup.sync(); - expect(questingGroup.quest.active).to.equal(true); - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js deleted file mode 100644 index b6d43f826b..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_force-start.test.js +++ /dev/null @@ -1,126 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('POST /groups/:groupId/quests/force-start', () => { - const PET_QUEST = 'whale'; - - let questingGroup; - let leader; - let partyMembers; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - }); - - context('failure conditions', () => { - it('does not force start a quest for a group in which user is not a member', async () => { - let nonMember = await generateUser(); - - await expect(nonMember.post(`/groups/${questingGroup._id}/quests/force-start`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not force start quest for a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/force-start`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('does not force start for a party without a pending quest', async () => { - await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questNotPending'), - }); - }); - - it('does not force start for a quest already underway', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - // quest will start after everyone has accepted - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(leader.post(`/groups/${questingGroup._id}/quests/force-start`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questAlreadyUnderway'), - }); - }); - - it('does not allow non-quest leader or non-group leader to force start a quest', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/force-start`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questOrGroupLeaderOnlyStartQuest'), - }); - }); - }); - - context('successfully force starting a quest', () => { - it('allows quest leader to force start quest', async () => { - let questLeader = partyMembers[0]; - await questLeader.update({[`items.quests.${PET_QUEST}`]: 1}); - await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await questLeader.post(`/groups/${questingGroup._id}/quests/force-start`); - - await questingGroup.sync(); - - expect(questingGroup.quest.active).to.eql(true); - }); - - it('allows group leader to force start quest', async () => { - let questLeader = partyMembers[0]; - await questLeader.update({[`items.quests.${PET_QUEST}`]: 1}); - await questLeader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await leader.post(`/groups/${questingGroup._id}/quests/force-start`); - - await questingGroup.sync(); - - expect(questingGroup.quest.active).to.eql(true); - }); - - it('sends back the quest object', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - let quest = await leader.post(`/groups/${questingGroup._id}/quests/force-start`); - - expect(quest.active).to.eql(true); - expect(quest.key).to.eql(PET_QUEST); - expect(quest.members).to.eql({ - [`${leader._id}`]: true, - }); - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js deleted file mode 100644 index 973a51a1a5..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js +++ /dev/null @@ -1,192 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - sleep, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; -import { quests as questScrolls } from '../../../../../common/script/content'; - -describe('POST /groups/:groupId/quests/invite/:questKey', () => { - let questingGroup; - let leader; - let member; - const PET_QUEST = 'whale'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 1, - }); - - questingGroup = group; - leader = groupLeader; - member = members[0]; - }); - - context('failure conditions', () => { - it('does not issue invites with an invalid group ID', async () => { - await expect(leader.post(`/groups/${generateUUID()}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not issue invites for a group in which user is not a member', async () => { - let { group } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 1, - }); - - let alternateGroup = group; - - await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not issue invites for Guilds', async () => { - let { group } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'public' }, - members: 1, - }); - - let alternateGroup = group; - - await expect(leader.post(`/groups/${alternateGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('does not issue invites with an invalid quest key', async () => { - const FAKE_QUEST = 'herkimer'; - - await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${FAKE_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questNotFound', {key: FAKE_QUEST}), - }); - }); - - it('does not issue invites for a quest the user does not own', async () => { - await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questNotOwned'), - }); - }); - - it('does not issue invites if the user is of insufficient Level', async () => { - const LEVELED_QUEST = 'atom1'; - const LEVELED_QUEST_REQ = questScrolls[LEVELED_QUEST].lvl; - const leaderUpdate = {}; - leaderUpdate[`items.quests.${LEVELED_QUEST}`] = 1; - leaderUpdate['stats.lvl'] = LEVELED_QUEST_REQ - 1; - - await leader.update(leaderUpdate); - - await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${LEVELED_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questLevelTooHigh', {level: LEVELED_QUEST_REQ}), - }); - }); - - it('does not issue invites if a quest is already underway', async () => { - const QUEST_IN_PROGRESS = 'atom1'; - const leaderUpdate = {}; - leaderUpdate[`items.quests.${PET_QUEST}`] = 1; - - await leader.update(leaderUpdate); - await questingGroup.update({ 'quest.key': QUEST_IN_PROGRESS }); - - await expect(leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questAlreadyUnderway'), - }); - }); - }); - - context('successfully issuing a quest invitation', () => { - beforeEach(async () => { - const memberUpdate = {}; - memberUpdate[`items.quests.${PET_QUEST}`] = 1; - - await Promise.all([ - leader.update(memberUpdate), - member.update(memberUpdate), - ]); - }); - - it('adds quest details to group object', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await questingGroup.sync(); - - let quest = questingGroup.quest; - - expect(quest.key).to.eql(PET_QUEST); - expect(quest.active).to.eql(false); - expect(quest.leader).to.eql(leader._id); - expect(quest.members).to.have.property(leader._id, true); - expect(quest.members).to.have.property(member._id, null); - expect(quest).to.have.property('progress'); - }); - - it('adds quest details to user objects', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await sleep(0.1); // member updates happen in the background - - await Promise.all([ - leader.sync(), - member.sync(), - ]); - - expect(leader.party.quest.key).to.eql(PET_QUEST); - expect(member.party.quest.key).to.eql(PET_QUEST); - expect(leader.party.quest.RSVPNeeded).to.eql(false); - expect(member.party.quest.RSVPNeeded).to.eql(true); - }); - - it('sends back the quest object', async () => { - let inviteResponse = await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - expect(inviteResponse.key).to.eql(PET_QUEST); - expect(inviteResponse.active).to.eql(false); - expect(inviteResponse.leader).to.eql(leader._id); - expect(inviteResponse.members).to.have.property(leader._id, true); - expect(inviteResponse.members).to.have.property(member._id, null); - expect(inviteResponse).to.have.property('progress'); - }); - - it('allows non-party-leader party members to send invites', async () => { - let inviteResponse = await member.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await questingGroup.sync(); - - expect(inviteResponse.key).to.eql(PET_QUEST); - expect(questingGroup.quest.key).to.eql(PET_QUEST); - }); - - it('starts quest automatically if user is in a solo party', async () => { - let leaderDetails = { balance: 10 }; - leaderDetails[`items.quests.${PET_QUEST}`] = 1; - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - leaderDetails, - }); - - await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`); - - await group.sync(); - - expect(group.quest.active).to.eql(true); - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js deleted file mode 100644 index 850cf53646..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_abort.test.js +++ /dev/null @@ -1,126 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /groups/:groupId/quests/abort', () => { - let questingGroup; - let partyMembers; - let user; - let leader; - - const PET_QUEST = 'whale'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - user = await generateUser(); - }); - - context('failure conditions', () => { - it('returns an error when group is not found', async () => { - await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/abort`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error for a group in which user is not a member', async () => { - await expect(user.post(`/groups/${questingGroup._id}/quests/abort`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error when group is a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/abort`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('returns an error when quest is not active', async () => { - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('noActiveQuestToAbort'), - }); - }); - - it('returns an error when non quest leader attempts to abort', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/abort`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyLeaderAbortQuest'), - }); - }); - }); - - it('aborts a quest', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - let res = await leader.post(`/groups/${questingGroup._id}/quests/abort`); - await Promise.all([ - leader.sync(), - questingGroup.sync(), - partyMembers[0].sync(), - partyMembers[1].sync(), - ]); - - let cleanUserQuestObj = { - key: null, - progress: { - up: 0, - down: 0, - collect: {}, - }, - completed: null, - RSVPNeeded: false, - }; - - expect(leader.party.quest).to.eql(cleanUserQuestObj); - expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj); - expect(partyMembers[1].party.quest).to.eql(cleanUserQuestObj); - expect(leader.items.quests[PET_QUEST]).to.equal(1); - expect(questingGroup.quest).to.deep.equal(res); - expect(questingGroup.quest).to.eql({ - key: null, - active: false, - leader: null, - progress: { - collect: {}, - }, - members: {}, - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js deleted file mode 100644 index f3bd03a180..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_cancel.test.js +++ /dev/null @@ -1,138 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /groups/:groupId/quests/cancel', () => { - let questingGroup; - let partyMembers; - let user; - let leader; - - const PET_QUEST = 'whale'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - user = await generateUser(); - }); - - context('failure conditions', () => { - it('returns an error when group is not found', async () => { - await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not reject quest for a group in which user is not a member', async () => { - await expect(user.post(`/groups/${questingGroup._id}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error when group is a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('returns an error when group is not on a quest', async () => { - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questInvitationDoesNotExist'), - }); - }); - - it('only the leader can cancel the quest', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyLeaderCancelQuest'), - }); - }); - - it('does not cancel a quest already underway', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - // quest will start after everyone has accepted - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(leader.post(`/groups/${questingGroup._id}/quests/cancel`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantCancelActiveQuest'), - }); - }); - }); - - it('cancels a quest', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - - let res = await leader.post(`/groups/${questingGroup._id}/quests/cancel`); - - await Promise.all([ - leader.sync(), - partyMembers[0].sync(), - partyMembers[1].sync(), - questingGroup.sync(), - ]); - - let clean = { - key: null, - progress: { - up: 0, - down: 0, - collect: {}, - }, - completed: null, - RSVPNeeded: false, - }; - - expect(leader.party.quest).to.eql(clean); - expect(partyMembers[1].party.quest).to.eql(clean); - expect(partyMembers[0].party.quest).to.eql(clean); - - expect(res).to.eql(questingGroup.quest); - expect(questingGroup.quest).to.eql({ - key: null, - active: false, - leader: null, - progress: { - collect: {}, - }, - members: {}, - }); - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js deleted file mode 100644 index 65d781c163..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_leave.test.js +++ /dev/null @@ -1,124 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /groups/:groupId/quests/leave', () => { - let questingGroup; - let partyMembers; - let user; - let leader; - - const PET_QUEST = 'whale'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - user = await generateUser(); - }); - - context('failure conditions', () => { - it('returns an error when group is not found', async () => { - await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error for a group in which user is not a member', async () => { - await expect(user.post(`/groups/${questingGroup._id}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error when group is a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('returns an error when quest is not active', async () => { - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('noActiveQuestToLeave'), - }); - }); - - it('returns an error when quest leader attempts to leave', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(leader.post(`/groups/${questingGroup._id}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questLeaderCannotLeaveQuest'), - }); - }); - - it('returns an error when non quest member attempts to leave', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`); - - await expect(partyMembers[1].post(`/groups/${questingGroup._id}/quests/leave`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notPartOfQuest'), - }); - }); - }); - - it('leaves a quest', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - let leaveResult = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/leave`); - await Promise.all([ - partyMembers[0].sync(), - questingGroup.sync(), - ]); - - expect(partyMembers[0].party.quest).to.eql({ - key: null, - progress: { - up: 0, - down: 0, - collect: {}, - }, - completed: null, - RSVPNeeded: false, - }); - expect(questingGroup.quest).to.deep.equal(leaveResult); - expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false; - }); -}); diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js deleted file mode 100644 index 2dcddfe727..0000000000 --- a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js +++ /dev/null @@ -1,146 +0,0 @@ -import { - createAndPopulateGroup, - translate as t, - generateUser, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /groups/:groupId/quests/reject', () => { - let questingGroup; - let partyMembers; - let user; - let leader; - - const PET_QUEST = 'whale'; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 2, - }); - - questingGroup = group; - leader = groupLeader; - partyMembers = members; - - await leader.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - user = await generateUser(); - }); - - context('failure conditions', () => { - it('returns an error when group is not found', async () => { - await expect(partyMembers[0].post(`/groups/${generateUUID()}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('does not accept quest for a group in which user is not a member', async () => { - await expect(user.post(`/groups/${questingGroup._id}/quests/accept`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('groupNotFound'), - }); - }); - - it('returns an error when group is a guild', async () => { - let { group: guild, groupLeader: guildLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'guild', privacy: 'private' }, - }); - - await expect(guildLeader.post(`/groups/${guild._id}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('guildQuestsNotSupported'), - }); - }); - - it('returns an error when group is not on a quest', async () => { - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questInvitationDoesNotExist'), - }); - }); - - it('return an error when a user rejects an invite twice', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('questAlreadyRejected'), - }); - }); - - it('return an error when a user rejects an invite already accepted', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('questAlreadyAccepted'), - }); - }); - - it('does not reject invite for a quest already underway', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - // quest will start after everyone has accepted - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/accept`); - - await expect(partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('questAlreadyUnderway'), - }); - }); - }); - - context('successfully quest rejection', () => { - let cleanUserQuestObj = { - key: null, - progress: { - up: 0, - down: 0, - collect: {}, - }, - completed: null, - RSVPNeeded: false, - }; - - it('rejects a quest invitation', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - - let res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`); - await partyMembers[0].sync(); - await questingGroup.sync(); - - expect(partyMembers[0].party.quest).to.eql(cleanUserQuestObj); - expect(questingGroup.quest.members[partyMembers[0]._id]).to.be.false; - expect(questingGroup.quest.active).to.be.false; - expect(res).to.eql(questingGroup.quest); - }); - - it('starts the quest when the last user reject', async () => { - await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); - await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`); - await partyMembers[1].post(`/groups/${questingGroup._id}/quests/reject`); - await questingGroup.sync(); - - expect(questingGroup.quest.active).to.be.true; - }); - }); -}); diff --git a/test/api/v3/integration/status/GET-status.test.js b/test/api/v3/integration/status/GET-status.test.js deleted file mode 100644 index 1d4d33a7d7..0000000000 --- a/test/api/v3/integration/status/GET-status.test.js +++ /dev/null @@ -1,12 +0,0 @@ -import { - requester, -} from '../../../../helpers/api-v3-integration.helper'; - -describe('GET /status', () => { - it('returns status: up', async () => { - let res = await requester().get('/status'); - expect(res).to.eql({ - status: 'up', - }); - }); -}); diff --git a/test/api/v3/integration/tags/DELETE-tags_id.test.js b/test/api/v3/integration/tags/DELETE-tags_id.test.js deleted file mode 100644 index c03e8bb9e0..0000000000 --- a/test/api/v3/integration/tags/DELETE-tags_id.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('DELETE /tags/:tagId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('deletes a tag given it\'s id', async () => { - let tagName = 'Tag 1'; - let tag = await user.post('/tags', {name: tagName}); - let numberOfTags = (await user.get('/tags')).length; - - await user.del(`/tags/${tag.id}`); - - let tags = await user.get('/tags'); - let tagNames = tags.map((t) => { - return t.name; - }); - - expect(tags.length).to.equal(numberOfTags - 1); - expect(tagNames).to.not.include(tagName); - }); -}); diff --git a/test/api/v3/integration/tags/GET-tags.test.js b/test/api/v3/integration/tags/GET-tags.test.js deleted file mode 100644 index 7a24963474..0000000000 --- a/test/api/v3/integration/tags/GET-tags.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /tags', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns all user\'s tags', async () => { - let tag1 = await user.post('/tags', {name: 'Tag 1'}); - let tag2 = await user.post('/tags', {name: 'Tag 2'}); - - let tags = await user.get('/tags'); - - expect(tags.length).to.equal(2 + 3); // + 3 because 1 is a default task - expect(tags[tags.length - 2].name).to.equal(tag1.name); - expect(tags[tags.length - 1].name).to.equal(tag2.name); - }); -}); diff --git a/test/api/v3/integration/tags/GET-tags_id.test.js b/test/api/v3/integration/tags/GET-tags_id.test.js deleted file mode 100644 index 4ab818593d..0000000000 --- a/test/api/v3/integration/tags/GET-tags_id.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /tags/:tagId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns a tag given it\'s id', async () => { - let createdTag = await user.post('/tags', {name: 'Tag 1'}); - let tag = await user.get(`/tags/${createdTag.id}`); - - expect(tag).to.deep.equal(createdTag); - }); - - it('handles non-existing tags'); -}); diff --git a/test/api/v3/integration/tags/POST-tag-reorder.test.js b/test/api/v3/integration/tags/POST-tag-reorder.test.js deleted file mode 100644 index 0710cecf3e..0000000000 --- a/test/api/v3/integration/tags/POST-tag-reorder.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /reorder-tags', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns error when no parameters are provided', async () => { - await expect(user.post('/reorder-tags')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'Invalid request parameters.', - }); - }); - - it('returns error when tag is not found', async () => { - await expect(user.post('/reorder-tags', {tagId: 'fake-id', to: 3})) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('tagNotFound'), - }); - }); - - it('updates tags', async () => { - let tag1Name = 'Tag 1'; - let tag2Name = 'Tag 2'; - await user.post('/tags', {name: tag1Name}); - await user.post('/tags', {name: tag2Name}); - await user.sync(); - - await user.post('/reorder-tags', {tagId: user.tags[4].id, to: 3}); - await user.sync(); - - expect(user.tags[3].name).to.equal(tag2Name); - expect(user.tags[4].name).to.equal(tag1Name); - }); -}); diff --git a/test/api/v3/integration/tags/POST-tags.test.js b/test/api/v3/integration/tags/POST-tags.test.js deleted file mode 100644 index 93f2dfdb60..0000000000 --- a/test/api/v3/integration/tags/POST-tags.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /tags', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('creates a tag correctly', async () => { - let tagName = 'Tag 1'; - let createdTag = await user.post('/tags', { - name: tagName, - ignored: false, - }); - - let tag = await user.get(`/tags/${createdTag.id}`); - - expect(tag.name).to.equal(tagName); - expect(tag.ignored).to.not.exist; - expect(tag).to.deep.equal(createdTag); - }); -}); diff --git a/test/api/v3/integration/tags/PUT-tags_id.test.js b/test/api/v3/integration/tags/PUT-tags_id.test.js deleted file mode 100644 index 4c16453ac3..0000000000 --- a/test/api/v3/integration/tags/PUT-tags_id.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('PUT /tags/:tagId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('updates a tag given it\'s id', async () => { - let updatedTagName = 'Tag updated'; - let createdTag = await user.post('/tags', {name: 'Tag 1'}); - let updatedTag = await user.put(`/tags/${createdTag.id}`, { - name: updatedTagName, - ignored: true, - }); - - createdTag = await user.get(`/tags/${updatedTag.id}`); - - expect(updatedTag.name).to.equal(updatedTagName); - expect(updatedTag.ignored).to.not.exist; - - expect(createdTag.name).to.equal(updatedTagName); - expect(createdTag.ignored).to.not.exist; - }); -}); diff --git a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js b/test/api/v3/integration/tasks/DELETE-tasks_id.test.js deleted file mode 100644 index bb92e4759f..0000000000 --- a/test/api/v3/integration/tasks/DELETE-tasks_id.test.js +++ /dev/null @@ -1,59 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('DELETE /tasks/:id', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - context('task can be deleted', () => { - let task; - - beforeEach(async () => { - task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - }); - - it('deletes a user\'s task', async () => { - await user.del(`/tasks/${task._id}`); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - }); - - context('task cannot be deleted', () => { - it('cannot delete a non-existant task', async () => { - await expect(user.del('/tasks/550e8400-e29b-41d4-a716-446655440000')).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('cannot delete a task owned by someone else', async () => { - let anotherUser = await generateUser(); - let anotherUsersTask = await anotherUser.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - - await expect(user.del(`/tasks/${anotherUsersTask._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('removes a task from user.tasksOrder'); // TODO - }); -}); diff --git a/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js deleted file mode 100644 index 3fed4cfaed..0000000000 --- a/test/api/v3/integration/tasks/GET-tasks_challenge_challengeId.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { each } from 'lodash'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /tasks/:taskId', () => { - let user; - let guild; - let challenge; - let task; - let tasksToTest = { - habit: { - text: 'test habit', - type: 'habit', - up: false, - down: true, - }, - todo: { - text: 'test todo', - type: 'todo', - }, - daily: { - text: 'test daily', - type: 'daily', - frequency: 'daily', - everyX: 5, - startDate: new Date(), - }, - reward: { - text: 'test reward', - type: 'reward', - }, - }; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('returns error when incorrect id is passed', async () => { - await expect(user.get(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - each(tasksToTest, (taskValue, taskType) => { - context(`${taskType}`, () => { - before(async () => { - task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue); - }); - - it('gets challenge task', async () => { - let getTask = await user.get(`/tasks/${task._id}`); - expect(getTask).to.eql(task); - }); - - it('returns error when user is not a member of the challenge', async () => { - let anotherUser = await generateUser(); - - await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/GET-tasks_id.test.js b/test/api/v3/integration/tasks/GET-tasks_id.test.js deleted file mode 100644 index 99f2d769f4..0000000000 --- a/test/api/v3/integration/tasks/GET-tasks_id.test.js +++ /dev/null @@ -1,58 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /tasks/:id', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - context('task can be accessed', async () => { - let task; - - beforeEach(async () => { - task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - }); - - it('gets specified task', async () => { - let getTask = await user.get(`/tasks/${task._id}`); - expect(getTask).to.eql(task); - }); - - // TODO after challenges are implemented - it('can get active challenge task that user does not own'); // Yes? - }); - - context('task cannot be accessed', () => { - it('cannot get a non-existant task', async () => { - let dummyId = generateUUID(); - - await expect(user.get(`/tasks/${dummyId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('cannot get a task owned by someone else', async () => { - let anotherUser = await generateUser(); - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - - await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/GET-tasks_user.test.js b/test/api/v3/integration/tasks/GET-tasks_user.test.js deleted file mode 100644 index 406c0d43fb..0000000000 --- a/test/api/v3/integration/tasks/GET-tasks_user.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /tasks/user', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns all user\'s tasks', async () => { - let createdTasks = await user.post('/tasks/user', [{text: 'test habit', type: 'habit'}, {text: 'test todo', type: 'todo'}]); - let tasks = await user.get('/tasks/user'); - expect(tasks.length).to.equal(createdTasks.length + 1); // + 1 because 1 is a default task - }); - - it('returns only a type of user\'s tasks if req.query.type is specified', async () => { - let createdTasks = await user.post('/tasks/user', [ - {text: 'test habit', type: 'habit'}, - {text: 'test daily', type: 'daily'}, - {text: 'test reward', type: 'reward'}, - {text: 'test todo', type: 'todo'}, - ]); - let habits = await user.get('/tasks/user?type=habits'); - let dailys = await user.get('/tasks/user?type=dailys'); - let rewards = await user.get('/tasks/user?type=rewards'); - - expect(habits.length).to.be.at.least(1); - expect(habits[0]._id).to.equal(createdTasks[0]._id); - expect(dailys.length).to.be.at.least(1); - expect(dailys[0]._id).to.equal(createdTasks[1]._id); - expect(rewards.length).to.be.at.least(1); - expect(rewards[0]._id).to.equal(createdTasks[2]._id); - }); - - it('returns uncompleted todos if req.query.type is "todos"', async () => { - let existingTodos = await user.get('/tasks/user?type=todos'); - - // populate user with other task types - await user.post('/tasks/user', [ - {text: 'daily', type: 'daily'}, - {text: 'reward', type: 'reward'}, - {text: 'habit', type: 'habit'}, - ]); - - let newUncompletedTodos = await user.post('/tasks/user', [ - {text: 'test todo 1', type: 'todo'}, - {text: 'test todo 2', type: 'todo'}, - ]); - let todoToBeCompleted = await user.post('/tasks/user', { - text: 'wll be completed todo', type: 'todo', - }); - - await user.post(`/tasks/${todoToBeCompleted._id}/score/up`); - - let uncompletedTodos = [...existingTodos, ...newUncompletedTodos]; - - let todos = await user.get('/tasks/user?type=todos'); - - expect(todos.length).to.be.gte(2); - expect(todos.length).to.eql(uncompletedTodos.length); - expect(todos.every(task => task.type === 'todo')); - expect(todos.every(task => task.completed === false)); - }); - - it('returns completed todos sorted by reverse completion date if req.query.type is "completeTodos"', async () => { - let todo1 = await user.post('/tasks/user', {text: 'todo to complete 1', type: 'todo'}); - let todo2 = await user.post('/tasks/user', {text: 'todo to complete 2', type: 'todo'}); - - await user.sync(); - let initialTodoCount = user.tasksOrder.todos.length; - - await user.post(`/tasks/${todo2._id}/score/up`); - await user.post(`/tasks/${todo1._id}/score/up`); - await user.sync(); - - expect(user.tasksOrder.todos.length).to.equal(initialTodoCount - 2); - - let completedTodos = await user.get('/tasks/user?type=completedTodos'); - expect(completedTodos.length).to.equal(2); - expect(completedTodos[completedTodos.length - 1].text).to.equal('todo to complete 2'); // last is the todo that was completed most recently - }); -}); diff --git a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js b/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js deleted file mode 100644 index d6cdf21749..0000000000 --- a/test/api/v3/integration/tasks/POST-tasks_clearCompletedTodos.test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /tasks/clearCompletedTodos', () => { - it('deletes all completed todos except the ones from a challenge', async () => { - let user = await generateUser({balance: 1}); - let guild = await generateGroup(user); - let challenge = await generateChallenge(user, guild); - - let initialTodoCount = user.tasksOrder.todos.length; - await user.post('/tasks/user', [ - {text: 'todo 1', type: 'todo'}, - {text: 'todo 2', type: 'todo'}, - {text: 'todo 3', type: 'todo'}, - {text: 'todo 4', type: 'todo'}, - {text: 'todo 5', type: 'todo'}, - ]); - - await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'todo 6', - type: 'todo', - }); - - let tasks = await user.get('/tasks/user?type=todos'); - expect(tasks.length).to.equal(initialTodoCount + 6); - - for (let task of tasks) { - if (['todo 2', 'todo 3', 'todo 6'].indexOf(task.text) !== -1) { - await user.post(`/tasks/${task._id}/score/up`); // eslint-disable-line babel/no-await-in-loop - } - } - - await user.post('/tasks/clearCompletedTodos'); - let completedTodos = await user.get('/tasks/user?type=completedTodos'); - let todos = await user.get('/tasks/user?type=todos'); - let allTodos = todos.concat(completedTodos); - expect(allTodos.length).to.equal(initialTodoCount + 4); // + 6 - 3 completed (but one is from challenge) - expect(allTodos[allTodos.length - 1].text).to.equal('todo 6'); - }); -}); diff --git a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js deleted file mode 100644 index 86236fd110..0000000000 --- a/test/api/v3/integration/tasks/POST-tasks_id_score_direction.test.js +++ /dev/null @@ -1,298 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:id/score/:direction', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'stats.gp': 100, - }); - }); - - context('all', () => { - it('requires a task id', async () => { - await expect(user.post('/tasks/123/score/up')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires a task direction', async () => { - await expect(user.post(`/tasks/${generateUUID()}/score/tt`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - }); - - context('todos', () => { - let todo; - - beforeEach(async () => { - todo = await user.post('/tasks/user', { - text: 'test todo', - type: 'todo', - }); - }); - - it('completes todo when direction is up', async () => { - await user.post(`/tasks/${todo._id}/score/up`); - let task = await user.get(`/tasks/${todo._id}`); - - expect(task.completed).to.equal(true); - expect(task.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type - }); - - it('moves completed todos out of user.tasksOrder.todos', async () => { - let getUser = await user.get('/user'); - expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1); - - await user.post(`/tasks/${todo._id}/score/up`); - let updatedTask = await user.get(`/tasks/${todo._id}`); - expect(updatedTask.completed).to.equal(true); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(-1); - }); - - it('moves un-completed todos back into user.tasksOrder.todos', async () => { - let getUser = await user.get('/user'); - expect(getUser.tasksOrder.todos.indexOf(todo._id)).to.not.equal(-1); - - await user.post(`/tasks/${todo._id}/score/up`); - await user.post(`/tasks/${todo._id}/score/down`); - - let updatedTask = await user.get(`/tasks/${todo._id}`); - expect(updatedTask.completed).to.equal(false); - - let updatedUser = await user.get('/user'); - let l = updatedUser.tasksOrder.todos.length; - expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).not.to.equal(-1); - expect(updatedUser.tasksOrder.todos.indexOf(todo._id)).to.equal(l - 1); // Check that it was pushed at the bottom - }); - - it('uncompletes todo when direction is down', async () => { - await user.post(`/tasks/${todo._id}/score/down`); - let updatedTask = await user.get(`/tasks/${todo._id}`); - - expect(updatedTask.completed).to.equal(false); - expect(updatedTask.dateCompleted).to.be.a('undefined'); - }); - - it('scores up todo even if it is already completed'); // Yes? - - it('scores down todo even if it is already uncompleted'); // Yes? - - context('user stats when direction is up', () => { - let updatedUser; - - beforeEach(async () => { - await user.post(`/tasks/${todo._id}/score/up`); - updatedUser = await user.get('/user'); - }); - - it('increases user\'s mp', () => { - expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); - }); - - it('increases user\'s exp', () => { - expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); - }); - - it('increases user\'s gold', () => { - expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); - }); - }); - - context('user stats when direction is down', () => { - let updatedUser; - - beforeEach(async () => { - await user.post(`/tasks/${todo._id}/score/down`); - updatedUser = await user.get('/user'); - }); - - it('decreases user\'s mp', () => { - expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); - }); - - it('decreases user\'s exp', () => { - expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); - }); - - it('decreases user\'s gold', () => { - expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); - }); - }); - }); - - context('dailys', () => { - let daily; - - beforeEach(async () => { - daily = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - }); - }); - - it('completes daily when direction is up', async () => { - await user.post(`/tasks/${daily._id}/score/up`); - let task = await user.get(`/tasks/${daily._id}`); - - expect(task.completed).to.equal(true); - }); - - it('uncompletes daily when direction is down', async () => { - await user.post(`/tasks/${daily._id}/score/down`); - let task = await user.get(`/tasks/${daily._id}`); - - expect(task.completed).to.equal(false); - }); - - it('scores up daily even if it is already completed'); // Yes? - - it('scores down daily even if it is already uncompleted'); // Yes? - - context('user stats when direction is up', () => { - let updatedUser; - - beforeEach(async () => { - await user.post(`/tasks/${daily._id}/score/up`); - updatedUser = await user.get('/user'); - }); - - it('increases user\'s mp', () => { - expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); - }); - - it('increases user\'s exp', () => { - expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); - }); - - it('increases user\'s gold', () => { - expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); - }); - }); - - context('user stats when direction is down', () => { - let updatedUser; - - beforeEach(async () => { - await user.post(`/tasks/${daily._id}/score/down`); - updatedUser = await user.get('/user'); - }); - - it('decreases user\'s mp', () => { - expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); - }); - - it('decreases user\'s exp', () => { - expect(updatedUser.stats.exp).to.be.lessThan(user.stats.exp); - }); - - it('decreases user\'s gold', () => { - expect(updatedUser.stats.gp).to.be.lessThan(user.stats.gp); - }); - }); - }); - - context('habits', () => { - let habit, minusHabit, plusHabit, neitherHabit; // eslint-disable-line no-unused-vars - - beforeEach(async () => { - habit = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - - minusHabit = await user.post('/tasks/user', { - text: 'test min habit', - type: 'habit', - up: false, - }); - - plusHabit = await user.post('/tasks/user', { - text: 'test plus habit', - type: 'habit', - down: false, - }); - - neitherHabit = await user.post('/tasks/user', { - text: 'test neither habit', - type: 'habit', - up: false, - down: false, - }); - }); - - it('prevents plus only habit from scoring down'); // Yes? - - it('prevents minus only habit from scoring up'); // Yes? - - it('increases user\'s mp when direction is up', async () => { - await user.post(`/tasks/${habit._id}/score/up`); - let updatedUser = await user.get('/user'); - - expect(updatedUser.stats.mp).to.be.greaterThan(user.stats.mp); - }); - - it('decreases user\'s mp when direction is down', async () => { - await user.post(`/tasks/${habit._id}/score/down`); - let updatedUser = await user.get('/user'); - - expect(updatedUser.stats.mp).to.be.lessThan(user.stats.mp); - }); - - it('increases user\'s exp when direction is up', async () => { - await user.post(`/tasks/${habit._id}/score/up`); - let updatedUser = await user.get('/user'); - - expect(updatedUser.stats.exp).to.be.greaterThan(user.stats.exp); - }); - - it('increases user\'s gold when direction is up', async () => { - await user.post(`/tasks/${habit._id}/score/up`); - let updatedUser = await user.get('/user'); - - expect(updatedUser.stats.gp).to.be.greaterThan(user.stats.gp); - }); - }); - - context('reward', () => { - let reward, updatedUser; - - beforeEach(async () => { - reward = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - value: 5, - }); - - await user.post(`/tasks/${reward._id}/score/up`); - updatedUser = await user.get('/user'); - }); - - it('purchases reward', () => { - expect(user.stats.gp).to.equal(updatedUser.stats.gp + 5); - }); - - it('does not change user\'s mp', () => { - expect(user.stats.mp).to.equal(updatedUser.stats.mp); - }); - - it('does not change user\'s exp', () => { - expect(user.stats.exp).to.equal(updatedUser.stats.exp); - }); - - it('does not allow a down direction', () => { - expect(user.stats.mp).to.equal(updatedUser.stats.mp); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js b/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js deleted file mode 100644 index 211c7ea975..0000000000 --- a/test/api/v3/integration/tasks/POST-tasks_move_taskId_to_position.test.js +++ /dev/null @@ -1,84 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:taskId/move/to/:position', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('requires a valid taskId', async () => { - await expect(user.post('/tasks/123/move/to/1')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires a numeric position parameter', async () => { - await expect(user.post(`/tasks/${generateUUID()}/move/to/notANumber`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('taskId must match a valid task', async () => { - await expect(user.post(`/tasks/${generateUUID()}/move/to/1`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('can move task to new position', async () => { - let tasks = await user.post('/tasks/user', [ - {type: 'habit', text: 'habit 1'}, - {type: 'habit', text: 'habit 2'}, - {type: 'daily', text: 'daily 1'}, - {type: 'habit', text: 'habit 3'}, - {type: 'habit', text: 'habit 4'}, - {type: 'todo', text: 'todo 1'}, - {type: 'habit', text: 'habit 5'}, - ]); - - let taskToMove = tasks[1]; - expect(taskToMove.text).to.equal('habit 2'); - let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/3`); - expect(newOrder[3]).to.equal(taskToMove._id); - expect(newOrder.length).to.equal(5); - }); - - it('can\'t move completed todo', async () => { - let task = await user.post('/tasks/user', {type: 'todo', text: 'todo 1'}); - await user.post(`/tasks/${task._id}/score/up`); - - await expect(user.post(`/tasks/${task._id}/move/to/1`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('cantMoveCompletedTodo'), - }); - }); - - it('can push to bottom', async () => { - let tasks = await user.post('/tasks/user', [ - {type: 'habit', text: 'habit 1'}, - {type: 'habit', text: 'habit 2'}, - {type: 'daily', text: 'daily 1'}, - {type: 'habit', text: 'habit 3'}, - {type: 'habit', text: 'habit 4'}, - {type: 'todo', text: 'todo 1'}, - {type: 'habit', text: 'habit 5'}, - ]); - - let taskToMove = tasks[1]; - expect(taskToMove.text).to.equal('habit 2'); - let newOrder = await user.post(`/tasks/${tasks[1]._id}/move/to/-1`); - expect(newOrder[4]).to.equal(taskToMove._id); - expect(newOrder.length).to.equal(5); - }); -}); diff --git a/test/api/v3/integration/tasks/POST-tasks_user.test.js b/test/api/v3/integration/tasks/POST-tasks_user.test.js deleted file mode 100644 index 623d9bb4a4..0000000000 --- a/test/api/v3/integration/tasks/POST-tasks_user.test.js +++ /dev/null @@ -1,593 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/user', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - context('validates params', async () => { - it('returns an error if req.body.type is absent', async () => { - await expect(user.post('/tasks/user', { - notType: 'habit', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidTaskType'), - }); - }); - - it('returns an error if req.body.type is not valid', async () => { - await expect(user.post('/tasks/user', { - type: 'habitF', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidTaskType'), - }); - }); - - it('returns an error if one object inside an array is invalid', async () => { - await expect(user.post('/tasks/user', [ - {type: 'habitF'}, - {type: 'habit'}, - ])).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidTaskType'), - }); - }); - - it('returns an error if req.body.text is absent', async () => { - await expect(user.post('/tasks/user', { - type: 'habit', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'habit validation failed', - }); - }); - - it('does not update user.tasksOrder.{taskType} when the task is not saved because invalid', async () => { - let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits; - await expect(user.post('/tasks/user', { - type: 'habit', - })).to.eventually.be.rejected.and.eql({ // this block is necessary - code: 400, - error: 'BadRequest', - message: 'habit validation failed', - }); - - let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits; - expect(updatedHabitsOrder).to.eql(originalHabitsOrder); - }); - - it('does not update user.tasksOrder.{taskType} when a task inside an array is not saved because invalid', async () => { - let originalHabitsOrder = (await user.get('/user')).tasksOrder.habits; - await expect(user.post('/tasks/user', [ - {type: 'habit'}, // Missing text - {type: 'habit', text: 'valid'}, // Valid - ])).to.eventually.be.rejected.and.eql({ // this block is necessary - code: 400, - error: 'BadRequest', - message: 'habit validation failed', - }); - - let updatedHabitsOrder = (await user.get('/user')).tasksOrder.habits; - expect(updatedHabitsOrder).to.eql(originalHabitsOrder); - }); - - it('does not save any task sent in an array when 1 is invalid', async () => { - let originalTasks = await user.get('/tasks/user'); - await expect(user.post('/tasks/user', [ - {type: 'habit'}, // Missing text - {type: 'habit', text: 'valid'}, // Valid - ])).to.eventually.be.rejected.and.eql({ // this block is necessary - code: 400, - error: 'BadRequest', - message: 'habit validation failed', - }).then(async () => { - let updatedTasks = await user.get('/tasks/user'); - - expect(updatedTasks).to.eql(originalTasks); - }); - }); - - it('automatically sets "task.userId" to user\'s uuid', async () => { - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - - expect(task.userId).to.equal(user._id); - }); - - it(`ignores setting userId, history, createdAt, - updatedAt, challenge, completed, - dateCompleted fields`, async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - userId: 123, - history: [123], - createdAt: 'yesterday', - updatedAt: 'tomorrow', - challenge: 'no', - completed: true, - dateCompleted: 'never', - value: 324, // ignored because not a reward - }); - - expect(task.userId).to.equal(user._id); - expect(task.history).to.eql([]); - expect(task.createdAt).not.to.equal('yesterday'); - expect(task.updatedAt).not.to.equal('tomorrow'); - expect(task.challenge).not.to.equal('no'); - expect(task.completed).to.equal(false); - expect(task.streak).not.to.equal('never'); - expect(task.value).not.to.equal(324); - }); - - it('ignores invalid fields', async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - notValid: true, - }); - - expect(task).not.to.have.property('notValid'); - }); - }); - - context('all types', () => { - it('can create reminders', async () => { - let id1 = generateUUID(); - - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - reminders: [ - {id: id1, startDate: new Date(), time: new Date()}, - ], - }); - - expect(task.reminders).to.be.an('array'); - expect(task.reminders.length).to.eql(1); - expect(task.reminders[0]).to.be.an('object'); - expect(task.reminders[0].id).to.eql(id1); - expect(task.reminders[0].startDate).to.be.a('string'); // json doesn't have dates - expect(task.reminders[0].time).to.be.a('string'); - }); - }); - - context('habits', () => { - it('creates a habit', async () => { - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - }); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test habit'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('habit'); - expect(task.up).to.eql(false); - expect(task.down).to.eql(true); - }); - - it('updates user.tasksOrder.habits when a new habit is created', async () => { - let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length; - let task = await user.post('/tasks/user', { - type: 'habit', - text: 'an habit', - }); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.habits[0]).to.eql(task._id); - expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 1); - }); - - it('updates user.tasksOrder.habits when multiple habits are created', async () => { - let originalHabitsOrderLen = (await user.get('/user')).tasksOrder.habits.length; - let [task, task2] = await user.post('/tasks/user', [{ - type: 'habit', - text: 'an habit', - }, { - type: 'habit', - text: 'another habit', - }]); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.habits[0]).to.eql(task2._id); - expect(updatedUser.tasksOrder.habits[1]).to.eql(task._id); - expect(updatedUser.tasksOrder.habits.length).to.eql(originalHabitsOrderLen + 2); - }); - - it('creates multiple habits', async () => { - let [task, task2] = await user.post('/tasks/user', [{ - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - }, { - text: 'test habit 2', - type: 'habit', - up: true, - down: false, - notes: 1977, - }]); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test habit'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('habit'); - expect(task.up).to.eql(false); - expect(task.down).to.eql(true); - - expect(task2.userId).to.equal(user._id); - expect(task2.text).to.eql('test habit 2'); - expect(task2.notes).to.eql('1977'); - expect(task2.type).to.eql('habit'); - expect(task2.up).to.eql(true); - expect(task2.down).to.eql(false); - }); - - it('defaults to setting up and down to true', async () => { - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - notes: 1976, - }); - - expect(task.up).to.eql(true); - expect(task.down).to.eql(true); - }); - - it('cannot create checklists', async () => { - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - checklist: [ - {_id: 123, completed: false, text: 'checklist'}, - ], - }); - - expect(task).not.to.have.property('checklist'); - }); - }); - - context('todos', () => { - it('creates a todo', async () => { - let task = await user.post('/tasks/user', { - text: 'test todo', - type: 'todo', - notes: 1976, - }); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test todo'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('todo'); - }); - - it('creates multiple todos', async () => { - let [task, task2] = await user.post('/tasks/user', [{ - text: 'test todo', - type: 'todo', - notes: 1976, - }, { - text: 'test todo 2', - type: 'todo', - notes: 1977, - }]); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test todo'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('todo'); - - expect(task2.userId).to.equal(user._id); - expect(task2.text).to.eql('test todo 2'); - expect(task2.notes).to.eql('1977'); - expect(task2.type).to.eql('todo'); - }); - - it('updates user.tasksOrder.todos when a new todo is created', async () => { - let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length; - let task = await user.post('/tasks/user', { - type: 'todo', - text: 'a todo', - }); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.todos[0]).to.eql(task._id); - expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 1); - }); - - it('updates user.tasksOrder.todos when multiple todos are created', async () => { - let originalTodosOrderLen = (await user.get('/user')).tasksOrder.todos.length; - let [task, task2] = await user.post('/tasks/user', [{ - type: 'todo', - text: 'a todo', - }, { - type: 'todo', - text: 'another todo', - }]); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.todos[0]).to.eql(task2._id); - expect(updatedUser.tasksOrder.todos[1]).to.eql(task._id); - expect(updatedUser.tasksOrder.todos.length).to.eql(originalTodosOrderLen + 2); - }); - - it('can create checklists', async () => { - let task = await user.post('/tasks/user', { - text: 'test todo', - type: 'todo', - checklist: [ - {completed: false, text: 'checklist'}, - ], - }); - - expect(task.checklist).to.be.an('array'); - expect(task.checklist.length).to.eql(1); - expect(task.checklist[0]).to.be.an('object'); - expect(task.checklist[0].text).to.eql('checklist'); - expect(task.checklist[0].completed).to.eql(false); - expect(task.checklist[0].id).to.be.a('string'); - }); - }); - - context('dailys', () => { - it('creates a daily', async () => { - let now = new Date(); - - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - notes: 1976, - frequency: 'daily', - everyX: 5, - startDate: now, - }); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test daily'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('daily'); - expect(task.frequency).to.eql('daily'); - expect(task.everyX).to.eql(5); - expect(new Date(task.startDate)).to.eql(now); - }); - - it('creates multiple dailys', async () => { - let [task, task2] = await user.post('/tasks/user', [{ - text: 'test daily', - type: 'daily', - notes: 1976, - }, { - text: 'test daily 2', - type: 'daily', - notes: 1977, - }]); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test daily'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('daily'); - - expect(task2.userId).to.equal(user._id); - expect(task2.text).to.eql('test daily 2'); - expect(task2.notes).to.eql('1977'); - expect(task2.type).to.eql('daily'); - }); - - it('updates user.tasksOrder.dailys when a new daily is created', async () => { - let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length; - let task = await user.post('/tasks/user', { - type: 'daily', - text: 'a daily', - }); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.dailys[0]).to.eql(task._id); - expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 1); - }); - - it('updates user.tasksOrder.dailys when multiple dailys are created', async () => { - let originalDailysOrderLen = (await user.get('/user')).tasksOrder.dailys.length; - let [task, task2] = await user.post('/tasks/user', [{ - type: 'daily', - text: 'a daily', - }, { - type: 'daily', - text: 'another daily', - }]); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.dailys[0]).to.eql(task2._id); - expect(updatedUser.tasksOrder.dailys[1]).to.eql(task._id); - expect(updatedUser.tasksOrder.dailys.length).to.eql(originalDailysOrderLen + 2); - }); - - it('defaults to a weekly frequency, with every day set', async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - }); - - expect(task.frequency).to.eql('weekly'); - expect(task.everyX).to.eql(1); - expect(task.repeat).to.eql({ - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - su: true, - }); - }); - - it('allows repeat field to be configured', async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - repeat: { - m: false, - w: false, - su: false, - }, - }); - - expect(task.repeat).to.eql({ - m: false, - t: true, - w: false, - th: true, - f: true, - s: true, - su: false, - }); - }); - - it('defaults startDate to today', async () => { - let today = (new Date()).getDay(); - - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - }); - - expect((new Date(task.startDate)).getDay()).to.eql(today); - }); - - it('can create checklists', async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - checklist: [ - {completed: false, text: 'checklist'}, - ], - }); - - expect(task.checklist).to.be.an('array'); - expect(task.checklist.length).to.eql(1); - expect(task.checklist[0]).to.be.an('object'); - expect(task.checklist[0].text).to.eql('checklist'); - expect(task.checklist[0].completed).to.eql(false); - expect(task.checklist[0].id).to.be.a('string'); - }); - }); - - context('rewards', () => { - it('creates a reward', async () => { - let task = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - notes: 1976, - value: 10, - }); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test reward'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('reward'); - expect(task.value).to.eql(10); - }); - - it('creates multiple rewards', async () => { - let [task, task2] = await user.post('/tasks/user', [{ - text: 'test reward', - type: 'reward', - notes: 1976, - value: 11, - }, { - text: 'test reward 2', - type: 'reward', - notes: 1977, - value: 12, - }]); - - expect(task.userId).to.equal(user._id); - expect(task.text).to.eql('test reward'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('reward'); - expect(task.value).to.eql(11); - - expect(task2.userId).to.equal(user._id); - expect(task2.text).to.eql('test reward 2'); - expect(task2.notes).to.eql('1977'); - expect(task2.type).to.eql('reward'); - expect(task2.value).to.eql(12); - }); - - it('updates user.tasksOrder.rewards when a new reward is created', async () => { - let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length; - let task = await user.post('/tasks/user', { - type: 'reward', - text: 'a reward', - }); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.rewards[0]).to.eql(task._id); - expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 1); - }); - - it('updates user.tasksOrder.dreward when multiple rewards are created', async () => { - let originalRewardsOrderLen = (await user.get('/user')).tasksOrder.rewards.length; - let [task, task2] = await user.post('/tasks/user', [{ - type: 'reward', - text: 'a reward', - }, { - type: 'reward', - text: 'another reward', - }]); - - let updatedUser = await user.get('/user'); - expect(updatedUser.tasksOrder.rewards[0]).to.eql(task2._id); - expect(updatedUser.tasksOrder.rewards[1]).to.eql(task._id); - expect(updatedUser.tasksOrder.rewards.length).to.eql(originalRewardsOrderLen + 2); - }); - - it('defaults to a 0 value', async () => { - let task = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - }); - - expect(task.value).to.eql(0); - }); - - it('requires value to be coerced into a number', async () => { - let task = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - value: '10', - }); - - expect(task.value).to.eql(10); - }); - - it('cannot create checklists', async () => { - let task = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - checklist: [ - {_id: 123, completed: false, text: 'checklist'}, - ], - }); - - expect(task).not.to.have.property('checklist'); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js b/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js deleted file mode 100644 index 88343cb47c..0000000000 --- a/test/api/v3/integration/tasks/PUT-tasks_challenge_challengeId.test.js +++ /dev/null @@ -1,323 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('PUT /tasks/:id', () => { - let user; - let guild; - let challenge; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - context('errors', () => { - let task; - - beforeEach(async () => { - task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - }); - - it('returns error when incorrect id is passed', async () => { - await expect(user.put(`/tasks/${generateUUID()}`, { - text: 'some new text', - up: false, - down: false, - notes: 'some new notes', - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('returns error when user is not a member of the challenge', async () => { - let anotherUser = await generateUser(); - - await expect(anotherUser.put(`/tasks/${task._id}`, { - text: 'some new text', - up: false, - down: false, - notes: 'some new notes', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - }); - - context('validates params', () => { - let task; - - beforeEach(async () => { - task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - }); - - it(`ignores setting _id, type, userId, history, createdAt, - updatedAt, challenge, completed, streak, - dateCompleted fields`, async () => { - let savedTask = await user.put(`/tasks/${task._id}`, { - _id: 123, - type: 'daily', - userId: 123, - history: [123], - createdAt: 'yesterday', - updatedAt: 'tomorrow', - challenge: 'no', - completed: true, - streak: 25, - dateCompleted: 'never', - value: 324, // ignored because not a reward - }); - - expect(savedTask._id).to.equal(task._id); - expect(savedTask.type).to.equal(task.type); - expect(savedTask.userId).to.equal(task.userId); - expect(savedTask.history).to.eql(task.history); - expect(savedTask.createdAt).to.equal(task.createdAt); - expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt); - expect(savedTask.challenge._id).to.equal(task.challenge._id); - expect(savedTask.completed).to.equal(task.completed); - expect(savedTask.streak).to.equal(task.streak); - expect(savedTask.dateCompleted).to.equal(task.dateCompleted); - expect(savedTask.value).to.equal(task.value); - }); - - it('ignores invalid fields', async () => { - let savedTask = await user.put(`/tasks/${task._id}`, { - notValid: true, - }); - - expect(savedTask.notValid).to.be.undefined; - }); - }); - - context('habits', () => { - let habit; - - beforeEach(async () => { - habit = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - notes: 1976, - }); - }); - - it('updates a habit', async () => { - let savedHabit = await user.put(`/tasks/${habit._id}`, { - text: 'some new text', - up: false, - down: false, - notes: 'some new notes', - }); - - expect(savedHabit.text).to.eql('some new text'); - expect(savedHabit.notes).to.eql('some new notes'); - expect(savedHabit.up).to.eql(false); - expect(savedHabit.down).to.eql(false); - }); - }); - - context('todos', () => { - let todo; - - beforeEach(async () => { - todo = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test todo', - type: 'todo', - notes: 1976, - }); - }); - - it('updates a todo', async () => { - let savedTodo = await user.put(`/tasks/${todo._id}`, { - text: 'some new text', - notes: 'some new notes', - }); - - expect(savedTodo.text).to.eql('some new text'); - expect(savedTodo.notes).to.eql('some new notes'); - }); - - it('can update checklists (replace it)', async () => { - await user.put(`/tasks/${todo._id}`, { - checklist: [ - {text: 123, completed: false}, - {text: 456, completed: true}, - ], - }); - - let savedTodo = await user.put(`/tasks/${todo._id}`, { - checklist: [ - {text: 789, completed: false}, - ], - }); - - expect(savedTodo.checklist.length).to.equal(1); - expect(savedTodo.checklist[0].text).to.equal('789'); - expect(savedTodo.checklist[0].completed).to.equal(false); - }); - - it('can update tags (replace them)', async () => { - let finalUUID = generateUUID(); - await user.put(`/tasks/${todo._id}`, { - tags: [generateUUID(), generateUUID()], - }); - - let savedTodo = await user.put(`/tasks/${todo._id}`, { - tags: [finalUUID], - }); - - expect(savedTodo.tags.length).to.equal(1); - expect(savedTodo.tags[0]).to.equal(finalUUID); - }); - }); - - context('dailys', () => { - let daily; - - beforeEach(async () => { - daily = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test daily', - type: 'daily', - notes: 1976, - }); - }); - - it('updates a daily', async () => { - let savedDaily = await user.put(`/tasks/${daily._id}`, { - text: 'some new text', - notes: 'some new notes', - frequency: 'daily', - everyX: 5, - }); - - expect(savedDaily.text).to.eql('some new text'); - expect(savedDaily.notes).to.eql('some new notes'); - expect(savedDaily.frequency).to.eql('daily'); - expect(savedDaily.everyX).to.eql(5); - }); - - it('can update checklists (replace it)', async () => { - await user.put(`/tasks/${daily._id}`, { - checklist: [ - {text: 123, completed: false}, - {text: 456, completed: true}, - ], - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - checklist: [ - {text: 789, completed: false}, - ], - }); - - expect(savedDaily.checklist.length).to.equal(1); - expect(savedDaily.checklist[0].text).to.equal('789'); - expect(savedDaily.checklist[0].completed).to.equal(false); - }); - - it('can update tags (replace them)', async () => { - let finalUUID = generateUUID(); - await user.put(`/tasks/${daily._id}`, { - tags: [generateUUID(), generateUUID()], - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - tags: [finalUUID], - }); - - expect(savedDaily.tags.length).to.equal(1); - expect(savedDaily.tags[0]).to.equal(finalUUID); - }); - - it('updates repeat, even if frequency is set to daily', async () => { - await user.put(`/tasks/${daily._id}`, { - frequency: 'daily', - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - repeat: { - m: false, - su: false, - }, - }); - - expect(savedDaily.repeat).to.eql({ - m: false, - t: true, - w: true, - th: true, - f: true, - s: true, - su: false, - }); - }); - - it('updates everyX, even if frequency is set to weekly', async () => { - await user.put(`/tasks/${daily._id}`, { - frequency: 'weekly', - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - everyX: 5, - }); - - expect(savedDaily.everyX).to.eql(5); - }); - - it('defaults startDate to today if none date object is passed in', async () => { - let savedDaily = await user.put(`/tasks/${daily._id}`, { - frequency: 'weekly', - }); - - expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay()); - }); - }); - - context('rewards', () => { - let reward; - - beforeEach(async () => { - reward = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test reward', - type: 'reward', - notes: 1976, - value: 10, - }); - }); - - it('updates a reward', async () => { - let savedReward = await user.put(`/tasks/${reward._id}`, { - text: 'some new text', - notes: 'some new notes', - value: 11, - }); - - expect(savedReward.text).to.eql('some new text'); - expect(savedReward.notes).to.eql('some new notes'); - expect(savedReward.value).to.eql(11); - }); - - it('requires value to be coerced into a number', async () => { - let savedReward = await user.put(`/tasks/${reward._id}`, { - value: '100', - }); - - expect(savedReward.value).to.eql(100); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js deleted file mode 100644 index 1325f8b392..0000000000 --- a/test/api/v3/integration/tasks/PUT-tasks_id.test.js +++ /dev/null @@ -1,396 +0,0 @@ -import { - generateUser, - generateGroup, - sleep, - generateChallenge, -} from '../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('PUT /tasks/:id', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - context('validates params', () => { - let task; - - beforeEach(async () => { - task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - }); - - it(`ignores setting _id, type, userId, history, createdAt, - updatedAt, challenge, completed, streak, - dateCompleted fields`, async () => { - let savedTask = await user.put(`/tasks/${task._id}`, { - _id: 123, - type: 'daily', - userId: 123, - history: [123], - createdAt: 'yesterday', - updatedAt: 'tomorrow', - challenge: 'no', - completed: true, - streak: 25, - dateCompleted: 'never', - }); - - expect(savedTask._id).to.equal(task._id); - expect(savedTask.type).to.equal(task.type); - expect(savedTask.userId).to.equal(task.userId); - expect(savedTask.history).to.eql(task.history); - expect(savedTask.createdAt).to.equal(task.createdAt); - expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt); - expect(savedTask.challenge).to.equal(task.challenge); - expect(savedTask.completed).to.eql(task.completed); - expect(savedTask.streak).to.equal(savedTask.streak); // it's an habit, dailies can change it - expect(savedTask.dateCompleted).to.equal(task.dateCompleted); - }); - - it('ignores invalid fields', async () => { - let savedTask = await user.put(`/tasks/${task._id}`, { - notValid: true, - }); - - expect(savedTask.notValid).to.be.undefined; - }); - - it(`only allows setting streak, reminders, checklist, notes, attribute, tags - fields for challenge tasks owned by a user`, async () => { - let guild = await generateGroup(user); - let challenge = await generateChallenge(user, guild); - - let challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily in challenge', - reminders: [ - {time: new Date(), startDate: new Date()}, - ], - checklist: [ - {text: 123, completed: false}, - ], - }); - await sleep(2); - - await user.sync(); - - // Pick challenge task - let challengeUserTaskId = user.tasksOrder.dailys[user.tasksOrder.dailys.length - 1]; - - let challengeUserTask = await user.get(`/tasks/${challengeUserTaskId}`); - - let savedChallengeUserTask = await user.put(`/tasks/${challengeUserTaskId}`, { - _id: 123, - type: 'daily', - userId: 123, - history: [123], - createdAt: 'yesterday', - updatedAt: 'tomorrow', - challenge: 'no', - completed: true, - streak: 25, - priority: 1.5, - repeat: { - m: false, - }, - everyX: 15, - frequency: 'weekly', - text: 'new text', - dateCompleted: 'never', - reminders: [ - {time: new Date(), startDate: new Date()}, - {time: new Date(), startDate: new Date()}, - ], - checklist: [ - {text: 123, completed: false}, - {text: 456, completed: true}, - ], - notes: 'new notes', - attribute: 'per', - tags: [challengeUserTaskId], - }); - - // original task is not touched - let updatedChallengeTask = await user.get(`/tasks/${challengeTask._id}`); - expect(updatedChallengeTask).to.eql(challengeTask); - - // ignored - expect(savedChallengeUserTask._id).to.equal(challengeUserTask._id); - expect(savedChallengeUserTask.type).to.equal(challengeUserTask.type); - expect(savedChallengeUserTask.repeat.m).to.equal(true); - expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority); - expect(savedChallengeUserTask.frequency).to.equal(challengeUserTask.frequency); - expect(savedChallengeUserTask.userId).to.equal(challengeUserTask.userId); - expect(savedChallengeUserTask.text).to.equal(challengeUserTask.text); - expect(savedChallengeUserTask.history).to.eql(challengeUserTask.history); - expect(savedChallengeUserTask.createdAt).to.equal(challengeUserTask.createdAt); - expect(savedChallengeUserTask.updatedAt).to.be.greaterThan(challengeUserTask.updatedAt); - expect(savedChallengeUserTask.challenge).to.eql(challengeUserTask.challenge); - expect(savedChallengeUserTask.completed).to.equal(challengeUserTask.completed); - expect(savedChallengeUserTask.dateCompleted).to.equal(challengeUserTask.dateCompleted); - expect(savedChallengeUserTask.priority).to.equal(challengeUserTask.priority); - - // changed - expect(savedChallengeUserTask.notes).to.equal('new notes'); - expect(savedChallengeUserTask.attribute).to.equal('per'); - expect(savedChallengeUserTask.tags).to.eql([challengeUserTaskId]); - expect(savedChallengeUserTask.streak).to.equal(25); - expect(savedChallengeUserTask.reminders.length).to.equal(2); - expect(savedChallengeUserTask.checklist.length).to.equal(2); - }); - }); - - context('all types', () => { - let daily; - - beforeEach(async () => { - daily = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - notes: 1976, - }); - }); - - it('can update reminders (replace them)', async () => { - await user.put(`/tasks/${daily._id}`, { - reminders: [ - {time: new Date(), startDate: new Date()}, - ], - }); - - let id1 = generateUUID(); - let id2 = generateUUID(); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - reminders: [ - {id: id1, time: new Date(), startDate: new Date()}, - {id: id2, time: new Date(), startDate: new Date()}, - ], - }); - - expect(savedDaily.reminders.length).to.equal(2); - expect(savedDaily.reminders[0].id).to.equal(id1); - expect(savedDaily.reminders[1].id).to.equal(id2); - }); - }); - - context('habits', () => { - let habit; - - beforeEach(async () => { - habit = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - notes: 1976, - }); - }); - - it('updates a habit', async () => { - let savedHabit = await user.put(`/tasks/${habit._id}`, { - text: 'some new text', - up: false, - down: false, - notes: 'some new notes', - }); - - expect(savedHabit.text).to.eql('some new text'); - expect(savedHabit.notes).to.eql('some new notes'); - expect(savedHabit.up).to.eql(false); - expect(savedHabit.down).to.eql(false); - }); - }); - - context('todos', () => { - let todo; - - beforeEach(async () => { - todo = await user.post('/tasks/user', { - text: 'test todo', - type: 'todo', - notes: 1976, - }); - }); - - it('updates a todo', async () => { - let savedTodo = await user.put(`/tasks/${todo._id}`, { - text: 'some new text', - notes: 'some new notes', - }); - - expect(savedTodo.text).to.eql('some new text'); - expect(savedTodo.notes).to.eql('some new notes'); - }); - - it('can update checklists (replace it)', async () => { - await user.put(`/tasks/${todo._id}`, { - checklist: [ - {text: 123, completed: false}, - {text: 456, completed: true}, - ], - }); - - let savedTodo = await user.put(`/tasks/${todo._id}`, { - checklist: [ - {text: 789, completed: false}, - ], - }); - - expect(savedTodo.checklist.length).to.equal(1); - expect(savedTodo.checklist[0].text).to.equal('789'); - expect(savedTodo.checklist[0].completed).to.equal(false); - }); - - it('can update tags (replace them)', async () => { - let finalUUID = generateUUID(); - await user.put(`/tasks/${todo._id}`, { - tags: [generateUUID(), generateUUID()], - }); - - let savedTodo = await user.put(`/tasks/${todo._id}`, { - tags: [finalUUID], - }); - - expect(savedTodo.tags.length).to.equal(1); - expect(savedTodo.tags[0]).to.equal(finalUUID); - }); - }); - - context('dailys', () => { - let daily; - - beforeEach(async () => { - daily = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - notes: 1976, - }); - }); - - it('updates a daily', async () => { - let savedDaily = await user.put(`/tasks/${daily._id}`, { - text: 'some new text', - notes: 'some new notes', - frequency: 'daily', - everyX: 5, - }); - - expect(savedDaily.text).to.eql('some new text'); - expect(savedDaily.notes).to.eql('some new notes'); - expect(savedDaily.frequency).to.eql('daily'); - expect(savedDaily.everyX).to.eql(5); - }); - - it('can update checklists (replace it)', async () => { - await user.put(`/tasks/${daily._id}`, { - checklist: [ - {text: 123, completed: false}, - {text: 456, completed: true}, - ], - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - checklist: [ - {text: 789, completed: false}, - ], - }); - - expect(savedDaily.checklist.length).to.equal(1); - expect(savedDaily.checklist[0].text).to.equal('789'); - expect(savedDaily.checklist[0].completed).to.equal(false); - }); - - it('can update tags (replace them)', async () => { - let finalUUID = generateUUID(); - await user.put(`/tasks/${daily._id}`, { - tags: [generateUUID(), generateUUID()], - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - tags: [finalUUID], - }); - - expect(savedDaily.tags.length).to.equal(1); - expect(savedDaily.tags[0]).to.equal(finalUUID); - }); - - it('updates repeat, even if frequency is set to daily', async () => { - await user.put(`/tasks/${daily._id}`, { - frequency: 'daily', - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - repeat: { - m: false, - su: false, - }, - }); - - expect(savedDaily.repeat).to.eql({ - m: false, - t: true, - w: true, - th: true, - f: true, - s: true, - su: false, - }); - }); - - it('updates everyX, even if frequency is set to weekly', async () => { - await user.put(`/tasks/${daily._id}`, { - frequency: 'weekly', - }); - - let savedDaily = await user.put(`/tasks/${daily._id}`, { - everyX: 5, - }); - - expect(savedDaily.everyX).to.eql(5); - }); - - it('defaults startDate to today if none date object is passed in', async () => { - let savedDaily = await user.put(`/tasks/${daily._id}`, { - frequency: 'weekly', - }); - - expect((new Date(savedDaily.startDate)).getDay()).to.eql((new Date()).getDay()); - }); - }); - - context('rewards', () => { - let reward; - - beforeEach(async () => { - reward = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - notes: 1976, - value: 10, - }); - }); - - it('updates a reward', async () => { - let savedReward = await user.put(`/tasks/${reward._id}`, { - text: 'some new text', - notes: 'some new notes', - value: 10, - }); - - expect(savedReward.text).to.eql('some new text'); - expect(savedReward.notes).to.eql('some new notes'); - expect(savedReward.value).to.eql(10); - }); - - it('requires value to be coerced into a number', async () => { - let savedReward = await user.put(`/tasks/${reward._id}`, { - value: '100', - }); - - expect(savedReward.value).to.eql(100); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js deleted file mode 100644 index eac6d455ea..0000000000 --- a/test/api/v3/integration/tasks/challenges/DELETE-tasks_challenge_challengeId_checklist_itemId.test.js +++ /dev/null @@ -1,115 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /tasks/:taskId/checklist/:itemId', () => { - let user; - let guild; - let challenge; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('fails on task not found', async () => { - await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('fails on checklist item not found', async () => { - let createdTask = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'daily with checklist', - }); - - await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); - - it('returns error when user is not a member of the challenge', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - let anotherUser = await generateUser(); - - await expect(anotherUser.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - - it('deletes a checklist item from a daily', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); - - await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); - savedTask = await user.get(`/tasks/${task._id}`); - - expect(savedTask.checklist.length).to.equal(0); - }); - - it('deletes a checklist item from a todo', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'todo', - text: 'Todo with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); - - await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); - savedTask = await user.get(`/tasks/${task._id}`); - - expect(savedTask.checklist.length).to.equal(0); - }); - - it('does not work with habits', async () => { - let habit = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('does not work with rewards', async () => { - let reward = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js b/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js deleted file mode 100644 index 34c93c2f05..0000000000 --- a/test/api/v3/integration/tasks/challenges/DELETE-tasks_id_challenge_challengeId.test.js +++ /dev/null @@ -1,115 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - sleep, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /tasks/:id', () => { - let user; - let guild; - let challenge; - let task; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - beforeEach(async () => { - task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - }); - - it('cannot delete a non-existant task', async () => { - await expect(user.del(`/tasks/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('returns error when user is not leader of the challenge', async () => { - let anotherUser = await generateUser(); - - await expect(anotherUser.del(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - - it('deletes a user\'s task', async () => { - await user.del(`/tasks/${task._id}`); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - context('challenge member', () => { - let anotherUser; - let anotherUsersNewChallengeTaskID; - let newChallengeTask; - - beforeEach(async () => { - anotherUser = await generateUser(); - await user.post(`/groups/${guild._id}/invite`, { uuids: [anotherUser._id] }); - await anotherUser.post(`/groups/${guild._id}/join`); - await anotherUser.post(`/challenges/${challenge._id}/join`); - - newChallengeTask = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - - let anotherUserWithNewChallengeTask = await anotherUser.get('/user'); - anotherUsersNewChallengeTaskID = anotherUserWithNewChallengeTask.tasksOrder.habits[0]; - }); - - it('returns error when user attempts to delete an active challenge task', async () => { - await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`)) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantDeleteChallengeTasks'), - }); - }); - - it('allows user to delete challenge task after user leaves challenge', async () => { - await anotherUser.post(`/challenges/${challenge._id}/leave`); - - await anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`); - - await expect(anotherUser.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - // TODO for some reason this test fails on TravisCI, review after mongodb indexes have been added - xit('allows user to delete challenge task after challenge task is broken', async () => { - await expect(user.del(`/tasks/${newChallengeTask._id}`)); - - await sleep(2); - - await expect(anotherUser.del(`/tasks/${anotherUsersNewChallengeTaskID}`)); - - await sleep(2); - - await expect(anotherUser.get(`/tasks/${anotherUsersNewChallengeTaskID}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js b/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js deleted file mode 100644 index 2399db04b6..0000000000 --- a/test/api/v3/integration/tasks/challenges/GET_tasks_challenge.id.test.js +++ /dev/null @@ -1,86 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; -import { each } from 'lodash'; - -describe('GET /tasks/challenge/:challengeId', () => { - let user; - let guild; - let challenge; - let task; - let tasks = []; - let challengeWithTask; - let tasksToTest = { - habit: { - text: 'test habit', - type: 'habit', - up: false, - down: true, - }, - todo: { - text: 'test todo', - type: 'todo', - }, - daily: { - text: 'test daily', - type: 'daily', - frequency: 'daily', - everyX: 5, - startDate: new Date(), - }, - reward: { - text: 'test reward', - type: 'reward', - }, - }; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('returns error when challenge is not found', async () => { - let dummyId = generateUUID(); - - await expect(user.get(`/tasks/challenge/${dummyId}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - each(tasksToTest, (taskValue, taskType) => { - context(`${taskType}`, () => { - before(async () => { - task = await user.post(`/tasks/challenge/${challenge._id}`, taskValue); - tasks.push(task); - challengeWithTask = await user.get(`/challenges/${challenge._id}`); - }); - - it('gets challenge tasks', async () => { - let getTask = await user.get(`/tasks/challenge/${challengeWithTask._id}`); - expect(getTask).to.eql(tasks); - }); - - it('gets challenge tasks filtered by type', async () => { - let challengeTasks = await user.get(`/tasks/challenge/${challengeWithTask._id}?type=${task.type}s`); - expect(challengeTasks).to.eql([task]); - }); - - it('cannot get a task owned by someone else', async () => { - let anotherUser = await generateUser(); - - await expect(anotherUser.get(`/tasks/challenge/${challengeWithTask._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js deleted file mode 100644 index 06bc7b68b2..0000000000 --- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_challengeId_taskId_checklist.test.js +++ /dev/null @@ -1,119 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:taskId/checklist/', () => { - let user; - let guild; - let challenge; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('fails on task not found', async () => { - await expect(user.post(`/tasks/${generateUUID()}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('returns error when user is not a member of the challenge', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily with checklist', - }); - - let anotherUser = await generateUser(); - - await expect(anotherUser.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - ignored: false, - _id: 123, - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - - it('adds a checklist item to a daily', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - ignored: false, - _id: 123, - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); - expect(savedTask.checklist[0].completed).to.equal(false); - expect(savedTask.checklist[0].id).to.be.a('string'); - expect(savedTask.checklist[0].id).to.not.equal('123'); - expect(savedTask.checklist[0].ignored).to.be.an('undefined'); - }); - - it('adds a checklist item to a todo', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'todo', - text: 'Todo with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - ignored: false, - _id: 123, - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); - expect(savedTask.checklist[0].completed).to.equal(false); - expect(savedTask.checklist[0].id).to.be.a('string'); - expect(savedTask.checklist[0].id).to.not.equal('123'); - expect(savedTask.checklist[0].ignored).to.be.an('undefined'); - }); - - it('does not add a checklist to habits', async () => { - let habit = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.post(`/tasks/${habit._id}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('does not add a checklist to rewards', async () => { - let reward = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.post(`/tasks/${reward._id}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js deleted file mode 100644 index 62899ffb59..0000000000 --- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenge_id.test.js +++ /dev/null @@ -1,125 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../../helpers/api-v3-integration.helper'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/challenge/:challengeId', () => { - let user; - let guild; - let challenge; - - beforeEach(async () => { - user = await generateUser({balance: 1}); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('returns error when challenge is not found', async () => { - let fakeChallengeId = generateUUID(); - - await expect(user.post(`/tasks/challenge/${fakeChallengeId}`, { - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('returns error when user does not have the challenge', async () => { - let userWithoutChallenge = await generateUser(); - - await expect(userWithoutChallenge.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('challengeNotFound'), - }); - }); - - it('returns error when non leader tries to edit challenge', async () => { - let userThatIsNotLeaderOfChallenge = await generateUser({ - challenges: [challenge._id], - }); - - await expect(userThatIsNotLeaderOfChallenge.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - - it('creates a habit', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - up: false, - down: true, - notes: 1976, - }); - let challengeWithTask = await user.get(`/challenges/${challenge._id}`); - - expect(challengeWithTask.tasksOrder.habits.indexOf(task._id)).to.be.above(-1); - expect(task.challenge.id).to.equal(challenge._id); - expect(task.text).to.eql('test habit'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('habit'); - expect(task.up).to.eql(false); - expect(task.down).to.eql(true); - }); - - it('creates a todo', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test todo', - type: 'todo', - notes: 1976, - }); - let challengeWithTask = await user.get(`/challenges/${challenge._id}`); - - expect(challengeWithTask.tasksOrder.todos.indexOf(task._id)).to.be.above(-1); - expect(task.challenge.id).to.equal(challenge._id); - expect(task.text).to.eql('test todo'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('todo'); - }); - - it('creates a daily', async () => { - let now = new Date(); - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test daily', - type: 'daily', - notes: 1976, - frequency: 'daily', - everyX: 5, - startDate: now, - }); - let challengeWithTask = await user.get(`/challenges/${challenge._id}`); - - expect(challengeWithTask.tasksOrder.dailys.indexOf(task._id)).to.be.above(-1); - expect(task.challenge.id).to.equal(challenge._id); - expect(task.text).to.eql('test daily'); - expect(task.notes).to.eql('1976'); - expect(task.type).to.eql('daily'); - expect(task.frequency).to.eql('daily'); - expect(task.everyX).to.eql(5); - expect(new Date(task.startDate)).to.eql(now); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js deleted file mode 100644 index a264826884..0000000000 --- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, -} from '../../../../../helpers/api-integration/v3'; -import { find } from 'lodash'; - -describe('POST /tasks/:id/score/:direction', () => { - let user; - let guild; - let challenge; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - context('habits', () => { - let habit; - let usersChallengeTaskId; - let previousTaskHistory; - - before(async () => { - habit = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test habit', - type: 'habit', - }); - let updatedUser = await user.get('/user'); - usersChallengeTaskId = updatedUser.tasksOrder.habits[0]; - }); - - it('scores and adds history', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: habit._id}); - previousTaskHistory = task.history[0]; - - expect(task.value).to.equal(1); - expect(task.history).to.have.lengthOf(1); - }); - - it('should update the history', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: habit._id}); - - expect(task.history).to.have.lengthOf(1); - expect(task.history[0].date).to.not.equal(previousTaskHistory.date); - expect(task.history[0].value).to.not.equal(previousTaskHistory.value); - }); - }); - - context('dailies', () => { - let daily; - let usersChallengeTaskId; - let previousTaskHistory; - - before(async () => { - daily = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test daily', - type: 'daily', - }); - let updatedUser = await user.get('/user'); - usersChallengeTaskId = updatedUser.tasksOrder.dailys[0]; - }); - - it('it scores and adds history', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: daily._id}); - previousTaskHistory = task.history[0]; - - expect(task.history).to.have.lengthOf(1); - expect(task.value).to.equal(1); - }); - - it('should update the history', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: daily._id}); - - expect(task.history).to.have.lengthOf(1); - expect(task.history[0].date).to.not.equal(previousTaskHistory.date); - expect(task.history[0].value).to.not.equal(previousTaskHistory.value); - }); - }); - - context('todos', () => { - let todo; - let usersChallengeTaskId; - - before(async () => { - todo = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test todo', - type: 'todo', - }); - let updatedUser = await user.get('/user'); - usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; - }); - - it('scores but does not add history', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: todo._id}); - - expect(task.history).to.not.exist; - expect(task.value).to.equal(1); - }); - }); - - context('rewards', () => { - let reward; - let usersChallengeTaskId; - - before(async () => { - reward = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test reward', - type: 'reward', - }); - let updatedUser = await user.get('/user'); - usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; - }); - - it('does not score', async () => { - await user.post(`/tasks/${usersChallengeTaskId}/score/up`); - - let tasks = await user.get(`/tasks/challenge/${challenge._id}`); - let task = find(tasks, {_id: reward._id}); - - expect(task.history).to.not.exist; - expect(task.value).to.equal(0); - }); - }); -}); diff --git a/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js b/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js deleted file mode 100644 index 4dc2ceaa3e..0000000000 --- a/test/api/v3/integration/tasks/challenges/PUT-tasks_challenge_challengeId_tasksId_checklist_itemId.test.js +++ /dev/null @@ -1,155 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('PUT /tasks/:taskId/checklist/:itemId', () => { - let user; - let guild; - let challenge; - - before(async () => { - user = await generateUser(); - guild = await generateGroup(user); - challenge = await generateChallenge(user, guild); - }); - - it('fails on task not found', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'todo', - text: 'Todo with checklist', - }); - - await expect(user.put(`/tasks/${task._id}/checklist/${generateUUID()}`, { - text: 'updated', - completed: true, - _id: 123, // ignored - })) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); - - it('returns error when user is not a member of the challenge', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'todo', - text: 'Todo with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - let anotherUser = await generateUser(); - - await expect(anotherUser.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { - text: 'updated', - completed: true, - _id: 123, // ignored - })) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyChalLeaderEditTasks'), - }); - }); - - it('updates a checklist item on dailies', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { - text: 'updated', - completed: true, - _id: 123, // ignored - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('updated'); - expect(savedTask.checklist[0].completed).to.equal(true); - expect(savedTask.checklist[0].id).to.not.equal('123'); - }); - - it('updates a checklist item on todos', async () => { - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - type: 'todo', - text: 'Todo with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { - text: 'updated', - completed: true, - _id: 123, // ignored - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('updated'); - expect(savedTask.checklist[0].completed).to.equal(true); - expect(savedTask.checklist[0].id).to.not.equal('123'); - }); - - it('fails on habits', async () => { - let habit = await user.post('/tasks/user', { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on rewards', async () => { - let reward = await user.post('/tasks/user', { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on task not found', async () => { - await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('fails on checklist item not found', async () => { - let createdTask = await user.post('/tasks/user', { - type: 'daily', - text: 'daily with checklist', - }); - - await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js deleted file mode 100644 index 2cf08bbece..0000000000 --- a/test/api/v3/integration/tasks/checklists/DELETE-tasks_taskId_checklist_itemId.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /tasks/:taskId/checklist/:itemId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('deletes a checklist item', async () => { - let task = await user.post('/tasks/user', { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, {text: 'Checklist Item 1', completed: false}); - - await user.del(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`); - savedTask = await user.get(`/tasks/${task._id}`); - - expect(savedTask.checklist.length).to.equal(0); - }); - - it('does not work with habits', async () => { - let habit = await user.post('/tasks/user', { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.del(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('does not work with rewards', async () => { - let reward = await user.post('/tasks/user', { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.del(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on task not found', async () => { - await expect(user.del(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('fails on checklist item not found', async () => { - let createdTask = await user.post('/tasks/user', { - type: 'daily', - text: 'daily with checklist', - }); - - await expect(user.del(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js deleted file mode 100644 index 7166db02da..0000000000 --- a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist.test.js +++ /dev/null @@ -1,73 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:taskId/checklist/', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('adds a checklist item to a task', async () => { - let task = await user.post('/tasks/user', { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - ignored: false, - _id: 123, - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('Checklist Item 1'); - expect(savedTask.checklist[0].completed).to.equal(false); - expect(savedTask.checklist[0].id).to.be.a('string'); - expect(savedTask.checklist[0].id).to.not.equal('123'); - expect(savedTask.checklist[0].ignored).to.be.an('undefined'); - }); - - it('does not add a checklist to habits', async () => { - let habit = await user.post('/tasks/user', { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.post(`/tasks/${habit._id}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('does not add a checklist to rewards', async () => { - let reward = await user.post('/tasks/user', { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.post(`/tasks/${reward._id}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on task not found', async () => { - await expect(user.post(`/tasks/${generateUUID()}/checklist`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js b/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js deleted file mode 100644 index edb65dfb65..0000000000 --- a/test/api/v3/integration/tasks/checklists/POST-tasks_taskId_checklist_itemId_score.test.js +++ /dev/null @@ -1,79 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:taskId/checklist/:itemId/score', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('scores a checklist item', async () => { - let task = await user.post('/tasks/user', { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - savedTask = await user.post(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}/score`); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].completed).to.equal(true); - }); - - it('fails on habits', async () => { - let habit = await user.post('/tasks/user', { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.post(`/tasks/${habit._id}/checklist/${generateUUID()}/score`, { - text: 'Checklist Item 1', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on rewards', async () => { - let reward = await user.post('/tasks/user', { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.post(`/tasks/${reward._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on task not found', async () => { - await expect(user.post(`/tasks/${generateUUID()}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('fails on checklist item not found', async () => { - let createdTask = await user.post('/tasks/user', { - type: 'daily', - text: 'daily with checklist', - }); - - await expect(user.post(`/tasks/${createdTask._id}/checklist/${generateUUID()}/score`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js b/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js deleted file mode 100644 index 003bcb2650..0000000000 --- a/test/api/v3/integration/tasks/checklists/PUT-tasks_taskId_checklist_itemId.test.js +++ /dev/null @@ -1,83 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('PUT /tasks/:taskId/checklist/:itemId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('updates a checklist item', async () => { - let task = await user.post('/tasks/user', { - type: 'daily', - text: 'Daily with checklist', - }); - - let savedTask = await user.post(`/tasks/${task._id}/checklist`, { - text: 'Checklist Item 1', - completed: false, - }); - - savedTask = await user.put(`/tasks/${task._id}/checklist/${savedTask.checklist[0].id}`, { - text: 'updated', - completed: true, - _id: 123, // ignored - }); - - expect(savedTask.checklist.length).to.equal(1); - expect(savedTask.checklist[0].text).to.equal('updated'); - expect(savedTask.checklist[0].completed).to.equal(true); - expect(savedTask.checklist[0].id).to.not.equal('123'); - }); - - it('fails on habits', async () => { - let habit = await user.post('/tasks/user', { - type: 'habit', - text: 'habit with checklist', - }); - - await expect(user.put(`/tasks/${habit._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on rewards', async () => { - let reward = await user.post('/tasks/user', { - type: 'reward', - text: 'reward with checklist', - }); - - await expect(user.put(`/tasks/${reward._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('checklistOnlyDailyTodo'), - }); - }); - - it('fails on task not found', async () => { - await expect(user.put(`/tasks/${generateUUID()}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('fails on checklist item not found', async () => { - let createdTask = await user.post('/tasks/user', { - type: 'daily', - text: 'daily with checklist', - }); - - await expect(user.put(`/tasks/${createdTask._id}/checklist/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('checklistItemNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js deleted file mode 100644 index ebb1a3c9e8..0000000000 --- a/test/api/v3/integration/tasks/tags/DELETE-tasks_taskId_tags_tagId.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('DELETE /tasks/:taskId/tags/:tagId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('removes a tag from a task', async () => { - let task = await user.post('/tasks/user', { - type: 'habit', - text: 'Task with tag', - }); - - let tag = await user.post('/tags', {name: 'Tag 1'}); - - await user.post(`/tasks/${task._id}/tags/${tag.id}`); - await user.del(`/tasks/${task._id}/tags/${tag.id}`); - - let updatedTask = await user.get(`/tasks/${task._id}`); - - expect(updatedTask.tags.length).to.equal(0); - }); - - it('only deletes existing tags', async () => { - let createdTask = await user.post('/tasks/user', { - type: 'habit', - text: 'Task with tag', - }); - - await expect(user.del(`/tasks/${createdTask._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('tagNotFound'), - }); - }); -}); diff --git a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js b/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js deleted file mode 100644 index d6cea02036..0000000000 --- a/test/api/v3/integration/tasks/tags/POST-tasks_taskId_tags_tagId.test.js +++ /dev/null @@ -1,55 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateUUID } from 'uuid'; - -describe('POST /tasks/:taskId/tags/:tagId', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('adds a tag to a task', async () => { - let task = await user.post('/tasks/user', { - type: 'habit', - text: 'Task with tag', - }); - - let tag = await user.post('/tags', {name: 'Tag 1'}); - let savedTask = await user.post(`/tasks/${task._id}/tags/${tag.id}`); - - expect(savedTask.tags[0]).to.equal(tag.id); - }); - - it('does not add a tag to a task twice', async () => { - let task = await user.post('/tasks/user', { - type: 'habit', - text: 'Task with tag', - }); - - let tag = await user.post('/tags', {name: 'Tag 1'}); - - await user.post(`/tasks/${task._id}/tags/${tag.id}`); - - await expect(user.post(`/tasks/${task._id}/tags/${tag.id}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('alreadyTagged'), - }); - }); - - it('does not add a non existing tag to a task', async () => { - let task = await user.post('/tasks/user', { - type: 'habit', - text: 'Task with tag', - }); - - await expect(user.post(`/tasks/${task._id}/tags/${generateUUID()}`)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); -}); diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js deleted file mode 100644 index 2a284add53..0000000000 --- a/test/api/v3/integration/user/DELETE-user.test.js +++ /dev/null @@ -1,176 +0,0 @@ -import { - checkExistence, - createAndPopulateGroup, - generateGroup, - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import { - find, - each, - map, -} from 'lodash'; -import Bluebird from 'bluebird'; - -describe('DELETE /user', () => { - let user; - let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js - - beforeEach(async () => { - user = await generateUser({balance: 10}); - }); - - it('returns an errors if password is wrong', async () => { - await expect(user.del('/user', { - password: 'wrong-password', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('wrongPassword'), - }); - }); - - it('returns an error if user has active subscription', async () => { - let userWithSubscription = await generateUser({'purchased.plan.customerId': 'fake-customer-id'}); - - await expect(userWithSubscription.del('/user', { - password, - })).to.be.rejected.and.to.eventually.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cannotDeleteActiveAccount'), - }); - }); - - it('deletes the user\'s tasks', async () => { - // gets the user's tasks ids - let ids = []; - each(user.tasksOrder, (idsForOrder) => { - ids.push(...idsForOrder); - }); - - expect(ids.length).to.be.above(0); // make sure the user has some task to delete - - await user.del('/user', { - password, - }); - - await Bluebird.all(map(ids, id => { - return expect(checkExistence('tasks', id)).to.eventually.eql(false); - })); - }); - - it('deletes the user', async () => { - await user.del('/user', { - password, - }); - await expect(checkExistence('users', user._id)).to.eventually.eql(false); - }); - - context('last member of a party', () => { - let party; - - beforeEach(async () => { - party = await generateGroup(user, { - type: 'party', - privacy: 'private', - }); - }); - - it('deletes party when user is the only member', async () => { - await user.del('/user', { - password, - }); - await expect(checkExistence('party', party._id)).to.eventually.eql(false); - }); - }); - - context('last member of a private guild', () => { - let privateGuild; - - beforeEach(async () => { - privateGuild = await generateGroup(user, { - type: 'guild', - privacy: 'private', - }); - }); - - it('deletes guild when user is the only member', async () => { - await user.del('/user', { - password, - }); - await expect(checkExistence('groups', privateGuild._id)).to.eventually.eql(false); - }); - }); - - context('groups user is leader of', () => { - let guild, oldLeader, newLeader; - - beforeEach(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 1, - }); - - guild = group; - newLeader = members[0]; - oldLeader = groupLeader; - }); - - it('chooses new group leader for any group user was the leader of', async () => { - await oldLeader.del('/user', { - password, - }); - - let updatedGuild = await newLeader.get(`/groups/${guild._id}`); - - expect(updatedGuild.leader).to.exist; - expect(updatedGuild.leader._id).to.not.eql(oldLeader._id); - }); - }); - - context('groups user is a part of', () => { - let group1, group2, userToDelete, otherUser; - - beforeEach(async () => { - userToDelete = await generateUser({balance: 10}); - - group1 = await generateGroup(userToDelete, { - type: 'guild', - privacy: 'public', - }); - - let {group, members} = await createAndPopulateGroup({ - groupDetails: { - type: 'guild', - privacy: 'public', - }, - members: 3, - }); - - group2 = group; - otherUser = members[0]; - - await userToDelete.post(`/groups/${group2._id}/join`); - }); - - it('removes user from all groups user was a part of', async () => { - await userToDelete.del('/user', { - password, - }); - - let updatedGroup1Members = await otherUser.get(`/groups/${group1._id}/members`); - let updatedGroup2Members = await otherUser.get(`/groups/${group2._id}/members`); - let userInGroup = find(updatedGroup2Members, (member) => { - return member._id === userToDelete._id; - }); - - expect(updatedGroup1Members).to.be.empty; - expect(updatedGroup2Members).to.not.be.empty; - expect(userInGroup).to.not.exist; - }); - }); -}); diff --git a/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js b/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js deleted file mode 100644 index 46844dd855..0000000000 --- a/test/api/v3/integration/user/DELETE-user_delete_webhook.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -let user; -let endpoint = '/user/webhook'; - -describe('DELETE /user/webhook', () => { - beforeEach(async () => { - user = await generateUser(); - }); - - it('succeeds', async () => { - let id = 'some-id'; - user.preferences.webhooks[id] = { url: 'http://some-url.com', enabled: true }; - await user.sync(); - expect(user.preferences.webhooks).to.eql({}); - let response = await user.del(`${endpoint}/${id}`); - expect(response).to.eql({}); - await user.sync(); - expect(user.preferences.webhooks).to.eql({}); - }); -}); diff --git a/test/api/v3/integration/user/DELETE-user_messages.test.js b/test/api/v3/integration/user/DELETE-user_messages.test.js deleted file mode 100644 index 98df8e0209..0000000000 --- a/test/api/v3/integration/user/DELETE-user_messages.test.js +++ /dev/null @@ -1,27 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('DELETE user message', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ inbox: { messages: { first: 'message', second: 'message' } } }); - expect(user.inbox.messages.first).to.eql('message'); - expect(user.inbox.messages.second).to.eql('message'); - }); - - it('one message', async () => { - let result = await user.del('/user/messages/first'); - await user.sync(); - expect(result).to.eql({ second: 'message' }); - expect(user.inbox.messages).to.eql({ second: 'message' }); - }); - - it('clear all', async () => { - let result = await user.del('/user/messages'); - await user.sync(); - expect(user.inbox.messages).to.eql({}); - expect(result).to.eql({}); - }); -}); diff --git a/test/api/v3/integration/user/GET-user.test.js b/test/api/v3/integration/user/GET-user.test.js deleted file mode 100644 index f4ed75f03f..0000000000 --- a/test/api/v3/integration/user/GET-user.test.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /user', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns the authenticated user', async () => { - let returnedUser = await user.get('/user'); - expect(returnedUser._id).to.equal(user._id); - }); - - it('does not return private paths (and apiToken)', async () => { - let returnedUser = await user.get('/user'); - - expect(returnedUser.auth.local.hashed_password).to.not.exist; - expect(returnedUser.auth.local.salt).to.not.exist; - expect(returnedUser.apiToken).to.not.exist; - }); -}); diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js deleted file mode 100644 index bbcaaf6249..0000000000 --- a/test/api/v3/integration/user/GET-user_anonymized.test.js +++ /dev/null @@ -1,90 +0,0 @@ -import { - generateUser, - generateHabit, - generateDaily, - generateReward, -} from '../../../../helpers/api-integration/v3'; -import common from '../../../../../common'; -import { v4 as generateUUID } from 'uuid'; - -describe('GET /user/anonymized', () => { - let user; - let endpoint = '/user/anonymized'; - - before(async () => { - user = await generateUser(); - await user.update({ newMessages: ['some', 'new', 'messages'], profile: 'profile', 'purchased.plan': 'purchased plan', - contributor: 'contributor', invitations: 'invitations', 'items.special.nyeReceived': 'some', 'items.special.valentineReceived': 'some', - webhooks: 'some', 'achievements.challenges': 'some', - 'inbox.messages': [{ text: 'some text' }], - tags: [{ name: 'some name', challenge: 'some challenge' }], - }); - - await generateHabit({ userId: user._id }); - await generateHabit({ userId: user._id, text: generateUUID() }); - let daily = await generateDaily({ userId: user._id, checklist: [{ completed: false, text: 'this-text' }] }); - expect(daily.checklist[0].text.substr(0, 5)).to.not.eql('item '); - await generateReward({ userId: user._id, text: 'some text 4' }); - - expect(user.newMessages).to.exist; - expect(user.profile).to.exist; - expect(user.purchased.plan).to.exist; - expect(user.contributor).to.exist; - expect(user.invitations).to.exist; - expect(user.items.special.nyeReceived).to.exist; - expect(user.items.special.valentineReceived).to.exist; - expect(user.webhooks).to.exist; - expect(user.achievements.challenges).to.exist; - expect(user.inbox.messages[0].text).to.exist; - expect(user.inbox.messages[0].text).to.not.eql('inbox message text'); - expect(user.tags[0].name).to.exist; - expect(user.tags[0].name).to.not.eql('tag'); - expect(user.tags[0].challenge).to.not.eql('challenge'); - }); - - it('returns the authenticated user', async () => { - let returnedUser = await user.get(endpoint); - returnedUser = returnedUser.user; - expect(returnedUser._id).to.equal(user._id); - }); - - it('does not return private paths (and apiToken)', async () => { - let returnedUser = await user.get(endpoint); - let tasks2 = returnedUser.tasks; - returnedUser = returnedUser.user; - expect(returnedUser.auth.local).to.not.exist; - expect(returnedUser.apiToken).to.not.exist; - expect(returnedUser.stats.maxHealth).to.eql(common.maxHealth); - expect(returnedUser.stats.toNextLevel).to.eql(common.tnl(user.stats.lvl)); - expect(returnedUser.stats.maxMP).to.eql(30); // TODO why 30? - expect(returnedUser.newMessages).to.not.exist; - expect(returnedUser.profile).to.not.exist; - expect(returnedUser.purchased.plan).to.not.exist; - expect(returnedUser.contributor).to.not.exist; - expect(returnedUser.invitations).to.not.exist; - expect(returnedUser.items.special.nyeReceived).to.not.exist; - expect(returnedUser.items.special.valentineReceived).to.not.exist; - expect(returnedUser.webhooks).to.not.exist; - expect(returnedUser.achievements.challenges).to.not.exist; - _.forEach(returnedUser.inbox.messages, (msg) => { - expect(msg.text).to.eql('inbox message text'); - }); - _.forEach(returnedUser.tags, (tag) => { - expect(tag.name).to.eql('tag'); - expect(tag.challenge).to.eql('challenge'); - }); - // tasks - expect(tasks2).to.exist; - expect(tasks2.length).to.eql(5); // +1 because generateUser() assigns one todo - expect(tasks2[0].checklist).to.exist; - _.forEach(tasks2, (task) => { - expect(task.text).to.eql('task text'); - expect(task.notes).to.eql('task notes'); - if (task.checklist) { - _.forEach(task.checklist, (c) => { - expect(c.text.substr(0, 5)).to.eql('item '); - }); - } - }); - }); -}); diff --git a/test/api/v3/integration/user/GET-user_inventory_buy.test.js b/test/api/v3/integration/user/GET-user_inventory_buy.test.js deleted file mode 100644 index fd2a25b4ee..0000000000 --- a/test/api/v3/integration/user/GET-user_inventory_buy.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('GET /user/inventory/buy', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('returns the gear items available for purchase', async () => { - let buyList = await user.get('/user/inventory/buy'); - - expect(_.find(buyList, item => { - return item.text === t('armorWarrior1Text'); - })).to.exist; - - expect(_.find(buyList, item => { - return item.text === t('armorWarrior2Text'); - })).to.not.exist; - }); -}); diff --git a/test/api/v3/integration/user/POST-user_addPushDevice.test.js b/test/api/v3/integration/user/POST-user_addPushDevice.test.js deleted file mode 100644 index 1a3a5d4f03..0000000000 --- a/test/api/v3/integration/user/POST-user_addPushDevice.test.js +++ /dev/null @@ -1,35 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/addPushDevice', () => { - let user; - let regId = '10'; - let type = 'someRandomType'; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error if user already has the push device', async () => { - await user.post('/user/addPushDevice', {type, regId}); - await expect(user.post('/user/addPushDevice', {type, regId})) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('pushDeviceAlreadyAdded'), - }); - }); - - // More tests in common code unit tests - - it('adds a push device to the user', async () => { - let response = await user.post('/user/addPushDevice', {type, regId}); - await user.sync(); - - expect(response.message).to.equal(t('pushDeviceAdded')); - expect(user.pushDevices[0].type).to.equal(type); - expect(user.pushDevices[0].regId).to.equal(regId); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_add_webhook.test.js b/test/api/v3/integration/user/POST-user_add_webhook.test.js deleted file mode 100644 index d13f15baa4..0000000000 --- a/test/api/v3/integration/user/POST-user_add_webhook.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -let user; -let endpoint = '/user/webhook'; - -describe('POST /user/webhook', () => { - beforeEach(async () => { - user = await generateUser(); - }); - - it('validates', async () => { - await expect(user.post(endpoint, { enabled: true })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidUrl'), - }); - }); - - it('successfully adds the webhook', async () => { - expect(user.preferences.webhooks).to.eql({}); - let response = await user.post(endpoint, { enabled: true, url: 'http://some-url.com'}); - expect(response.id).to.exist; - await user.sync(); - expect(user.preferences.webhooks).to.not.eql({}); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_allocate.test.js b/test/api/v3/integration/user/POST-user_allocate.test.js deleted file mode 100644 index 02d4990092..0000000000 --- a/test/api/v3/integration/user/POST-user_allocate.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/allocate', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('returns an error if an invalid attribute is supplied', async () => { - await expect(user.post('/user/allocate?stat=invalid')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidAttribute', {attr: 'invalid'}), - }); - }); - - it('returns an error if the user doesn\'t have attribute points', async () => { - await expect(user.post('/user/allocate')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughAttrPoints'), - }); - }); - - it('allocates attribute points', async () => { - await user.update({'stats.points': 1}); - let res = await user.post('/user/allocate?stat=con'); - await user.sync(); - expect(user.stats.con).to.equal(1); - expect(user.stats.points).to.equal(0); - expect(res.con).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_allocate_now.test.js b/test/api/v3/integration/user/POST-user_allocate_now.test.js deleted file mode 100644 index b45f2156be..0000000000 --- a/test/api/v3/integration/user/POST-user_allocate_now.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/allocate-now', () => { - // More tests in common code unit tests - - it('auto allocates all points', async () => { - let user = await generateUser({ - 'stats.points': 5, - 'stats.int': 3, - 'stats.con': 9, - 'stats.per': 9, - 'stats.str': 9, - 'preferences.allocationMode': 'flat', - }); - - let res = await user.post('/user/allocate-now'); - await user.sync(); - - expect(res).to.eql(user.stats); - expect(user.stats.points).to.equal(0); - expect(user.stats.con).to.equal(9); - expect(user.stats.int).to.equal(8); - expect(user.stats.per).to.equal(9); - expect(user.stats.str).to.equal(9); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_block.test.js b/test/api/v3/integration/user/POST-user_block.test.js deleted file mode 100644 index 51766eb51e..0000000000 --- a/test/api/v3/integration/user/POST-user_block.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('block user', () => { - let user; - let blockedUser; - let blockedUser2; - - beforeEach(async () => { - blockedUser = await generateUser(); - blockedUser2 = await generateUser(); - user = await generateUser({ inbox: { blocks: [blockedUser._id] } }); - expect(user.inbox.blocks.length).to.eql(1); - expect(user.inbox.blocks).to.eql([blockedUser._id]); - }); - - it('validates uuid', async () => { - await expect(user.post('/user/block/1')).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidUUID'), - }); - }); - - it('successfully', async () => { - let response = await user.post(`/user/block/${blockedUser2._id}`); - await user.sync(); - expect(response).to.eql([blockedUser._id, blockedUser2._id]); - expect(user.inbox.blocks.length).to.eql(2); - expect(user.inbox.blocks).to.include(blockedUser2._id); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy.test.js b/test/api/v3/integration/user/POST-user_buy.test.js deleted file mode 100644 index ffad12f2a0..0000000000 --- a/test/api/v3/integration/user/POST-user_buy.test.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable camelcase */ - -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import shared from '../../../../../common/script'; - -let content = shared.content; - -describe('POST /user/buy/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'stats.gp': 400, - }); - }); - - // More tests in common code unit tests - - it('returns an error if the item is not found', async () => { - await expect(user.post('/user/buy/notExisting')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('itemNotFound', {key: 'notExisting'}), - }); - }); - - it('buys a potion', async () => { - await user.update({ - 'stats.gp': 400, - }); - - let potion = content.potion; - let res = await user.post('/user/buy/potion'); - await user.sync(); - - expect(user.stats.hp).to.equal(50); - expect(res.data).to.eql(user.stats); - expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); - }); - - it('buys a piece of gear', async () => { - let key = 'armor_warrior_1'; - - await user.post(`/user/buy/${key}`); - await user.sync(); - - expect(user.items.gear.owned).to.eql({ - armor_warrior_1: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_armoire.test.js b/test/api/v3/integration/user/POST-user_buy_armoire.test.js deleted file mode 100644 index 32b8134647..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_armoire.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/buy-armoire', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'stats.gp': 400, - }); - }); - - // More tests in common code unit tests - - it('returns an error if user does not have enough gold', async () => { - await user.update({ - 'stats.gp': 5, - }); - - await expect(user.post('/user/buy-armoire')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageNotEnoughGold'), - }); - }); - - it('reduces gold when buying from the armoire', async () => { - await user.post('/user/buy-armoire'); - - await user.sync(); - - expect(user.stats.gp).to.equal(300); - }); - - xit('buys a piece of armoire', async () => { - // Skipped because can't stub predictableRandom correctly - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_gear.test.js b/test/api/v3/integration/user/POST-user_buy_gear.test.js deleted file mode 100644 index f577263d4a..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_gear.test.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable camelcase */ - -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/buy-gear/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'stats.gp': 400, - }); - }); - - // More tests in common code unit tests - - it('returns an error if the item is not found', async () => { - await expect(user.post('/user/buy-gear/notExisting')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('itemNotFound', {key: 'notExisting'}), - }); - }); - - it('buys a piece of gear', async () => { - let key = 'armor_warrior_1'; - - await user.post(`/user/buy-gear/${key}`); - await user.sync(); - - expect(user.items.gear.owned).to.eql({ - armor_warrior_1: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_health_potion.test.js b/test/api/v3/integration/user/POST-user_buy_health_potion.test.js deleted file mode 100644 index 835e893bd7..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_health_potion.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import shared from '../../../../../common/script'; - -let content = shared.content; - -describe('POST /user/buy-health-potion', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'stats.hp': 40, - }); - }); - - // More tests in common code unit tests - - it('returns an error if user does not have enough gold', async () => { - await expect(user.post('/user/buy-health-potion')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageNotEnoughGold'), - }); - }); - - it('buys a potion', async () => { - await user.update({ - 'stats.gp': 400, - }); - - let potion = content.potion; - let res = await user.post('/user/buy-health-potion'); - await user.sync(); - - expect(user.stats.hp).to.equal(50); - expect(res.data).to.eql(user.stats); - expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js deleted file mode 100644 index da7116d732..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/buy-mystery-set/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'purchased.plan.consecutive.trinkets': 1, - }); - }); - - // More tests in common code unit tests - - it('returns an error if the mystery set is not found', async () => { - await expect(user.post('/user/buy-mystery-set/notExisting')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('mysterySetNotFound'), - }); - }); - - it('buys a mystery set', async () => { - let key = 301404; - - let res = await user.post(`/user/buy-mystery-set/${key}`); - await user.sync(); - - expect(res.data).to.eql({ - items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared - purchasedPlanConsecutive: user.purchased.plan.consecutive, - }); - expect(res.message).to.equal(t('hourglassPurchaseSet')); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_quest.test.js b/test/api/v3/integration/user/POST-user_buy_quest.test.js deleted file mode 100644 index 3330988537..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_quest.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import shared from '../../../../../common/script'; - -let content = shared.content; - -describe('POST /user/buy-quest/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('returns an error if the quest is not found', async () => { - await expect(user.post('/user/buy-quest/notExisting')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('questNotFound', {key: 'notExisting'}), - }); - }); - - it('buys a quest', async () => { - let key = 'dilatoryDistress1'; - let item = content.quests[key]; - - await user.update({'stats.gp': 250}); - let res = await user.post(`/user/buy-quest/${key}`); - await user.sync(); - - expect(res.data).to.eql(user.items.quests); - expect(res.message).to.equal(t('messageBought', { - itemText: item.text(), - })); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_buy_special_spell.test.js b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js deleted file mode 100644 index 2ae16d1baf..0000000000 --- a/test/api/v3/integration/user/POST-user_buy_special_spell.test.js +++ /dev/null @@ -1,43 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import shared from '../../../../../common/script'; - -let content = shared.content; - -describe('POST /user/buy-special-spell/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('returns an error if the special spell is not found', async () => { - await expect(user.post('/user/buy-special-spell/notExisting')) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('spellNotFound', {spellId: 'notExisting'}), - }); - }); - - it('buys a special spell', async () => { - let key = 'thankyou'; - let item = content.special[key]; - - await user.update({'stats.gp': 250}); - let res = await user.post(`/user/buy-special-spell/${key}`); - await user.sync(); - - expect(res.data).to.eql({ - items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared - stats: user.stats, - }); - expect(res.message).to.equal(t('messageBought', { - itemText: item.text(), - })); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_change-class.test.js b/test/api/v3/integration/user/POST-user_change-class.test.js deleted file mode 100644 index d4b4192f23..0000000000 --- a/test/api/v3/integration/user/POST-user_change-class.test.js +++ /dev/null @@ -1,30 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/change-class', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'flags.classSelected': false, - 'stats.lvl': 10, - }); - }); - - // More tests in common code unit tests - - it('changes class', async () => { - let res = await user.post('/user/change-class?class=rogue'); - await user.sync(); - - expect(res).to.eql(JSON.parse( - JSON.stringify({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - items: user.items, - }) - )); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js deleted file mode 100644 index 8ada2ede7d..0000000000 --- a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js +++ /dev/null @@ -1,172 +0,0 @@ -import { - generateUser, - translate as t, - createAndPopulateGroup, - generateChallenge, - sleep, -} from '../../../../helpers/api-integration/v3'; - -import { v4 as generateUUID } from 'uuid'; - -describe('POST /user/class/cast/:spellId', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error if spell does not exist', async () => { - await user.update({'stats.class': 'rogue'}); - let spellId = 'invalidSpell'; - await expect(user.post(`/user/class/cast/${spellId}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('spellNotFound', {spellId}), - }); - }); - - it('returns an error if spell does not exist in user\'s class', async () => { - let spellId = 'pickPocket'; - await expect(user.post(`/user/class/cast/${spellId}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('spellNotFound', {spellId}), - }); - }); - - it('returns an error if spell.mana > user.mana', async () => { - await user.update({'stats.class': 'rogue'}); - await expect(user.post('/user/class/cast/backStab')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughMana'), - }); - }); - - it('returns an error if spell.value > user.gold', async () => { - await expect(user.post('/user/class/cast/birthday')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageNotEnoughGold'), - }); - }); - - it('returns an error if spell.lvl > user.level', async () => { - await user.update({'stats.mp': 200, 'stats.class': 'wizard'}); - await expect(user.post('/user/class/cast/earth')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('spellLevelTooHigh', {level: 13}), - }); - }); - - it('returns an error if user doesn\'t own the spell', async () => { - await expect(user.post('/user/class/cast/snowball')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('spellNotOwned'), - }); - }); - - it('returns an error if targetId is not an UUID', async () => { - await expect(user.post('/user/class/cast/spellId?targetId=notAnUUID')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('returns an error if targetId is required but missing', async () => { - await user.update({'stats.class': 'rogue', 'stats.lvl': 11}); - await expect(user.post('/user/class/cast/pickPocket')) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('targetIdUUID'), - }); - }); - - it('returns an error if targeted task doesn\'t exist', async () => { - await user.update({'stats.class': 'rogue', 'stats.lvl': 11}); - await expect(user.post(`/user/class/cast/pickPocket?targetId=${generateUUID()}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - }); - - it('returns an error if a challenge task was targeted', async () => { - let {group, groupLeader} = await createAndPopulateGroup(); - let challenge = await generateChallenge(groupLeader, group); - await groupLeader.post(`/tasks/challenge/${challenge._id}`, [ - {type: 'habit', text: 'task text'}, - ]); - await groupLeader.update({'stats.class': 'rogue', 'stats.lvl': 11}); - await sleep(0.5); - await groupLeader.sync(); - await expect(groupLeader.post(`/user/class/cast/pickPocket?targetId=${groupLeader.tasksOrder.habits[0]}`)) - .to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('challengeTasksNoCast'), - }); - }); - - it('returns an error if targeted party member doesn\'t exist', async () => { - let {groupLeader} = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 1, - }); - await groupLeader.update({'items.special.snowball': 3}); - - let target = generateUUID(); - await expect(groupLeader.post(`/user/class/cast/snowball?targetId=${target}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userWithIDNotFound', {userId: target}), - }); - }); - - it('returns an error if party does not exists', async () => { - await user.update({'items.special.snowball': 3}); - - await expect(user.post(`/user/class/cast/snowball?targetId=${generateUUID()}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('partyNotFound'), - }); - }); - - it('send message in party chat if party && !spell.silent', async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - members: 1, - }); - await groupLeader.update({'stats.mp': 200, 'stats.class': 'wizard', 'stats.lvl': 13}); - await groupLeader.post('/user/class/cast/earth'); - await sleep(1); - await group.sync(); - expect(group.chat[0]).to.exists; - expect(group.chat[0].uuid).to.equal('system'); - }); - - // TODO find a way to have sinon working in integration tests - // it doesn't work when tests are running separately from server - it('passes correct target to spell when targetType === \'task\''); - it('passes correct target to spell when targetType === \'tasks\''); - it('passes correct target to spell when targetType === \'self\''); - it('passes correct target to spell when targetType === \'party\''); - it('passes correct target to spell when targetType === \'user\''); - it('passes correct target to spell when targetType === \'party\' and user is not in a party'); - it('passes correct target to spell when targetType === \'user\' and user is not in a party'); -}); diff --git a/test/api/v3/integration/user/POST-user_custom-day-start.test.js b/test/api/v3/integration/user/POST-user_custom-day-start.test.js deleted file mode 100644 index 868b9ae91d..0000000000 --- a/test/api/v3/integration/user/POST-user_custom-day-start.test.js +++ /dev/null @@ -1,47 +0,0 @@ -import moment from 'moment'; -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -let user; -let endpoint = '/user/custom-day-start'; - -describe('POST /user/custom-day-start', () => { - beforeEach(async () => { - user = await generateUser(); - }); - - it('updates user.preferences.dayStart', async () => { - expect(user.preferences.dayStart).to.eql(0); - - await user.post(endpoint, { dayStart: 1 }); - await user.sync(); - - expect(user.preferences.dayStart).to.eql(1); - }); - - it('sets lastCron to the current time to prevent an unexpected cron', async () => { - let oldCron = moment().subtract(7, 'hours'); - - await user.update({lastCron: oldCron}); - await user.post(endpoint, { dayStart: 1 }); - await user.sync(); - - expect(user.lastCron.valueOf()).to.be.gt(oldCron.valueOf()); - }); - - it('returns a confirmation message', async () => { - let {message} = await user.post(endpoint, { dayStart: 1 }); - - expect(message).to.eql(t('customDayStartHasChanged')); - }); - - it('errors if invalid value is passed', async () => { - await expect(user.post(endpoint, { dayStart: 'foo' })) - .to.eventually.be.rejected; - - await expect(user.post(endpoint, { dayStart: 24})) - .to.eventually.be.rejected; - }); -}); diff --git a/test/api/v3/integration/user/POST-user_disable-classes.test.js b/test/api/v3/integration/user/POST-user_disable-classes.test.js deleted file mode 100644 index 0632a8adc7..0000000000 --- a/test/api/v3/integration/user/POST-user_disable-classes.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/disable-classes', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('disable classes', async () => { - let res = await user.post('/user/disable-classes'); - await user.sync(); - - expect(res).to.eql(JSON.parse( - JSON.stringify({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - }) - )); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_equip_type_key.test.js b/test/api/v3/integration/user/POST-user_equip_type_key.test.js deleted file mode 100644 index c5cde777df..0000000000 --- a/test/api/v3/integration/user/POST-user_equip_type_key.test.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable camelcase */ - -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/equip/:type/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('equip an item', async () => { - await user.update({ - 'items.gear.owned': { - weapon_warrior_0: true, - weapon_warrior_1: true, - weapon_warrior_2: true, - weapon_wizard_1: true, - weapon_wizard_2: true, - shield_base_0: true, - shield_warrior_1: true, - }, - 'items.gear.equipped': { - weapon: 'weapon_warrior_0', - shield: 'shield_base_0', - }, - 'stats.gp': 200, - }); - - await user.post('/user/equip/equipped/weapon_warrior_1'); - let res = await user.post('/user/equip/equipped/weapon_warrior_2'); - await user.sync(); - - expect(res).to.eql(JSON.parse(JSON.stringify(user.items))); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_feed_pet_food.test.js b/test/api/v3/integration/user/POST-user_feed_pet_food.test.js deleted file mode 100644 index 7581c266ee..0000000000 --- a/test/api/v3/integration/user/POST-user_feed_pet_food.test.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable camelcase */ - -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import content from '../../../../../common/script/content'; - -describe('POST /user/feed/:pet/:food', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('does not enjoy the food', async () => { - await user.update({ - 'items.pets.Wolf-Base': 5, - 'items.food.Milk': 2, - }); - - let food = content.food.Milk; - let [egg, potion] = 'Wolf-Base'.split('-'); - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let res = await user.post('/user/feed/Wolf-Base/Milk'); - await user.sync(); - expect(res).to.eql({ - data: user.items.pets['Wolf-Base'], - message: t('messageDontEnjoyFood', { - egg: t('petName', { - potion: potionText, - egg: eggText, - }), - foodText: food.text(), - }), - }); - - expect(user.items.food.Milk).to.equal(1); - expect(user.items.pets['Wolf-Base']).to.equal(7); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js b/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js deleted file mode 100644 index 9621377beb..0000000000 --- a/test/api/v3/integration/user/POST-user_hatch_egg_hatchingPotion.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/hatch/:egg/:hatchingPotion', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('hatch a new pet', async () => { - await user.update({ - 'items.eggs.Wolf': 1, - 'items.hatchingPotions.Base': 1, - }); - let res = await user.post('/user/hatch/Wolf/Base'); - await user.sync(); - expect(user.items.pets['Wolf-Base']).to.equal(5); - expect(user.items.eggs.Wolf).to.equal(0); - expect(user.items.hatchingPotions.Base).to.equal(0); - - expect(res).to.eql({ - message: t('messageHatched'), - data: JSON.parse(JSON.stringify(user.items)), - }); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_mark_pms_read.test.js b/test/api/v3/integration/user/POST-user_mark_pms_read.test.js deleted file mode 100644 index 50552359ef..0000000000 --- a/test/api/v3/integration/user/POST-user_mark_pms_read.test.js +++ /dev/null @@ -1,22 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/mark-pms-read', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('marks user\'s private messages as read', async () => { - await user.update({ - 'inbox.newMessages': 1, - }); - await user.post('/user/mark-pms-read'); - await user.sync(); - expect(user.inbox.newMessages).to.equal(0); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js deleted file mode 100644 index d9e9fe7326..0000000000 --- a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js +++ /dev/null @@ -1,26 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/open-mystery-item', () => { - let user; - let mysteryItemKey = 'eyewear_special_summerRogue'; - - beforeEach(async () => { - user = await generateUser({ - 'purchased.plan.mysteryItems': [mysteryItemKey], - }); - }); - - // More tests in common code unit tests - - it('opens a mystery item', async () => { - let response = await user.post('/user/open-mystery-item'); - await user.sync(); - - expect(user.items.gear.owned[mysteryItemKey]).to.be.true; - expect(response.message).to.equal(t('mysteryItemOpened')); - expect(response.data).to.deep.equal(user.items.gear.owned); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_purchase.test.js b/test/api/v3/integration/user/POST-user_purchase.test.js deleted file mode 100644 index dff6d59c48..0000000000 --- a/test/api/v3/integration/user/POST-user_purchase.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/purchase/:type/:key', () => { - let user; - let type = 'hatchingPotions'; - let key = 'Base'; - - beforeEach(async () => { - user = await generateUser({ - balance: 40, - }); - }); - - // More tests in common code unit tests - - it('returns an error when key is not provided', async () => { - await expect(user.post('/user/purchase/gems/gem')) - .to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('mustSubscribeToPurchaseGems'), - }); - }); - - it('purchases a gem item', async () => { - await user.post(`/user/purchase/${type}/${key}`); - await user.sync(); - - expect(user.items[type][key]).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js b/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js deleted file mode 100644 index cd43334d00..0000000000 --- a/test/api/v3/integration/user/POST-user_purchase_hourglass.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/purchase-hourglass/:type/:key', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'purchased.plan.consecutive.trinkets': 2, - }); - }); - - // More tests in common code unit tests - - it('buys a hourglass pet', async () => { - let response = await user.post('/user/purchase-hourglass/pets/MantisShrimp-Base'); - await user.sync(); - - expect(response.message).to.eql(t('hourglassPurchase')); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_read_card.test.js b/test/api/v3/integration/user/POST-user_read_card.test.js deleted file mode 100644 index 3b3573b6cc..0000000000 --- a/test/api/v3/integration/user/POST-user_read_card.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/read-card/:cardType', () => { - let user; - let cardType = 'greeting'; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error when unknown cardType is provded', async () => { - await expect(user.post('/user/read-card/randomCardType')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cardTypeNotAllowed'), - }); - }); - - // More tests in common code unit tests - - it('reads a card', async () => { - await user.update({ - 'items.special.greetingReceived': [true], - 'flags.cardReceived': true, - }); - - let response = await user.post(`/user/read-card/${cardType}`); - await user.sync(); - - expect(response.message).to.equal(t('readCard', {cardType})); - expect(user.items.special[`${cardType}Received`]).to.be.empty; - expect(user.flags.cardReceived).to.be.false; - }); -}); diff --git a/test/api/v3/integration/user/POST-user_rebirth.test.js b/test/api/v3/integration/user/POST-user_rebirth.test.js deleted file mode 100644 index 21fbed0b8d..0000000000 --- a/test/api/v3/integration/user/POST-user_rebirth.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import { - generateUser, - generateDaily, - generateReward, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/rebirth', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error when user balance is too low', async () => { - await expect(user.post('/user/rebirth')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('resets user\'s tasks', async () => { - await user.update({ - balance: 2, - }); - - let daily = await generateDaily({ - text: 'test habit', - type: 'daily', - value: 1, - streak: 1, - userId: user._id, - }); - - let reward = await generateReward({ - text: 'test reward', - type: 'reward', - value: 1, - userId: user._id, - }); - - let response = await user.post('/user/rebirth'); - await user.sync(); - - let updatedDaily = await user.get(`/tasks/${daily._id}`); - let updatedReward = await user.get(`/tasks/${reward._id}`); - - expect(response.message).to.equal(t('rebirthComplete')); - expect(updatedDaily.streak).to.equal(0); - expect(updatedDaily.value).to.equal(0); - expect(updatedReward.value).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_release_both.test.js b/test/api/v3/integration/user/POST-user_release_both.test.js deleted file mode 100644 index 8c47d95dfe..0000000000 --- a/test/api/v3/integration/user/POST-user_release_both.test.js +++ /dev/null @@ -1,48 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/release-both', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(async () => { - user = await generateUser({ - 'items.currentMount': animal, - 'items.currentPet': animal, - 'items.pets': {animal: 5}, - 'items.mounts': {animal: true}, - }); - }); - - it('returns an error when user balance is too low and user does not have triadBingo', async () => { - await expect(user.post('/user/release-both')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('grants triad bingo with gems', async () => { - await user.update({ - balance: 1.5, - }); - - let response = await user.post('/user/release-both'); - await user.sync(); - - expect(response.message).to.equal(t('mountsAndPetsReleased')); - expect(user.balance).to.equal(0); - expect(user.items.currentMount).to.be.empty; - expect(user.items.currentPet).to.be.empty; - expect(user.items.pets[animal]).to.be.empty; - expect(user.items.mounts[animal]).to.equal(null); - expect(user.achievements.beastMasterCount).to.equal(1); - expect(user.achievements.mountMasterCount).to.equal(1); - expect(user.achievements.triadBingoCount).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_release_mounts.test.js b/test/api/v3/integration/user/POST-user_release_mounts.test.js deleted file mode 100644 index 86391599f0..0000000000 --- a/test/api/v3/integration/user/POST-user_release_mounts.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/release-mounts', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(async () => { - user = await generateUser({ - 'items.currentMount': animal, - 'items.mounts': {animal: true}, - }); - }); - - it('returns an error when user balance is too low', async () => { - await expect(user.post('/user/release-mounts')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('releases mounts', async () => { - await user.update({ - balance: 1, - }); - - let response = await user.post('/user/release-mounts'); - await user.sync(); - - expect(response.message).to.equal(t('mountsReleased')); - expect(user.balance).to.equal(0); - expect(user.items.currentMount).to.be.empty; - expect(user.items.mounts[animal]).to.equal(null); - expect(user.achievements.mountMasterCount).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_release_pets.test.js b/test/api/v3/integration/user/POST-user_release_pets.test.js deleted file mode 100644 index a7f7b9b66f..0000000000 --- a/test/api/v3/integration/user/POST-user_release_pets.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/release-pets', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(async () => { - user = await generateUser({ - 'items.currentPet': animal, - 'items.pets': {animal: 5}, - }); - }); - - it('returns an error when user balance is too low', async () => { - await expect(user.post('/user/release-pets')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('releases pets', async () => { - await user.update({ - balance: 1, - }); - - let response = await user.post('/user/release-pets'); - await user.sync(); - - expect(response.message).to.equal(t('petsReleased')); - expect(user.balance).to.equal(0); - expect(user.items.currentPet).to.be.empty; - expect(user.items.pets[animal]).to.equal(0); - expect(user.achievements.beastMasterCount).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_reroll.test.js b/test/api/v3/integration/user/POST-user_reroll.test.js deleted file mode 100644 index 29774d1239..0000000000 --- a/test/api/v3/integration/user/POST-user_reroll.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { - generateUser, - generateDaily, - generateReward, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/reroll', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error when user balance is too low', async () => { - await expect(user.post('/user/reroll')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('resets user\'s tasks', async () => { - await user.update({ - balance: 2, - }); - - let daily = await generateDaily({ - text: 'test habit', - type: 'daily', - userId: user._id, - }); - - let reward = await generateReward({ - text: 'test reward', - type: 'reward', - value: 1, - userId: user._id, - }); - - let response = await user.post('/user/reroll'); - await user.sync(); - - let updatedDaily = await user.get(`/tasks/${daily._id}`); - let updatedReward = await user.get(`/tasks/${reward._id}`); - - expect(response.message).to.equal(t('fortifyComplete')); - expect(updatedDaily.value).to.equal(0); - expect(updatedReward.value).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_reset.test.js b/test/api/v3/integration/user/POST-user_reset.test.js deleted file mode 100644 index 2baf7bd083..0000000000 --- a/test/api/v3/integration/user/POST-user_reset.test.js +++ /dev/null @@ -1,104 +0,0 @@ -import { - generateUser, - generateGroup, - generateChallenge, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/reset', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('resets user\'s habits', async () => { - let task = await user.post('/tasks/user', { - text: 'test habit', - type: 'habit', - }); - - await user.post('/user/reset'); - await user.sync(); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - - expect(user.tasksOrder.habits).to.be.empty; - }); - - it('resets user\'s dailys', async () => { - let task = await user.post('/tasks/user', { - text: 'test daily', - type: 'daily', - }); - - await user.post('/user/reset'); - await user.sync(); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - - expect(user.tasksOrder.dailys).to.be.empty; - }); - - it('resets user\'s todos', async () => { - let task = await user.post('/tasks/user', { - text: 'test todo', - type: 'todo', - }); - - await user.post('/user/reset'); - await user.sync(); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - - expect(user.tasksOrder.todos).to.be.empty; - }); - - it('resets user\'s rewards', async () => { - let task = await user.post('/tasks/user', { - text: 'test reward', - type: 'reward', - }); - - await user.post('/user/reset'); - await user.sync(); - - await expect(user.get(`/tasks/${task._id}`)).to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('taskNotFound'), - }); - - expect(user.tasksOrder.rewards).to.be.empty; - }); - - it('does not delete challenge tasks', async () => { - let guild = await generateGroup(user); - let challenge = await generateChallenge(user, guild); - let task = await user.post(`/tasks/challenge/${challenge._id}`, { - text: 'test challenge habit', - type: 'habit', - }); - - await user.post('/user/reset'); - await user.sync(); - - let userChallengeTask = await user.get(`/tasks/${task._id}`); - - expect(userChallengeTask).to.eql(task); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_revive.test.js b/test/api/v3/integration/user/POST-user_revive.test.js deleted file mode 100644 index 6ba85ac87f..0000000000 --- a/test/api/v3/integration/user/POST-user_revive.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/revive', () => { - let user; - - beforeEach(async () => { - user = await generateUser({ - 'user.items.gear.owned': {weaponKey: true}, - }); - }); - - it('returns an error when user is not dead', async () => { - await expect(user.post('/user/revive')) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cannotRevive'), - }); - }); - - // More tests in common code unit tests - - it('decreases a stat', async () => { - await user.update({ - 'stats.str': 2, - 'stats.hp': 0, - }); - - await user.post('/user/revive'); - await user.sync(); - - expect(user.stats.str).to.equal(1); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_sell.test.js b/test/api/v3/integration/user/POST-user_sell.test.js deleted file mode 100644 index 1914336175..0000000000 --- a/test/api/v3/integration/user/POST-user_sell.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; -import content from '../../../../../common/script/content'; - -describe('POST /user/sell/:type/:key', () => { - let user; - let type = 'eggs'; - let key = 'Wolf'; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('returns an error when user does not have item', async () => { - await expect(user.post(`/user/sell/${type}/${key}`)) - .to.eventually.be.rejected.and.eql({ - code: 404, - error: 'NotFound', - message: t('userItemsKeyNotFound', {type}), - }); - }); - - it('sells an item', async () => { - await user.update({ - items: { - eggs: { - Wolf: 1, - }, - }, - }); - - await user.post(`/user/sell/${type}/${key}`); - await user.sync(); - - expect(user.stats.gp).to.equal(content[type][key].value); - }); -}); diff --git a/test/api/v3/integration/user/POST-user_sleep.test.js b/test/api/v3/integration/user/POST-user_sleep.test.js deleted file mode 100644 index 0e9773150e..0000000000 --- a/test/api/v3/integration/user/POST-user_sleep.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import { - generateUser, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/sleep', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - // More tests in common code unit tests - - it('toggles sleep status', async () => { - let res = await user.post('/user/sleep'); - expect(res).to.eql(true); - await user.sync(); - expect(user.preferences.sleep).to.be.true; - - let res2 = await user.post('/user/sleep'); - expect(res2).to.eql(false); - await user.sync(); - expect(user.preferences.sleep).to.be.false; - }); -}); diff --git a/test/api/v3/integration/user/POST-user_unlock.js b/test/api/v3/integration/user/POST-user_unlock.js deleted file mode 100644 index 6dbdb3c1b1..0000000000 --- a/test/api/v3/integration/user/POST-user_unlock.js +++ /dev/null @@ -1,37 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -describe('POST /user/unlock', () => { - let user; - let unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; - let unlockCost = 1.25; - let usersStartingGems = 5; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('returns an error when user balance is too low', async () => { - await expect(user.post(`/user/unlock?path=${unlockPath}`)) - .to.eventually.be.rejected.and.to.eql({ - code: 401, - error: 'NotAuthorized', - message: t('notEnoughGems'), - }); - }); - - // More tests in common code unit tests - - it('reduces a user\'s balance', async () => { - await user.update({ - balance: usersStartingGems, - }); - let response = await user.post(`/user/unlock?path=${unlockPath}`); - await user.sync(); - - expect(response.message).to.equal(t('unlocked')); - expect(user.balance).to.equal(usersStartingGems - unlockCost); - }); -}); diff --git a/test/api/v3/integration/user/PUT-user.test.js b/test/api/v3/integration/user/PUT-user.test.js deleted file mode 100644 index f606c95c2c..0000000000 --- a/test/api/v3/integration/user/PUT-user.test.js +++ /dev/null @@ -1,201 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -import { each, get } from 'lodash'; - -describe('PUT /user', () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - context('Allowed Operations', () => { - it('updates the user', async () => { - await user.put('/user', { - 'profile.name': 'Frodo', - 'preferences.costume': true, - 'stats.hp': 14, - }); - - await user.sync(); - - expect(user.profile.name).to.eql('Frodo'); - expect(user.preferences.costume).to.eql(true); - expect(user.stats.hp).to.eql(14); - }); - }); - - context('Top Level Protected Operations', () => { - let protectedOperations = { - 'gem balance': {balance: 100}, - auth: {'auth.blocked': true, 'auth.timestamps.created': new Date()}, - contributor: {'contributor.level': 9, 'contributor.admin': true, 'contributor.text': 'some text'}, - backer: {'backer.tier': 10, 'backer.npc': 'Bilbo'}, - subscriptions: {'purchased.plan.extraMonths': 500, 'purchased.plan.consecutive.trinkets': 1000}, - 'customization gem purchases': {'purchased.background.tavern': true, 'purchased.skin.bear': true}, - }; - - each(protectedOperations, (data, testName) => { - it(`does not allow updating ${testName}`, async () => { - let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] }); - - await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: errorText, - }); - }); - }); - }); - - context('Sub-Level Protected Operations', () => { - let protectedOperations = { - 'class stat': {'stats.class': 'wizard'}, - 'flags unless whitelisted': {'flags.dropsEnabled': true}, - webhooks: {'preferences.webhooks': [1, 2, 3]}, - sleep: {'preferences.sleep': true}, - 'disable classes': {'preferences.disableClasses': true}, - }; - - each(protectedOperations, (data, testName) => { - it(`does not allow updating ${testName}`, async () => { - let errorText = t('messageUserOperationProtected', { operation: Object.keys(data)[0] }); - - await expect(user.put('/user', data)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: errorText, - }); - }); - }); - }); - - context('Default Appearance Preferences', () => { - let testCases = { - shirt: 'yellow', - skin: 'ddc994', - 'hair.color': 'blond', - 'hair.bangs': 2, - 'hair.base': 1, - 'hair.flower': 4, - size: 'broad', - }; - - each(testCases, (item, type) => { - const update = {}; - update[`preferences.${type}`] = item; - - it(`updates user with ${type} that is a default`, async () => { - let dbUpdate = {}; - dbUpdate[`purchased.${type}.${item}`] = true; - await user.update(dbUpdate); - - // Sanity checks to make sure user is not already equipped with item - expect(get(user.preferences, type)).to.not.eql(item); - - let updatedUser = await user.put('/user', update); - - expect(get(updatedUser.preferences, type)).to.eql(item); - }); - }); - - it('returns an error if user tries to update body size with invalid type', async () => { - await expect(user.put('/user', { - 'preferences.size': 'round', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('mustPurchaseToSet', { val: 'round', key: 'preferences.size' }), - }); - }); - - it('can set beard to default', async () => { - await user.update({ - 'purchased.hair.beard': 3, - 'preferences.hair.beard': 3, - }); - - let updatedUser = await user.put('/user', { - 'preferences.hair.beard': 0, - }); - - expect(updatedUser.preferences.hair.beard).to.eql(0); - }); - - it('can set mustache to default', async () => { - await user.update({ - 'purchased.hair.mustache': 2, - 'preferences.hair.mustache': 2, - }); - - let updatedUser = await user.put('/user', { - 'preferences.hair.mustache': 0, - }); - - expect(updatedUser.preferences.hair.mustache).to.eql(0); - }); - }); - - context('Purchasable Appearance Preferences', () => { - let testCases = { - background: 'volcano', - shirt: 'convict', - skin: 'cactus', - 'hair.base': 7, - 'hair.beard': 2, - 'hair.color': 'rainbow', - 'hair.mustache': 2, - }; - - each(testCases, (item, type) => { - const update = {}; - update[`preferences.${type}`] = item; - - it(`returns an error if user tries to update ${type} with ${type} the user does not own`, async () => { - await expect(user.put('/user', update)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('mustPurchaseToSet', {val: item, key: `preferences.${type}`}), - }); - }); - - it(`updates user with ${type} user does own`, async () => { - let dbUpdate = {}; - dbUpdate[`purchased.${type}.${item}`] = true; - await user.update(dbUpdate); - - // Sanity check to make sure user is not already equipped with item - expect(get(user.preferences, type)).to.not.eql(item); - - let updatedUser = await user.put('/user', update); - - expect(get(updatedUser.preferences, type)).to.eql(item); - }); - }); - }); - - context('Improvement Categories', () => { - it('sets valid categories', async () => { - await user.put('/user', { - 'preferences.improvementCategories': ['work', 'school'], - }); - - await user.sync(); - - expect(user.preferences.improvementCategories).to.eql(['work', 'school']); - }); - - it('discards invalid categories', async () => { - await expect(user.put('/user', { - 'preferences.improvementCategories': ['work', 'procrastination', 'school'], - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: 'User validation failed', - }); - }); - }); -}); diff --git a/test/api/v3/integration/user/PUT-user_update_webhook.test.js b/test/api/v3/integration/user/PUT-user_update_webhook.test.js deleted file mode 100644 index 13ca9ff00c..0000000000 --- a/test/api/v3/integration/user/PUT-user_update_webhook.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../helpers/api-integration/v3'; - -let user; -let url = 'http://new-url.com'; -let enabled = true; - -describe('PUT /user/webhook/:id', () => { - beforeEach(async () => { - user = await generateUser(); - }); - - it('validation fails', async () => { - await expect(user.put('/user/webhook/some-id'), { enabled: true }).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidUrl'), - }); - }); - - it('succeeds', async () => { - let response = await user.post('/user/webhook', { enabled: true, url: 'http://some-url.com'}); - await user.sync(); - expect(user.preferences.webhooks[response.id].url).to.not.eql(url); - let response2 = await user.put(`/user/webhook/${response.id}`, {url, enabled}); - expect(response2.url).to.eql(url); - await user.sync(); - expect(user.preferences.webhooks[response.id].url).to.eql(url); - }); -}); diff --git a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js deleted file mode 100644 index cf1354b095..0000000000 --- a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js +++ /dev/null @@ -1,40 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; - -describe('DELETE social registration', () => { - let user; - let endpoint = '/user/auth/social/facebook'; - beforeEach(async () => { - user = await generateUser(); - await user.update({ 'auth.facebook.id': 'some-fb-id' }); - expect(user.auth.local.username).to.not.be.empty; - expect(user.auth.facebook).to.not.be.empty; - }); - context('of NOT-FACEBOOK', () => { - it('is not supported', async () => { - await expect(user.del('/user/auth/social/SOME-OTHER-NETWORK')).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlyFbSupported'), - }); - }); - }); - context('of facebook', () => { - it('fails if local registration does not exist for this user', async () => { - await user.update({ 'auth.local': { ok: true } }); - await expect(user.del(endpoint)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantDetachFb'), - }); - }); - it('succeeds', async () => { - let response = await user.del(endpoint); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.facebook).to.be.empty; - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/GET-logout.test.js b/test/api/v3/integration/user/auth/GET-logout.test.js deleted file mode 100644 index 731c523bde..0000000000 --- a/test/api/v3/integration/user/auth/GET-logout.test.js +++ /dev/null @@ -1,3 +0,0 @@ -describe('GET /user/auth/logout', () => { - // TODO Test manually -}); diff --git a/test/api/v3/integration/user/auth/POST-firebase.test.js b/test/api/v3/integration/user/auth/POST-firebase.test.js deleted file mode 100644 index 7ebd5a20cb..0000000000 --- a/test/api/v3/integration/user/auth/POST-firebase.test.js +++ /dev/null @@ -1,18 +0,0 @@ -import { - generateUser, -} from '../../../../../helpers/api-integration/v3'; -import moment from 'moment'; - -describe('POST /user/auth/firebase', () => { - let user; - - before(async () => { - user = await generateUser(); - }); - - it('returns a Firebase token', async () => { - let {token, expires} = await user.post('/user/auth/firebase'); - expect(moment(expires).isValid()).to.be.true; - expect(token).to.be.a('string'); - }); -}); diff --git a/test/api/v3/integration/user/auth/POST-login-local.test.js b/test/api/v3/integration/user/auth/POST-login-local.test.js deleted file mode 100644 index 571b23c3ea..0000000000 --- a/test/api/v3/integration/user/auth/POST-login-local.test.js +++ /dev/null @@ -1,69 +0,0 @@ -import { - generateUser, - requester, - translate as t, -} from '../../../../../helpers/api-integration/v3'; - -describe('POST /user/auth/local/login', () => { - let api; - let user; - let endpoint = '/user/auth/local/login'; - let password = 'password'; - beforeEach(async () => { - api = requester(); - user = await generateUser(); - }); - it('success with username', async () => { - let response = await api.post(endpoint, { - username: user.auth.local.username, - password, - }); - expect(response.apiToken).to.eql(user.apiToken); - }); - it('success with email', async () => { - let response = await api.post(endpoint, { - username: user.auth.local.email, - password, - }); - expect(response.apiToken).to.eql(user.apiToken); - }); - it('user is blocked', async () => { - await user.update({ 'auth.blocked': 1 }); - await expect(api.post(endpoint, { - username: user.auth.local.username, - password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('accountSuspended', { userId: user._id }), - }); - }); - it('wrong password', async () => { - await expect(api.post(endpoint, { - username: user.auth.local.username, - password: 'wrong-password', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('invalidLoginCredentialsLong'), - }); - }); - it('missing username', async () => { - await expect(api.post(endpoint, { - password: 'wrong-password', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - it('missing password', async () => { - await expect(api.post(endpoint, { - username: user.auth.local.username, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js deleted file mode 100644 index 63d8f755a5..0000000000 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ /dev/null @@ -1,349 +0,0 @@ -import { - generateUser, - requester, - translate as t, - createAndPopulateGroup, -} from '../../../../../helpers/api-integration/v3'; -import { v4 as generateRandomUserName } from 'uuid'; -import { each } from 'lodash'; -import { encrypt } from '../../../../../../website/server/libs/api-v3/encryption'; - -describe('POST /user/auth/local/register', () => { - context('username and email are free', () => { - let api; - - beforeEach(async () => { - api = requester(); - }); - - it('registers a new user', async () => { - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let password = 'password'; - - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user._id).to.exist; - expect(user.apiToken).to.exist; - expect(user.auth.local.username).to.eql(username); - }); - - it('requires password and confirmPassword to match', async () => { - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let password = 'password'; - let confirmPassword = 'not password'; - - await expect(api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires a username', async () => { - let email = `${generateRandomUserName()}@example.com`; - let password = 'password'; - let confirmPassword = 'password'; - - await expect(api.post('/user/auth/local/register', { - email, - password, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires an email', async () => { - let username = generateRandomUserName(); - let password = 'password'; - - await expect(api.post('/user/auth/local/register', { - username, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires a valid email', async () => { - let username = generateRandomUserName(); - let email = 'notanemail@sdf'; - let password = 'password'; - - await expect(api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('requires a password', async () => { - let username = generateRandomUserName(); - let email = `${username}@example.com`; - let confirmPassword = 'password'; - - await expect(api.post('/user/auth/local/register', { - username, - email, - confirmPassword, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - }); - - context('attach to facebook user', () => { - let user; - let email = 'some@email.net'; - let username = 'some-username'; - let password = 'some-password'; - beforeEach(async () => { - user = await generateUser(); - }); - it('checks onlySocialAttachLocal', async () => { - await expect(user.post('/user/auth/local/register', { - email, - username, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('onlySocialAttachLocal'), - }); - }); - it('succeeds', async () => { - await user.update({ 'auth.facebook.id': 'some-fb-id', 'auth.local': { ok: true } }); - await user.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - await user.sync(); - expect(user.auth.local.username).to.eql(username); - expect(user.auth.local.email).to.eql(email); - }); - }); - - context('login is already taken', () => { - let username, email, api; - - beforeEach(async () => { - api = requester(); - username = generateRandomUserName(); - email = `${username}@example.com`; - - return generateUser({ - 'auth.local.username': username, - 'auth.local.lowerCaseUsername': username, - 'auth.local.email': email, - }); - }); - - it('rejects if username is already taken', async () => { - let uniqueEmail = `${generateRandomUserName()}@exampe.com`; - let password = 'password'; - - await expect(api.post('/user/auth/local/register', { - username, - email: uniqueEmail, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('usernameTaken'), - }); - }); - - it('rejects if email is already taken', async () => { - let uniqueUsername = generateRandomUserName(); - let password = 'password'; - - await expect(api.post('/user/auth/local/register', { - username: uniqueUsername, - email, - password, - confirmPassword: password, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('emailTaken'), - }); - }); - }); - - context('req.query.groupInvite', () => { - let api, username, email, password; - - beforeEach(() => { - api = requester(); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('does not crash the signup process when it\'s invalid', async () => { - let user = await api.post('/user/auth/local/register?groupInvite=aaaaInvalid', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user._id).to.be.a('string'); - }); - - it('supports invite using req.query.groupInvite', async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { type: 'party', privacy: 'private' }, - }); - - let invite = encrypt(JSON.stringify({ - id: group._id, - inviter: groupLeader._id, - sentAt: Date.now(), // so we can let it expire - })); - - let user = await api.post(`/user/auth/local/register?groupInvite=${invite}`, { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.invitations.party).to.eql({ - id: group._id, - name: group.name, - inviter: groupLeader._id, - }); - }); - }); - - context('successful login via api', () => { - let api, username, email, password; - - beforeEach(() => { - api = requester(); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('sets all site tour values to -2 (already seen)', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.flags.tour).to.not.be.empty; - - each(user.flags.tour, (value) => { - expect(value).to.eql(-2); - }); - }); - - it('populates user with default todos, not no other task types', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.tasksOrder.todos).to.not.be.empty; - expect(user.tasksOrder.dailys).to.be.empty; - expect(user.tasksOrder.habits).to.be.empty; - expect(user.tasksOrder.rewards).to.be.empty; - }); - - it('populates user with default tags', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.tags).to.not.be.empty; - }); - }); - - context('successful login with habitica-web header', () => { - let api, username, email, password; - - beforeEach(() => { - api = requester({}, {'x-client': 'habitica-web'}); - username = generateRandomUserName(); - email = `${username}@example.com`; - password = 'password'; - }); - - it('sets all common tutorial flags to true', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.flags.tour).to.not.be.empty; - - each(user.flags.tutorial.common, (value) => { - expect(value).to.eql(true); - }); - }); - - it('populates user with default todos, habits, and rewards', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.tasksOrder.todos).to.not.be.empty; - expect(user.tasksOrder.dailys).to.be.empty; - expect(user.tasksOrder.habits).to.not.be.empty; - expect(user.tasksOrder.rewards).to.not.be.empty; - }); - - it('populates user with default tags', async () => { - let user = await api.post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - expect(user.tags).to.not.be.empty; - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js deleted file mode 100644 index 773d199db6..0000000000 --- a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js +++ /dev/null @@ -1,38 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-integration/v3'; - -describe('POST /user/reset-password', async () => { - let endpoint = '/user/reset-password'; - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('resets password', async () => { - let previousPassword = user.auth.local.hashed_password; - let response = await user.post(endpoint, { - email: user.auth.local.email, - }); - expect(response).to.eql({ data: {}, message: t('passwordReset') }); - await user.sync(); - expect(user.auth.local.hashed_password).to.not.eql(previousPassword); - }); - - it('same message on error as on success', async () => { - let response = await user.post(endpoint, { - email: 'nonExistent@email.com', - }); - expect(response).to.eql({ data: {}, message: t('passwordReset') }); - }); - - it('errors if email is not provided', async () => { - await expect(user.post(endpoint)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_email.test.js b/test/api/v3/integration/user/auth/PUT-user_update_email.test.js deleted file mode 100644 index 47357d3c85..0000000000 --- a/test/api/v3/integration/user/auth/PUT-user_update_email.test.js +++ /dev/null @@ -1,79 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-v3-integration.helper'; - -const ENDPOINT = '/user/auth/update-email'; - -describe('PUT /user/auth/update-email', () => { - let newEmail = 'some-new-email_2@example.net'; - let oldPassword = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js - - context('Local Authenticaion User', async () => { - let user; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('does not change email if email is not provided', async () => { - await expect(user.put(ENDPOINT)).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('does not change email if password is not provided', async () => { - await expect(user.put(ENDPOINT, { - newEmail, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - - it('does not change email if wrong password is provided', async () => { - await expect(user.put(ENDPOINT, { - newEmail, - password: 'wrong password', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('wrongPassword'), - }); - }); - - it('changes email if new email and existing password are provided', async () => { - let response = await user.put(ENDPOINT, { - newEmail, - password: oldPassword, - }); - expect(response).to.eql({ email: 'some-new-email_2@example.net' }); - - await user.sync(); - expect(user.auth.local.email).to.eql(newEmail); - }); - }); - - context('Social Login User', async () => { - let socialUser; - - beforeEach(async () => { - socialUser = await generateUser(); - await socialUser.update({ 'auth.local': { ok: true } }); - }); - - it('does not change email if user.auth.local.email does not exist for this user', async () => { - await expect(socialUser.put(ENDPOINT, { - newEmail, - password: oldPassword, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('userHasNoLocalRegistration'), - }); - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js deleted file mode 100644 index bcc1ac25d3..0000000000 --- a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-v3-integration.helper'; - -const ENDPOINT = '/user/auth/update-password'; - -describe('PUT /user/auth/update-password', async () => { - let user; - let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js - let wrongPassword = 'wrong-password'; - let newPassword = 'new-password'; - - beforeEach(async () => { - user = await generateUser(); - }); - - it('successfully changes the password', async () => { - let previousHashedPassword = user.auth.local.hashed_password; - let response = await user.put(ENDPOINT, { - password, - newPassword, - confirmPassword: newPassword, - }); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.local.hashed_password).to.not.eql(previousHashedPassword); - }); - - it('returns an error when confirmPassword does not match newPassword', async () => { - await expect(user.put(ENDPOINT, { - password, - newPassword, - confirmPassword: `${newPassword}-wrong-confirmation`, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('passwordConfirmationMatch'), - }); - }); - - it('returns an error when existing password is wrong', async () => { - await expect(user.put(ENDPOINT, { - password: wrongPassword, - newPassword, - confirmPassword: newPassword, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('wrongPassword'), - }); - }); -}); diff --git a/test/api/v3/integration/user/auth/PUT-user_update_username.test.js b/test/api/v3/integration/user/auth/PUT-user_update_username.test.js deleted file mode 100644 index 372248db84..0000000000 --- a/test/api/v3/integration/user/auth/PUT-user_update_username.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import { - generateUser, - translate as t, -} from '../../../../../helpers/api-v3-integration.helper'; - -const ENDPOINT = '/user/auth/update-username'; - -describe('PUT /user/auth/update-username', async () => { - let user; - let newUsername = 'new-username'; - let password = 'password'; // from habitrpg/test/helpers/api-integration/v3/object-generators.js - - beforeEach(async () => { - user = await generateUser(); - }); - - it('successfully changes username', async () => { - let response = await user.put(ENDPOINT, { - username: newUsername, - password, - }); - expect(response).to.eql({ username: newUsername }); - await user.sync(); - expect(user.auth.local.username).to.eql(newUsername); - }); - - context('errors', async () => { - it('prevents username update if new username is already taken', async () => { - let existingUsername = 'existing-username'; - await generateUser({'auth.local.username': existingUsername, 'auth.local.lowerCaseUsername': existingUsername }); - - await expect(user.put(ENDPOINT, { - username: existingUsername, - password, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('usernameTaken'), - }); - }); - - it('errors if password is wrong', async () => { - await expect(user.put(ENDPOINT, { - username: newUsername, - password: 'wrong-password', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('wrongPassword'), - }); - }); - - it('prevents social-only user from changing username', async () => { - let socialUser = await generateUser({ 'auth.local': { ok: true } }); - - await expect(socialUser.put(ENDPOINT, { - username: newUsername, - password, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('userHasNoLocalRegistration'), - }); - }); - - it('errors if new username is not provided', async () => { - await expect(user.put(ENDPOINT, { - password, - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('invalidReqParams'), - }); - }); - }); -}); diff --git a/test/api/v3/unit/libs/analyticsService.test.js b/test/api/v3/unit/libs/analyticsService.test.js deleted file mode 100644 index 771678cc3d..0000000000 --- a/test/api/v3/unit/libs/analyticsService.test.js +++ /dev/null @@ -1,309 +0,0 @@ -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; - -import nock from 'nock'; - -describe('analyticsService', () => { - let amplitudeNock, gaNock; - - beforeEach(() => { - amplitudeNock = nock('https://api.amplitude.com') - .filteringPath(/httpapi.*/g, '') - .post('/') - .reply(200, {status: 'OK'}); - - gaNock = nock('http://www.google-analytics.com'); - }); - - describe('#track', () => { - let eventType, data; - - beforeEach(() => { - eventType = 'Cron'; - data = { - category: 'behavior', - uuid: 'unique-user-id', - resting: true, - cronCount: 5, - }; - }); - - context('Amplitude', () => { - it('calls out to amplitude', () => { - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('uses a dummy user id if none is provided', () => { - delete data.uuid; - - amplitudeNock - .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sets platform as server', () => { - amplitudeNock - .filteringPath(/httpapi.*platform.*server.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends details about event', () => { - amplitudeNock - .filteringPath(/httpapi.*event_properties%22%3A%7B%22category%22%3A%22behavior%22%2C%22resting%22%3Atrue%2C%22cronCount%22%3A5%7D%2C%22.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for gear if itemKey is provided', () => { - data.itemKey = 'headAccessory_special_foxEars'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Fox%20Ears.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for egg if itemKey is provided', () => { - data.itemKey = 'Wolf'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Wolf%20Egg.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for food if itemKey is provided', () => { - data.itemKey = 'Cake_Skeleton'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Bare%20Bones%20Cake.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for hatching potion if itemKey is provided', () => { - data.itemKey = 'Golden'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Golden%20Hatching%20Potion.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for quest if itemKey is provided', () => { - data.itemKey = 'atom1'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Attack%20of%20the%20Mundane%2C%20Part%201%3A%20Dish%20Disaster!.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends english item name for purchased spell if itemKey is provided', () => { - data.itemKey = 'seafoam'; - - amplitudeNock - .filteringPath(/httpapi.*itemName.*Seafoam.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends user data if provided', () => { - let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; - let user = { - stats, - contributor: { level: 1 }, - purchased: { plan: { planId: 'foo-plan' } }, - flags: {tour: {intro: -2}}, - habits: [{_id: 'habit'}], - dailys: [{_id: 'daily'}], - todos: [{_id: 'todo'}], - rewards: [{_id: 'reward'}], - }; - - data.user = user; - - amplitudeNock - .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, ''); - - return analyticsService.track(eventType, data) - .then(() => { - amplitudeNock.done(); - }); - }); - }); - - context('GA', () => { - it('calls out to GA', () => { - gaNock - .post('/collect') - .reply(200, {status: 'OK'}); - - return analyticsService.track(eventType, data) - .then(() => { - gaNock.done(); - }); - }); - - it('sends details about event', () => { - gaNock - .post('/collect', /ec=behavior&ea=Cron&v=1&tid=GA_ID&cid=.*&t=event/) - .reply(200, {status: 'OK'}); - - return analyticsService.track(eventType, data) - .then(() => { - gaNock.done(); - }); - }); - }); - }); - - describe('#trackPurchase', () => { - let data; - - beforeEach(() => { - data = { - uuid: 'user-id', - sku: 'paypal-checkout', - paymentMethod: 'PayPal', - itemPurchased: 'Gems', - purchaseValue: 8, - purchaseType: 'checkout', - gift: false, - quantity: 1, - }; - }); - - context('Amplitude', () => { - it('calls out to amplitude', () => { - return analyticsService.trackPurchase(data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('uses a dummy user id if none is provided', () => { - delete data.uuid; - - amplitudeNock - .filteringPath(/httpapi.*user_id.*no-user-id-was-provided.*/g, ''); - - return analyticsService.trackPurchase(data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sets platform as server', () => { - amplitudeNock - .filteringPath(/httpapi.*platform.*server.*/g, ''); - - return analyticsService.trackPurchase(data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends details about purchase', () => { - amplitudeNock - .filteringPath(/httpapi.*aypal-checkout%22%2C%22paymentMethod%22%3A%22PayPal%22%2C%22itemPurchased%22%3A%22Gems%22%2C%22purchaseType%22%3A%22checkout%22%2C%22gift%22%3Afalse%2C%22quantity%22%3A1%7D%2C%22event_type%22%3A%22purchase%22%2C%22revenue.*/g, ''); - - return analyticsService.trackPurchase(data) - .then(() => { - amplitudeNock.done(); - }); - }); - - it('sends user data if provided', () => { - let stats = { class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30 }; - let user = { - stats, - contributor: { level: 1 }, - purchased: { plan: { planId: 'foo-plan' } }, - flags: {tour: {intro: -2}}, - habits: [{_id: 'habit'}], - dailys: [{_id: 'daily'}], - todos: [{_id: 'todo'}], - rewards: [{_id: 'reward'}], - }; - - data.user = user; - - amplitudeNock - .filteringPath(/httpapi.*user_properties%22%3A%7B%22Class%22%3A%22wizard%22%2C%22Experience%22%3A5%2C%22Gold%22%3A23%2C%22Health%22%3A10%2C%22Level%22%3A4%2C%22Mana%22%3A30%2C%22tutorialComplete%22%3Atrue%2C%22Number%20Of%20Tasks%22%3A%7B%22habits%22%3A1%2C%22dailys%22%3A1%2C%22todos%22%3A1%2C%22rewards%22%3A1%7D%2C%22contributorLevel%22%3A1%2C%22subscription%22%3A%22foo-plan%22%7D%2C%22.*/g, ''); - - return analyticsService.trackPurchase(data) - .then(() => { - amplitudeNock.done(); - }); - }); - }); - - context('GA', () => { - it('calls out to GA', () => { - gaNock - .post('/collect') - .reply(200, {status: 'OK'}); - - return analyticsService.trackPurchase(data) - .then(() => { - gaNock.done(); - }); - }); - - it('sends details about purchase', () => { - gaNock - .post('/collect', /ti=user-id&tr=8&v=1&tid=GA_ID&cid=.*&t=transaction/) - .reply(200, {status: 'OK'}) - .post('/collect', /ec=commerce&ea=checkout&el=PayPal&ev=8&v=1&tid=GA_ID&cid=.*&t=event/) - .reply(200, {status: 'OK'}); - - return analyticsService.trackPurchase(data) - .then(() => { - gaNock.done(); - }); - }); - }); - }); - - describe('mockAnalyticsService', () => { - it('has stubbed track method', () => { - expect(analyticsService.mockAnalyticsService).to.respondTo('track'); - }); - - it('has stubbed trackPurchase method', () => { - expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase'); - }); - }); -}); diff --git a/test/api/v3/unit/libs/baseModel.test.js b/test/api/v3/unit/libs/baseModel.test.js deleted file mode 100644 index 39bf7df047..0000000000 --- a/test/api/v3/unit/libs/baseModel.test.js +++ /dev/null @@ -1,97 +0,0 @@ -import baseModel from '../../../../../website/server/libs/api-v3/baseModel'; -import mongoose from 'mongoose'; - -describe('Base model plugin', () => { - let schema; - - beforeEach(() => { - schema = new mongoose.Schema(); - sandbox.stub(schema, 'add'); - }); - - it('adds a _id field to the schema', () => { - schema.plugin(baseModel); - - expect(schema.add).to.be.calledWith(sinon.match({ - _id: sinon.match.object, - })); - }); - - it('can add timestamps fields', () => { - schema.plugin(baseModel, {timestamps: true}); - - expect(schema.add).to.be.calledTwice; - }); - - it('can sanitize input objects', () => { - schema.plugin(baseModel, { - noSet: ['noUpdateForMe'], - }); - - expect(schema.statics.sanitize).to.exist; - let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true}); - - expect(sanitized).to.have.property('ok'); - expect(sanitized).not.to.have.property('noUpdateForMe'); - expect(sanitized.noUpdateForMe).to.equal(undefined); - }); - - it('accepts an array of additional fields to sanitize at runtime', () => { - schema.plugin(baseModel, { - noSet: ['noUpdateForMe'], - }); - - expect(schema.statics.sanitize).to.exist; - let sanitized = schema.statics.sanitize({ok: true, noUpdateForMe: true, usuallySettable: true}, ['usuallySettable']); - - expect(sanitized).to.have.property('ok'); - expect(sanitized).not.to.have.property('noUpdateForMe'); - expect(sanitized).not.to.have.property('usuallySettable'); - }); - - - it('can make fields private', () => { - schema.plugin(baseModel, { - private: ['amPrivate'], - }); - - expect(schema.options.toJSON.transform).to.exist; - let objToTransform = {ok: true, amPrivate: true}; - let privatized = schema.options.toJSON.transform({}, objToTransform); - - expect(privatized).to.have.property('ok'); - expect(privatized).not.to.have.property('amPrivate'); - }); - - it('accepts a further transform function for toJSON', () => { - let options = { - private: ['amPrivate'], - toJSONTransform: sandbox.stub().returns(true), - }; - - schema.plugin(baseModel, options); - - let objToTransform = {ok: true, amPrivate: true}; - let doc = {doc: true}; - let privatized = schema.options.toJSON.transform(doc, objToTransform); - - expect(privatized).to.equals(true); - expect(options.toJSONTransform).to.be.calledWith(objToTransform, doc); - }); - - it('accepts a transform function for sanitize', () => { - let options = { - private: ['amPrivate'], - sanitizeTransform: sandbox.stub().returns(true), - }; - - schema.plugin(baseModel, options); - - expect(schema.options.toJSON.transform).to.exist; - let objToSanitize = {ok: true, noUpdateForMe: true}; - let sanitized = schema.statics.sanitize(objToSanitize); - - expect(sanitized).to.equals(true); - expect(options.sanitizeTransform).to.be.calledWith(objToSanitize); - }); -}); diff --git a/test/api/v3/unit/libs/buildManifest.test.js b/test/api/v3/unit/libs/buildManifest.test.js deleted file mode 100644 index 1444738f10..0000000000 --- a/test/api/v3/unit/libs/buildManifest.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import { - getManifestFiles, -} from '../../../../../website/server/libs/api-v3/buildManifest'; - -describe('Build Manifest', () => { - describe('getManifestFiles', () => { - it('returns an html string', () => { - let htmlCode = getManifestFiles('app'); - - expect(htmlCode.startsWith(' { - expect(() => { - getManifestFiles('strange name here'); - }).to.throw(Error); - }); - }); -}); diff --git a/test/api/v3/unit/libs/collectionManipulators.test.js b/test/api/v3/unit/libs/collectionManipulators.test.js deleted file mode 100644 index da44fd5319..0000000000 --- a/test/api/v3/unit/libs/collectionManipulators.test.js +++ /dev/null @@ -1,88 +0,0 @@ -import mongoose from 'mongoose'; -import { - removeFromArray, -} from '../../../../../website/server/libs/api-v3/collectionManipulators'; - -describe('Collection Manipulators', () => { - describe('removeFromArray', () => { - it('removes element from array', () => { - let array = ['a', 'b', 'c', 'd']; - - removeFromArray(array, 'c'); - - expect(array).to.not.include('c'); - }); - - it('removes object from array', () => { - let array = [ - { id: 'a', foo: 'bar' }, - { id: 'b', foo: 'bar' }, - { id: 'c', foo: 'bar' }, - { id: 'd', foo: 'bar' }, - { id: 'e', foo: 'bar' }, - ]; - - removeFromArray(array, { id: 'c' }); - - expect(array).to.not.include({ id: 'c', foo: 'bar' }); - }); - - it('does not change array if value is not found', () => { - let array = ['a', 'b', 'c', 'd']; - - removeFromArray(array, 'z'); - - expect(array).to.have.a.lengthOf(4); - expect(array[0]).to.eql('a'); - expect(array[1]).to.eql('b'); - expect(array[2]).to.eql('c'); - expect(array[3]).to.eql('d'); - }); - - it('returns the removed element', () => { - let array = ['a', 'b', 'c']; - - let result = removeFromArray(array, 'b'); - - expect(result).to.eql('b'); - }); - - it('returns the removed object element', () => { - let array = [ - { id: 'a', foo: 'bar' }, - { id: 'b', foo: 'bar' }, - { id: 'c', foo: 'bar' }, - { id: 'd', foo: 'bar' }, - { id: 'e', foo: 'bar' }, - ]; - - let result = removeFromArray(array, { id: 'c' }); - - expect(result).to.eql({ id: 'c', foo: 'bar' }); - }); - - it('returns false if item is not found', () => { - let array = ['a', 'b', 'c']; - - let result = removeFromArray(array, 'z'); - - expect(result).to.eql(false); - }); - - it('persists removal of element when mongoose document is saved', async () => { - let schema = new mongoose.Schema({ - array: Array, - }); - let Model = mongoose.model('ModelToTestRemoveFromArray', schema); - let model = await new Model({ - array: ['a', 'b', 'c'], - }).save(); // Initial creation - - removeFromArray(model.array, 'b'); - - let savedModel = await model.save(); - - expect(savedModel.array).to.not.include('b'); - }); - }); -}); diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js deleted file mode 100644 index a7e732442c..0000000000 --- a/test/api/v3/unit/libs/cron.test.js +++ /dev/null @@ -1,573 +0,0 @@ -/* eslint-disable global-require */ -import moment from 'moment'; -import { cron } from '../../../../../website/server/libs/api-v3/cron'; -import { model as User } from '../../../../../website/server/models/user'; -import * as Tasks from '../../../../../website/server/models/task'; -import { clone } from 'lodash'; -import common from '../../../../../common'; - -// const scoreTask = common.ops.scoreTask; - -describe('cron', () => { - let user; - let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; - let daysMissed = 0; - let analytics = { - track: sinon.spy(), - }; - - beforeEach(() => { - user = new User({ - auth: { - local: { - username: 'username', - lowerCaseUsername: 'username', - email: 'email@email.email', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }); - - user._statsComputed = { - mp: 10, - }; - }); - - it('updates user.auth.timestamps.loggedin and lastCron', () => { - let now = new Date(); - - cron({user, tasksByType, daysMissed, analytics, now}); - - expect(user.auth.timestamps.loggedin).to.equal(now); - expect(user.lastCron).to.equal(now); - }); - - it('updates user.preferences.timezoneOffsetAtLastCron', () => { - let timezoneOffsetFromUserPrefs = 1; - - cron({user, tasksByType, daysMissed, analytics, timezoneOffsetFromUserPrefs}); - - expect(user.preferences.timezoneOffsetAtLastCron).to.equal(timezoneOffsetFromUserPrefs); - }); - - it('resets user.items.lastDrop.count', () => { - user.items.lastDrop.count = 4; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.items.lastDrop.count).to.equal(0); - }); - - it('increments user cron count', () => { - let cronCountBefore = user.flags.cronCount; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore); - }); - - describe('end of the month perks', () => { - beforeEach(() => { - user.purchased.plan.customerId = 'subscribedId'; - user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); - }); - - it('resets plan.gemsBought on a new month', () => { - user.purchased.plan.gemsBought = 10; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.gemsBought).to.equal(0); - }); - - it('resets plan.dateUpdated on a new month', () => { - let currentMonth = moment().format('MMYYYY'); - cron({user, tasksByType, daysMissed, analytics}); - expect(moment(user.purchased.plan.dateUpdated).format('MMYYYY')).to.equal(currentMonth); - }); - - it('increments plan.consecutive.count', () => { - user.purchased.plan.consecutive.count = 0; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.count).to.equal(1); - }); - - it('decrements plan.consecutive.offset when offset is greater than 0', () => { - user.purchased.plan.consecutive.offset = 1; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.offset).to.equal(0); - }); - - it('increments plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => { - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.trinkets).to.equal(1); - }); - - it('increments plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => { - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(5); - }); - - it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => { - user.purchased.plan.consecutive.gemCapExtra = 25; - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does not reset plan stats if we are before the last day of the cancelled month', () => { - user.purchased.plan.dateTerminated = moment(new Date()).add({days: 1}); - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.customerId).to.exist; - }); - - it('does reset plan stats until we are after the last day of the cancelled month', () => { - user.purchased.plan.dateTerminated = moment(new Date()).subtract({days: 1}); - user.purchased.plan.consecutive.gemCapExtra = 20; - user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.purchased.plan.customerId).to.not.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.be.empty; - expect(user.purchased.plan.consecutive.count).to.be.empty; - expect(user.purchased.plan.consecutive.offset).to.be.empty; - }); - }); - - describe('end of the month perks when user is not subscribed', () => { - it('does not reset plan.gemsBought on a new month', () => { - user.purchased.plan.gemsBought = 10; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.gemsBought).to.equal(10); - }); - - it('does not reset plan.dateUpdated on a new month', () => { - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.dateUpdated).to.be.empty; - }); - - it('does not increment plan.consecutive.count', () => { - user.purchased.plan.consecutive.count = 0; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.count).to.equal(0); - }); - - it('does not decrement plan.consecutive.offset when offset is greater than 0', () => { - user.purchased.plan.consecutive.offset = 1; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.offset).to.equal(1); - }); - - it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', () => { - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.trinkets).to.equal(0); - }); - - it('doest not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => { - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0); - }); - - it('does not increment plan.consecutive.gemCapExtra when user has reached the gemCap limit', () => { - user.purchased.plan.consecutive.gemCapExtra = 25; - user.purchased.plan.consecutive.count = 5; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(25); - }); - - it('does nothing to plan stats if we are before the last day of the cancelled month', () => { - user.purchased.plan.dateTerminated = moment(new Date()).add({days: 1}); - cron({user, tasksByType, daysMissed, analytics}); - expect(user.purchased.plan.customerId).to.not.exist; - }); - - xit('does nothing to plan stats when we are after the last day of the cancelled month', () => { - user.purchased.plan.dateTerminated = moment(new Date()).subtract({days: 1}); - user.purchased.plan.consecutive.gemCapExtra = 20; - user.purchased.plan.consecutive.count = 5; - user.purchased.plan.consecutive.offset = 1; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.purchased.plan.customerId).to.exist; - expect(user.purchased.plan.consecutive.gemCapExtra).to.exist; - expect(user.purchased.plan.consecutive.count).to.exist; - expect(user.purchased.plan.consecutive.offset).to.exist; - }); - }); - - describe('user is sleeping', () => { - beforeEach(() => { - user.preferences.sleep = true; - }); - - it('clears user buffs', () => { - user.stats.buffs = { - str: 1, - int: 1, - per: 1, - con: 1, - stealth: 1, - streaks: true, - }; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.stats.buffs.str).to.equal(0); - expect(user.stats.buffs.int).to.equal(0); - expect(user.stats.buffs.per).to.equal(0); - expect(user.stats.buffs.con).to.equal(0); - expect(user.stats.buffs.stealth).to.equal(0); - expect(user.stats.buffs.streaks).to.be.false; - }); - - it('resets all dailies without damaging user', () => { - let daily = { - text: 'test daily', - type: 'daily', - frequency: 'daily', - everyX: 5, - startDate: new Date(), - }; - - let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap - tasksByType.dailys.push(task); - tasksByType.dailys[0].completed = true; - - let healthBefore = user.stats.hp; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(tasksByType.dailys[0].completed).to.be.false; - expect(user.stats.hp).to.equal(healthBefore); - }); - }); - - describe('todos', () => { - beforeEach(() => { - let todo = { - text: 'test todo', - type: 'todo', - value: 0, - }; - - let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap - tasksByType.todos.push(task); - }); - - it('should make uncompleted todos redder', () => { - let valueBefore = tasksByType.todos[0].value; - cron({user, tasksByType, daysMissed, analytics}); - expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore); - }); - - it('should add history of completed todos to user history', () => { - tasksByType.todos[0].completed = true; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.history.todos).to.be.lengthOf(1); - }); - }); - - describe('dailys', () => { - beforeEach(() => { - let daily = { - text: 'test daily', - type: 'daily', - }; - - let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap - tasksByType.dailys = []; - tasksByType.dailys.push(task); - - user._statsComputed = { - con: 1, - }; - }); - - it('should add history', () => { - cron({user, tasksByType, daysMissed, analytics}); - expect(tasksByType.dailys[0].history).to.be.lengthOf(1); - }); - - it('should set tasks completed to false', () => { - tasksByType.dailys[0].completed = true; - cron({user, tasksByType, daysMissed, analytics}); - expect(tasksByType.dailys[0].completed).to.be.false; - }); - - it('should reset task checklist for completed dailys', () => { - tasksByType.dailys[0].checklist.push({title: 'test', completed: false}); - tasksByType.dailys[0].completed = true; - cron({user, tasksByType, daysMissed, analytics}); - expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; - }); - - it('should reset task checklist for dailys with scheduled misses', () => { - daysMissed = 10; - tasksByType.dailys[0].checklist.push({title: 'test', completed: false}); - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - cron({user, tasksByType, daysMissed, analytics}); - expect(tasksByType.dailys[0].checklist[0].completed).to.be.false; - }); - - it('should do damage for missing a daily', () => { - daysMissed = 1; - let hpBefore = user.stats.hp; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.stats.hp).to.be.lessThan(hpBefore); - }); - - it('should not do damage for missing a daily if user stealth buff is greater than or equal to days missed', () => { - daysMissed = 1; - let hpBefore = user.stats.hp; - user.stats.buffs.stealth = 2; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.stats.hp).to.equal(hpBefore); - }); - - it('should do less damage for missing a daily with partial completion', () => { - daysMissed = 1; - let hpBefore = user.stats.hp; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - cron({user, tasksByType, daysMissed, analytics}); - let hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp; - - hpBefore = user.stats.hp; - tasksByType.dailys[0].checklist.push({title: 'test', completed: true}); - tasksByType.dailys[0].checklist.push({title: 'test2', completed: false}); - cron({user, tasksByType, daysMissed, analytics}); - let hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp; - - expect(hpDifferenceOfPartiallyIncompleteDaily).to.be.lessThan(hpDifferenceOfFullyIncompleteDaily); - }); - - it('should decrement quest progress down for missing a daily', () => { - daysMissed = 1; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - - let progress = cron({user, tasksByType, daysMissed, analytics}); - - expect(progress.down).to.equal(-1); - }); - }); - - describe('habits', () => { - beforeEach(() => { - let habit = { - text: 'test habit', - type: 'habit', - }; - - let task = new Tasks.habit(Tasks.Task.sanitize(habit)); // eslint-disable-line babel/new-cap - tasksByType.habits = []; - tasksByType.habits.push(task); - }); - - it('should decrement only up value', () => { - tasksByType.habits[0].value = 1; - tasksByType.habits[0].down = false; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(tasksByType.habits[0].value).to.be.lessThan(1); - }); - - it('should decrement only down value', () => { - tasksByType.habits[0].value = 1; - tasksByType.habits[0].up = false; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(tasksByType.habits[0].value).to.be.lessThan(1); - }); - - it('should do nothing to habits with both up and down', () => { - tasksByType.habits[0].value = 1; - tasksByType.habits[0].up = true; - tasksByType.habits[0].down = true; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(tasksByType.habits[0].value).to.equal(1); - }); - }); - - describe('perfect day', () => { - beforeEach(() => { - let daily = { - text: 'test daily', - type: 'daily', - }; - - let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap - tasksByType.dailys = []; - tasksByType.dailys.push(task); - - user._statsComputed = { - con: 1, - }; - }); - - it('stores a new entry in user.history.exp', () => { - user.stats.lvl = 2; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.history.exp).to.have.lengthOf(1); - expect(user.history.exp[0].value).to.equal(150); - }); - - it('increments perfect day achievement', () => { - tasksByType.dailys[0].completed = true; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.achievements.perfect).to.equal(1); - }); - - it('increments user buffs if they have a perfect day', () => { - tasksByType.dailys[0].completed = true; - - let previousBuffs = clone(user.stats.buffs); - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str); - expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int); - expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per); - expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con); - }); - - it('clears buffs if user does not have a perfect day', () => { - daysMissed = 1; - tasksByType.dailys[0].completed = false; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - - user.stats.buffs = { - str: 1, - int: 1, - per: 1, - con: 1, - stealth: 0, - streaks: true, - }; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.stats.buffs.str).to.equal(0); - expect(user.stats.buffs.int).to.equal(0); - expect(user.stats.buffs.per).to.equal(0); - expect(user.stats.buffs.con).to.equal(0); - expect(user.stats.buffs.stealth).to.equal(0); - expect(user.stats.buffs.streaks).to.be.false; - }); - }); - - describe('adding mp', () => { - it('should add mp to user', () => { - let mpBefore = user.stats.mp; - tasksByType.dailys[0].completed = true; - user._statsComputed.maxMP = 100; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.stats.mp).to.be.greaterThan(mpBefore); - }); - - it('set user\'s mp to user._statsComputed.maxMP when user.stats.mp is greater', () => { - user.stats.mp = 120; - user._statsComputed.maxMP = 100; - cron({user, tasksByType, daysMissed, analytics}); - expect(user.stats.mp).to.equal(user._statsComputed.maxMP); - }); - }); - - describe('quest progress', () => { - beforeEach(() => { - let daily = { - text: 'test daily', - type: 'daily', - }; - - let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap - tasksByType.dailys = []; - tasksByType.dailys.push(task); - - user._statsComputed = { - con: 1, - }; - - daysMissed = 1; - tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1}); - }); - - it('resets user progress', () => { - cron({user, tasksByType, daysMissed, analytics}); - expect(user.party.quest.progress.up).to.equal(0); - expect(user.party.quest.progress.down).to.equal(0); - expect(user.party.quest.progress.collect).to.be.empty; - }); - - it('applies the user progress', () => { - let progress = cron({user, tasksByType, daysMissed, analytics}); - expect(progress.down).to.equal(-1); - }); - }); - - describe('private messages', () => { - let lastMessageId; - - beforeEach(() => { - let maxPMs = 200; - for (let index = 0; index < maxPMs - 1; index += 1) { - let messageId = common.uuid(); - user.inbox.messages[messageId] = { - id: messageId, - text: `test ${index}`, - timestamp: Number(new Date()), - likes: {}, - flags: {}, - flagCount: 0, - }; - } - - lastMessageId = common.uuid(); - user.inbox.messages[lastMessageId] = { - id: lastMessageId, - text: `test ${lastMessageId}`, - timestamp: Number(new Date()), - likes: {}, - flags: {}, - flagCount: 0, - }; - }); - - xit('does not clear pms under 200', () => { - cron({user, tasksByType, daysMissed, analytics}); - expect(user.inbox.messages[lastMessageId]).to.exist; - }); - - xit('clears pms over 200', () => { - let messageId = common.uuid(); - user.inbox.messages[messageId] = { - id: messageId, - text: `test ${messageId}`, - timestamp: Number(new Date()), - likes: {}, - flags: {}, - flagCount: 0, - }; - - cron({user, tasksByType, daysMissed, analytics}); - - expect(user.inbox.messages[messageId]).to.not.exist; - }); - }); -}); diff --git a/test/api/v3/unit/libs/email.test.js b/test/api/v3/unit/libs/email.test.js deleted file mode 100644 index bb76e05cfb..0000000000 --- a/test/api/v3/unit/libs/email.test.js +++ /dev/null @@ -1,232 +0,0 @@ -/* eslint-disable global-require */ -import request from 'request'; -import nconf from 'nconf'; -import nodemailer from 'nodemailer'; -import Bluebird from 'bluebird'; -import requireAgain from 'require-again'; -import logger from '../../../../../website/server/libs/api-v3/logger'; - -function defer () { - let resolve; - let reject; - - let promise = new Bluebird((resolveParam, rejectParam) => { - resolve = resolveParam; - reject = rejectParam; - }); - - return { - resolve, - reject, - promise, - }; -} - -function getUser () { - return { - _id: 'random _id', - auth: { - local: { - username: 'username', - email: 'email@email', - }, - facebook: { - emails: [{ - value: 'email@facebook', - }], - displayName: 'fb display name', - }, - }, - profile: { - name: 'profile name', - }, - preferences: { - emailNotifications: { - unsubscribeFromAll: false, - }, - }, - }; -} - -describe('emails', () => { - let pathToEmailLib = '../../../../../website/server/libs/api-v3/email'; - - describe('sendEmail', () => { - it('can send an email using the default transport', () => { - let sendMailSpy = sandbox.stub().returns(defer().promise); - - sandbox.stub(nodemailer, 'createTransport').returns({ - sendMail: sendMailSpy, - }); - - let attachEmail = requireAgain(pathToEmailLib); - attachEmail.send(); - expect(sendMailSpy).to.be.calledOnce; - }); - - it('logs errors', (done) => { - let deferred = defer(); - let sendMailSpy = sandbox.stub().returns(deferred.promise); - - sandbox.stub(nodemailer, 'createTransport').returns({ - sendMail: sendMailSpy, - }); - sandbox.stub(logger, 'error'); - - let attachEmail = requireAgain(pathToEmailLib); - attachEmail.send(); - expect(sendMailSpy).to.be.calledOnce; - deferred.reject(); - - // wait for unhandledRejection event to fire - setTimeout(() => { - expect(logger.error).to.be.calledOnce; - done(); - }, 20); - }); - }); - - describe('getUserInfo', () => { - it('returns an empty object if no field request', () => { - let attachEmail = requireAgain(pathToEmailLib); - let getUserInfo = attachEmail.getUserInfo; - expect(getUserInfo({}, [])).to.be.empty; - }); - - it('returns correct user data', () => { - let attachEmail = requireAgain(pathToEmailLib); - let getUserInfo = attachEmail.getUserInfo; - let user = getUser(); - let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); - - expect(data).to.have.property('name', user.profile.name); - expect(data).to.have.property('email', user.auth.local.email); - expect(data).to.have.property('_id', user._id); - expect(data).to.have.property('canSend', true); - }); - - it('returns correct user data [facebook users]', () => { - let attachEmail = requireAgain(pathToEmailLib); - let getUserInfo = attachEmail.getUserInfo; - let user = getUser(); - delete user.profile.name; - delete user.auth.local; - - let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); - - expect(data).to.have.property('name', user.auth.facebook.displayName); - expect(data).to.have.property('email', user.auth.facebook.emails[0].value); - expect(data).to.have.property('_id', user._id); - expect(data).to.have.property('canSend', true); - }); - - it('has fallbacks for missing data', () => { - let attachEmail = requireAgain(pathToEmailLib); - let getUserInfo = attachEmail.getUserInfo; - let user = getUser(); - delete user.profile.name; - delete user.auth.local.email; - delete user.auth.facebook; - - let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); - - expect(data).to.have.property('name', user.auth.local.username); - expect(data).not.to.have.property('email'); - expect(data).to.have.property('_id', user._id); - expect(data).to.have.property('canSend', true); - }); - }); - - describe('sendTxnEmail', () => { - beforeEach(() => { - sandbox.stub(request, 'post'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('can send a txn email to one recipient', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - let attachEmail = requireAgain(pathToEmailLib); - let sendTxnEmail = attachEmail.sendTxn; - let emailType = 'an email type'; - let mailingInfo = { - name: 'my name', - email: 'my@email', - }; - - sendTxnEmail(mailingInfo, emailType); - expect(request.post).to.be.calledWith(sinon.match({ - json: { - data: { - emailType: sinon.match.same(emailType), - to: sinon.match((value) => { - return Array.isArray(value) && value[0].name === mailingInfo.name; - }, 'matches mailing info array'), - }, - }, - })); - }); - - it('does not send email if address is missing', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - let attachEmail = requireAgain(pathToEmailLib); - let sendTxnEmail = attachEmail.sendTxn; - let emailType = 'an email type'; - let mailingInfo = { - name: 'my name', - // email: 'my@email', - }; - - sendTxnEmail(mailingInfo, emailType); - expect(request.post).not.to.be.called; - }); - - it('uses getUserInfo in case of user data', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - let attachEmail = requireAgain(pathToEmailLib); - let sendTxnEmail = attachEmail.sendTxn; - let emailType = 'an email type'; - let mailingInfo = getUser(); - - sendTxnEmail(mailingInfo, emailType); - expect(request.post).to.be.calledWith(sinon.match({ - json: { - data: { - emailType: sinon.match.same(emailType), - to: sinon.match(val => val[0]._id === mailingInfo._id), - }, - }, - })); - }); - - it('sends email with some default variables', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - let attachEmail = requireAgain(pathToEmailLib); - let sendTxnEmail = attachEmail.sendTxn; - let emailType = 'an email type'; - let mailingInfo = { - name: 'my name', - email: 'my@email', - }; - let variables = [1, 2, 3]; - - sendTxnEmail(mailingInfo, emailType, variables); - expect(request.post).to.be.calledWith(sinon.match({ - json: { - data: { - variables: sinon.match((value) => { - return value[0].name === 'BASE_URL'; - }, 'matches variables'), - personalVariables: sinon.match((value) => { - return value[0].rcpt === mailingInfo.email && - value[0].vars[0].name === 'RECIPIENT_NAME' && - value[0].vars[1].name === 'RECIPIENT_UNSUB_URL'; - }, 'matches personal variables'), - }, - }, - })); - }); - }); -}); diff --git a/test/api/v3/unit/libs/encryption.test.js b/test/api/v3/unit/libs/encryption.test.js deleted file mode 100644 index a63a527e74..0000000000 --- a/test/api/v3/unit/libs/encryption.test.js +++ /dev/null @@ -1,15 +0,0 @@ -import { - encrypt, - decrypt, -} from '../../../../../website/server/libs/api-v3/encryption'; - -describe('encryption', () => { - it('can encrypt and decrypt', () => { - let data = 'some secret text'; - let encrypted = encrypt(data); - let decrypted = decrypt(encrypted); - - expect(encrypted).not.to.equal(data); - expect(data).to.equal(decrypted); - }); -}); diff --git a/test/api/v3/unit/libs/errors.test.js b/test/api/v3/unit/libs/errors.test.js deleted file mode 100644 index efa694d5ab..0000000000 --- a/test/api/v3/unit/libs/errors.test.js +++ /dev/null @@ -1,122 +0,0 @@ -// TODO move to shared tests -import { - CustomError, - NotAuthorized, - BadRequest, - InternalServerError, - NotFound, -} from '../../../../../website/server/libs/api-v3/errors'; - -describe('Custom Errors', () => { - describe('CustomError', () => { - it('is an instance of Error', () => { - let customError = new CustomError(); - - expect(customError).to.be.an.instanceOf(Error); - }); - }); - - describe('NotAuthorized', () => { - it('is an instance of CustomError', () => { - let notAuthorizedError = new NotAuthorized(); - - expect(notAuthorizedError).to.be.an.instanceOf(CustomError); - }); - - it('it returns an http code of 401', () => { - let notAuthorizedError = new NotAuthorized(); - - expect(notAuthorizedError.httpCode).to.eql(401); - }); - - it('returns a default message', () => { - let notAuthorizedError = new NotAuthorized(); - - expect(notAuthorizedError.message).to.eql('Not authorized.'); - }); - - it('allows a custom message', () => { - let notAuthorizedError = new NotAuthorized('Custom Error Message'); - - expect(notAuthorizedError.message).to.eql('Custom Error Message'); - }); - }); - - describe('NotFound', () => { - it('is an instance of CustomError', () => { - let notAuthorizedError = new NotFound(); - - expect(notAuthorizedError).to.be.an.instanceOf(CustomError); - }); - - it('it returns an http code of 404', () => { - let notAuthorizedError = new NotFound(); - - expect(notAuthorizedError.httpCode).to.eql(404); - }); - - it('returns a default message', () => { - let notAuthorizedError = new NotFound(); - - expect(notAuthorizedError.message).to.eql('Not found.'); - }); - - it('allows a custom message', () => { - let notAuthorizedError = new NotFound('Custom Error Message'); - - expect(notAuthorizedError.message).to.eql('Custom Error Message'); - }); - }); - - describe('BadRequest', () => { - it('is an instance of CustomError', () => { - let badRequestError = new BadRequest(); - - expect(badRequestError).to.be.an.instanceOf(CustomError); - }); - - it('it returns an http code of 401', () => { - let badRequestError = new BadRequest(); - - expect(badRequestError.httpCode).to.eql(400); - }); - - it('returns a default message', () => { - let badRequestError = new BadRequest(); - - expect(badRequestError.message).to.eql('Bad request.'); - }); - - it('allows a custom message', () => { - let badRequestError = new BadRequest('Custom Error Message'); - - expect(badRequestError.message).to.eql('Custom Error Message'); - }); - }); - - describe('InternalServerError', () => { - it('is an instance of CustomError', () => { - let internalServerError = new InternalServerError(); - - expect(internalServerError).to.be.an.instanceOf(CustomError); - }); - - it('it returns an http code of 500', () => { - let internalServerError = new InternalServerError(); - - expect(internalServerError.httpCode).to.eql(500); - }); - - it('returns a default message', () => { - let internalServerError = new InternalServerError(); - - expect(internalServerError.message).to.eql('An unexpected error occurred.'); - }); - - it('allows a custom message', () => { - let internalServerError = new InternalServerError('Custom Error Message'); - - expect(internalServerError.message).to.eql('Custom Error Message'); - }); - }); -}); diff --git a/test/api/v3/unit/libs/i18n.test.js b/test/api/v3/unit/libs/i18n.test.js deleted file mode 100644 index 06ebcbc0b6..0000000000 --- a/test/api/v3/unit/libs/i18n.test.js +++ /dev/null @@ -1,39 +0,0 @@ -import { - translations, - localePath, - langCodes, -} from '../../../../../website/server/libs/api-v3/i18n'; -import fs from 'fs'; -import path from 'path'; - -describe('i18n', () => { - let listOfLocales = []; - - before((done) => { - fs.readdir(localePath, (err, files) => { - if (err) return done(err); - - files.forEach((file) => { - if (fs.statSync(path.join(localePath, file)).isDirectory() === false) return; - listOfLocales.push(file); - }); - - listOfLocales = listOfLocales.sort(); - done(); - }); - }); - - describe('translations', () => { - it('includes a translation object for each locale', () => { - listOfLocales.forEach((locale) => { - expect(translations[locale]).to.be.an('object'); - }); - }); - }); - - describe('langCodes', () => { - it('is a list of all the language codes', () => { - expect(langCodes.sort()).to.eql(listOfLocales); - }); - }); -}); diff --git a/test/api/v3/unit/libs/logger.js b/test/api/v3/unit/libs/logger.js deleted file mode 100644 index b7e1d490fc..0000000000 --- a/test/api/v3/unit/libs/logger.js +++ /dev/null @@ -1,57 +0,0 @@ -import winston from 'winston'; -import requireAgain from 'require-again'; - -/* eslint-disable global-require */ -describe('logger', () => { - let pathToLoggerLib = '../../../../../website/server/libs/api-v3/logger'; - let infoSpy; - let errorSpy; - - beforeEach(() => { - infoSpy = sandbox.stub(); - errorSpy = sandbox.stub(); - sandbox.stub(winston, 'Logger').returns({ - info: infoSpy, - error: errorSpy, - }); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('info', () => { - let attachLogger = requireAgain(pathToLoggerLib); - attachLogger.info(1, 2, 3); - expect(infoSpy).to.be.calledOnce; - expect(infoSpy).to.be.calledWith(1, 2, 3); - }); - - describe('error', () => { - it('with custom arguments', () => { - let attachLogger = requireAgain(pathToLoggerLib); - attachLogger.error(1, 2, 3, 4); - expect(errorSpy).to.be.calledOnce; - expect(errorSpy).to.be.calledWith(1, 2, 3, 4); - }); - - it('with error', () => { - let attachLogger = requireAgain(pathToLoggerLib); - let errInstance = new Error('An error.'); - attachLogger.error(errInstance, { - data: 1, - }, 2, 3); - expect(errorSpy).to.be.calledOnce; - // using calledWith doesn't work - let lastCallArgs = errorSpy.lastCall.args; - - expect(lastCallArgs[3]).to.equal(3); - expect(lastCallArgs[2]).to.equal(2); - expect(lastCallArgs[1]).to.eql({ - data: 1, - fullError: errInstance, - }); - expect(lastCallArgs[0]).to.eql(errInstance.stack); - }); - }); -}); diff --git a/test/api/v3/unit/libs/password.test.js b/test/api/v3/unit/libs/password.test.js deleted file mode 100644 index 68290aebc6..0000000000 --- a/test/api/v3/unit/libs/password.test.js +++ /dev/null @@ -1,41 +0,0 @@ -import { - encrypt as encryptPassword, - makeSalt, -} from '../../../../../website/server/libs/api-v3/password'; - -describe('Password Utilities', () => { - describe('Encrypt', () => { - it('always encrypt the same password to the same value when using the same salt', () => { - let textPassword = 'mySecretPassword'; - let salt = makeSalt(); - let encryptedPassword = encryptPassword(textPassword, salt); - - expect(encryptPassword(textPassword, salt)).to.eql(encryptedPassword); - }); - - it('never encrypt the same password to the same value when using a different salt', () => { - let textPassword = 'mySecretPassword'; - let aSalt = makeSalt(); - let anotherSalt = makeSalt(); - let anEncryptedPassword = encryptPassword(textPassword, aSalt); - let anotherEncryptedPassword = encryptPassword(textPassword, anotherSalt); - - expect(anEncryptedPassword).not.to.eql(anotherEncryptedPassword); - }); - }); - - describe('Make Salt', () => { - it('creates a salt with length 10 by default', () => { - let salt = makeSalt(); - - expect(salt.length).to.eql(10); - }); - - it('can create a salt of any length', () => { - let length = 24; - let salt = makeSalt(length); - - expect(salt.length).to.eql(length); - }); - }); -}); diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js deleted file mode 100644 index 30fe78b643..0000000000 --- a/test/api/v3/unit/libs/payments.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as sender from '../../../../../website/server/libs/api-v3/email'; -import * as api from '../../../../../website/server/libs/api-v3/payments'; -import { model as User } from '../../../../../website/server/models/user'; -import moment from 'moment'; - -describe('payments/index', () => { - let fakeSend; - let data; - let user; - - describe('#createSubscription', () => { - beforeEach(async () => { - user = new User(); - }); - - it('succeeds', async () => { - data = { user, sub: { key: 'basic_3mo' } }; - expect(user.purchased.plan.planId).to.not.exist; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.exist; - }); - }); - - describe('#cancelSubscription', () => { - beforeEach(() => { - fakeSend = sinon.spy(sender, 'sendTxn'); - data = { user: new User() }; - }); - - afterEach(() => { - fakeSend.restore(); - }); - - it('plan.extraMonths is defined', () => { - api.cancelSubscription(data); - let terminated = data.user.purchased.plan.dateTerminated; - data.user.purchased.plan.extraMonths = 2; - api.cancelSubscription(data); - let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); - expect(difference - 60).to.be.lessThan(3); // the difference is approximately two months, +/- 2 days - }); - - it('plan.extraMonth is a fraction', () => { - api.cancelSubscription(data); - let terminated = data.user.purchased.plan.dateTerminated; - data.user.purchased.plan.extraMonths = 0.3; - api.cancelSubscription(data); - let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); - expect(difference - 10).to.be.lessThan(3); // the difference should be 10 days. - }); - - it('nextBill is defined', () => { - api.cancelSubscription(data); - let terminated = data.user.purchased.plan.dateTerminated; - data.nextBill = moment().add({ days: 25 }); - api.cancelSubscription(data); - let difference = Math.abs(moment(terminated).diff(data.user.purchased.plan.dateTerminated, 'days')); - expect(difference - 5).to.be.lessThan(2); // the difference should be 5 days, +/- 1 day - }); - - it('saves the canceled subscription for the user', () => { - expect(data.user.purchased.plan.dateTerminated).to.not.exist; - api.cancelSubscription(data); - expect(data.user.purchased.plan.dateTerminated).to.exist; - }); - - it('sends a text', async () => { - await api.cancelSubscription(data); - sinon.assert.called(fakeSend); - }); - }); -}); diff --git a/test/api/v3/unit/libs/preening.test.js b/test/api/v3/unit/libs/preening.test.js deleted file mode 100644 index af503ca480..0000000000 --- a/test/api/v3/unit/libs/preening.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import { preenHistory } from '../../../../../website/server/libs/api-v3/preening'; -import moment from 'moment'; -import sinon from 'sinon'; // eslint-disable-line no-shadow -import { generateHistory } from '../../../../helpers/api-unit.helper.js'; - -describe('preenHistory', () => { - let clock; - - beforeEach(() => { - // Replace system clocks so we can get predictable results - clock = sinon.useFakeTimers(Number(moment('2013-10-20').zone(0).startOf('day').toDate()), 'Date'); - }); - afterEach(() => { - return clock.restore(); - }); - - it('does not modify history if all entries are more recent than cutoff (free users)', () => { - let h = generateHistory(60); - expect(preenHistory(_.cloneDeep(h), false, 0)).to.eql(h); - }); - - it('does not modify history if all entries are more recent than cutoff (subscribers)', () => { - let h = generateHistory(365); - expect(preenHistory(_.cloneDeep(h), true, 0)).to.eql(h); - }); - - it('does aggregate data in monthly entries before cutoff (free users)', () => { - let h = generateHistory(81); // Jumps to July - let preened = preenHistory(_.cloneDeep(h), false, 0); - expect(preened.length).to.eql(62); // Keeps 60 days + 2 entries per august and july - }); - - it('does aggregate data in monthly entries before cutoff (subscribers)', () => { - let h = generateHistory(396); // Jumps to September 2012 - let preened = preenHistory(_.cloneDeep(h), true, 0); - expect(preened.length).to.eql(367); // Keeps 365 days + 2 entries per october and september - }); - - it('does aggregate data in monthly and yearly entries before cutoff (free users)', () => { - let h = generateHistory(731); // Jumps to October 21 2012 - let preened = preenHistory(_.cloneDeep(h), false, 0); - expect(preened.length).to.eql(73); // Keeps 60 days + 11 montly entries and 2 yearly entry for 2011 and 2012 - }); - - it('does aggregate data in monthly and yearly entries before cutoff (subscribers)', () => { - let h = generateHistory(1031); // Jumps to October 21 2012 - let preened = preenHistory(_.cloneDeep(h), true, 0); - expect(preened.length).to.eql(380); // Keeps 365 days + 13 montly entries and 2 yearly entries for 2011 and 2010 - }); - - it('correctly aggregates values', () => { - let h = generateHistory(63); // Compress last 3 days - let preened = preenHistory(_.cloneDeep(h), false, 0); - expect(preened[0].value).to.eql((61 + 62 + 63) / 3); - }); -}); diff --git a/test/api/v3/unit/libs/setupNconf.test.js b/test/api/v3/unit/libs/setupNconf.test.js deleted file mode 100644 index 3e848b845f..0000000000 --- a/test/api/v3/unit/libs/setupNconf.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import setupNconf from '../../../../../website/server/libs/api-v3/setupNconf'; - -import path from 'path'; -import nconf from 'nconf'; - -describe('setupNconf', () => { - beforeEach(() => { - sandbox.stub(nconf, 'argv').returnsThis(); - sandbox.stub(nconf, 'env').returnsThis(); - sandbox.stub(nconf, 'file').returnsThis(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('sets up nconf', () => { - setupNconf(); - - expect(nconf.argv).to.be.calledOnce; - expect(nconf.env).to.be.calledOnce; - expect(nconf.file).to.be.calledOnce; - - let regexString = `\\${path.sep}config.json$`; - expect(nconf.file).to.be.calledWithMatch('user', new RegExp(regexString)); - }); - - it('sets IS_PROD variable', () => { - setupNconf(); - expect(nconf.get('IS_PROD')).to.exist; - }); - - it('sets IS_DEV variable', () => { - setupNconf(); - expect(nconf.get('IS_DEV')).to.exist; - }); - - it('allows a custom config.json file to be passed in', () => { - setupNconf('customfile.json'); - - expect(nconf.file).to.be.calledOnce; - expect(nconf.file).to.be.calledWithMatch('user', 'customfile.json'); - }); -}); diff --git a/test/api/v3/unit/libs/webhooks.test.js b/test/api/v3/unit/libs/webhooks.test.js deleted file mode 100644 index 502bfe3839..0000000000 --- a/test/api/v3/unit/libs/webhooks.test.js +++ /dev/null @@ -1,135 +0,0 @@ -import request from 'request'; -import { sendTaskWebhook } from '../../../../../website/server/libs/api-v3/webhook'; - -describe('webhooks', () => { - beforeEach(() => { - sandbox.stub(request, 'post'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('sendTaskWebhook', () => { - let task = { - details: { _id: 'task-id' }, - delta: 1.4, - direction: 'up', - }; - - let data = { - task, - user: { _id: 'user-id' }, - }; - - it('does not send if no webhook endpoints exist', () => { - let webhooks = { }; - - sendTaskWebhook(webhooks, data); - - expect(request.post).to.not.be.called; - }); - - it('does not send if no webhooks are enabled', () => { - let webhooks = { - 'some-id': { - sort: 0, - id: 'some-id', - enabled: false, - url: 'http://example.org/endpoint', - }, - }; - - sendTaskWebhook(webhooks, data); - - expect(request.post).to.not.be.called; - }); - - it('does not send if webhook url is not valid', () => { - let webhooks = { - 'some-id': { - sort: 0, - id: 'some-id', - enabled: true, - url: 'http://malformedurl/endpoint', - }, - }; - - sendTaskWebhook(webhooks, data); - - expect(request.post).to.not.be.called; - }); - - it('sends task direction, task, task delta, and abridged user data', () => { - let webhooks = { - 'some-id': { - sort: 0, - id: 'some-id', - enabled: true, - url: 'http://example.org/endpoint', - }, - }; - - sendTaskWebhook(webhooks, data); - - expect(request.post).to.be.calledOnce; - expect(request.post).to.be.calledWith({ - url: 'http://example.org/endpoint', - body: { - direction: 'up', - task: { _id: 'task-id' }, - delta: 1.4, - user: { - _id: 'user-id', - }, - }, - json: true, - }); - }); - - it('sends a post request for each webhook endpoint', () => { - let webhooks = { - 'some-id': { - sort: 0, - id: 'some-id', - enabled: true, - url: 'http://example.org/endpoint', - }, - 'second-webhook': { - sort: 1, - id: 'second-webhook', - enabled: true, - url: 'http://example.com/2/endpoint', - }, - }; - - sendTaskWebhook(webhooks, data); - - expect(request.post).to.be.calledTwice; - expect(request.post).to.be.calledWith({ - url: 'http://example.org/endpoint', - body: { - direction: 'up', - task: { _id: 'task-id' }, - delta: 1.4, - user: { - _id: 'user-id', - }, - }, - json: true, - }); - expect(request.post).to.be.calledWith({ - url: 'http://example.com/2/endpoint', - body: { - direction: 'up', - task: { _id: 'task-id' }, - delta: 1.4, - user: { - _id: 'user-id', - }, - }, - json: true, - }); - }); - }); -}); diff --git a/test/api/v3/unit/middlewares/analytics.test.js b/test/api/v3/unit/middlewares/analytics.test.js deleted file mode 100644 index 2a25380713..0000000000 --- a/test/api/v3/unit/middlewares/analytics.test.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable global-require */ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; -import nconf from 'nconf'; -import requireAgain from 'require-again'; - -describe('analytics middleware', () => { - let res, req, next; - let pathToAnalyticsMiddleware = '../../../../../website/server/middlewares/api-v3/analytics'; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - }); - - it('attaches analytics object res.locals', () => { - let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); - - attachAnalytics(req, res, next); - - expect(res.analytics).to.exist; - }); - - it('attaches stubbed methods for non-prod environments', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); - let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); - - attachAnalytics(req, res, next); - - expect(res.analytics.track).to.eql(analyticsService.mockAnalyticsService.track); - expect(res.analytics.trackPurchase).to.eql(analyticsService.mockAnalyticsService.trackPurchase); - }); - - it('attaches real methods for prod environments', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - - let attachAnalytics = requireAgain(pathToAnalyticsMiddleware); - - attachAnalytics(req, res, next); - - expect(res.analytics.track).to.eql(analyticsService.track); - expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase); - }); -}); diff --git a/test/api/v3/unit/middlewares/cors.test.js b/test/api/v3/unit/middlewares/cors.test.js deleted file mode 100644 index 78d11651f8..0000000000 --- a/test/api/v3/unit/middlewares/cors.test.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable global-require */ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import cors from '../../../../../website/server/middlewares/api-v3/cors'; - -describe('cors middleware', () => { - let res, req, next; - - beforeEach(() => { - req = generateReq(); - res = generateRes(); - next = generateNext(); - }); - - it('sets the correct headers', () => { - cors(req, res, next); - expect(res.set).to.have.been.calledWith({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', - 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', - }); - expect(res.sendStatus).to.not.have.been.called; - expect(next).to.have.been.called.once; - }); - - it('responds immediately if method is OPTIONS', () => { - req.method = 'OPTIONS'; - cors(req, res, next); - expect(res.set).to.have.been.calledWith({ - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', - 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', - }); - expect(res.sendStatus).to.have.been.calledWith(200); - expect(next).to.not.have.been.called; - }); -}); diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js deleted file mode 100644 index f4e040a11c..0000000000 --- a/test/api/v3/unit/middlewares/cronMiddleware.js +++ /dev/null @@ -1,177 +0,0 @@ -import { - generateRes, - generateReq, - generateNext, - generateTodo, - generateDaily, -} from '../../../../helpers/api-unit.helper'; -import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron'; -import moment from 'moment'; -import { model as User } from '../../../../../website/server/models/user'; -import { model as Group } from '../../../../../website/server/models/group'; -import * as Tasks from '../../../../../website/server/models/task'; -import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; -import { v4 as generateUUID } from 'uuid'; - -describe('cron middleware', () => { - let res, req, next; - let user; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - user = new User({ - auth: { - local: { - username: 'username', - lowerCaseUsername: 'username', - email: 'email@email.email', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }); - - user._statsComputed = { - mp: 10, - maxMP: 100, - }; - - res.locals.user = user; - res.analytics = analyticsService; - }); - - it('calls next when user is not attached', () => { - res.locals.user = null; - cronMiddleware(req, res, next); - expect(next).to.be.calledOnce; - }); - - it('calls next when days have not been missed', () => { - cronMiddleware(req, res, next); - expect(next).to.be.calledOnce; - }); - - it('should clear todos older than 30 days for free users', async (done) => { - user.lastCron = moment(new Date()).subtract({days: 2}); - let task = generateTodo(user); - task.dateCompleted = moment(new Date()).subtract({days: 31}); - task.completed = true; - await task.save(); - - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; - expect(taskFound).to.not.exist; - done(); - }); - }); - }); - - it('should not clear todos older than 30 days for subscribed users', (done) => { - user.purchased.plan.customerId = 'subscribedId'; - user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); - user.lastCron = moment(new Date()).subtract({days: 2}); - let task = generateTodo(user); - task.dateCompleted = moment(new Date()).subtract({days: 31}); - task.completed = true; - task.save(); - - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; - expect(taskFound).to.exist; - done(); - }); - }); - }); - - it('should clear todos older than 90 days for subscribed users', (done) => { - user.purchased.plan.customerId = 'subscribedId'; - user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); - user.lastCron = moment(new Date()).subtract({days: 2}); - - let task = generateTodo(user); - task.dateCompleted = moment(new Date()).subtract({days: 91}); - task.completed = true; - task.save(); - - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; - expect(taskFound).to.not.exist; - done(); - }); - }); - }); - - it('should call next is user was not modified after cron', (done) => { - let hpBefore = user.stats.hp; - user.lastCron = moment(new Date()).subtract({days: 2}); - - user.save().then(function () { - cronMiddleware(req, res, function () { - expect(hpBefore).to.equal(user.stats.hp); - done(); - }); - }); - }); - - it('does damage for missing dailies', (done) => { - let hpBefore = user.stats.hp; - user.lastCron = moment(new Date()).subtract({days: 2}); - let daily = generateDaily(user); - daily.startDate = moment(new Date()).subtract({days: 2}); - daily.save(); - - cronMiddleware(req, res, () => { - expect(user.stats.hp).to.be.lessThan(hpBefore); - done(); - }); - }); - - it('updates tasks', (done) => { - user.lastCron = moment(new Date()).subtract({days: 2}); - let todo = generateTodo(user); - let todoValueBefore = todo.value; - - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: todo._id}, function (err, todoFound) { - expect(err).to.not.exist; - expect(todoFound.value).to.be.lessThan(todoValueBefore); - done(); - }); - }); - }); - - it('applies quest progress', async (done) => { - let hpBefore = user.stats.hp; - user.lastCron = moment(new Date()).subtract({days: 2}); - let daily = generateDaily(user); - daily.startDate = moment(new Date()).subtract({days: 2}); - daily.save(); - - let questKey = 'dilatory'; - user.party.quest.key = questKey; - - let party = new Group({ - type: 'party', - name: generateUUID(), - leader: user._id, - }); - party.quest.members[user._id] = true; - party.quest.key = questKey; - await party.save(); - - user.party._id = party._id; - await user.save(); - - party.startQuest(user); - - cronMiddleware(req, res, () => { - expect(user.stats.hp).to.be.lessThan(hpBefore); - done(); - }); - }); -}); diff --git a/test/api/v3/unit/middlewares/ensureAccessRight.test.js b/test/api/v3/unit/middlewares/ensureAccessRight.test.js deleted file mode 100644 index cc25e4f16b..0000000000 --- a/test/api/v3/unit/middlewares/ensureAccessRight.test.js +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable global-require */ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import i18n from '../../../../../common/script/i18n'; -import { ensureAdmin, ensureSudo } from '../../../../../website/server/middlewares/api-v3/ensureAccessRight'; -import { NotAuthorized } from '../../../../../website/server/libs/api-v3/errors'; - -describe('ensure access middlewares', () => { - let res, req, next; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - }); - - context('ensure admin', () => { - it('returns not authorized when user is not an admin', () => { - res.locals = {user: {contributor: {admin: false}}}; - - ensureAdmin(req, res, next); - - expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noAdminAccess'))); - }); - - it('passes when user is an admin', () => { - res.locals = {user: {contributor: {admin: true}}}; - - ensureAdmin(req, res, next); - - expect(next).to.be.calledOnce; - expect(next.args[0]).to.be.empty; - }); - }); - - context('ensure sudo', () => { - it('returns not authorized when user is not a sudo user', () => { - res.locals = {user: {contributor: {sudo: false}}}; - - ensureSudo(req, res, next); - - expect(next).to.be.calledWith(new NotAuthorized(i18n.t('noSudoAccess'))); - }); - - it('passes when user is a sudo user', () => { - res.locals = {user: {contributor: {sudo: true}}}; - - ensureSudo(req, res, next); - - expect(next).to.be.calledOnce; - expect(next.args[0]).to.be.empty; - }); - }); -}); diff --git a/test/api/v3/unit/middlewares/ensureDevelpmentMode.js b/test/api/v3/unit/middlewares/ensureDevelpmentMode.js deleted file mode 100644 index d7915b365f..0000000000 --- a/test/api/v3/unit/middlewares/ensureDevelpmentMode.js +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable global-require */ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import ensureDevelpmentMode from '../../../../../website/server/middlewares/api-v3/ensureDevelpmentMode'; -import { NotFound } from '../../../../../website/server/libs/api-v3/errors'; -import nconf from 'nconf'; - -describe('developmentMode middleware', () => { - let res, req, next; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - }); - - it('returns not found when in production mode', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - - ensureDevelpmentMode(req, res, next); - - expect(next).to.be.calledWith(new NotFound()); - }); - - it('passes when not in production', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false); - - ensureDevelpmentMode(req, res, next); - - expect(next).to.be.calledOnce; - expect(next.args[0]).to.be.empty; - }); -}); diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js deleted file mode 100644 index 72cad12a32..0000000000 --- a/test/api/v3/unit/middlewares/errorHandler.test.js +++ /dev/null @@ -1,173 +0,0 @@ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; - -import errorHandler from '../../../../../website/server/middlewares/api-v3/errorHandler'; -import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; -import { - getUserLanguage, - attachTranslateFunction, -} from '../../../../../website/server/middlewares/api-v3/language'; - -import { BadRequest } from '../../../../../website/server/libs/api-v3/errors'; -import logger from '../../../../../website/server/libs/api-v3/logger'; - -describe('errorHandler', () => { - let res, req, next; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - responseMiddleware(req, res, next); - getUserLanguage(req, res, next); - attachTranslateFunction(req, res, next); - - sandbox.stub(logger, 'error'); - }); - - it('sends internal server error if error is not a CustomError and is not identified', () => { - let error = new Error(); - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(500); - expect(res.json).to.be.calledWith({ - success: false, - error: 'InternalServerError', - message: 'An unexpected error occurred.', - }); - }); - - it('identifies errors with statusCode property and format them correctly', () => { - let error = new Error('Error message'); - error.statusCode = 400; - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(400); - expect(res.json).to.be.calledWith({ - success: false, - error: 'Error', - message: 'Error message', - }); - }); - - it('doesn\'t leak info about 500 errors', () => { - let error = new Error('Some secret error message'); - error.statusCode = 500; - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(500); - expect(res.json).to.be.calledWith({ - success: false, - error: 'InternalServerError', - message: 'An unexpected error occurred.', - }); - }); - - it('sends CustomError', () => { - let error = new BadRequest(); - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(400); - expect(res.json).to.be.calledWith({ - success: false, - error: 'BadRequest', - message: 'Bad request.', - }); - }); - - it('handle http-errors errors', () => { - let error = new Error('custom message'); - error.statusCode = 422; - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(error.statusCode); - expect(res.json).to.be.calledWith({ - success: false, - error: error.name, - message: error.message, - }); - }); - - it('handle express-validator errors', () => { - let error = [{param: 'param', msg: 'invalid param', value: 123}]; - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(400); - expect(res.json).to.be.calledWith({ - success: false, - error: 'BadRequest', - message: 'Invalid request parameters.', - errors: [ - { param: error[0].param, value: error[0].value, message: error[0].msg }, - ], - }); - }); - - it('handle Mongoose Validation errors', () => { - let error = new Error('User validation failed.'); - error.name = 'ValidationError'; - - error.errors = { - 'auth.local.email': { - path: 'auth.local.email', - message: 'Invalid email.', - value: 'not an email', - }, - }; - - errorHandler(error, req, res, next); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(400); - expect(res.json).to.be.calledWith({ - success: false, - error: 'BadRequest', - message: 'User validation failed.', - errors: [ - { path: 'auth.local.email', message: 'Invalid email.', value: 'not an email' }, - ], - }); - }); - - it('logs error', () => { - let error = new BadRequest(); - - errorHandler(error, req, res, next); - - expect(logger.error).to.be.calledOnce; - expect(logger.error).to.be.calledWithExactly(error, { - originalUrl: req.originalUrl, - headers: req.headers, - body: req.body, - }); - }); -}); diff --git a/test/api/v3/unit/middlewares/language.test.js b/test/api/v3/unit/middlewares/language.test.js deleted file mode 100644 index 23ef6deddd..0000000000 --- a/test/api/v3/unit/middlewares/language.test.js +++ /dev/null @@ -1,307 +0,0 @@ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import { - getUserLanguage, - attachTranslateFunction, -} from '../../../../../website/server/middlewares/api-v3/language'; -import common from '../../../../../common'; -import Bluebird from 'bluebird'; -import { model as User } from '../../../../../website/server/models/user'; - -const i18n = common.i18n; - -describe('language middleware', () => { - describe('res.t', () => { - let res, req, next; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - - sinon.stub(i18n, 't'); - }); - - afterEach(() => { - i18n.t.restore(); - }); - - it('attaches t method to res', () => { - attachTranslateFunction(req, res, next); - - expect(res.t).to.exist; - }); - - it('uses the language specified in req.language', () => { - req.language = 'de'; - - attachTranslateFunction(req, res, next); - res.t(1, 2); - - expect(i18n.t).to.be.calledOnce; - expect(i18n.t).to.be.calledWith(1, 2); - }); - }); - - describe('getUserLanguage', () => { - let res, req, next; - - let checkResT = (resToCheck) => { - expect(resToCheck.t).to.be.a('function'); - expect(resToCheck.t('help')).to.equal(i18n.t('help', req.language)); - }; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - attachTranslateFunction(req, res, next); - }); - - context('query parameter', () => { - it('uses the language in the query parameter if avalaible', () => { - req.query = { - lang: 'es', - }; - - getUserLanguage(req, res, next); - expect(req.language).to.equal('es'); - checkResT(res); - }); - - it('falls back to english if the query parameter language does not exists', () => { - req.query = { - lang: 'bla', - }; - - getUserLanguage(req, res, next); - expect(req.language).to.equal('en'); - checkResT(res); - }); - - it('uses query even if the request includes a user and session', () => { - req.query = { - lang: 'es', - }; - - req.locals = { - user: { - preferences: { - language: 'it', - }, - }, - }; - - req.session = { - userId: 123, - }; - - getUserLanguage(req, res, next); - expect(req.language).to.equal('es'); - checkResT(res); - }); - }); - - context('authorized request', () => { - it('uses the user preferred language if avalaible', () => { - req.locals = { - user: { - preferences: { - language: 'it', - }, - }, - }; - - getUserLanguage(req, res, next); - expect(req.language).to.equal('it'); - checkResT(res); - }); - - it('falls back to english if the user preferred language is not avalaible', (done) => { - req.locals = { - user: { - preferences: { - language: 'bla', - }, - }, - }; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('en'); - checkResT(res); - done(); - }); - }); - - it('uses the user preferred language even if a session is included in request', () => { - req.locals = { - user: { - preferences: { - language: 'it', - }, - }, - }; - - req.session = { - userId: 123, - }; - - getUserLanguage(req, res, next); - expect(req.language).to.equal('it'); - checkResT(res); - }); - }); - - context('request with session', () => { - it('uses the user preferred language if avalaible', (done) => { - sandbox.stub(User, 'findOne').returns({ - lean () { - return this; - }, - exec () { - return Bluebird.resolve({ - preferences: { - language: 'it', - }, - }); - }, - }); - - req.session = { - userId: 123, - }; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('it'); - checkResT(res); - done(); - }); - }); - }); - - context('browser fallback', () => { - it('uses browser specificed language', (done) => { - req.headers['accept-language'] = 'pt'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('pt'); - checkResT(res); - done(); - }); - }); - - it('uses first language in series if browser specifies multiple', (done) => { - req.headers['accept-language'] = 'he, pt, it'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('he'); - checkResT(res); - done(); - }); - }); - - it('skips invalid lanaguages and uses first language in series if browser specifies multiple', (done) => { - req.headers['accept-language'] = 'blah, he, pt, it'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('he'); - checkResT(res); - done(); - }); - }); - - it('uses normal version of language if specialized locale is passed in', (done) => { - req.headers['accept-language'] = 'fr-CA'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('fr'); - checkResT(res); - done(); - }); - }); - - it('uses normal version of language if specialized locale is passed in', (done) => { - req.headers['accept-language'] = 'fr-CA'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('fr'); - checkResT(res); - done(); - }); - }); - - it('uses es if es is passed in', (done) => { - req.headers['accept-language'] = 'es'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('es'); - checkResT(res); - done(); - }); - }); - - it('uses es_419 if applicable es-languages are passed in', (done) => { - req.headers['accept-language'] = 'es-mx'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('es_419'); - checkResT(res); - done(); - }); - }); - - it('uses es_419 if multiple es languages are passed in', (done) => { - req.headers['accept-language'] = 'es-GT, es-MX, es-CR'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('es_419'); - checkResT(res); - done(); - }); - }); - - it('zh', (done) => { - req.headers['accept-language'] = 'zh-TW'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('zh_TW'); - checkResT(res); - done(); - }); - }); - - it('uses english if browser specified language is not compatible', (done) => { - req.headers['accept-language'] = 'blah'; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('en'); - checkResT(res); - done(); - }); - }); - - it('uses english if browser does not specify', (done) => { - req.headers['accept-language'] = ''; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('en'); - checkResT(res); - done(); - }); - }); - - it('uses english if browser does not supply an accept-language header', (done) => { - delete req.headers['accept-language']; - - getUserLanguage(req, res, () => { - expect(req.language).to.equal('en'); - checkResT(res); - done(); - }); - }); - }); - }); -}); diff --git a/test/api/v3/unit/middlewares/maintenanceMode.test.js b/test/api/v3/unit/middlewares/maintenanceMode.test.js deleted file mode 100644 index 21cabe963d..0000000000 --- a/test/api/v3/unit/middlewares/maintenanceMode.test.js +++ /dev/null @@ -1,58 +0,0 @@ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import nconf from 'nconf'; -import requireAgain from 'require-again'; - -describe('maintenance mode middleware', () => { - let res, req, next; - let pathToMaintenanceModeMiddleware = '../../../../../website/server/middlewares/api-v3/maintenanceMode'; - - beforeEach(() => { - res = generateRes(); - next = generateNext(); - }); - - it('does not return 503 error when maintenance mode is off', () => { - req = generateReq(); - sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('false'); - let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); - - attachMaintenanceMode(req, res, next); - - expect(next).to.have.been.called.once; - expect(res.status).to.not.have.been.called; - }); - - it('returns 503 error when maintenance mode is on', () => { - req = generateReq(); - sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); - let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); - - attachMaintenanceMode(req, res, next); - - expect(next).to.not.have.been.called; - expect(res.status).to.have.been.calledOnce; - expect(res.status).to.have.been.calledWith(503); - }); - - it('renders maintenance page when request type is HTML', () => { - req = generateReq({headers: {accept: 'text/html'}}); - sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); - let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); - - attachMaintenanceMode(req, res, next); - expect(res.render).to.have.been.calledOnce; - }); - - it('sends error message when request type is JSON', () => { - req = generateReq({headers: {accept: 'application/json'}}); - sandbox.stub(nconf, 'get').withArgs('MAINTENANCE_MODE').returns('true'); - let attachMaintenanceMode = requireAgain(pathToMaintenanceModeMiddleware); - - attachMaintenanceMode(req, res, next); - expect(res.send).to.have.been.calledOnce; - }); -}); diff --git a/test/api/v3/unit/middlewares/response.js b/test/api/v3/unit/middlewares/response.js deleted file mode 100644 index a24bd881ce..0000000000 --- a/test/api/v3/unit/middlewares/response.js +++ /dev/null @@ -1,66 +0,0 @@ -import { - generateRes, - generateReq, - generateNext, -} from '../../../../helpers/api-unit.helper'; -import responseMiddleware from '../../../../../website/server/middlewares/api-v3/response'; - -describe('response middleware', () => { - let res, req, next; - - beforeEach(() => { - res = generateRes(); - req = generateReq(); - next = generateNext(); - }); - - - it('attaches respond method to res', () => { - responseMiddleware(req, res, next); - - expect(res.respond).to.exist; - }); - - it('can be used to respond to requests', () => { - responseMiddleware(req, res, next); - res.respond(200, {field: 1}); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(200); - expect(res.json).to.be.calledWith({ - success: true, - data: {field: 1}, - }); - }); - - it('can be passed a third parameter to be used as optional message', () => { - responseMiddleware(req, res, next); - res.respond(200, {field: 1}, 'hello'); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(200); - expect(res.json).to.be.calledWith({ - success: true, - data: {field: 1}, - message: 'hello', - }); - }); - - it('treats status >= 400 as failures', () => { - responseMiddleware(req, res, next); - res.respond(403, {field: 1}); - - expect(res.status).to.be.calledOnce; - expect(res.json).to.be.calledOnce; - - expect(res.status).to.be.calledWith(403); - expect(res.json).to.be.calledWith({ - success: false, - data: {field: 1}, - }); - }); -}); diff --git a/test/api/v3/unit/models/challenge.test.js b/test/api/v3/unit/models/challenge.test.js deleted file mode 100644 index b2f0e7ff98..0000000000 --- a/test/api/v3/unit/models/challenge.test.js +++ /dev/null @@ -1,161 +0,0 @@ -import { model as Challenge } from '../../../../../website/server/models/challenge'; -import { model as Group } from '../../../../../website/server/models/group'; -import { model as User } from '../../../../../website/server/models/user'; -import * as Tasks from '../../../../../website/server/models/task'; -import common from '../../../../../common/'; -import { each, find } from 'lodash'; - -describe('Challenge Model', () => { - let guild, leader, challenge, task; - let tasksToTest = { - habit: { - text: 'test habit', - type: 'habit', - up: false, - down: true, - }, - todo: { - text: 'test todo', - type: 'todo', - }, - daily: { - text: 'test daily', - type: 'daily', - frequency: 'daily', - everyX: 5, - startDate: new Date(), - }, - reward: { - text: 'test reward', - type: 'reward', - }, - }; - - beforeEach(async () => { - guild = new Group({ - name: 'test party', - type: 'guild', - }); - - leader = new User({ - guilds: [guild._id], - }); - - guild.leader = leader._id; - - challenge = new Challenge({ - name: 'Test Challenge', - shortName: 'Test', - leader: leader._id, - group: guild._id, - }); - - leader.challenges = [challenge._id]; - - await Promise.all([ - guild.save(), - leader.save(), - challenge.save(), - ]); - }); - - each(tasksToTest, (taskValue, taskType) => { - context(`${taskType}`, () => { - beforeEach(async() => { - task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue)); - task.challenge.id = challenge._id; - await task.save(); - }); - - it('adds tasks to challenge and challenge members', async () => { - await challenge.addTasks([task]); - - let updatedLeader = await User.findOne({_id: leader._id}); - let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); - let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { - return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; - }); - - expect(syncedTask).to.exist; - }); - - it('syncs a challenge to a user', async () => { - await challenge.addTasks([task]); - - let newMember = new User({ - guilds: [guild._id], - }); - await newMember.save(); - - await challenge.syncToUser(newMember); - - let updatedNewMember = await User.findById(newMember._id); - let updatedNewMemberTasks = await Tasks.Task.find({_id: { $in: updatedNewMember.tasksOrder[`${taskType}s`]}}); - let syncedTask = find(updatedNewMemberTasks, function findNewTask (updatedNewMemberTask) { - return updatedNewMemberTask.type === taskValue.type && updatedNewMemberTask.text === taskValue.text; - }); - - expect(updatedNewMember.challenges).to.contain(challenge._id); - expect(updatedNewMember.tags[3].id).to.equal(challenge._id); - expect(updatedNewMember.tags[3].name).to.equal(challenge.shortName); - expect(syncedTask).to.exist; - }); - - it('updates tasks to challenge and challenge members', async () => { - let updatedTaskName = 'Updated Test Habit'; - await challenge.addTasks([task]); - - let req = { - body: { text: updatedTaskName }, - }; - - Tasks.Task.sanitize(req.body); - _.assign(task, common.ops.updateTask(task.toObject(), req)[0]); - - await challenge.updateTask(task); - - let updatedLeader = await User.findOne({_id: leader._id}); - let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder[`${taskType}s`][0]); - - expect(updatedUserTask.text).to.equal(updatedTaskName); - }); - - it('removes a tasks to challenge and challenge members', async () => { - await challenge.addTasks([task]); - await challenge.removeTask(task); - - let updatedLeader = await User.findOne({_id: leader._id}); - let updatedUserTask = await Tasks.Task.findOne({_id: updatedLeader.tasksOrder[`${taskType}s`][0]}).exec(); - - expect(updatedUserTask.challenge.broken).to.equal('TASK_DELETED'); - }); - - it('unlinks and deletes challenge tasks for a user when remove-all is specified', async () => { - await challenge.addTasks([task]); - await challenge.unlinkTasks(leader, 'remove-all'); - - let updatedLeader = await User.findOne({_id: leader._id}); - let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); - let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { - return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; - }); - - expect(syncedTask).to.not.exist; - }); - - it('unlinks and keeps challenge tasks for a user when keep-all is specified', async () => { - await challenge.addTasks([task]); - await challenge.unlinkTasks(leader, 'keep-all'); - - let updatedLeader = await User.findOne({_id: leader._id}); - let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}}); - let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) { - return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text; - }); - - expect(syncedTask).to.exist; - expect(syncedTask.challenge._id).to.be.empty; - }); - }); - }); -}); diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js deleted file mode 100644 index 32007e068d..0000000000 --- a/test/api/v3/unit/models/group.test.js +++ /dev/null @@ -1,300 +0,0 @@ -import { sleep } from '../../../../helpers/api-unit.helper'; -import { model as Group } from '../../../../../website/server/models/group'; -import { model as User } from '../../../../../website/server/models/user'; -import { quests as questScrolls } from '../../../../../common/script/content'; -import * as email from '../../../../../website/server/libs/api-v3/email'; - -describe('Group Model', () => { - context('Instance Methods', () => { - describe('#startQuest', () => { - let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember; - - beforeEach(async () => { - sandbox.stub(email, 'sendTxn'); - - party = new Group({ - name: 'test party', - type: 'party', - privacy: 'private', - }); - - questLeader = new User({ - party: { _id: party._id }, - items: { - quests: { - whale: 1, - }, - }, - }); - - party.leader = questLeader._id; - - participatingMember = new User({ - party: { _id: party._id }, - }); - nonParticipatingMember = new User({ - party: { _id: party._id }, - }); - undecidedMember = new User({ - party: { _id: party._id }, - }); - - await Promise.all([ - party.save(), - questLeader.save(), - participatingMember.save(), - nonParticipatingMember.save(), - undecidedMember.save(), - ]); - }); - - context('Failure Conditions', () => { - it('throws an error if group is not a party', async () => { - let guild = new Group({ - type: 'guild', - }); - - await expect(guild.startQuest(participatingMember)).to.eventually.be.rejected; - }); - - it('throws an error if party is not on a quest', async () => { - await expect(party.startQuest(participatingMember)).to.eventually.be.rejected; - }); - - it('throws an error if quest is already active', async () => { - party.quest.key = 'whale'; - party.quest.active = true; - - await expect(party.startQuest(participatingMember)).to.eventually.be.rejected; - }); - }); - - context('Successes', () => { - beforeEach(() => { - party.quest.key = 'whale'; - party.quest.active = false; - party.quest.leader = questLeader._id; - party.quest.members = { }; - party.quest.members[questLeader._id] = true; - party.quest.members[participatingMember._id] = true; - party.quest.members[nonParticipatingMember._id] = false; - party.quest.members[undecidedMember._id] = null; - }); - - it('activates quest', () => { - party.startQuest(participatingMember); - - expect(party.quest.active).to.eql(true); - }); - - it('sets up boss quest', () => { - let bossQuest = questScrolls.whale; - party.quest.key = bossQuest.key; - - party.startQuest(participatingMember); - - expect(party.quest.progress.hp).to.eql(bossQuest.boss.hp); - }); - - it('sets up rage meter for rage boss quest', () => { - let rageBossQuest = questScrolls.trex_undead; - party.quest.key = rageBossQuest.key; - - party.startQuest(participatingMember); - - expect(party.quest.progress.rage).to.eql(0); - }); - - it('sets up collection quest', () => { - let collectionQuest = questScrolls.vice2; - party.quest.key = collectionQuest.key; - party.startQuest(participatingMember); - - expect(party.quest.progress.collect).to.eql({ - lightCrystal: 0, - }); - }); - - it('sets up collection quest with multiple items', () => { - let collectionQuest = questScrolls.evilsanta2; - party.quest.key = collectionQuest.key; - party.startQuest(participatingMember); - - expect(party.quest.progress.collect).to.eql({ - tracks: 0, - branches: 0, - }); - }); - - it('prunes non-participating members from quest members object', () => { - party.startQuest(participatingMember); - - let expectedQuestMembers = {}; - expectedQuestMembers[questLeader._id] = true; - expectedQuestMembers[participatingMember._id] = true; - - expect(party.quest.members).to.eql(expectedQuestMembers); - }); - - it('applies updates to user object directly if user is participating', async () => { - await party.startQuest(participatingMember); - - expect(participatingMember.party.quest.key).to.eql('whale'); - expect(participatingMember.party.quest.progress.down).to.eql(0); - expect(participatingMember.party.quest.progress.collect).to.eql({}); - expect(participatingMember.party.quest.completed).to.eql(null); - }); - - it('applies updates to other participating members', async () => { - await party.startQuest(nonParticipatingMember); - - questLeader = await User.findById(questLeader._id); - participatingMember = await User.findById(participatingMember._id); - - expect(participatingMember.party.quest.key).to.eql('whale'); - expect(participatingMember.party.quest.progress.down).to.eql(0); - expect(participatingMember.party.quest.progress.collect).to.eql({}); - expect(participatingMember.party.quest.completed).to.eql(null); - - expect(questLeader.party.quest.key).to.eql('whale'); - expect(questLeader.party.quest.progress.down).to.eql(0); - expect(questLeader.party.quest.progress.collect).to.eql({}); - expect(questLeader.party.quest.completed).to.eql(null); - }); - - it('does not apply updates to nonparticipating members', async () => { - await party.startQuest(participatingMember); - - nonParticipatingMember = await User.findById(nonParticipatingMember ._id); - undecidedMember = await User.findById(undecidedMember._id); - - expect(nonParticipatingMember.party.quest.key).to.not.eql('whale'); - expect(undecidedMember.party.quest.key).to.not.eql('whale'); - }); - - it('sends email to participating members that quest has started', async () => { - participatingMember.preferences.emailNotifications.questStarted = true; - questLeader.preferences.emailNotifications.questStarted = true; - await Promise.all([ - participatingMember.save(), - questLeader.save(), - ]); - - await party.startQuest(nonParticipatingMember); - - await sleep(0.5); - - expect(email.sendTxn).to.be.calledOnce; - - let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); - let typeOfEmail = email.sendTxn.args[0][1]; - - expect(memberIds).to.have.a.lengthOf(2); - expect(memberIds).to.include(participatingMember._id); - expect(memberIds).to.include(questLeader._id); - expect(typeOfEmail).to.eql('quest-started'); - }); - - it('sends email only to members who have not opted out', async () => { - participatingMember.preferences.emailNotifications.questStarted = false; - questLeader.preferences.emailNotifications.questStarted = true; - await Promise.all([ - participatingMember.save(), - questLeader.save(), - ]); - - await party.startQuest(nonParticipatingMember); - - await sleep(0.5); - - expect(email.sendTxn).to.be.calledOnce; - - let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); - - expect(memberIds).to.have.a.lengthOf(1); - expect(memberIds).to.not.include(participatingMember._id); - expect(memberIds).to.include(questLeader._id); - }); - - it('does not send email to initiating member', async () => { - participatingMember.preferences.emailNotifications.questStarted = true; - questLeader.preferences.emailNotifications.questStarted = true; - await Promise.all([ - participatingMember.save(), - questLeader.save(), - ]); - - await party.startQuest(participatingMember); - - await sleep(0.5); - - expect(email.sendTxn).to.be.calledOnce; - - let memberIds = _.pluck(email.sendTxn.args[0][0], '_id'); - - expect(memberIds).to.have.a.lengthOf(1); - expect(memberIds).to.not.include(participatingMember._id); - expect(memberIds).to.include(questLeader._id); - }); - - it('updates participting members (not including user)', async () => { - sandbox.spy(User, 'update'); - - await party.startQuest(nonParticipatingMember); - - let members = [questLeader._id, participatingMember._id]; - - expect(User.update).to.be.calledWith( - { _id: { $in: members } }, - { - $set: { - 'party.quest.key': 'whale', - 'party.quest.progress.down': 0, - 'party.quest.progress.collect': {}, - 'party.quest.completed': null, - }, - } - ); - }); - - it('updates non-user quest leader and decrements quest scroll', async () => { - sandbox.spy(User, 'update'); - - await party.startQuest(participatingMember); - - expect(User.update).to.be.calledWith( - { _id: questLeader._id }, - { - $inc: { - 'items.quests.whale': -1, - }, - } - ); - }); - - it('modifies the participating initiating user directly', async () => { - await party.startQuest(participatingMember); - - let userQuest = participatingMember.party.quest; - - expect(userQuest.key).to.eql('whale'); - expect(userQuest.progress.down).to.eql(0); - expect(userQuest.progress.collect).to.eql({}); - expect(userQuest.completed).to.eql(null); - }); - - it('does not modify user if not participating', async () => { - await party.startQuest(nonParticipatingMember); - - expect(nonParticipatingMember.party.quest.key).to.not.eql('whale'); - }); - - it('removes the quest directly if initiating user is the quest leader', async () => { - await party.startQuest(questLeader); - - expect(questLeader.items.quests.whale).to.eql(0); - }); - }); - }); - }); -}); diff --git a/test/api/v3/unit/models/task.test.js b/test/api/v3/unit/models/task.test.js deleted file mode 100644 index 1c3082d863..0000000000 --- a/test/api/v3/unit/models/task.test.js +++ /dev/null @@ -1,74 +0,0 @@ -import { model as Challenge } from '../../../../../website/server/models/challenge'; -import { model as Group } from '../../../../../website/server/models/group'; -import { model as User } from '../../../../../website/server/models/user'; -import * as Tasks from '../../../../../website/server/models/task'; -import { each } from 'lodash'; -import { generateHistory } from '../../../../helpers/api-unit.helper.js'; - -describe('Task Model', () => { - let guild, leader, challenge, task; - let tasksToTest = { - habit: { - text: 'test habit', - type: 'habit', - up: false, - down: true, - }, - daily: { - text: 'test daily', - type: 'daily', - frequency: 'daily', - everyX: 5, - startDate: new Date(), - }, - }; - - beforeEach(async () => { - guild = new Group({ - name: 'test guild', - type: 'guild', - }); - - leader = new User({ - guilds: [guild._id], - }); - - guild.leader = leader._id; - - challenge = new Challenge({ - name: 'Test Challenge', - shortName: 'Test', - leader: leader._id, - group: guild._id, - }); - - leader.challenges = [challenge._id]; - - await Promise.all([ - guild.save(), - leader.save(), - challenge.save(), - ]); - }); - - each(tasksToTest, (taskValue, taskType) => { - context(`${taskType}`, () => { - beforeEach(async() => { - task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue)); - task.challenge.id = challenge._id; - task.history = generateHistory(396); - await task.save(); - }); - - it('preens challenge tasks history when scored', async () => { - let historyLengthBeforePreen = task.history.length; - - await task.scoreChallengeTask(1.2); - - let updatedTask = await Tasks.Task.findOne({_id: task._id}); - - expect(historyLengthBeforePreen).to.be.greaterThan(updatedTask.history.length); - }); - }); - }); -}); diff --git a/test/api/v3/unit/models/user.test.js b/test/api/v3/unit/models/user.test.js deleted file mode 100644 index d7f509712c..0000000000 --- a/test/api/v3/unit/models/user.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import { model as User } from '../../../../../website/server/models/user'; - -describe('User Model', () => { - it('keeps user._tmp when calling .toJSON', () => { - let user = new User({ - auth: { - local: { - username: 'username', - lowerCaseUsername: 'username', - email: 'email@email.email', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }); - - user._tmp = {ok: true}; - user._nonTmp = {ok: true}; - - expect(user._tmp).to.eql({ok: true}); - expect(user._nonTmp).to.eql({ok: true}); - - let toObject = user.toObject(); - let toJSON = user.toJSON(); - - expect(toObject).to.not.have.keys('_tmp'); - expect(toObject).to.not.have.keys('_nonTmp'); - - expect(toJSON).to.have.any.key('_tmp'); - expect(toJSON._tmp).to.eql({ok: true}); - expect(toJSON).to.not.have.keys('_nonTmp'); - }); -}); diff --git a/test/common/algos.mocha.js b/test/common/algos.mocha.js new file mode 100644 index 0000000000..12581410bf --- /dev/null +++ b/test/common/algos.mocha.js @@ -0,0 +1,1491 @@ +/* eslint-disable camelcase, func-names, no-shadow */ + +import { + generateUser, + generateDaily, + generateHabit, + generateTodo, +} from '../helpers/common.helper'; + +import { + DAY_MAPPING, + startOfWeek, + startOfDay, + daysSince, +} from '../../common/script/cron'; + +let expect = require('expect.js'); +let sinon = require('sinon'); +let moment = require('moment'); +let test_helper = require('./test_helper'); +let shared = require('../../common/script/index.js'); +let $w = (s) => { + return s.split(' '); +}; + +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; +test_helper.addCustomMatchers(); + +/* Helper Functions */ +let rewrapUser = (user) => { + user._wrapped = false; + shared.wrap(user); + return user; +}; + +let beforeAfter = (options = {}) => { + let lastCron; + let user = generateUser(); + let daily = generateDaily(); + let habit = generateHabit(); + let todo = generateTodo(); + + user.dailys.push(daily); + user.habits.push(habit); + user.todos.push(todo); + + let ref = [user, _.cloneDeep(user)]; + let before = ref[0]; + let after = ref[1]; + + rewrapUser(after); + if (options.dayStart) { + before.preferences.dayStart = after.preferences.dayStart = options.dayStart; + } + before.preferences.timezoneOffset = after.preferences.timezoneOffset = options.timezoneOffset || moment().zone(); + before.preferences.timezoneOffsetAtLastCron = after.preferences.timezoneOffsetAtLastCron = before.preferences.timezoneOffset; + if (options.limitOne) { + before[`${options.limitOne}s`] = [before[`${options.limitOne}s`][0]]; + after[`${options.limitOne}s`] = [after[`${options.limitOne}s`][0]]; + } + if (options.daysAgo) { + lastCron = moment(options.now || Number(new Date())).subtract({ + days: options.daysAgo, + }); + } + if (options.daysAgo && options.cronAfterStart) { + lastCron.add({ + hours: options.dayStart, + minutes: 1, + }); + } + if (options.daysAgo) { + lastCron = Number(lastCron); + } + _.each([before, after], (obj) => { + if (options.daysAgo) { + obj.lastCron = lastCron; + } + }); + return { + before, + after, + }; +}; + +let expectLostPoints = (before, after, taskType) => { + if (taskType === 'daily' || taskType === 'habit') { + expect(after.stats.hp).to.be.lessThan(before.stats.hp); + expect(after[`${taskType}s`][0].history).to.have.length(1); + } else { + expect(after.history.todos).to.have.length(1); + } + expect(after).toHaveExp(0); + expect(after).toHaveGP(0); + expect(after[`${taskType}s`][0].value).to.be.lessThan(before[`${taskType}s`][0].value); +}; + +let expectGainedPoints = (before, after, taskType) => { + expect(after.stats.hp).to.be(50); + expect(after.stats.exp).to.be.greaterThan(before.stats.exp); + expect(after.stats.gp).to.be.greaterThan(before.stats.gp); + expect(after[`${taskType}s`][0].value).to.be.greaterThan(before[`${taskType}s`][0].value); + if (taskType === 'habit') { + expect(after[`${taskType}s`][0].history).to.have.length(1); + } +}; + +let expectNoChange = (before, after) => { + _.each($w('stats items gear dailys todos rewards preferences'), (attr) => { + expect(after[attr]).to.eql(before[attr]); + }); +}; + +let expectClosePoints = (before, after, taskType) => { + expect(Math.abs(after.stats.exp - before.stats.exp)).to.be.lessThan(0.0001); + expect(Math.abs(after.stats.gp - before.stats.gp)).to.be.lessThan(0.0001); + expect(Math.abs(after[taskType + 's'][0].value - before[taskType + 's'][0].value)).to.be.lessThan(0.0001); // eslint-disable-line prefer-template +}; + +let expectDayResetNoDamage = (b, a) => { + let ref = [_.cloneDeep(b), _.cloneDeep(a)]; + let before = ref[0]; + let after = ref[1]; + + _.each(after.dailys, (task, i) => { + expect(task.completed).to.be(false); + expect(before.dailys[i].value).to.be(task.value); + expect(before.dailys[i].streak).to.be(task.streak); + expect(task.history).to.have.length(1); + }); + _.each(after.todos, (task, i) => { + expect(task.completed).to.be(false); + expect(before.todos[i].value).to.be.greaterThan(task.value); + }); + expect(after.history.todos).to.have.length(1); + _.each([before, after], (obj) => { + delete obj.stats.buffs; + _.each($w('dailys todos history lastCron'), (path) => { + return delete obj[path]; + }); + }); + delete after._tmp; + expectNoChange(before, after); +}; + +let repeatWithoutLastWeekday = () => { + let repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + }; + + if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { + repeat.su = false; + } else { + repeat.s = false; + } + return { + repeat, + }; +}; + +describe('User', () => { + it('calculates max MP', () => { + let user = generateUser(); + + expect(user).toHaveMaxMP(30); + user.stats.int = 10; + expect(user).toHaveMaxMP(50); + user.stats.lvl = 5; + expect(user).toHaveMaxMP(54); + user.stats.class = 'wizard'; + user.items.gear.equipped.weapon = 'weapon_wizard_1'; + expect(user).toHaveMaxMP(63); + }); + + it('handles perfect days', () => { + let user = generateUser(); + + user.dailys = []; + _.times(3, () => { + return user.dailys.push(shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + })); + }); + let cron = () => { + user.lastCron = moment().subtract(1, 'days'); + return user.fns.cron(); + }; + + cron(); + expect(user.stats.buffs.str).to.be(0); + expect(user.achievements.perfect).to.not.be.ok(); + user.dailys[0].completed = true; + cron(); + expect(user.stats.buffs.str).to.be(0); + expect(user.achievements.perfect).to.not.be.ok(); + _.each(user.dailys, (d) => { + d.completed = true; + }); + cron(); + expect(user.stats.buffs.str).to.be(1); + expect(user.achievements.perfect).to.be(1); + + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; + _.each(user.dailys.slice(1), (d) => { + d.completed = true; + }); + cron(); + expect(user.stats.buffs.str).to.be(1); + expect(user.achievements.perfect).to.be(2); + }); + + describe('Resting in the Inn', () => { + let user = null; + let cron = null; + + beforeEach(() => { + user = generateUser(); + user.preferences.sleep = true; + cron = () => { + user.lastCron = moment().subtract(1, 'days'); + return user.fns.cron(); + }; + user.dailys = []; + _.times(2, () => { + return user.dailys.push(shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + })); + }); + }); + + it('remains in the inn on cron', () => { + cron(); + expect(user.preferences.sleep).to.be(true); + }); + + it('resets dailies', () => { + user.dailys[0].completed = true; + cron(); + expect(user.dailys[0].completed).to.be(false); + }); + + it('resets checklist on incomplete dailies', () => { + user.dailys[0].checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + cron(); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('resets checklist on complete dailies', () => { + user.dailys[0].checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + user.dailys[0].completed = true; + cron(); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('does not reset checklist on grey incomplete dailies', () => { + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; + user.dailys[0].checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: true, + }, + ]; + cron(); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(true); + }); + }); + + it('resets checklist on complete grey complete dailies', () => { + let yesterday = moment().subtract(1, 'days'); + + user.dailys[0].repeat[DAY_MAPPING[yesterday.day()]] = false; + user.dailys[0].checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: true, + }, + ]; + user.dailys[0].completed = true; + cron(); + _.each(user.dailys[0].checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('does not damage user for incomplete dailies', () => { + expect(user).toHaveHP(50); + user.dailys[0].completed = true; + user.dailys[1].completed = false; + cron(); + expect(user).toHaveHP(50); + }); + + it('gives credit for complete dailies', () => { + user.dailys[0].completed = true; + expect(user.dailys[0].history).to.be.empty; + cron(); + expect(user.dailys[0].history).to.not.be.empty; + }); + + it('damages user for incomplete dailies after checkout', () => { + expect(user).toHaveHP(50); + user.dailys[0].completed = true; + user.dailys[1].completed = false; + user.preferences.sleep = false; + cron(); + expect(user.stats.hp).to.be.lessThan(50); + }); + }); + + describe('Death', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('revives correctly', () => { + user.stats = { + gp: 10, + exp: 100, + lvl: 2, + hp: 0, + class: 'warrior', + }; + user.items.gear.owned.weapon_warrior_0 = true; + user.ops.revive(); + + expect(user).toHaveGP(0); + expect(user).toHaveExp(0); + expect(user).toHaveLevel(1); + expect(user).toHaveHP(50); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: false, + eyewear_special_blackTopFrame: true, + eyewear_special_blueTopFrame: true, + eyewear_special_greenTopFrame: true, + eyewear_special_pinkTopFrame: true, + eyewear_special_redTopFrame: true, + eyewear_special_yellowTopFrame: true, + eyewear_special_whiteTopFrame: true, + }); + }); + + it('doesn\'t break unbreakables', () => { + let ce = shared.countExists; + + user.items.gear.owned = { + weapon_warrior_0: true, + shield_warrior_1: true, + shield_rogue_1: true, + head_special_nye: true, + }; + + expect(ce(user.items.gear.owned)).to.be(4); + + user.stats.hp = 0; + user.ops.revive(); + + expect(ce(user.items.gear.owned)).to.be(3); + + user.stats.hp = 0; + user.ops.revive(); + + expect(ce(user.items.gear.owned)).to.be(2); + + user.stats.hp = 0; + user.ops.revive(); + + expect(ce(user.items.gear.owned)).to.be(2); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: false, + shield_warrior_1: false, + shield_rogue_1: true, + head_special_nye: true, + }); + }); + + it('handles event items', () => { + user.items.gear.owned.head_special_nye = true; + + shared.content.gear.flat.head_special_nye.event.start = '2012-01-01'; + shared.content.gear.flat.head_special_nye.event.end = '2012-02-01'; + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); + delete user.items.gear.owned.head_special_nye; + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(false); + shared.content.gear.flat.head_special_nye.event.start = moment().subtract(5, 'days'); + shared.content.gear.flat.head_special_nye.event.end = moment().add(5, 'days'); + expect(shared.content.gear.flat.head_special_nye.canOwn(user)).to.be(true); + }); + }); + + describe('Rebirth', () => { + it('removes correct gear', () => { + let user = generateUser(); + + user.stats.lvl = 100; + user.items.gear.owned = { + weapon_warrior_0: true, + weapon_warrior_1: true, + armor_warrior_1: false, + armor_mystery_201402: true, + back_mystery_201402: false, + head_mystery_201402: true, + weapon_armoire_basicCrossbow: true, + }; + user.ops.rebirth(); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + weapon_warrior_1: false, + armor_warrior_1: false, + armor_mystery_201402: true, + back_mystery_201402: false, + head_mystery_201402: true, + weapon_armoire_basicCrossbow: false, + }); + }); + }); + + describe('store', () => { + it('buys a Quest scroll', () => { + let user = generateUser(); + + user.stats.gp = 205; + user.ops.buyQuest({ + params: { + key: 'dilatoryDistress1', + }, + }); + expect(user.items.quests).to.eql({ + dilatoryDistress1: 1, + }); + expect(user).toHaveGP(5); + }); + + it('does not buy Quests without enough Gold', () => { + let user = generateUser(); + + user.stats.gp = 1; + user.ops.buyQuest({ + params: { + key: 'dilatoryDistress1', + }, + }); + expect(user.items.quests).to.eql({}); + expect(user).toHaveGP(1); + }); + + it('does not buy nonexistent Quests', () => { + let user = generateUser(); + + user.stats.gp = 9999; + user.ops.buyQuest({ + params: { + key: 'snarfblatter', + }, + }); + expect(user.items.quests).to.eql({}); + expect(user).toHaveGP(9999); + }); + + it('does not buy Gem-premium Quests', () => { + let user = generateUser(); + + user.stats.gp = 9999; + user.ops.buyQuest({ + params: { + key: 'kraken', + }, + }); + expect(user.items.quests).to.eql({}); + expect(user).toHaveGP(9999); + }); + }); + + describe('Gem purchases', () => { + it('does not purchase items without enough Gems', () => { + let user = generateUser(); + + user.items.eggs = {}; + user.items.gear.owned = {}; + + user.ops.purchase({ + params: { + type: 'eggs', + key: 'Cactus', + }, + }); + user.ops.purchase({ + params: { + type: 'gear', + key: 'headAccessory_special_foxEars', + }, + }); + user.ops.unlock({ + query: { + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', + }, + }); + expect(user.items.eggs).to.eql({}); + expect(user.items.gear.owned).to.eql({}); + }); + + it('purchases an egg', () => { + let user = generateUser(); + + user.balance = 1; + user.ops.purchase({ + params: { + type: 'eggs', + key: 'Cactus', + }, + }); + expect(user.items.eggs).to.eql({ + Cactus: 1, + }); + expect(user.balance).to.eql(0.25); + }); + + it('purchases fox ears', () => { + let user = generateUser(); + + user.balance = 1; + user.ops.purchase({ + params: { + type: 'gear', + key: 'headAccessory_special_foxEars', + }, + }); + + expect(user.items.gear.owned.headAccessory_special_foxEars).to.eql(true); + expect(user.balance).to.eql(0.5); + }); + + it('unlocks all the animal ears at once', () => { + let user = generateUser(); + + user.balance = 2; + user.ops.unlock({ + query: { + path: 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars', + }, + }); + + expect(user.items.gear.owned.headAccessory_special_bearEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_cactusEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_foxEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_lionEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_pandaEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_pigEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_tigerEars).to.eql(true); + expect(user.items.gear.owned.headAccessory_special_wolfEars).to.eql(true); + expect(user.balance).to.eql(0.75); + }); + }); + + describe('spells', () => { + _.each(shared.content.spells, (spellClass) => { + _.each(spellClass, (spell) => { + it(`${spell.text} has valid values`, () => { + expect(spell.target).to.match(/^(task|self|party|user)$/); + expect(spell.mana).to.be.an('number'); + if (spell.lvl) { + expect(spell.lvl).to.be.an('number'); + expect(spell.lvl).to.be.above(0); + } + expect(spell.cast).to.be.a('function'); + }); + }); + }); + }); + + describe('drop system', () => { + let user = null; + const MIN_RANGE_FOR_POTION = 0; + const MAX_RANGE_FOR_POTION = 0.3; + const MIN_RANGE_FOR_EGG = 0.4; + const MAX_RANGE_FOR_EGG = 0.6; + const MIN_RANGE_FOR_FOOD = 0.7; + const MAX_RANGE_FOR_FOOD = 1; + + beforeEach(function () { + user = generateUser(); + user.flags.dropsEnabled = true; + this.task_id = shared.uuid(); + return user.ops.addTask({ + body: { + type: 'daily', + id: this.task_id, + }, + }); + }); + + it('drops a hatching potion', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_POTION; random <= MAX_RANGE_FOR_POTION; random += 0.1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up', + }, + }); + expect(user.items.eggs).to.be.empty; + expect(user.items.hatchingPotions).to.not.be.empty; + expect(user.items.food).to.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + + it('drops a pet egg', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_EGG; random <= MAX_RANGE_FOR_EGG; random += 0.1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up', + }, + }); + expect(user.items.eggs).to.not.be.empty; + expect(user.items.hatchingPotions).to.be.empty; + expect(user.items.food).to.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + + it('drops food', function () { + let results = []; + + for (let random = MIN_RANGE_FOR_FOOD; random <= MAX_RANGE_FOR_FOOD; random += 0.1) { + sinon.stub(user.fns, 'predictableRandom').returns(random); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up', + }, + }); + expect(user.items.eggs).to.be.empty; + expect(user.items.hatchingPotions).to.be.empty; + expect(user.items.food).to.not.be.empty; + results.push(user.fns.predictableRandom.restore()); + } + return results; + }); + + it('does not get a drop', function () { + sinon.stub(user.fns, 'predictableRandom').returns(0.5); + user.ops.score({ + params: { + id: this.task_id, + direction: 'up', + }, + }); + expect(user.items.eggs).to.eql({}); + expect(user.items.hatchingPotions).to.eql({}); + expect(user.items.food).to.eql({}); + + user.fns.predictableRandom.restore(); + }); + }); + + describe('Quests', () => { + _.each(shared.content.quests, (quest) => { + it(`${ quest.text() } has valid values`, () => { + expect(quest.notes()).to.be.an('string'); + if (quest.completion) { + expect(quest.completion()).to.be.an('string'); + } + if (quest.previous) { + expect(quest.previous).to.be.an('string'); + } + if (quest.canBuy()) { + expect(quest.value).to.be.greaterThan(0); + } + expect(quest.drop.gp).to.not.be.lessThan(0); + expect(quest.drop.exp).to.not.be.lessThan(0); + expect(quest.category).to.match(/pet|unlockable|gold|world/); + if (quest.drop.items) { + expect(quest.drop.items).to.be.an(Array); + } + if (quest.boss) { + expect(quest.boss.name()).to.be.an('string'); + expect(quest.boss.hp).to.be.greaterThan(0); + expect(quest.boss.str).to.be.greaterThan(0); + } else if (quest.collect) { + _.each(quest.collect, (collect) => { + expect(collect.text()).to.be.an('string'); + expect(collect.count).to.be.greaterThan(0); + }); + } + }); + }); + }); + + describe('Achievements', () => { + _.each(shared.content.classes, (klass) => { + let user = generateUser(); + + user.achievements.ultimateGearSets = {}; + + user.stats.gp = 10000; + _.each(shared.content.gearTypes, (type) => { + _.each([1, 2, 3, 4, 5], (i) => { + return user.ops.buy({ + params: `${type}_${klass}_${i}`, + }); + }); + }); + + it(`does not get ultimateGear ${klass}`, () => { + expect(user.achievements.ultimateGearSets[klass]).to.not.be.ok(); + }); + _.each(shared.content.gearTypes, (type) => { + return user.ops.buy({ + params: `${type}_${klass}_6`, + }); + }); + + xit(`gets ultimateGear ${klass}`, () => { + expect(user.achievements.ultimateGearSets[klass]).to.be.ok(); + }); + }); + + it('does not remove existing Ultimate Gear achievements', () => { + let user = generateUser(); + + user.achievements.ultimateGearSets = { + healer: true, + wizard: true, + rogue: true, + warrior: true, + }; + user.items.gear.owned.shield_warrior_5 = false; + user.items.gear.owned.weapon_rogue_6 = false; + user.ops.buy({ + params: 'shield_warrior_5', + }); + expect(user.achievements.ultimateGearSets).to.eql({ + healer: true, + wizard: true, + rogue: true, + warrior: true, + }); + }); + }); + + describe('unlocking features', () => { + it('unlocks drops at level 3', () => { + let user = generateUser(); + + user.stats.lvl = 3; + user.fns.updateStats(user.stats); + expect(user.flags.dropsEnabled).to.be.ok(); + }); + + it('unlocks Rebirth at level 50', () => { + let user = generateUser(); + + user.stats.lvl = 50; + user.fns.updateStats(user.stats); + expect(user.flags.rebirthEnabled).to.be.ok(); + }); + + describe('level-awarded Quests', () => { + it('gets Attack of the Mundane at level 15', () => { + let user = generateUser(); + + user.stats.lvl = 15; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.atom1).to.be.ok(); + expect(user.items.quests.atom1).to.eql(1); + }); + + it('gets Vice at level 30', () => { + let user = generateUser(); + + user.stats.lvl = 30; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.vice1).to.be.ok(); + expect(user.items.quests.vice1).to.eql(1); + }); + + it('gets Golden Knight at level 40', () => { + let user = generateUser(); + + user.stats.lvl = 40; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.goldenknight1).to.be.ok(); + expect(user.items.quests.goldenknight1).to.eql(1); + }); + + it('gets Moonstone Chain at level 60', () => { + let user = generateUser(); + + user.stats.lvl = 60; + user.fns.updateStats(user.stats); + expect(user.flags.levelDrops.moonstone1).to.be.ok(); + expect(user.items.quests.moonstone1).to.eql(1); + }); + }); + }); +}); + +describe('Simple Scoring', () => { + beforeEach(function () { + let ref = beforeAfter(); + + this.before = ref.before; + this.after = ref.after; + }); + + it('Habits : Up', function () { + this.after.ops.score({ + params: { + id: this.after.habits[0].id, + direction: 'down', + }, + query: { + times: 5, + }, + }); + expectLostPoints(this.before, this.after, 'habit'); + }); + + it('Habits : Down', function () { + this.after.ops.score({ + params: { + id: this.after.habits[0].id, + direction: 'up', + }, + query: { + times: 5, + }, + }); + expectGainedPoints(this.before, this.after, 'habit'); + }); + + it('Dailys : Up', function () { + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'up', + }, + }); + expectGainedPoints(this.before, this.after, 'daily'); + }); + + it('Dailys : Up, Down', function () { + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'up', + }, + }); + this.after.ops.score({ + params: { + id: this.after.dailys[0].id, + direction: 'down', + }, + }); + expectClosePoints(this.before, this.after, 'daily'); + }); + + it('Todos : Up', function () { + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'up', + }, + }); + expectGainedPoints(this.before, this.after, 'todo'); + }); + + it('Todos : Up, Down', function () { + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'up', + }, + }); + this.after.ops.score({ + params: { + id: this.after.todos[0].id, + direction: 'down', + }, + }); + expectClosePoints(this.before, this.after, 'todo'); + }); +}); + +describe('Cron', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('computes shouldCron', () => { + let paths = {}; + + user.fns.cron({ + paths, + }); + expect(user.lastCron).to.not.be.ok; + user.lastCron = Number(moment().subtract(1, 'days')); + paths = {}; + user.fns.cron({ + paths, + }); + expect(user.lastCron).to.be.greaterThan(0); + }); + + it('only dailies & todos are affected', () => { + let ref = beforeAfter({ + daysAgo: 1, + }); + let before = ref.before; + let after = ref.after; + + before.dailys = before.todos = after.dailys = after.todos = []; + after.fns.cron(); + before.stats.mp = after.stats.mp; + expect(after.lastCron).to.not.be(before.lastCron); + delete after.stats.buffs; + delete before.stats.buffs; + expect(before.stats).to.eql(after.stats); + + let beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards); + let afterTasks = after.habits.concat(after.dailys).concat(after.todos).concat(after.rewards); + + expect(beforeTasks).to.eql(afterTasks); + }); + + describe('preening', () => { + beforeEach(function () { + this.clock = sinon.useFakeTimers(Date.parse('2013-11-20'), 'Date'); + }); + afterEach(function () { + return this.clock.restore(); + }); + + it('should preen user history', function () { + let ref = beforeAfter({ + daysAgo: 1, + }); + let after = ref.after; + + let history = [ + { + date: '09/01/2012', + value: 0, + }, { + date: '10/01/2012', + value: 0, + }, { + date: '11/01/2012', + value: 2, + }, { + date: '12/01/2012', + value: 2, + }, { + date: '01/01/2013', + value: 1, + }, { + date: '01/15/2013', + value: 3, + }, { + date: '02/01/2013', + value: 2, + }, { + date: '02/15/2013', + value: 4, + }, { + date: '03/01/2013', + value: 3, + }, { + date: '03/15/2013', + value: 5, + }, { + date: '04/01/2013', + value: 4, + }, { + date: '04/15/2013', + value: 6, + }, { + date: '05/01/2013', + value: 5, + }, { + date: '05/15/2013', + value: 7, + }, { + date: '06/01/2013', + value: 6, + }, { + date: '06/15/2013', + value: 8, + }, { + date: '07/01/2013', + value: 7, + }, { + date: '07/15/2013', + value: 9, + }, { + date: '08/01/2013', + value: 8, + }, { + date: '08/15/2013', + value: 10, + }, { + date: '09/01/2013', + value: 9, + }, { + date: '09/15/2013', + value: 11, + }, { + date: '010/01/2013', + value: 10, + }, { + date: '010/15/2013', + value: 12, + }, { + date: '011/01/2013', + value: 12, + }, { + date: '011/02/2013', + value: 13, + }, { + date: '011/03/2013', + value: 14, + }, { + date: '011/04/2013', + value: 15, + }, + ]; + + after.history = { + exp: _.cloneDeep(history), + todos: _.cloneDeep(history), + }; + after.habits[0].history = _.cloneDeep(history); + after.fns.cron(); + after.history.exp.pop(); + after.history.todos.pop(); + _.each([after.history.exp, after.history.todos, after.habits[0].history], function (arr) { + expect(_.map(arr, (x) => { + return x.value; + })).to.eql([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]); + }); + }); + }); + + describe('Todos', () => { + it('1 day missed', () => { + let ref = beforeAfter({ + daysAgo: 1, + }); + let before = ref.before; + let after = ref.after; + + before.dailys = after.dailys = []; + after.fns.cron(); + expect(after).toHaveHP(50); + expect(after).toHaveExp(0); + expect(after).toHaveGP(0); + expect(before.todos[0].value).to.be(0); + expect(after.todos[0].value).to.be(-1); + expect(after.history.todos).to.have.length(1); + }); + + it('2 days missed', () => { + let ref = beforeAfter({ + daysAgo: 2, + }); + let before = ref.before; + let after = ref.after; + + before.dailys = after.dailys = []; + after.fns.cron(); + expect(before.todos[0].value).to.be(0); + expect(after.todos[0].value).to.be(-1); + }); + }); + + describe('cron day calculations', () => { + let dayStart = 4; + let fstr = 'YYYY-MM-DD HH: mm: ss'; + + it('startOfDay before dayStart', () => { + let start = startOfDay({ + now: moment('2014-10-09 02: 30: 00'), + dayStart, + }); + + expect(start.format(fstr)).to.eql('2014-10-08 04: 00: 00'); + }); + + it('startOfDay after dayStart', () => { + let start = startOfDay({ + now: moment('2014-10-09 05: 30: 00'), + dayStart, + }); + + expect(start.format(fstr)).to.eql('2014-10-09 04: 00: 00'); + }); + + it('daysSince cron before, now after', () => { + let lastCron = moment('2014-10-09 02: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 11: 30: 00'), + dayStart, + }); + + expect(days).to.eql(1); + }); + + it('daysSince cron before, now before', () => { + let lastCron = moment('2014-10-09 02: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 03: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + + it('daysSince cron after, now after', () => { + let lastCron = moment('2014-10-09 05: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 06: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + + it('daysSince cron after, now tomorrow before', () => { + let lastCron = moment('2014-10-09 12: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-10 01: 30: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + + it('daysSince cron after, now tomorrow after', () => { + let lastCron = moment('2014-10-09 12: 30: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-10 10: 30: 00'), + dayStart, + }); + + expect(days).to.eql(1); + }); + xit('daysSince, last cron before new dayStart', () => { + let lastCron = moment('2014-10-09 01: 00: 00'); + let days = daysSince(lastCron, { + now: moment('2014-10-09 05: 00: 00'), + dayStart, + }); + + expect(days).to.eql(0); + }); + }); + + describe('dailies', () => { + describe('new day', () => { + /* + This section runs through a 'cron matrix' of all permutations (that I can easily account for). It sets + task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron, + and so on - testing each possible outcome along the way + */ + + function runCron (options) { + _.each([480, 240, 0, -120], function (timezoneOffset) { + let now = startOfWeek({ + timezoneOffset, + }).add(options.currentHour || 0, 'hours'); + + let ref = beforeAfter({ + now, + timezoneOffset, + daysAgo: 1, + cronAfterStart: options.cronAfterStart || true, + dayStart: options.dayStart || 0, + limitOne: 'daily', + }); + + let before = ref.before; + let after = ref.after; + + if (options.repeat) { + before.dailys[0].repeat = after.dailys[0].repeat = options.repeat; + } + before.dailys[0].streak = after.dailys[0].streak = 10; + if (options.checked) { + before.dailys[0].completed = after.dailys[0].completed = true; + } + before.dailys[0].startDate = after.dailys[0].startDate = moment().subtract(30, 'days'); + if (options.shouldDo) { + expect(shared.shouldDo(now.toDate(), after.dailys[0], { + timezoneOffset, + dayStart: options.dayStart, + now, + })).to.be.ok(); + } + after.fns.cron({ + now, + }); + before.stats.mp = after.stats.mp; + + if (options.expect === 'losePoints') { + expectLostPoints(before, after, 'daily'); + } else if (options.expect === 'noChange') { + expectNoChange(before, after); + } else if (options.expect === 'noDamage') { + expectDayResetNoDamage(before, after); + } + + return { + before, + after, + }; + }); + } + + let cronMatrix = { + steps: { + 'due yesterday': { + defaults: { + daysAgo: 1, + cronAfterStart: true, + limitOne: 'daily', + }, + steps: { + '(simple)': { + expect: 'losePoints', + }, + 'due today': { + defaults: { + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + }, + }, + steps: { + 'pre-dayStart': { + defaults: { + currentHour: 3, + dayStart: 4, + shouldDo: true, + }, + steps: { + checked: { + checked: true, + expect: 'noChange', + }, + 'un-checked': { + checked: false, + expect: 'noChange', + }, + }, + }, + 'post-dayStart': { + defaults: { + currentHour: 5, + dayStart: 4, + shouldDo: true, + }, + steps: { + checked: { + checked: true, + expect: 'noDamage', + }, + unchecked: { + checked: false, + expect: 'losePoints', + }, + }, + }, + }, + }, + 'NOT due today': { + defaults: { + repeat: { + su: true, + m: false, + t: true, + w: true, + th: true, + f: true, + s: true, + }, + }, + steps: { + 'pre-dayStart': { + defaults: { + currentHour: 3, + dayStart: 4, + shouldDo: true, + }, + steps: { + checked: { + checked: true, + expect: 'noChange', + }, + 'un-checked': { + checked: false, + expect: 'noChange', + }, + }, + }, + 'post-dayStart': { + defaults: { + currentHour: 5, + dayStart: 4, + shouldDo: false, + }, + steps: { + checked: { + checked: true, + expect: 'noDamage', + }, + unchecked: { + checked: false, + expect: 'losePoints', + }, + }, + }, + }, + }, + }, + }, + 'not due yesterday': { + defaults: repeatWithoutLastWeekday(), + steps: { + '(simple)': { + expect: 'noDamage', + }, + 'post-dayStart': { + currentHour: 5, + dayStart: 4, + expect: 'noDamage', + }, + 'pre-dayStart': { + currentHour: 3, + dayStart: 4, + expect: 'noChange', + }, + }, + }, + }, + }; + + let recurseCronMatrix = (obj, options = {}) => { + if (obj.steps) { + _.each(obj.steps, (step, text) => { + let o = _.cloneDeep(options); + + if (!o.text) { + o.text = ''; + } + o.text += `${text}`; + return recurseCronMatrix(step, _.defaults(o, obj.defaults)); + }); + } else { + it(`${options.text}`, () => { + return runCron(_.defaults(obj, options)); + }); + } + }; + + return recurseCronMatrix(cronMatrix); + }); + }); +}); + +describe('Helper', () => { + it('calculates gold coins', () => { + expect(shared.gold(10)).to.eql(10); + expect(shared.gold(1.957)).to.eql(1); + expect(shared.gold()).to.eql(0); + }); + + it('calculates silver coins', () => { + expect(shared.silver(10)).to.eql(0); + expect(shared.silver(1.957)).to.eql(95); + expect(shared.silver(0.01)).to.eql('01'); + expect(shared.silver()).to.eql('00'); + }); + + it('calculates experience to next level', () => { + expect(shared.tnl(1)).to.eql(150); + expect(shared.tnl(2)).to.eql(160); + expect(shared.tnl(10)).to.eql(260); + expect(shared.tnl(99)).to.eql(3580); + }); + + it('calculates the start of the day', () => { + let fstr = 'YYYY-MM-DD HH: mm: ss'; + let today = '2013-01-01 00: 00: 00'; + let zone = moment(today).zone(); + + expect(startOfDay({ + now: new Date(2013, 0, 1, 0), + }, { + timezoneOffset: zone, + }).format(fstr)).to.eql(today); + expect(startOfDay({ + now: new Date(2013, 0, 1, 5), + }, { + timezoneOffset: zone, + }).format(fstr)).to.eql(today); + expect(startOfDay({ + now: new Date(2013, 0, 1, 23, 59, 59), + timezoneOffset: zone, + }).format(fstr)).to.eql(today); + }); +}); diff --git a/test/common/dailies.js b/test/common/dailies.js new file mode 100644 index 0000000000..d3aa84cae8 --- /dev/null +++ b/test/common/dailies.js @@ -0,0 +1,499 @@ +/* eslint-disable camelcase */ +import { + startOfWeek, +} from '../../common/script/cron'; + +let expect = require('expect.js'); // eslint-disable-line no-shadow +let moment = require('moment'); +let shared = require('../../common/script/index.js'); + +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; + +let repeatWithoutLastWeekday = () => { // eslint-disable-line no-unused-vars + let repeat = { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + }; + + if (startOfWeek(moment().zone(0)).isoWeekday() === 1) { + repeat.su = false; + } else { + repeat.s = false; + } + return { + repeat, + }; +}; + + +/* Helper Functions */ + +import { + generateUser, +} from '../helpers/common.helper'; + +let cron = (usr, missedDays = 1) => { + usr.lastCron = moment().subtract(missedDays, 'days'); + usr.fns.cron(); +}; + +describe('daily/weekly that repeats everyday (default)', () => { + let user = null; + let daily = null; + let weekly = null; + + describe('when startDate is in the future', () => { + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().add(7, 'days'), + frequency: 'daily', + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().add(7, 'days'), + frequency: 'weekly', + repeat: { + su: true, + m: true, + t: true, + w: true, + th: true, + f: true, + s: true, + }, + }), + ]; + daily = user.dailys[0]; + weekly = user.dailys[1]; + }); + + it('does not damage user for not completing it', () => { + cron(user); + expect(user.stats.hp).to.be(50); + }); + + it('does not change value on cron if daily is incomplete', () => { + cron(user); + expect(daily.value).to.be(0); + expect(weekly.value).to.be(0); + }); + + it('does not reset checklists if daily is not marked as complete', () => { + let checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + expect(daily.checklist[0].completed).to.be(true); + expect(daily.checklist[1].completed).to.be(true); + expect(daily.checklist[2].completed).to.be(false); + expect(weekly.checklist[0].completed).to.be(true); + expect(weekly.checklist[1].completed).to.be(true); + expect(weekly.checklist[2].completed).to.be(false); + }); + + it('resets checklists if daily is marked as complete', () => { + let checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + + daily.checklist = checklist; + weekly.checklist = checklist; + daily.completed = true; + weekly.completed = true; + cron(user); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('is due on startDate', () => { + let daily_due_today = shared.shouldDo(moment(), daily); + let daily_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), daily); + + expect(daily_due_today).to.be(false); + expect(daily_due_on_start_date).to.be(true); + + let weekly_due_today = shared.shouldDo(moment(), weekly); + let weekly_due_on_start_date = shared.shouldDo(moment().add(7, 'days'), weekly); + + expect(weekly_due_today).to.be(false); + expect(weekly_due_on_start_date).to.be(true); + }); + }); + + describe('when startDate is in the past', () => { + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'daily', + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(7, 'days'), + frequency: 'weekly', + }), + ]; + daily = user.dailys[0]; + weekly = user.dailys[1]; + }); + + it('does damage user for not completing it', () => { + cron(user); + expect(user.stats.hp).to.be.lessThan(50); + }); + + it('decreases value on cron if daily is incomplete', () => { + cron(user, 1); + expect(daily.value).to.be(-1); + expect(weekly.value).to.be(-1); + }); + + it('decreases value on cron once only if daily is incomplete and multiple days are missed', () => { + cron(user, 7); + expect(daily.value).to.be(-1); + expect(weekly.value).to.be(-1); + }); + + it('resets checklists if daily is not marked as complete', () => { + let checklist; + + checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('resets checklists if daily is marked as complete', () => { + let checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + }); + + describe('when startDate is today', () => { + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'daily', + }), shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(1, 'days'), + frequency: 'weekly', + }), + ]; + daily = user.dailys[0]; + weekly = user.dailys[1]; + }); + + it('does damage user for not completing it', () => { + cron(user); + expect(user.stats.hp).to.be.lessThan(50); + }); + + it('decreases value on cron if daily is incomplete', () => { + cron(user); + expect(daily.value).to.be.lessThan(0); + expect(weekly.value).to.be.lessThan(0); + }); + + it('resets checklists if daily is not marked as complete', () => { + let checklist; + + checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + daily.checklist = checklist; + weekly.checklist = checklist; + cron(user); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('resets checklists if daily is marked as complete', () => { + let checklist; + + checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, { + text: '2', + id: 'checklist-two', + completed: true, + }, { + text: '3', + id: 'checklist-three', + completed: false, + }, + ]; + daily.checklist = checklist; + daily.completed = true; + weekly.checklist = checklist; + weekly.completed = true; + cron(user); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + _.each(weekly.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + }); +}); + +describe('daily that repeats every x days', () => { + let user = null; + let daily = null; + + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment(), + frequency: 'daily', + }), + ]; + daily = user.dailys[0]; + }); + _.times(11, (due) => { + it(`where x equals ${due}`, () => { + daily.everyX = due; + _.times(30, (day) => { + let isDue; + + isDue = shared.shouldDo(moment().add(day, 'days'), daily); + if (day % due === 0) { + expect(isDue).to.be(true); + } + if (day % due !== 0) { + expect(isDue).to.be(false); + } + }); + }); + }); +}); + +describe('daily that repeats every X days when multiple days are missed', () => { + let everyX = 3; + let startDateDaysAgo = everyX * 3; + let user = null; + let daily = null; + + describe('including missing a due date', () => { + let missedDays = everyX * 2 + 1; + + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX, + }), + ]; + daily = user.dailys[0]; + }); + + it('decreases value on cron once only if daily is incomplete', () => { + cron(user, missedDays); + expect(daily.value).to.be(-1); + }); + + it('resets checklists if daily is incomplete', () => { + let checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, + ]; + + daily.checklist = checklist; + cron(user, missedDays); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + + it('resets checklists if daily is marked as complete', () => { + let checklist; + + checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, + ]; + daily.checklist = checklist; + daily.completed = true; + cron(user, missedDays); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + }); + + describe('but not missing a due date', () => { + let missedDays; + + missedDays = everyX - 1; + beforeEach(() => { + user = generateUser(); + user.dailys = [ + shared.taskDefaults({ + type: 'daily', + startDate: moment().subtract(startDateDaysAgo, 'days'), + frequency: 'daily', + everyX, + }), + ]; + daily = user.dailys[0]; + }); + + it('does not decrease value on cron', () => { + cron(user, missedDays); + expect(daily.value).to.be(0); + }); + + it('does not reset checklists if daily is incomplete', () => { + let checklist; + + checklist = [ + { + text: '1', + id: 'checklist-one', + completed: true, + }, + ]; + daily.checklist = checklist; + cron(user, missedDays); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(true); + }); + }); + + it('resets checklists if daily is marked as complete', () => { + let checklist; + + checklist = [ + { + text: 1, + id: 'checklist-one', + completed: true, + }, + ]; + daily.checklist = checklist; + daily.completed = true; + cron(user, missedDays); + _.each(daily.checklist, (box) => { + expect(box.completed).to.be(false); + }); + }); + }); +}); diff --git a/test/common/fns/autoAllocate.test.js b/test/common/fns/autoAllocate.test.js deleted file mode 100644 index e00f8dd17d..0000000000 --- a/test/common/fns/autoAllocate.test.js +++ /dev/null @@ -1,86 +0,0 @@ -import autoAllocate from '../../../common/script/fns/autoAllocate'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.fns.autoAllocate', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('user.preferences.allocationMode === flat', () => { - user.stats.con = 5; - user.stats.int = 5; - user.stats.per = 3; - user.stats.str = 8; - - user.preferences.allocationMode = 'flat'; - - autoAllocate(user); - - expect(user.stats.con).to.equal(5); - expect(user.stats.int).to.equal(5); - expect(user.stats.per).to.equal(4); - expect(user.stats.str).to.equal(8); - }); - - it('user.preferences.allocationMode === taskbased', () => { - user.stats.con = 5; - user.stats.int = 5; - user.stats.per = 3; - user.stats.str = 8; - user.stats.training.con = 2; - user.stats.training.int = 5; - user.stats.training.per = 7; - user.stats.training.str = 4; - - user.preferences.allocationMode = 'taskbased'; - - autoAllocate(user); - - expect(user.stats.con).to.equal(5); - expect(user.stats.int).to.equal(5); - expect(user.stats.per).to.equal(4); - expect(user.stats.str).to.equal(8); - - expect(user.stats.training.con).to.equal(0); - expect(user.stats.training.int).to.equal(0); - expect(user.stats.training.per).to.equal(0); - expect(user.stats.training.str).to.equal(0); - }); - - it('user.preferences.allocationMode === classbased', () => { - user.stats.lvl = 35; - user.stats.class = 'healer'; - user.stats.con = 5; - user.stats.int = 5; - user.stats.per = 3; - user.stats.str = 8; - - user.preferences.allocationMode = 'classbased'; - - autoAllocate(user); - - expect(user.stats.con).to.equal(6); - expect(user.stats.int).to.equal(5); - expect(user.stats.per).to.equal(3); - expect(user.stats.str).to.equal(8); - }); - - it('user.preferences.allocationMode === anything', () => { - user.stats.con = 5; - user.stats.int = 5; - user.stats.per = 3; - user.stats.str = 8; - user.preferences.allocationMode = 'wrong'; - - autoAllocate(user); - - expect(user.stats.con).to.equal(5); - expect(user.stats.int).to.equal(5); - expect(user.stats.per).to.equal(3); - expect(user.stats.str).to.equal(9); - }); -}); diff --git a/test/common/fns/crit.test.js b/test/common/fns/crit.test.js deleted file mode 100644 index 4f43c55fa1..0000000000 --- a/test/common/fns/crit.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import crit from '../../../common/script/fns/crit'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('crit', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('computes', () => { - let result = crit(user); - expect(result).to.eql(1); - }); -}); diff --git a/test/common/fns/handleTwoHanded.js b/test/common/fns/handleTwoHanded.js deleted file mode 100644 index 8d191ff114..0000000000 --- a/test/common/fns/handleTwoHanded.js +++ /dev/null @@ -1,38 +0,0 @@ -import handleTwoHanded from '../../../common/script/fns/handleTwoHanded'; -import content from '../../../common/script/content/index'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.fns.handleTwoHanded', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('uses "messageTwoHandedUnequip" message if item is a shield and current weapon is two handed (and sets the user\'s weapon to the base one)', () => { - let item = content.gear.tree.shield.warrior['2']; - let currentWeapon = content.gear.tree.weapon.armoire.rancherLasso; - user.items.gear.equipped.weapon = 'weapon_armoire_rancherLasso'; - - let message = handleTwoHanded(user, item); - expect(message).to.equal(i18n.t('messageTwoHandedUnequip', { - twoHandedText: currentWeapon.text(), offHandedText: item.text(), - })); - expect(user.items.gear.equipped.weapon).to.equal('weapon_base_0'); - }); - - it('uses "messageTwoHandedEquip" message if item is two handed and currentShield exists but is not "shield_base_0" (and sets the user\'s shield to the base one)', () => { - let item = content.gear.tree.weapon.armoire.rancherLasso; - let currentShield = content.gear.tree.shield.armoire.gladiatorShield; - user.items.gear.equipped.shield = 'shield_armoire_gladiatorShield'; - - let message = handleTwoHanded(user, item); - expect(message).to.equal(i18n.t('messageTwoHandedEquip', { - twoHandedText: item.text(), offHandedText: currentShield.text(), - })); - expect(user.items.gear.equipped.shield).to.equal('shield_base_0'); - }); -}); diff --git a/test/common/fns/predictableRandom.test.js b/test/common/fns/predictableRandom.test.js deleted file mode 100644 index 1cd47fc426..0000000000 --- a/test/common/fns/predictableRandom.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import predictableRandom from '../../../common/script/fns/predictableRandom'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.fns.predictableRandom', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('returns a number', () => { - expect(predictableRandom(user)).to.be.a('number'); - }); - - it('returns the same value when user.stats is the same and no seed is passed', () => { - user.stats.hp = 43; - user.stats.gp = 34; - - let val1 = predictableRandom(user); - let val2 = predictableRandom(user); - - expect(val2).to.equal(val1); - }); - - it('returns a different value when user.stats is not the same and no seed is passed', () => { - user.stats.hp = 43; - user.stats.gp = 34; - let val1 = predictableRandom(user); - - user.stats.gp = 35; - let val2 = predictableRandom(user); - - expect(val2).to.not.equal(val1); - }); - - it('returns the same value when the same seed is passed', () => { - let val1 = predictableRandom(user, 4452673762); - let val2 = predictableRandom(user, 4452673762); - - expect(val2).to.equal(val1); - }); - - it('returns a different value when a different seed is passed', () => { - let val1 = predictableRandom(user, 4452673761); - let val2 = predictableRandom(user, 4452673762); - - expect(val2).to.not.equal(val1); - }); -}); diff --git a/test/common/fns/randomDrop.test.js b/test/common/fns/randomDrop.test.js deleted file mode 100644 index 9569067ffc..0000000000 --- a/test/common/fns/randomDrop.test.js +++ /dev/null @@ -1,165 +0,0 @@ -// TODO disable until we can find a way to stub predictableRandom - -/* eslint-disable */ - -import randomDrop from '../../../common/script/fns/randomDrop'; -import { - generateUser, - generateTodo, - generateHabit, - generateDaily, - generateReward, -} from '../../helpers/common.helper'; -// import predictableRandom from '../../../common/script/fns/predictableRandom'; // eslint-disable -import content from '../../../common/script/content/index'; - -xdescribe('common.fns.randomDrop', () => { - let user; - let task; - let predictableRandom; - - beforeEach(() => { - user = generateUser(); - user._tmp = user._tmp ? user._tmp : {}; - task = generateTodo({ userId: user._id }); - predictableRandom = () => { - return 0.5; - }; - }); - - /** - * function signature as follows: - * randomDrop(user, modifiers) {} - * modifiers = { task, delta = null } - **/ - - it('drops an item for the user.party.quest.progress', () => { - expect(user.party.quest.progress.collect).to.eql({}); - user.party.quest.key = 'vice2'; - let collectWhat = Object.keys(content.quests[user.party.quest.key].collect)[0]; // lightCrystal - predictableRandom = () => { - return 0.0001; - }; - randomDrop(user, { task }); - expect(user.party.quest.progress.collect[collectWhat]).to.eql(1); - randomDrop(user, { task }); - expect(user.party.quest.progress.collect[collectWhat]).to.eql(2); - }); - - context('drops enabled', () => { - beforeEach(() => { - user.flags.dropsEnabled = true; - task.priority = 100000; - }); - - it('does nothing if user.items.lastDrop.count is exceeded', () => { - user.items.lastDrop.count = 100; - randomDrop(user, { task }); - expect(user._tmp).to.eql({}); - }); - - it('drops something when the task is a todo', () => { - expect(user._tmp).to.eql({}); - user.flags.dropsEnabled = true; - predictableRandom = () => { - return 0.1; - }; - randomDrop(user, { task }); - expect(user._tmp).to.not.eql({}); - }); - - it('drops something when the task is a habit', () => { - task = generateHabit({ userId: user._id }); - expect(user._tmp).to.eql({}); - user.flags.dropsEnabled = true; - predictableRandom = () => { - return 0.1; - }; - randomDrop(user, { task }); - expect(user._tmp).to.not.eql({}); - }); - - it('drops something when the task is a daily', () => { - task = generateDaily({ userId: user._id }); - expect(user._tmp).to.eql({}); - user.flags.dropsEnabled = true; - predictableRandom = () => { - return 0.1; - }; - randomDrop(user, { task }); - expect(user._tmp).to.not.eql({}); - }); - - it('drops something when the task is a reward', () => { - task = generateReward({ userId: user._id }); - expect(user._tmp).to.eql({}); - user.flags.dropsEnabled = true; - predictableRandom = () => { - return 0.1; - }; - randomDrop(user, { task }); - expect(user._tmp).to.not.eql({}); - }); - - it('drops food', () => { - predictableRandom = () => { - return 0.65; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('Food'); - }); - - it('drops eggs', () => { - predictableRandom = () => { - return 0.35; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('Egg'); - }); - - context('drops hatching potion', () => { - it('drops a very rare potion', () => { - predictableRandom = () => { - return 0.01; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('HatchingPotion'); - expect(user._tmp.drop.value).to.eql(5); - expect(user._tmp.drop.key).to.eql('Golden'); - }); - - it('drops a rare potion', () => { - predictableRandom = () => { - return 0.08; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('HatchingPotion'); - expect(user._tmp.drop.value).to.eql(4); - let acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']; - expect(acceptableDrops).to.contain(user._tmp.drop.key); // deterministically 'CottonCandyBlue' - }); - - it('drops an uncommon potion', () => { - predictableRandom = () => { - return 0.17; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('HatchingPotion'); - expect(user._tmp.drop.value).to.eql(3); - let acceptableDrops = ['Red', 'Shade', 'Skeleton']; - expect(acceptableDrops).to.contain(user._tmp.drop.key); // always skeleton - }); - - it('drops a common potion', () => { - predictableRandom = () => { - return 0.20; - }; - randomDrop(user, { task }); - expect(user._tmp.drop.type).to.eql('HatchingPotion'); - expect(user._tmp.drop.value).to.eql(2); - let acceptableDrops = ['Base', 'White', 'Desert']; - expect(acceptableDrops).to.contain(user._tmp.drop.key); // always Desert - }); - }); - }); -}); diff --git a/test/common/fns/randomVal.js b/test/common/fns/randomVal.js deleted file mode 100644 index b4b8e377d3..0000000000 --- a/test/common/fns/randomVal.js +++ /dev/null @@ -1,119 +0,0 @@ -import randomVal from '../../../common/script/fns/randomVal'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.fns.randomVal', () => { - let user; - let obj = { - a: 1, - b: 2, - c: 3, - d: 4, - }; - - beforeEach(() => { - user = generateUser(); - }); - - describe('returns a random property value from an object', () => { - it('returns the same value when the seed is the same', () => { - let val1 = randomVal(user, obj, { - seed: 222, - }); - - let val2 = randomVal(user, obj, { - seed: 222, - }); - - expect(val2).to.equal(val1); - }); - - it('returns the same value when user.stats is the same', () => { - user.stats.gp = 34; - let val1 = randomVal(user, obj); - let val2 = randomVal(user, obj); - - expect(val2).to.equal(val1); - }); - - it('returns a different value when the seed is different', () => { - let val1 = randomVal(user, obj, { - seed: 222, - }); - - let val2 = randomVal(user, obj, { - seed: 333, - }); - - expect(val2).to.not.equal(val1); - }); - - it('returns a different value when user.stats is different', () => { - user.stats.gp = 34; - let val1 = randomVal(user, obj); - user.stats.gp = 343; - let val2 = randomVal(user, obj); - - expect(val2).to.not.equal(val1); - }); - }); - - describe('returns a random key from an object', () => { - it('returns the same key when the seed is the same', () => { - let key1 = randomVal(user, obj, { - key: true, - seed: 222, - }); - - let key2 = randomVal(user, obj, { - key: true, - seed: 222, - }); - - expect(key2).to.equal(key1); - }); - - it('returns the same key when user.stats is the same', () => { - user.stats.gp = 45; - let key1 = randomVal(user, obj, { - key: true, - }); - - let key2 = randomVal(user, obj, { - key: true, - }); - - expect(key2).to.equal(key1); - }); - - it('returns a different key when the seed is different', () => { - let key1 = randomVal(user, obj, { - key: true, - seed: 222, - }); - - let key2 = randomVal(user, obj, { - key: true, - seed: 333, - }); - - expect(key2).to.not.equal(key1); - }); - - it('returns a different key when user.stats is different', () => { - user.stats.gp = 45; - let key1 = randomVal(user, obj, { - key: true, - }); - - user.stats.gp = 43; - - let key2 = randomVal(user, obj, { - key: true, - }); - - expect(key2).to.not.equal(key1); - }); - }); -}); diff --git a/test/common/fns/statsComputed.test.js b/test/common/fns/statsComputed.test.js deleted file mode 100644 index 07b009368d..0000000000 --- a/test/common/fns/statsComputed.test.js +++ /dev/null @@ -1,28 +0,0 @@ -import statsComputed from '../../../common/script/libs/statsComputed'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('common.fns.statsComputed', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('returns the same result if called directly, through user.fns.statsComputed, or user._statsComputed', () => { - let result = statsComputed(user); - let result2 = user._statsComputed; - let result3 = user.fns.statsComputed(); - expect(result).to.eql(result2); - expect(result).to.eql(result3); - }); - - it('returns default values', () => { - let result = statsComputed(user); - expect(result.per).to.eql(0); - expect(result.con).to.eql(0); - expect(result.str).to.eql(0); - expect(result.maxMP).to.eql(30); - }); -}); diff --git a/test/common/fns/ultimateGear.js b/test/common/fns/ultimateGear.js deleted file mode 100644 index 8da991b565..0000000000 --- a/test/common/fns/ultimateGear.js +++ /dev/null @@ -1,33 +0,0 @@ -import ultimateGear from '../../../common/script/fns/ultimateGear'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.fns.ultimateGear', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('sets armoirEnabled when partial achievement already achieved', () => { - let items = { - gear: { - owned: { - toObject: () => { - return { - armor_warrior_5: true, // eslint-disable-line camelcase - shield_warrior_5: true, // eslint-disable-line camelcase - head_warrior_5: true, // eslint-disable-line camelcase - weapon_warrior_6: true, // eslint-disable-line camelcase - }; - }, - }, - }, - }; - - user.items = items; - ultimateGear(user); - expect(user.flags.armoireEnabled).to.equal(true); - }); -}); diff --git a/test/common/fns/updateStats.test.js b/test/common/fns/updateStats.test.js deleted file mode 100644 index cea5f6e6ca..0000000000 --- a/test/common/fns/updateStats.test.js +++ /dev/null @@ -1,170 +0,0 @@ -import updateStats from '../../../common/script/fns/updateStats'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('common.fns.updateStats', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - context('No Hp', () => { - it('updates user\s hp', () => { - let stats = { hp: 0 }; - expect(user.stats.hp).to.not.eql(0); - updateStats(user, stats); - expect(user.stats.hp).to.eql(0); - updateStats(user, { hp: 2 }); - expect(user.stats.hp).to.eql(2); - }); - - it('does not lower hp below 0', () => { - let stats = { - hp: -5, - }; - updateStats(user, stats); - expect(user.stats.hp).to.eql(0); - }); - }); - - context('Stat Allocation', () => { - it('adds only attribute points up to user\'s level', () => { - let stats = { - exp: 261, - }; - expect(user.stats.points).to.eql(0); - - user.stats.lvl = 10; - - updateStats(user, stats); - - expect(user.stats.points).to.eql(11); - }); - - it('adds an attibute point when user\'s stat points are less than max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 24; - - updateStats(user, stats); - - expect(user.stats.points).to.eql(1); - }); - - it('does not add an attibute point when user\'s stat points are equal to max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 25; - - updateStats(user, stats); - - expect(user.stats.points).to.eql(0); - }); - - it('does not add an attibute point when user\'s stat points + unallocated points are equal to max level', () => { - let stats = { - exp: 3581, - }; - - user.stats.lvl = 99; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 15; - user.stats.points = 10; - - updateStats(user, stats); - - expect(user.stats.points).to.eql(10); - }); - - it('only awards stat points up to level 100 if user is missing unallocated stat points and is over level 100', () => { - let stats = { - exp: 5581, - }; - - user.stats.lvl = 104; - user.stats.str = 25; - user.stats.int = 25; - user.stats.con = 25; - user.stats.per = 15; - user.stats.points = 0; - - updateStats(user, stats); - - expect(user.stats.points).to.eql(10); - }); - - context('assigns flags.levelDrops', () => { - it('for atom1', () => { - user.stats.lvl = 16; - user.flags.levelDrops.atom1 = false; - expect(user.items.quests.atom1).to.eql(undefined); - updateStats(user, { atom1: true }); - expect(user.items.quests.atom1).to.eql(1); - expect(user.flags.levelDrops.atom1).to.eql(true); - updateStats(user, { atom1: true }); - expect(user.items.quests.atom1).to.eql(1); // no change - }); - it('for vice1', () => { - user.stats.lvl = 31; - user.flags.levelDrops.vice1 = false; - expect(user.items.quests.vice1).to.eql(undefined); - updateStats(user, { vice1: true }); - expect(user.items.quests.vice1).to.eql(1); - expect(user.flags.levelDrops.vice1).to.eql(true); - updateStats(user, { vice1: true }); - expect(user.items.quests.vice1).to.eql(1); - }); - it('moonstone', () => { - user.stats.lvl = 60; - user.flags.levelDrops.moonstone1 = false; - expect(user.items.quests.moonstone1).to.eql(undefined); - updateStats(user, { moonstone1: true }); - expect(user.flags.levelDrops.moonstone1).to.eql(true); - expect(user.items.quests.moonstone1).to.eql(1); - updateStats(user, { moonstone1: true }); - expect(user.items.quests.moonstone1).to.eql(1); - }); - it('for goldenknight1', () => { - user.stats.lvl = 40; - user.flags.levelDrops.goldenknight1 = false; - expect(user.items.quests.goldenknight1).to.eql(undefined); - updateStats(user, { goldenknight1: true }); - expect(user.items.quests.goldenknight1).to.eql(1); - expect(user.flags.levelDrops.goldenknight1).to.eql(true); - updateStats(user, { goldenknight1: true }); - expect(user.items.quests.goldenknight1).to.eql(1); - }); - }); - - // @TODO: Set up sinon sandbox - xit('auto allocates stats if automaticAllocation is turned on', () => { - sandbox.stub(user.fns, 'autoAllocate'); - - let stats = { - exp: 261, - }; - - user.stats.lvl = 10; - - user.fns.updateStats(stats); - - expect(user.fns.autoAllocate).to.be.calledOnce; - }); - }); -}); diff --git a/test/common/libs/appliedTags.test.js b/test/common/libs/appliedTags.test.js deleted file mode 100644 index 66f3de4758..0000000000 --- a/test/common/libs/appliedTags.test.js +++ /dev/null @@ -1,10 +0,0 @@ -import appliedTags from '../../../common/script/libs/appliedTags'; - -describe('appliedTags', () => { - it('returns the tasks', () => { - let userTags = [{ id: 'tag1', name: 'tag 1' }, { id: 'tag2', name: 'tag 2' }, { id: 'tag3', name: 'tag 3' }]; - let taskTags = ['tag2', 'tag3']; - let result = appliedTags(userTags, taskTags); - expect(result).to.eql('tag 2, tag 3'); - }); -}); diff --git a/test/common/libs/gold.test.js b/test/common/libs/gold.test.js deleted file mode 100644 index 2cdfc3ef65..0000000000 --- a/test/common/libs/gold.test.js +++ /dev/null @@ -1,11 +0,0 @@ -import gold from '../../../common/script/libs/gold'; - -describe('gold', () => { - it('is 0', () => { - expect(gold()).to.eql('0'); - }); - - it('is 5 in 5.2 of gold', () => { - expect(gold(5.2)).to.eql(5); - }); -}); diff --git a/test/common/libs/noTags.test.js b/test/common/libs/noTags.test.js deleted file mode 100644 index dcd2481854..0000000000 --- a/test/common/libs/noTags.test.js +++ /dev/null @@ -1,13 +0,0 @@ -import noTags from '../../../common/script/libs/noTags'; - -describe('noTags', () => { - it('returns true for no tags', () => { - let result = noTags([]); - expect(result).to.eql(true); - }); - - it('returns false for some tags', () => { - let result = noTags(['a', 'b', 'c']); - expect(result).to.eql(false); - }); -}); diff --git a/test/common/libs/percent.test.js b/test/common/libs/percent.test.js deleted file mode 100644 index 9d1ba31024..0000000000 --- a/test/common/libs/percent.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import percent from '../../../common/script/libs/percent'; - -describe('percent', () => { - it('with direction "up"', () => { - expect(percent(1, 10, 'up')).to.eql(10); - expect(percent(1, 20, 'up')).to.eql(5); - expect(percent(1.22, 10.99, 'up')).to.eql(12); - }); - - it('with direction "down"', () => { - expect(percent(1, 10, 'down')).to.eql(10); - expect(percent(1, 20, 'down')).to.eql(5); - expect(percent(1.22, 10.99, 'down')).to.eql(11); - }); - - it('with no direction', () => { - expect(percent(1.22, 10.99)).to.eql(11); - }); -}); diff --git a/test/common/libs/pickDeep.js b/test/common/libs/pickDeep.js deleted file mode 100644 index 4a8741269d..0000000000 --- a/test/common/libs/pickDeep.js +++ /dev/null @@ -1,34 +0,0 @@ -import pickDeep from '../../../common/script/libs/pickDeep'; - -describe('pickDeep', () => { - it('throws an error if "properties" is not an array', () => { - expect(pickDeep).to.throw(Error); - }); - - it('returns an object of properties taken from the input object', () => { - let obj = { - a: true, - b: [1, 2, 3], - c: { - nested: { - two: { - times: true, - }, - }, - }, - d: false, - }; - - let res = pickDeep(obj, ['a', 'b[0]', 'c.nested.two.times']); - expect(res.a).to.be.true; - expect(res.b).to.eql([1]); - expect(res.c).to.eql({ - nested: { - two: { - times: true, - }, - }, - }); - expect(res).to.not.have.property('d'); - }); -}); diff --git a/test/common/libs/refPush.js b/test/common/libs/refPush.js deleted file mode 100644 index 4183845c9a..0000000000 --- a/test/common/libs/refPush.js +++ /dev/null @@ -1,53 +0,0 @@ -import shared from '../../../common'; -import { v4 as generateUUID } from 'uuid'; - -describe('refPush', () => { - it('it hashes one object into another by its id', () => { - let referenceObject = {}; - let objectToHash = { - a: 1, - id: generateUUID(), - }; - - shared.refPush(referenceObject, objectToHash); - - expect(referenceObject[objectToHash.id].a).to.equal(objectToHash.a); - expect(referenceObject[objectToHash.id].id).to.equal(objectToHash.id); - expect(referenceObject[objectToHash.id].sort).to.equal(0); - }); - - it('it hashes one object into another by a uuid when object does not have an id', () => { - let referenceObject = {}; - let objectToHash = { - a: 1, - }; - - shared.refPush(referenceObject, objectToHash); - - let hashedObject = _.find(referenceObject, (hashedItem) => { - return objectToHash.a === hashedItem.a; - }); - - expect(hashedObject.a).to.equal(objectToHash.a); - expect(hashedObject.id).to.equal(objectToHash.id); - expect(hashedObject.sort).to.equal(0); - }); - - it('it hashes one object into another by a id and gives it the highest sort value', () => { - let referenceObject = {}; - referenceObject[generateUUID()] = { b: 2, sort: 1 }; - let objectToHash = { - a: 1, - }; - - shared.refPush(referenceObject, objectToHash); - - let hashedObject = _.find(referenceObject, (hashedItem) => { - return objectToHash.a === hashedItem.a; - }); - - expect(hashedObject.a).to.equal(objectToHash.a); - expect(hashedObject.id).to.equal(objectToHash.id); - expect(hashedObject.sort).to.equal(2); - }); -}); diff --git a/test/common/libs/silver.test.js b/test/common/libs/silver.test.js deleted file mode 100644 index 5bd614fcd1..0000000000 --- a/test/common/libs/silver.test.js +++ /dev/null @@ -1,19 +0,0 @@ -import silver from '../../../common/script/libs/silver'; - -describe('silver', () => { - it('is 0', () => { - expect(silver(0)).to.eql('00'); - }); - - it('20 coins in 5.2 of gold: two decimal places', () => { - expect(silver(5.2)).to.eql('20'); - }); - - it('4 coint in 5.04 of gold: one decimal place', () => { - expect(silver(5.04)).to.eql('04'); - }); - - it('is no value', () => { - expect(silver()).to.eql('00'); - }); -}); diff --git a/test/common/libs/splitWhitespace.test.js b/test/common/libs/splitWhitespace.test.js deleted file mode 100644 index 0b445d5a38..0000000000 --- a/test/common/libs/splitWhitespace.test.js +++ /dev/null @@ -1,7 +0,0 @@ -import splitWhitespace from '../../../common/script/libs/splitWhitespace'; - -describe('splitWhitespace', () => { - it('returns an array', () => { - expect(splitWhitespace('a b')).to.eql(['a', 'b']); - }); -}); diff --git a/test/common/libs/taskClasses.test.js b/test/common/libs/taskClasses.test.js deleted file mode 100644 index 226d740bac..0000000000 --- a/test/common/libs/taskClasses.test.js +++ /dev/null @@ -1,82 +0,0 @@ -import taskClasses from '../../../common/script/libs/taskClasses'; - -describe('taskClasses', () => { - let task = {}; - let filters = {}; - let result; - - describe('a todo task', () => { - beforeEach(() => { - task = { type: 'todo', _editing: false, tags: [] }; - }); - - it('is hidden', () => { - filters = { a: true }; - result = taskClasses(task, filters, 0, Number(new Date()), false, true); - expect(result).to.eql('hidden'); - }); - it('is beingEdited', () => { - task._editing = true; - result = taskClasses(task, filters); - expect(result.split(' ').indexOf('beingEdited')).to.not.eql(-1); - }); - it('is completed', () => { - task.completed = true; - result = taskClasses(task, filters); - expect(result.split(' ').indexOf('completed')).to.not.eql(-1); - task.completed = false; - result = taskClasses(task, filters); - expect(result.split(' ').indexOf('completed')).to.eql(-1); - expect(result.split(' ').indexOf('uncompleted')).to.not.eql(-1); - }); - }); - - describe('a daily task', () => { - it('is completed', () => { - task = { type: 'daily' }; - result = taskClasses(task); - expect(result.split(' ').indexOf('completed')).to.not.eql(-1); - }); - - it('is uncompleted'); // this requires stubbing the internal dependency shouldDo in taskClasses - }); - - describe('a habit', () => { - it('that is wide', () => { - task = { type: 'habit', up: true, down: true }; - result = taskClasses(task); - expect(result.split(' ').indexOf('habit-wide')).to.not.eql(-1); - }); - it('that is narrow', () => { - task = { type: 'habit' }; - result = taskClasses(task); - expect(result.split(' ').indexOf('habit-narrow')).to.not.eql(-1); - }); - }); - - describe('varies based on priority', () => { - it('trivial', () => { - task.priority = 0.1; - result = taskClasses(task); - expect(result.split(' ').indexOf('difficulty-trivial')).to.not.eql(-1); - }); - it('hard', () => { - task.priority = 2; - result = taskClasses(task); - expect(result.split(' ').indexOf('difficulty-hard')).to.not.eql(-1); - }); - }); - - describe('varies based on value', () => { - it('color-worst', () => { - task.value = -30; - result = taskClasses(task); - expect(result.split(' ').indexOf('color-worst')).to.not.eql(-1); - }); - it('color-neutral', () => { - task.value = 0; - result = taskClasses(task); - expect(result.split(' ').indexOf('color-neutral')).to.not.eql(-1); - }); - }); -}); diff --git a/test/common/libs/taskDefaults.test.js b/test/common/libs/taskDefaults.test.js deleted file mode 100644 index c970634137..0000000000 --- a/test/common/libs/taskDefaults.test.js +++ /dev/null @@ -1,61 +0,0 @@ -import taskDefaults from '../../../common/script/libs/taskDefaults'; - -describe('taskDefaults', () => { - it('applies defaults to undefined type or habit', () => { - let task = taskDefaults(); - expect(task.type).to.eql('habit'); - expect(task._id).to.exist; - expect(task.text).to.eql(task._id); - expect(task.tags).to.eql([]); - expect(task.value).to.eql(0); - expect(task.priority).to.eql(1); - expect(task.up).to.eql(true); - expect(task.down).to.eql(true); - expect(task.history).to.eql([]); - }); - - it('applies defaults to a daily', () => { - let task = taskDefaults({ type: 'daily' }); - expect(task.type).to.eql('daily'); - expect(task._id).to.exist; - expect(task.text).to.eql(task._id); - expect(task.tags).to.eql([]); - expect(task.value).to.eql(0); - expect(task.priority).to.eql(1); - expect(task.history).to.eql([]); - expect(task.completed).to.eql(false); - expect(task.streak).to.eql(0); - expect(task.repeat).to.eql({ - m: true, - t: true, - w: true, - th: true, - f: true, - s: true, - su: true, - }); - expect(task.frequency).to.eql('weekly'); - expect(task.startDate).to.exist; - }); - - it('applies defaults a reward', () => { - let task = taskDefaults({ type: 'reward' }); - expect(task.type).to.eql('reward'); - expect(task._id).to.exist; - expect(task.text).to.eql(task._id); - expect(task.tags).to.eql([]); - expect(task.value).to.eql(10); - expect(task.priority).to.eql(1); - }); - - it('applies defaults a todo', () => { - let task = taskDefaults({ type: 'todo' }); - expect(task.type).to.eql('todo'); - expect(task._id).to.exist; - expect(task.text).to.eql(task._id); - expect(task.tags).to.eql([]); - expect(task.value).to.eql(0); - expect(task.priority).to.eql(1); - expect(task.completed).to.eql(false); - }); -}); diff --git a/test/common/libs/updateStore.js b/test/common/libs/updateStore.js deleted file mode 100644 index 97d00076af..0000000000 --- a/test/common/libs/updateStore.js +++ /dev/null @@ -1,57 +0,0 @@ -import shared from '../../../common'; -import { - generateUser, -} from '../../helpers/common.helper'; -import i18n from '../../../common/script/i18n'; - -describe('updateStore', () => { - context('returns a list of gear items available for purchase', () => { - let user = generateUser(); - user.items.gear.owned.armor_armoire_lunarArmor = false; // eslint-disable-line camelcase - user.contributor.level = 2; - user.purchased.plan.mysteryItems = ['armor_mystery_201402']; - user.items.gear.owned.armor_mystery_201402 = false; // eslint-disable-line camelcase - - let list = shared.updateStore(user); - - it('contains the first item not purchased for each gear type', () => { - expect(_.find(list, item => { - return item.text() === i18n.t('armorWarrior1Text'); - })).to.exist; - - expect(_.find(list, item => { - return item.text() === i18n.t('armorWarrior2Text'); - })).to.not.exist; - }); - - it('contains mystery items the user can own', () => { - expect(_.find(list, item => { - return item.text() === i18n.t('armorMystery201402Text'); - })).to.exist; - - expect(_.find(list, item => { - return item.text() === i18n.t('armorMystery201403Text'); - })).to.not.exist; - }); - - it('contains special items the user can own', () => { - expect(_.find(list, item => { - return item.text() === i18n.t('armorSpecial1Text'); - })).to.exist; - - expect(_.find(list, item => { - return item.text() === i18n.t('headSpecial1Text'); - })).to.not.exist; - }); - - it('contains armoire items the user can own', () => { - expect(_.find(list, item => { - return item.text() === i18n.t('armorArmoireLunarArmorText'); - })).to.exist; - - expect(_.find(list, item => { - return item.text() === i18n.t('armorArmoireGladiatorArmorText'); - })).to.not.exist; - }); - }); -}); diff --git a/test/common/ops/addPushDevice.js b/test/common/ops/addPushDevice.js deleted file mode 100644 index 535d288384..0000000000 --- a/test/common/ops/addPushDevice.js +++ /dev/null @@ -1,59 +0,0 @@ -import addPushDevice from '../../../common/script/ops/addPushDevice'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, - BadRequest, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.addPushDevice', () => { - let user; - let regId = '10'; - let type = 'someRandomType'; - - beforeEach(() => { - user = generateUser(); - user.stats.hp = 0; - }); - - it('returns an error when regId is not provided', (done) => { - try { - addPushDevice(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('regIdRequired')); - done(); - } - }); - - it('returns an error when type is not provided', (done) => { - try { - addPushDevice(user, {body: {regId}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('typeRequired')); - done(); - } - }); - - it('adds a push device', () => { - let [, message] = addPushDevice(user, {body: {regId, type}}); - - expect(message).to.equal(i18n.t('pushDeviceAdded')); - expect(user.pushDevices[0].type).to.equal(type); - expect(user.pushDevices[0].regId).to.equal(regId); - }); - - it('does not a push device twice', (done) => { - try { - addPushDevice(user, {body: {regId, type}}); - addPushDevice(user, {body: {regId, type}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('pushDeviceAlreadyAdded')); - done(); - } - }); -}); diff --git a/test/common/ops/addTask.js b/test/common/ops/addTask.js deleted file mode 100644 index cb207036db..0000000000 --- a/test/common/ops/addTask.js +++ /dev/null @@ -1,139 +0,0 @@ -import addTask from '../../../common/script/ops/addTask'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.addTask', () => { - let user; - - beforeEach(() => { - user = generateUser(); - user.habits = []; - user.todos = []; - user.dailys = []; - user.rewards = []; - }); - - it('adds an habit', () => { - let habit = addTask(user, { - body: { - type: 'habit', - text: 'habit', - down: false, - }, - }); - - expect(user.tasksOrder.habits).to.eql([ - habit._id, - ]); - expect(habit._id).to.be.a('string'); - expect(habit.text).to.equal('habit'); - expect(habit.type).to.equal('habit'); - expect(habit.up).to.equal(true); - expect(habit.down).to.equal(false); - expect(habit.history).to.eql([]); - expect(habit.checklist).to.not.exists; - }); - - it('adds an habtit when type is invalid', () => { - let habit = addTask(user, { - body: { - type: 'invalid', - text: 'habit', - down: false, - }, - }); - - expect(user.tasksOrder.habits).to.eql([ - habit._id, - ]); - expect(habit._id).to.be.a('string'); - expect(habit.text).to.equal('habit'); - expect(habit.type).to.equal('habit'); - expect(habit.up).to.equal(true); - expect(habit.down).to.equal(false); - expect(habit.history).to.eql([]); - expect(habit.checklist).to.not.exists; - }); - - it('adds a daily', () => { - let daily = addTask(user, { - body: { - type: 'daily', - text: 'daily', - }, - }); - - expect(user.tasksOrder.dailys).to.eql([ - daily._id, - ]); - expect(daily._id).to.be.a('string'); - expect(daily.type).to.equal('daily'); - expect(daily.text).to.equal('daily'); - expect(daily.history).to.eql([]); - expect(daily.checklist).to.eql([]); - expect(daily.completed).to.be.false; - expect(daily.up).to.not.exists; - }); - - it('adds a todo', () => { - let todo = addTask(user, { - body: { - type: 'todo', - text: 'todo', - }, - }); - - expect(user.tasksOrder.todos).to.eql([ - todo._id, - ]); - expect(todo._id).to.be.a('string'); - expect(todo.type).to.equal('todo'); - expect(todo.text).to.equal('todo'); - expect(todo.checklist).to.eql([]); - expect(todo.completed).to.be.false; - expect(todo.up).to.not.exists; - }); - - it('adds a reward', () => { - let reward = addTask(user, { - body: { - type: 'reward', - text: 'reward', - }, - }); - - expect(user.tasksOrder.rewards).to.eql([ - reward._id, - ]); - expect(reward._id).to.be.a('string'); - expect(reward.type).to.equal('reward'); - expect(reward.text).to.equal('reward'); - expect(reward.value).to.equal(10); - expect(reward.up).to.not.exists; - }); - - context('respects preferences', () => { - it('true', () => { - user.preferences.newTaskEdit = true; - user.preferences.tagsCollapsed = true; - user.preferences.advancedCollapsed = false; - let task = addTask(user); - - expect(task._editing).to.be.true; - expect(task._tags).to.be.true; - expect(task._advanced).to.be.true; - }); - - it('false', () => { - user.preferences.newTaskEdit = false; - user.preferences.tagsCollapsed = false; - user.preferences.advancedCollapsed = true; - let task = addTask(user); - - expect(task._editing).to.not.exists; - expect(task._tags).to.not.exists; - expect(task._advanced).to.not.exists; - }); - }); -}); diff --git a/test/common/ops/addWebhook.test.js b/test/common/ops/addWebhook.test.js deleted file mode 100644 index 11d26e622b..0000000000 --- a/test/common/ops/addWebhook.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import addWebhook from '../../../common/script/ops/addWebhook'; -import { - BadRequest, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.addWebhook', () => { - let user; - let req; - - beforeEach(() => { - user = generateUser(); - req = { body: { - enabled: true, - url: 'http://some-url.com', - } }; - }); - - context('adds webhook', () => { - it('validates req.body.url', (done) => { - delete req.body.url; - try { - addWebhook(user, req); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidUrl')); - done(); - } - }); - - it('validates req.body.enabled', (done) => { - delete req.body.enabled; - try { - addWebhook(user, req); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidEnabled')); - done(); - } - }); - - it('calls marksModified()', () => { - user.markModified = sinon.spy(); - addWebhook(user, req); - expect(user.markModified.called).to.eql(true); - }); - - it('succeeds', () => { - expect(user.preferences.webhooks).to.eql({}); - addWebhook(user, req); - expect(user.preferences.webhooks).to.not.eql({}); - }); - }); -}); diff --git a/test/common/ops/allocate.js b/test/common/ops/allocate.js deleted file mode 100644 index 65f3c74fc9..0000000000 --- a/test/common/ops/allocate.js +++ /dev/null @@ -1,63 +0,0 @@ -import allocate from '../../../common/script/ops/allocate'; -import { - BadRequest, - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.allocate', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('throws an error if an invalid attribute is supplied', (done) => { - try { - allocate(user, { - query: {stat: 'notValid'}, - }); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'notValid'})); - done(); - } - }); - - it('throws an error if the user doesn\'t have attribute points', (done) => { - try { - allocate(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughAttrPoints')); - done(); - } - }); - - it('defaults to the "str" attribute', () => { - expect(user.stats.str).to.equal(0); - user.stats.points = 1; - allocate(user); - expect(user.stats.str).to.equal(1); - }); - - it('allocates attribute points', () => { - expect(user.stats.con).to.equal(0); - user.stats.points = 1; - allocate(user, {query: {stat: 'con'}}); - expect(user.stats.con).to.equal(1); - expect(user.stats.points).to.equal(0); - }); - - it('increases mana when allocating to "int"', () => { - expect(user.stats.int).to.equal(0); - expect(user.stats.mp).to.equal(10); - user.stats.points = 1; - allocate(user, {query: {stat: 'int'}}); - expect(user.stats.int).to.equal(1); - expect(user.stats.mp).to.equal(11); - }); -}); diff --git a/test/common/ops/allocateNow.js b/test/common/ops/allocateNow.js deleted file mode 100644 index 4fe473ec9e..0000000000 --- a/test/common/ops/allocateNow.js +++ /dev/null @@ -1,30 +0,0 @@ -import allocateNow from '../../../common/script/ops/allocateNow'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.allocateNow', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('auto allocates all points', () => { - user.stats.points = 5; - user.stats.int = 3; - user.stats.con = 9; - user.stats.per = 9; - user.stats.str = 9; - user.preferences.allocationMode = 'flat'; - - let [data] = allocateNow(user); - - expect(user.stats.points).to.equal(0); - expect(user.stats.con).to.equal(9); - expect(user.stats.int).to.equal(8); - expect(user.stats.per).to.equal(9); - expect(user.stats.str).to.equal(9); - expect(data).to.eql(user.stats); - }); -}); diff --git a/test/common/ops/blockUser.test.js b/test/common/ops/blockUser.test.js deleted file mode 100644 index 950af25d0c..0000000000 --- a/test/common/ops/blockUser.test.js +++ /dev/null @@ -1,44 +0,0 @@ -import blockUser from '../../../common/script/ops/blockUser'; -import { - generateUser, -} from '../../helpers/common.helper'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.blockUser', () => { - let user; - let blockedUser; - let blockedUser2; - - beforeEach(() => { - blockedUser = generateUser(); - blockedUser2 = generateUser(); - user = generateUser(); - expect(user.inbox.blocks).to.eql([]); - }); - - it('validates uuid', (done) => { - try { - blockUser(user, { params: { uuid: 1 } }); - } catch (error) { - expect(error.message).to.eql(i18n.t('invalidUUID')); - done(); - } - }); - - it('blocks user', () => { - let [result] = blockUser(user, { params: { uuid: blockedUser._id } }); - expect(user.inbox.blocks).to.eql([blockedUser._id]); - expect(result).to.eql([blockedUser._id]); - [result] = blockUser(user, { params: { uuid: blockedUser2._id } }); - expect(user.inbox.blocks).to.eql([blockedUser._id, blockedUser2._id]); - expect(result).to.eql([blockedUser._id, blockedUser2._id]); - }); - - it('blocks, then unblocks user', () => { - blockUser(user, { params: { uuid: blockedUser._id } }); - expect(user.inbox.blocks).to.eql([blockedUser._id]); - let [result] = blockUser(user, { params: { uuid: blockedUser._id } }); - expect(user.inbox.blocks).to.eql([]); - expect(result).to.eql([]); - }); -}); diff --git a/test/common/ops/buy.js b/test/common/ops/buy.js deleted file mode 100644 index 9e05cc5225..0000000000 --- a/test/common/ops/buy.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable camelcase */ -import { - generateUser, -} from '../../helpers/common.helper'; -import buy from '../../../common/script/ops/buy'; -import { - BadRequest, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buy', () => { - let user; - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - equipped: { - weapon_warrior_0: true, - }, - }, - }, - stats: { gp: 200 }, - }); - }); - - it('returns error when key is not provided', (done) => { - try { - buy(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('missingKeyParam')); - done(); - } - }); - - it('recovers 15 hp', () => { - user.stats.hp = 30; - buy(user, {params: {key: 'potion'}}); - expect(user.stats.hp).to.eql(45); - }); - - it('adds equipment to inventory', () => { - user.stats.gp = 31; - buy(user, {params: {key: 'armor_warrior_1'}}); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - armor_warrior_1: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - }); -}); diff --git a/test/common/ops/buyArmoire.js b/test/common/ops/buyArmoire.js deleted file mode 100644 index cea6d7abde..0000000000 --- a/test/common/ops/buyArmoire.js +++ /dev/null @@ -1,205 +0,0 @@ -/* eslint-disable camelcase */ - -import sinon from 'sinon'; // eslint-disable-line no-shadow -import { - generateUser, -} from '../../helpers/common.helper'; -import count from '../../../common/script/count'; -import buyArmoire from '../../../common/script/ops/buyArmoire'; -import shared from '../../../common/script'; -import content from '../../../common/script/content/index'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buyArmoire', () => { - let user; - let YIELD_EQUIPMENT = 0.5; - let YIELD_FOOD = 0.7; - let YIELD_EXP = 0.9; - - let fullArmoire = {}; - - _(content.gearTypes).each((type) => { - _(content.gear.tree[type].armoire).each((gearObject) => { - let armoireKey = gearObject.key; - - fullArmoire[armoireKey] = true; - }).value(); - }).value(); - - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - equipped: { - weapon_warrior_0: true, - }, - }, - }, - stats: { gp: 200 }, - }); - - user.achievements.ultimateGearSets = { rogue: true }; - user.flags.armoireOpened = true; - user.stats.exp = 0; - user.items.food = {}; - - sinon.stub(shared.fns, 'randomVal'); - sinon.stub(shared.fns, 'predictableRandom'); - }); - - afterEach(() => { - shared.fns.randomVal.restore(); - shared.fns.predictableRandom.restore(); - }); - - context('failure conditions', () => { - it('does not open if user does not have enough gold', (done) => { - shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.stats.gp = 50; - - try { - buyArmoire(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - done(); - } - }); - - it('does not open without Ultimate Gear achievement', (done) => { - shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false}; - - try { - buyArmoire(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('cannotBuyItem')); - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - done(); - } - }); - }); - - context('non-gear awards', () => { - // Skipped because can't stub predictableRandom correctly - xit('gives Experience', () => { - shared.fns.predictableRandom.returns(YIELD_EXP); - - buyArmoire(user); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(46); - expect(user.stats.gp).to.eql(100); - }); - - // Skipped because can't stub predictableRandom correctly - xit('gives food', () => { - let honey = content.food.Honey; - - shared.fns.randomVal.returns(honey); - shared.fns.predictableRandom.returns(YIELD_FOOD); - - buyArmoire(user); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); - expect(user.items.food).to.eql({Honey: 1}); - expect(user.stats.exp).to.eql(0); - expect(user.stats.gp).to.eql(100); - }); - - // Skipped because can't stub predictableRandom correctly - xit('does not give equipment if all equipment has been found', () => { - shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.items.gear.owned = fullArmoire; - user.stats.gp = 150; - - buyArmoire(user); - - expect(user.items.gear.owned).to.eql(fullArmoire); - let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(0); - - expect(user.stats.exp).to.eql(30); - expect(user.stats.gp).to.eql(50); - }); - }); - - context('gear awards', () => { - beforeEach(() => { - let shield = content.gear.tree.shield.armoire.gladiatorShield; - - shared.fns.randomVal.returns(shield); - }); - - // Skipped because can't stub predictableRandom correctly - xit('always drops equipment the first time', () => { - delete user.flags.armoireOpened; - shared.fns.predictableRandom.returns(YIELD_EXP); - - buyArmoire(user); - - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - shield_armoire_gladiatorShield: true, - }); - - let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(_.size(fullArmoire) - 1); - expect(user.items.food).to.be.empty; - expect(user.stats.exp).to.eql(0); - expect(user.stats.gp).to.eql(100); - }); - - // Skipped because can't stub predictableRandom correctly - xit('gives more equipment', () => { - shared.fns.predictableRandom.returns(YIELD_EQUIPMENT); - user.items.gear.owned = { - weapon_warrior_0: true, - head_armoire_hornedIronHelm: true, - }; - user.stats.gp = 200; - - buyArmoire(user); - - expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true}); - let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire'); - - expect(armoireCount).to.eql(_.size(fullArmoire) - 2); - expect(user.stats.gp).to.eql(100); - }); - }); -}); diff --git a/test/common/ops/buyGear.js b/test/common/ops/buyGear.js deleted file mode 100644 index 4d1213e80d..0000000000 --- a/test/common/ops/buyGear.js +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable camelcase */ - -import sinon from 'sinon'; // eslint-disable-line no-shadow -import { - generateUser, -} from '../../helpers/common.helper'; -import buyGear from '../../../common/script/ops/buyGear'; -import shared from '../../../common/script'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buyGear', () => { - let user; - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - equipped: { - weapon_warrior_0: true, - }, - }, - }, - stats: { gp: 200 }, - }); - - sinon.stub(shared.fns, 'randomVal'); - sinon.stub(shared.fns, 'predictableRandom'); - }); - - afterEach(() => { - shared.fns.randomVal.restore(); - shared.fns.predictableRandom.restore(); - }); - - context('Gear', () => { - it('adds equipment to inventory', () => { - user.stats.gp = 31; - - buyGear(user, {params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.owned).to.eql({ - weapon_warrior_0: true, - armor_warrior_1: true, - eyewear_special_blackTopFrame: true, - eyewear_special_blueTopFrame: true, - eyewear_special_greenTopFrame: true, - eyewear_special_pinkTopFrame: true, - eyewear_special_redTopFrame: true, - eyewear_special_whiteTopFrame: true, - eyewear_special_yellowTopFrame: true, - }); - }); - - it('deducts gold from user', () => { - user.stats.gp = 31; - - buyGear(user, {params: {key: 'armor_warrior_1'}}); - - expect(user.stats.gp).to.eql(1); - }); - - it('auto equips equipment if user has auto-equip preference turned on', () => { - user.stats.gp = 31; - user.preferences.autoEquip = true; - - buyGear(user, {params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1'); - }); - - it('buyGears equipment but does not auto-equip', () => { - user.stats.gp = 31; - user.preferences.autoEquip = false; - - buyGear(user, {params: {key: 'armor_warrior_1'}}); - - expect(user.items.gear.equipped.property).to.not.equal('armor_warrior_1'); - }); - - it('does not buyGear equipment twice', (done) => { - user.stats.gp = 62; - buyGear(user, {params: {key: 'armor_warrior_1'}}); - - try { - buyGear(user, {params: {key: 'armor_warrior_1'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('equipmentAlreadyOwned')); - done(); - } - }); - - // TODO after user.ops.equip is done - xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => { - user.stats.gp = 100; - user.preferences.autoEquip = true; - buyGear(user, {params: {key: 'shield_warrior_1'}}); - user.ops.equip({params: {key: 'shield_warrior_1'}}); - buyGear(user, {params: {key: 'weapon_warrior_1'}}); - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - - buyGear(user, {params: {key: 'weapon_wizard_1'}}); - - expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0'); - expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1'); - }); - - // TODO after user.ops.equip is done - xit('buyGears two-handed equipment but does not automatically remove sword or shield', () => { - user.stats.gp = 100; - user.preferences.autoEquip = false; - buyGear(user, {params: {key: 'shield_warrior_1'}}); - user.ops.equip({params: {key: 'shield_warrior_1'}}); - buyGear(user, {params: {key: 'weapon_warrior_1'}}); - user.ops.equip({params: {key: 'weapon_warrior_1'}}); - - buyGear(user, {params: {key: 'weapon_wizard_1'}}); - - expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1'); - expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1'); - }); - - it('does not buyGear equipment without enough Gold', (done) => { - user.stats.gp = 20; - - try { - buyGear(user, {params: {key: 'armor_warrior_1'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - expect(user.items.gear.owned).to.not.have.property('armor_warrior_1'); - done(); - } - }); - }); -}); diff --git a/test/common/ops/buyHealthPotion.js b/test/common/ops/buyHealthPotion.js deleted file mode 100644 index 6a70f71b62..0000000000 --- a/test/common/ops/buyHealthPotion.js +++ /dev/null @@ -1,65 +0,0 @@ -/* eslint-disable camelcase */ -import { - generateUser, -} from '../../helpers/common.helper'; -import buyHealthPotion from '../../../common/script/ops/buyHealthPotion'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buyHealthPotion', () => { - let user; - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - equipped: { - weapon_warrior_0: true, - }, - }, - }, - stats: { gp: 200 }, - }); - }); - - context('Potion', () => { - it('recovers 15 hp', () => { - user.stats.hp = 30; - buyHealthPotion(user); - expect(user.stats.hp).to.eql(45); - }); - - it('does not increase hp above 50', () => { - user.stats.hp = 45; - buyHealthPotion(user); - expect(user.stats.hp).to.eql(50); - }); - - it('deducts 25 gp', () => { - user.stats.hp = 45; - buyHealthPotion(user); - - expect(user.stats.gp).to.eql(175); - }); - - it('does not purchase if not enough gp', (done) => { - user.stats.hp = 45; - user.stats.gp = 5; - try { - buyHealthPotion(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - expect(user.stats.hp).to.eql(45); - expect(user.stats.gp).to.eql(5); - - done(); - } - }); - }); -}); diff --git a/test/common/ops/buyMysterySet.js b/test/common/ops/buyMysterySet.js deleted file mode 100644 index 8e523a5a0a..0000000000 --- a/test/common/ops/buyMysterySet.js +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-disable camelcase */ - -import { - generateUser, -} from '../../helpers/common.helper'; -import buyMysterySet from '../../../common/script/ops/buyMysterySet'; -import { - NotAuthorized, - NotFound, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buyMysterySet', () => { - let user; - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - }, - }, - }, - }); - }); - - context('Mystery Sets', () => { - context('failure conditions', () => { - it('does not grant mystery sets without Mystic Hourglasses', (done) => { - try { - buyMysterySet(user, {params: {key: '201501'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); - expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true); - done(); - } - }); - - it('does not grant mystery set that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.gear.owned = { - weapon_warrior_0: true, - weapon_mystery_301404: true, - armor_mystery_301404: true, - head_mystery_301404: true, - eyewear_mystery_301404: true, - }; - - try { - buyMysterySet(user, {params: {key: '301404'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.eql(i18n.t('mysterySetNotFound')); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - done(); - } - }); - }); - - context('successful purchases', () => { - it('buys Steampunk Accessories Set', () => { - user.purchased.plan.consecutive.trinkets = 1; - buyMysterySet(user, {params: {key: '301404'}}); - - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true); - expect(user.items.gear.owned).to.have.property('weapon_mystery_301404', true); - expect(user.items.gear.owned).to.have.property('armor_mystery_301404', true); - expect(user.items.gear.owned).to.have.property('head_mystery_301404', true); - expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true); - }); - }); - }); -}); diff --git a/test/common/ops/buyQuest.js b/test/common/ops/buyQuest.js deleted file mode 100644 index aab691f64e..0000000000 --- a/test/common/ops/buyQuest.js +++ /dev/null @@ -1,81 +0,0 @@ -import { - generateUser, -} from '../../helpers/common.helper'; -import buyQuest from '../../../common/script/ops/buyQuest'; -import { - NotAuthorized, - NotFound, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.buyQuest', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('buys a Quest scroll', () => { - user.stats.gp = 205; - buyQuest(user, { - params: { - key: 'dilatoryDistress1', - }, - }); - expect(user.items.quests).to.eql({ - dilatoryDistress1: 1, - }); - expect(user.stats.gp).to.equal(5); - }); - - it('does not buy Quests without enough Gold', (done) => { - user.stats.gp = 1; - try { - buyQuest(user, { - params: { - key: 'dilatoryDistress1', - }, - }); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - expect(user.items.quests).to.eql({}); - expect(user.stats.gp).to.equal(1); - done(); - } - }); - - it('does not buy nonexistent Quests', (done) => { - user.stats.gp = 9999; - try { - buyQuest(user, { - params: { - key: 'snarfblatter', - }, - }); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('questNotFound', {key: 'snarfblatter'})); - expect(user.items.quests).to.eql({}); - expect(user.stats.gp).to.equal(9999); - done(); - } - }); - - it('does not buy Gem-premium Quests', (done) => { - user.stats.gp = 9999; - try { - buyQuest(user, { - params: { - key: 'kraken', - }, - }); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('questNotGoldPurchasable', {key: 'kraken'})); - expect(user.items.quests).to.eql({}); - expect(user.stats.gp).to.equal(9999); - done(); - } - }); -}); diff --git a/test/common/ops/buySpecialSpell.js b/test/common/ops/buySpecialSpell.js deleted file mode 100644 index 249f4bc8a6..0000000000 --- a/test/common/ops/buySpecialSpell.js +++ /dev/null @@ -1,79 +0,0 @@ -import buySpecialSpell from '../../../common/script/ops/buySpecialSpell'; -import { - BadRequest, - NotFound, - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import content from '../../../common/script/content/index'; - -describe('shared.ops.buySpecialSpell', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('throws an error if params.key is missing', (done) => { - try { - buySpecialSpell(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('missingKeyParam')); - done(); - } - }); - - it('throws an error if the spell doesn\'t exists', (done) => { - try { - buySpecialSpell(user, { - params: { - key: 'notExisting', - }, - }); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('spellNotFound', {spellId: 'notExisting'})); - done(); - } - }); - - it('throws an error if the user doesn\'t have enough gold', (done) => { - user.stats.gp = 1; - try { - buySpecialSpell(user, { - params: { - key: 'thankyou', - }, - }); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - done(); - } - }); - - it('buys an item', () => { - user.stats.gp = 11; - let item = content.special.thankyou; - - let [data, message] = buySpecialSpell(user, { - params: { - key: 'thankyou', - }, - }); - - expect(user.stats.gp).to.equal(1); - expect(user.items.special.thankyou).to.equal(1); - expect(data).to.eql({ - items: user.items, - stats: user.stats, - }); - expect(message).to.equal(i18n.t('messageBought', { - itemText: item.text(), - })); - }); -}); diff --git a/test/common/ops/changeClass.js b/test/common/ops/changeClass.js deleted file mode 100644 index ae4a180178..0000000000 --- a/test/common/ops/changeClass.js +++ /dev/null @@ -1,139 +0,0 @@ -import changeClass from '../../../common/script/ops/changeClass'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.changeClass', () => { - let user; - - beforeEach(() => { - user = generateUser(); - user.stats.lvl = 11; - user.stats.flagSelected = false; - }); - - it('user is not level 10', (done) => { - user.stats.lvl = 9; - try { - changeClass(user, {query: {class: 'rogue'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('lvl10ChangeClass')); - done(); - } - }); - - context('req.query.class is a valid class', () => { - it('errors if user.stats.flagSelected is true and user.balance < 0.75', (done) => { - user.flags.classSelected = true; - user.preferences.disableClasses = false; - user.balance = 0; - - try { - changeClass(user, {query: {class: 'rogue'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('changes class', () => { - user.stats.class = 'healer'; - user.items.gear.owned.armor_rogue_1 = true; // eslint-disable-line camelcase - - let [data] = changeClass(user, {query: {class: 'rogue'}}); - expect(data).to.eql({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - items: user.items, - }); - - expect(user.stats.class).to.equal('rogue'); - expect(user.flags.classSelected).to.be.true; - expect(user.items.gear.equipped.weapon).to.equal('weapon_rogue_0'); - expect(user.items.gear.owned.weapon_rogue_0).to.be.true; - expect(user.items.gear.equipped.armor).to.equal('armor_rogue_1'); - expect(user.items.gear.owned.armor_rogue_1).to.be.true; - expect(user.items.gear.equipped.shield).to.equal('shield_rogue_0'); - expect(user.items.gear.owned.shield_rogue_0).to.be.true; - expect(user.items.gear.equipped.head).to.equal('head_base_0'); - }); - }); - - context('req.query.class is missing or user.stats.flagSelected is true', () => { - it('has user.preferences.disableClasses === true', () => { - user.balance = 1; - user.preferences.disableClasses = true; - user.preferences.autoAllocate = true; - user.stats.points = 45; - user.stats.str = 1; - user.stats.con = 2; - user.stats.per = 3; - user.stats.int = 4; - user.flags.classSelected = true; - - let [data] = changeClass(user); - expect(data).to.eql({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - items: user.items, - }); - - expect(user.preferences.disableClasses).to.be.false; - expect(user.preferences.autoAllocate).to.be.false; - expect(user.balance).to.equal(1); - expect(user.stats.str).to.equal(0); - expect(user.stats.con).to.equal(0); - expect(user.stats.per).to.equal(0); - expect(user.stats.int).to.equal(0); - expect(user.stats.points).to.equal(11); - expect(user.flags.classSelected).to.equal(false); - }); - - context('has user.preferences.disableClasses !== true', () => { - it('and less than 3 gems', (done) => { - user.balance = 0.5; - try { - changeClass(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('and at least 3 gems', () => { - user.balance = 1; - user.stats.points = 45; - user.stats.str = 1; - user.stats.con = 2; - user.stats.per = 3; - user.stats.int = 4; - user.flags.classSelected = true; - - let [data] = changeClass(user); - expect(data).to.eql({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - items: user.items, - }); - - expect(user.balance).to.equal(0.25); - expect(user.stats.str).to.equal(0); - expect(user.stats.con).to.equal(0); - expect(user.stats.per).to.equal(0); - expect(user.stats.int).to.equal(0); - expect(user.stats.points).to.equal(11); - expect(user.flags.classSelected).to.equal(false); - }); - }); - }); -}); diff --git a/test/common/ops/clearCompleted.js b/test/common/ops/clearCompleted.js deleted file mode 100644 index 4dceb3c1f0..0000000000 --- a/test/common/ops/clearCompleted.js +++ /dev/null @@ -1,37 +0,0 @@ -import clearCompleted from '../../../common/script/ops/clearCompleted'; -import { - generateTodo, -} from '../../helpers/common.helper'; - -describe('shared.ops.clearCompleted', () => { - it('clear completed todos', () => { - let todos = [ - generateTodo({text: 'todo'}), - generateTodo({ - text: 'done', - completed: true, - }), - generateTodo({ - text: 'done chellenge broken', - completed: true, - challenge: { - id: 123, - broken: 'TASK_DELETED', - }, - }), - generateTodo({ - text: 'done chellenge not broken', - completed: true, - challenge: { - id: 123, - }, - }), - ]; - - clearCompleted(todos); - - expect(todos.length).to.equal(2); - expect(todos[0].text).to.equal('todo'); - expect(todos[1].text).to.equal('done chellenge not broken'); - }); -}); diff --git a/test/common/ops/clearPMs.test.js b/test/common/ops/clearPMs.test.js deleted file mode 100644 index cf1408e5a6..0000000000 --- a/test/common/ops/clearPMs.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import clearPMs from '../../../common/script/ops/clearPMs'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.clearPMs', () => { - let user; - - beforeEach(() => { - user = generateUser(); - user.inbox.messages = { first: 'message', second: 'message' }; - }); - - it('clears messages', () => { - expect(user.inbox.messages).to.not.eql({}); - let [result] = clearPMs(user); - expect(user.inbox.messages).to.eql({}); - expect(result).to.eql({}); - }); -}); diff --git a/test/common/ops/deletePM.test.js b/test/common/ops/deletePM.test.js deleted file mode 100644 index 109595eca9..0000000000 --- a/test/common/ops/deletePM.test.js +++ /dev/null @@ -1,20 +0,0 @@ -import deletePM from '../../../common/script/ops/deletePM'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.deletePM', () => { - let user; - - beforeEach(() => { - user = generateUser(); - user.inbox.messages = { first: 'message', second: 'message' }; - }); - - it('delete message', () => { - expect(user.inbox.messages).to.not.eql({ second: 'message' }); - let [response] = deletePM(user, { params: { id: 'first' } }); - expect(user.inbox.messages).to.eql({ second: 'message' }); - expect(response).to.eql({ second: 'message' }); - }); -}); diff --git a/test/common/ops/deleteWebhook.test.js b/test/common/ops/deleteWebhook.test.js deleted file mode 100644 index 8e27a09e3e..0000000000 --- a/test/common/ops/deleteWebhook.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import deleteWebhook from '../../../common/script/ops/deleteWebhook'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.deleteWebhook', () => { - let user; - let req; - - beforeEach(() => { - user = generateUser(); - req = { params: { id: 'some-id' } }; - }); - - it('succeeds', () => { - user.preferences.webhooks = { 'some-id': {}, 'another-id': {} }; - let [data] = deleteWebhook(user, req); - expect(user.preferences.webhooks).to.eql({'another-id': {}}); - expect(data).to.equal(user.preferences.webhooks); - }); -}); diff --git a/test/common/ops/disableClasses.js b/test/common/ops/disableClasses.js deleted file mode 100644 index 81ac1a9792..0000000000 --- a/test/common/ops/disableClasses.js +++ /dev/null @@ -1,35 +0,0 @@ -import disableClasses from '../../../common/script/ops/disableClasses'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.disableClasses', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('disable classes', () => { - user.stats.lvl = 34; - user.stats.str = 45; - user.stats.class = 'healer'; - user.preferences.disableClasses = false; - user.preferences.autoAllocate = false; - user.stats.points = 2; - - let [data] = disableClasses(user); - expect(data).to.eql({ - preferences: user.preferences, - stats: user.stats, - flags: user.flags, - }); - - expect(user.stats.class).to.equal('warrior'); - expect(user.flags.classSelected).to.equal(true); - expect(user.preferences.disableClasses).to.equal(true); - expect(user.preferences.autoAllocate).to.equal(true); - expect(user.stats.str).to.equal(34); - expect(user.stats.points).to.equal(0); - }); -}); diff --git a/test/common/ops/equip.js b/test/common/ops/equip.js deleted file mode 100644 index 8641aa102e..0000000000 --- a/test/common/ops/equip.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable camelcase */ -import equip from '../../../common/script/ops/equip'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import content from '../../../common/script/content/index'; - -describe('shared.ops.equip', () => { - let user; - - beforeEach(() => { - user = generateUser({ - items: { - gear: { - owned: { - weapon_warrior_0: true, - weapon_warrior_1: true, - weapon_warrior_2: true, - weapon_wizard_1: true, - weapon_wizard_2: true, - shield_base_0: true, - shield_warrior_1: true, - }, - equipped: { - weapon: 'weapon_warrior_0', - shield: 'shield_base_0', - }, - }, - }, - stats: {gp: 200}, - }); - }); - - context('Gear', () => { - it('should not send a message if a weapon is equipped while only having zero or one weapons equipped', () => { - equip(user, {params: {key: 'weapon_warrior_1'}}); - - // one-handed to one-handed - let [, message] = equip(user, {params: {key: 'weapon_warrior_2'}}); - expect(message).to.not.exists; - - // one-handed to two-handed - [, message] = equip(user, {params: {key: 'weapon_wizard_1'}}); - expect(message).to.not.exists; - - // two-handed to two-handed - [, message] = equip(user, {params: {key: 'weapon_wizard_2'}}); - expect(message).to.not.exists; - - // two-handed to one-handed - [, message] = equip(user, {params: {key: 'weapon_warrior_2'}}); - expect(message).to.not.exists; - }); - - it('should send messages if equipping a two-hander causes the off-hander to be unequipped', () => { - equip(user, {params: {key: 'weapon_warrior_1'}}); - equip(user, {params: {key: 'shield_warrior_1'}}); - - // equipping two-hander - let [data, message] = equip(user, {params: {key: 'weapon_wizard_1'}}); - let weapon = content.gear.flat.weapon_wizard_1; - let item = content.gear.flat.shield_warrior_1; - - let res = {data, message}; - expect(res).to.eql({ - message: i18n.t('messageTwoHandedEquip', {twoHandedText: weapon.text(), offHandedText: item.text()}), - data: user.items, - }); - }); - - it('should send messages if equipping an off-hand item causes a two-handed weapon to be unequipped', () => { - // equipping two-hander - equip(user, {params: {key: 'weapon_wizard_1'}}); - let weapon = content.gear.flat.weapon_wizard_1; - let shield = content.gear.flat.shield_warrior_1; - - let [data, message] = equip(user, {params: {key: 'shield_warrior_1'}}); - - let res = {data, message}; - expect(res).to.eql({ - message: i18n.t('messageTwoHandedUnequip', {twoHandedText: weapon.text(), offHandedText: shield.text()}), - data: user.items, - }); - }); - }); -}); diff --git a/test/common/ops/feed.js b/test/common/ops/feed.js deleted file mode 100644 index 0b726f5ec7..0000000000 --- a/test/common/ops/feed.js +++ /dev/null @@ -1,216 +0,0 @@ -import feed from '../../../common/script/ops/feed'; -import content from '../../../common/script/content'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.feed', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - context('failure conditions', () => { - it('does not allow feeding without specifying pet and food', (done) => { - try { - feed(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('missingPetFoodFeed')); - done(); - } - }); - - it('does not allow feeding if pet name format is invalid', (done) => { - try { - feed(user, {params: {pet: 'invalid', food: 'food'}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidPetName')); - done(); - } - }); - - it('does not allow feeding if food does not exists', (done) => { - try { - feed(user, {params: {pet: 'valid-pet', food: 'invalid food name'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('messageFoodNotFound')); - done(); - } - }); - - it('does not allow feeding if pet is not owned', (done) => { - try { - feed(user, {params: {pet: 'not-owned', food: 'Meat'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('messagePetNotFound')); - done(); - } - }); - - it('does not allow feeding if food is not owned', (done) => { - user.items.pets['Wolf-Base'] = 5; - try { - feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('messageFoodNotFound')); - done(); - } - }); - - it('does not allow feeding of special pets', (done) => { - user.items.pets['Wolf-Veteran'] = 5; - user.items.food.Meat = 1; - try { - feed(user, {params: {pet: 'Wolf-Veteran', food: 'Meat'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageCannotFeedPet')); - done(); - } - }); - - it('does not allow feeding of mounts', (done) => { - user.items.pets['Wolf-Base'] = -1; - user.items.mounts['Wolf-Base'] = true; - user.items.food.Meat = 1; - try { - feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageAlreadyMount')); - done(); - } - }); - }); - - context('successful feeding', () => { - it('evolves the pet if the food is a Saddle', () => { - user.items.pets['Wolf-Base'] = 5; - user.items.food.Saddle = 2; - user.items.currentPet = 'Wolf-Base'; - let [egg, potion] = 'Wolf-Base'.split('-'); - - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Saddle'}}); - expect(data).to.eql(user.items.pets['Wolf-Base']); - expect(message).to.eql(i18n.t('messageEvolve', { - egg: i18n.t('petName', { - potion: potionText, - egg: eggText, - }), - })); - - expect(user.items.food.Saddle).to.equal(1); - expect(user.items.pets['Wolf-Base']).to.equal(-1); - expect(user.items.mounts['Wolf-Base']).to.equal(true); - expect(user.items.currentPet).to.equal(''); - }); - - it('enjoys the food', () => { - user.items.pets['Wolf-Base'] = 5; - user.items.food.Meat = 2; - - let food = content.food.Meat; - let [egg, potion] = 'Wolf-Base'.split('-'); - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Meat'}}); - expect(data).to.eql(user.items.pets['Wolf-Base']); - expect(message).to.eql(i18n.t('messageLikesFood', { - egg: i18n.t('petName', { - potion: potionText, - egg: eggText, - }), - foodText: food.text(), - })); - - expect(user.items.food.Meat).to.equal(1); - expect(user.items.pets['Wolf-Base']).to.equal(10); - }); - - it('enjoys the food (premium potion)', () => { - user.items.pets['Wolf-Spooky'] = 5; - user.items.food.Milk = 2; - - let food = content.food.Milk; - let [egg, potion] = 'Wolf-Spooky'.split('-'); - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let [data, message] = feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}}); - expect(data).to.eql(user.items.pets['Wolf-Spooky']); - expect(message).to.eql(i18n.t('messageLikesFood', { - egg: i18n.t('petName', { - potion: potionText, - egg: eggText, - }), - foodText: food.text(), - })); - - expect(user.items.food.Milk).to.equal(1); - expect(user.items.pets['Wolf-Spooky']).to.equal(10); - }); - - it('does not like the food', () => { - user.items.pets['Wolf-Base'] = 5; - user.items.food.Milk = 2; - - let food = content.food.Milk; - let [egg, potion] = 'Wolf-Base'.split('-'); - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Milk'}}); - expect(data).to.eql(user.items.pets['Wolf-Base']); - expect(message).to.eql(i18n.t('messageDontEnjoyFood', { - egg: i18n.t('petName', { - potion: potionText, - egg: eggText, - }), - foodText: food.text(), - })); - - expect(user.items.food.Milk).to.equal(1); - expect(user.items.pets['Wolf-Base']).to.equal(7); - }); - - it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50', () => { - user.items.pets['Wolf-Base'] = 49; - user.items.food.Milk = 2; - user.items.currentPet = 'Wolf-Base'; - - let [egg, potion] = 'Wolf-Base'.split('-'); - let potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion; - let eggText = content.eggs[egg] ? content.eggs[egg].text() : egg; - - let [data, message] = feed(user, {params: {pet: 'Wolf-Base', food: 'Milk'}}); - expect(data).to.eql(user.items.pets['Wolf-Base']); - expect(message).to.eql(i18n.t('messageEvolve', { - egg: i18n.t('petName', { - potion: potionText, - egg: eggText, - }), - })); - - expect(user.items.food.Milk).to.equal(1); - expect(user.items.pets['Wolf-Base']).to.equal(-1); - expect(user.items.mounts['Wolf-Base']).to.equal(true); - expect(user.items.currentPet).to.equal(''); - }); - }); -}); diff --git a/test/common/ops/hatch.js b/test/common/ops/hatch.js deleted file mode 100644 index 87131db13b..0000000000 --- a/test/common/ops/hatch.js +++ /dev/null @@ -1,147 +0,0 @@ -import hatch from '../../../common/script/ops/hatch'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.hatch', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - context('Pet Hatching', () => { - context('failure conditions', () => { - it('does not allow hatching without specifying egg and potion', () => { - user.items.pets = {}; - try { - hatch(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('missingEggHatchingPotionHatch')); - expect(user.items.pets).to.be.empty; - } - }); - - it('does not allow hatching if user lacks specified egg', (done) => { - user.items.eggs.Wolf = 1; - user.items.hatchingPotions.Base = 1; - user.items.pets = {}; - try { - hatch(user, {params: {egg: 'Dragon', hatchingPotion: 'Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('messageMissingEggPotion')); - expect(user.items.pets).to.be.empty; - expect(user.items.eggs.Wolf).to.equal(1); - expect(user.items.hatchingPotions.Base).to.equal(1); - done(); - } - }); - - it('does not allow hatching if user lacks specified hatching potion', (done) => { - user.items.eggs.Wolf = 1; - user.items.hatchingPotions.Base = 1; - user.items.pets = {}; - try { - hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Golden'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('messageMissingEggPotion')); - expect(user.items.pets).to.be.empty; - expect(user.items.eggs.Wolf).to.equal(1); - expect(user.items.hatchingPotions.Base).to.equal(1); - done(); - } - }); - - it('does not allow hatching if user already owns target pet', (done) => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {'Wolf-Base': 10}; - try { - hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageAlreadyPet')); - expect(user.items.pets).to.eql({'Wolf-Base': 10}); - expect(user.items.eggs).to.eql({Wolf: 1}); - expect(user.items.hatchingPotions).to.eql({Base: 1}); - done(); - } - }); - - it('does not allow hatching quest pet egg using premium potion', (done) => { - user.items.eggs = {Cheetah: 1}; - user.items.hatchingPotions = {Spooky: 1}; - user.items.pets = {}; - try { - hatch(user, {params: {egg: 'Cheetah', hatchingPotion: 'Spooky'}}); - } 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({Cheetah: 1}); - expect(user.items.hatchingPotions).to.eql({Spooky: 1}); - done(); - } - }); - }); - - context('successful hatching', () => { - it('hatches a basic pet', () => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {}; - let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); - expect(message).to.equal(i18n.t('messageHatched')); - expect(data).to.eql(user.items); - expect(user.items.pets).to.eql({'Wolf-Base': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - }); - - it('hatches a quest pet', () => { - user.items.eggs = {Cheetah: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {}; - let [data, message] = hatch(user, {params: {egg: 'Cheetah', hatchingPotion: 'Base'}}); - expect(message).to.equal(i18n.t('messageHatched')); - expect(data).to.eql(user.items); - expect(user.items.pets).to.eql({'Cheetah-Base': 5}); - expect(user.items.eggs).to.eql({Cheetah: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - }); - - it('hatches a premium pet', () => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Spooky: 1}; - user.items.pets = {}; - let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}}); - expect(message).to.equal(i18n.t('messageHatched')); - expect(data).to.eql(user.items); - expect(user.items.pets).to.eql({'Wolf-Spooky': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Spooky: 0}); - }); - - it('hatches a pet previously raised to a mount', () => { - user.items.eggs = {Wolf: 1}; - user.items.hatchingPotions = {Base: 1}; - user.items.pets = {'Wolf-Base': -1}; - let [data, message] = hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Base'}}); - expect(message).to.eql(i18n.t('messageHatched')); - expect(data).to.eql(user.items); - expect(user.items.pets).to.eql({'Wolf-Base': 5}); - expect(user.items.eggs).to.eql({Wolf: 0}); - expect(user.items.hatchingPotions).to.eql({Base: 0}); - }); - }); - }); -}); diff --git a/test/common/ops/hourglassPurchase.js b/test/common/ops/hourglassPurchase.js deleted file mode 100644 index 98400f82b5..0000000000 --- a/test/common/ops/hourglassPurchase.js +++ /dev/null @@ -1,145 +0,0 @@ -import hourglassPurchase from '../../../common/script/ops/hourglassPurchase'; -import { - BadRequest, - NotAuthorized, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import content from '../../../common/script/content/index'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('user.ops.hourglassPurchase', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - context('failure conditions', () => { - it('return error when key is not provided', (done) => { - try { - hourglassPurchase(user, {params: {}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.eql(i18n.t('missingKeyParam')); - done(); - } - }); - - it('returns error when type is not provided', (done) => { - try { - hourglassPurchase(user, {params: {key: 'Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.eql(i18n.t('missingTypeParam')); - done(); - } - }); - - it('returns error when inccorect type is provided', (done) => { - try { - hourglassPurchase(user, {params: {type: 'notAType', key: 'MantisShrimp-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()})); - done(); - } - }); - - it('does not grant to pets without Mystic Hourglasses', (done) => { - try { - hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); - done(); - } - }); - - it('does not grant to mounts without Mystic Hourglasses', (done) => { - try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); - done(); - } - }); - - it('does not grant pet that is not part of the Time Travel Stable', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - - try { - hourglassPurchase(user, {params: {type: 'pets', key: 'Wolf-Veteran'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('notAllowedHourglass')); - done(); - } - }); - - it('does not grant mount that is not part of the Time Travel Stable', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - - try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'Orca-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('notAllowedHourglass')); - done(); - } - }); - - it('does not grant pet that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.pets = { - 'MantisShrimp-Base': true, - }; - - try { - hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('petsAlreadyOwned')); - done(); - } - }); - - it('does not grant mount that has already been purchased', (done) => { - user.purchased.plan.consecutive.trinkets = 1; - user.items.mounts = { - 'MantisShrimp-Base': true, - }; - - try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('mountsAlreadyOwned')); - done(); - } - }); - }); - - context('successful purchases', () => { - it('buys a pet', () => { - user.purchased.plan.consecutive.trinkets = 2; - - let [, message] = hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}}); - - expect(message).to.eql(i18n.t('hourglassPurchase')); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); - }); - - it('buys a mount', () => { - user.purchased.plan.consecutive.trinkets = 2; - - let [, message] = hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); - expect(message).to.eql(i18n.t('hourglassPurchase')); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); - }); - }); -}); diff --git a/test/common/ops/openMysteryItem.js b/test/common/ops/openMysteryItem.js deleted file mode 100644 index 712c032254..0000000000 --- a/test/common/ops/openMysteryItem.js +++ /dev/null @@ -1,38 +0,0 @@ -import openMysteryItem from '../../../common/script/ops/openMysteryItem'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - BadRequest, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; - -describe('shared.ops.openMysteryItem', () => { - let user; - - beforeEach(() => { - user = generateUser(); - }); - - it('returns error when item key is empty', (done) => { - try { - openMysteryItem(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('mysteryItemIsEmpty')); - done(); - } - }); - - it('opens mystery item', () => { - let mysteryItemKey = 'eyewear_special_summerRogue'; - - user.purchased.plan.mysteryItems = [mysteryItemKey]; - - let [data, message] = openMysteryItem(user); - - expect(user.items.gear.owned[mysteryItemKey]).to.be.true; - expect(message).to.equal(i18n.t('mysteryItemOpened')); - expect(data).to.equal(user.items.gear.owned); - }); -}); diff --git a/test/common/ops/purchase.js b/test/common/ops/purchase.js deleted file mode 100644 index 60227f21b2..0000000000 --- a/test/common/ops/purchase.js +++ /dev/null @@ -1,194 +0,0 @@ -import purchase from '../../../common/script/ops/purchase'; -import planGemLimits from '../../../common/script/libs/planGemLimits'; -import { - BadRequest, - NotAuthorized, - NotFound, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.purchase', () => { - let user; - let goldPoints = 40; - let gemsBought = 40; - - before(() => { - user = generateUser({'stats.class': 'rogue'}); - }); - - context('failure conditions', () => { - it('returns an error when type is not provided', (done) => { - try { - purchase(user, {params: {}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('typeRequired')); - done(); - } - }); - - it('returns an error when key is not provided', (done) => { - try { - purchase(user, {params: {type: 'gems'}}); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('keyRequired')); - done(); - } - }); - - it('prevents unsubscribed user from buying gems', (done) => { - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems')); - done(); - } - }); - - it('prevents user with not enough gold from buying gems', (done) => { - user.purchased.plan.customerId = 'customer-id'; - - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotEnoughGold')); - done(); - } - }); - - it('prevents user that have reached the conversion cap from buying gems', (done) => { - user.stats.gp = goldPoints; - user.purchased.plan.gemsBought = gemsBought; - - try { - purchase(user, {params: {type: 'gems', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap})); - done(); - } - }); - - it('returns error when unknown type is provided', (done) => { - try { - purchase(user, {params: {type: 'randomType', key: 'gem'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('notAccteptedType')); - done(); - } - }); - - it('returns error when user attempts to purchase a piece of gear they own', (done) => { - user.items.gear.owned['shield_rogue_1'] = true; // eslint-disable-line dot-notation - - try { - purchase(user, {params: {type: 'gear', key: 'shield_rogue_1'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('alreadyHave')); - done(); - } - }); - - it('returns error when unknown item is requested', (done) => { - try { - purchase(user, {params: {type: 'gear', key: 'randomKey'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('contentKeyNotFound', {type: 'gear'})); - done(); - } - }); - - it('returns error when user does not have permission to buy an item', (done) => { - try { - purchase(user, {params: {type: 'gear', key: 'eyewear_mystery_301405'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('messageNotAvailable')); - done(); - } - }); - - it('returns error when user does not have enough gems to buy an item', (done) => { - try { - purchase(user, {params: {type: 'gear', key: 'headAccessory_special_wolfEars'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - }); - - context('successful purchase', () => { - let userGemAmount = 10; - - before(() => { - user.balance = userGemAmount; - user.stats.gp = goldPoints; - user.purchased.plan.gemsBought = 0; - }); - - it('purchases gems', () => { - let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}}); - - expect(message).to.equal(i18n.t('plusOneGem')); - expect(user.balance).to.equal(userGemAmount + 0.25); - expect(user.purchased.plan.gemsBought).to.equal(1); - expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate); - }); - - it('purchases eggs', () => { - let type = 'eggs'; - let key = 'Wolf'; - - purchase(user, {params: {type, key}}); - - expect(user.items[type][key]).to.equal(1); - }); - - it('purchases hatchingPotions', () => { - let type = 'hatchingPotions'; - let key = 'Base'; - - purchase(user, {params: {type, key}}); - - expect(user.items[type][key]).to.equal(1); - }); - - it('purchases food', () => { - let type = 'food'; - let key = 'Meat'; - - purchase(user, {params: {type, key}}); - - expect(user.items[type][key]).to.equal(1); - }); - - it('purchases quests', () => { - let type = 'quests'; - let key = 'gryphon'; - - purchase(user, {params: {type, key}}); - - expect(user.items[type][key]).to.equal(1); - }); - - it('purchases gear', () => { - let type = 'gear'; - let key = 'headAccessory_special_tigerEars'; - - purchase(user, {params: {type, key}}); - - expect(user.items.gear.owned[key]).to.be.true; - }); - }); -}); diff --git a/test/common/ops/readCard.js b/test/common/ops/readCard.js deleted file mode 100644 index 5d771ab0d6..0000000000 --- a/test/common/ops/readCard.js +++ /dev/null @@ -1,48 +0,0 @@ -import readCard from '../../../common/script/ops/readCard'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - BadRequest, - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.readCard', () => { - let user; - let cardType = 'greeting'; - - beforeEach(() => { - user = generateUser(); - user.items.special[`${cardType}Received`] = [true]; - user.flags.cardReceived = true; - }); - - it('returns an error when cardType is not provided', (done) => { - try { - readCard(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('cardTypeRequired')); - done(); - } - }); - - it('returns an error when unknown cardType is provided', (done) => { - try { - readCard(user, {params: {cardType: 'randomCardType'}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('cardTypeNotAllowed')); - done(); - } - }); - - it('reads a card', () => { - let [, message] = readCard(user, {params: {cardType: 'greeting'}}); - - expect(message).to.equal(i18n.t('readCard', {cardType})); - expect(user.items.special[`${cardType}Received`]).to.be.empty; - expect(user.flags.cardReceived).to.be.false; - }); -}); diff --git a/test/common/ops/rebirth.js b/test/common/ops/rebirth.js deleted file mode 100644 index 9916144669..0000000000 --- a/test/common/ops/rebirth.js +++ /dev/null @@ -1,236 +0,0 @@ -import rebirth from '../../../common/script/ops/rebirth'; -import i18n from '../../../common/script/i18n'; -import { MAX_LEVEL } from '../../../common/script/constants'; -import { - generateUser, - generateHabit, - generateDaily, - generateTodo, - generateReward, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.rebirth', () => { - let user; - let animal = 'Wolf-Base'; - let userStats = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp']; - let tasks = []; - - beforeEach(() => { - user = generateUser(); - user.balance = 2; - tasks = [generateHabit(), generateDaily(), generateTodo(), generateReward()]; - }); - - it('returns an error when user balance is too low and user is less than max level', (done) => { - user.balance = 0; - - try { - rebirth(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('rebirths a user with enough gems', () => { - let [, message] = rebirth(user); - - expect(message).to.equal(i18n.t('rebirthComplete')); - }); - - it('rebirths a user with not enough gems but max level', () => { - user.balance = 0; - user.stats.lvl = MAX_LEVEL; - - let [, message] = rebirth(user); - - expect(message).to.equal(i18n.t('rebirthComplete')); - }); - - it('rebirths a user with not enough gems but more than max level', () => { - user.balance = 0; - user.stats.lvl = MAX_LEVEL + 1; - - let [, message] = rebirth(user); - - expect(message).to.equal(i18n.t('rebirthComplete')); - }); - - it('resets user\'s tasks values except for rewards to 0', () => { - tasks[0].value = 1; - tasks[1].value = 1; - tasks[2].value = 1; - tasks[3].value = 1; // Reward - - rebirth(user, tasks); - - expect(tasks[0].value).to.equal(0); - expect(tasks[1].value).to.equal(0); - expect(tasks[2].value).to.equal(0); - expect(tasks[3].value).to.equal(1); // Reward - }); - - it('resets user\'s daily streaks to 0', () => { - tasks[1].streak = 1; // Daily - - rebirth(user, tasks); - - expect(tasks[1].streak).to.equal(0); - }); - - it('resets a user\'s buffs', () => { - user.stats.buffs = {test: 'test'}; - - rebirth(user); - - expect(user.stats.buffs).to.be.empty; - }); - - it('resets a user\'s health points', () => { - user.stats.hp = 40; - - rebirth(user); - - expect(user.stats.hp).to.equal(50); - }); - - it('resets a user\'s class', () => { - user.stats.class = 'rouge'; - - rebirth(user); - - expect(user.stats.class).to.equal('warrior'); - }); - - it('resets a user\'s stats', () => { - user.stats.class = 'rouge'; - _.each(userStats, function setUsersStats (value) { - user.stats[value] = 10; - }); - - rebirth(user); - - _.each(userStats, function resetUserStats (value) { - user.stats[value] = 0; - }); - }); - - it('resets a user\'s gear', () => { - let gearReset = { - armor: 'armor_base_0', - weapon: 'weapon_warrior_0', - head: 'head_base_0', - shield: 'shield_base_0', - }; - - rebirth(user); - - expect(user.items.gear.equipped).to.deep.equal(gearReset); - expect(user.items.gear.costume).to.deep.equal(gearReset); - expect(user.preferences.costume).to.be.false; - }); - - it('resets a user\'s gear owned', () => { - user.items.gear.owned.weapon_warrior_1 = true; // eslint-disable-line camelcase - rebirth(user); - - expect(user.items.gear.owned.weapon_warrior_1).to.be.false; - expect(user.items.gear.owned.weapon_warrior_0).to.be.true; - }); - - it('resets a user\'s current pet', () => { - user.items.pets[animal] = true; - user.items.currentPet = animal; - rebirth(user); - - expect(user.items.currentPet).to.be.empty; - }); - - it('resets a user\'s current mount', () => { - user.items.mounts[animal] = true; - user.items.currentMount = animal; - rebirth(user); - - expect(user.items.currentMount).to.be.empty; - }); - - it('resets a user\'s flags', () => { - user.flags.itemsEnabled = true; - user.flags.dropsEnabled = true; - user.flags.classSelected = true; - user.flags.rebirthEnabled = true; - user.flags.levelDrops = {test: 'test'}; - - rebirth(user); - - expect(user.flags.itemsEnabled).to.be.false; - expect(user.flags.dropsEnabled).to.be.false; - expect(user.flags.classSelected).to.be.false; - expect(user.flags.rebirthEnabled).to.be.false; - expect(user.flags.levelDrops).to.be.empty; - }); - - it('does not reset rebirthEnabled if user has beastMaster', () => { - user.achievements.beastMaster = 1; - user.flags.rebirthEnabled = true; - - rebirth(user); - - expect(user.flags.rebirthEnabled).to.be.true; - }); - - it('sets rebirth achievement', () => { - rebirth(user); - - expect(user.achievements.rebirths).to.equal(1); - expect(user.achievements.rebirthLevel).to.equal(user.stats.lvl); - }); - - it('increments rebirth achievements', () => { - user.stats.lvl = 2; - user.achievements.rebirths = 1; - user.achievements.rebirthLevel = 1; - - rebirth(user); - - expect(user.achievements.rebirths).to.equal(2); - expect(user.achievements.rebirthLevel).to.equal(2); - }); - - it('does not increment rebirth achievements when level is lower than previous', () => { - user.stats.lvl = 2; - user.achievements.rebirths = 1; - user.achievements.rebirthLevel = 3; - - rebirth(user); - - expect(user.achievements.rebirths).to.equal(1); - expect(user.achievements.rebirthLevel).to.equal(3); - }); - - it('always increments rebirth achievements when level is MAX_LEVEL', () => { - user.stats.lvl = MAX_LEVEL; - user.achievements.rebirths = 1; - user.achievements.rebirthLevel = MAX_LEVEL + 1; // this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test - - rebirth(user); - - expect(user.achievements.rebirths).to.equal(2); - expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL); - }); - - it('always increments rebirth achievements when level is greater than MAX_LEVEL', () => { - user.stats.lvl = MAX_LEVEL + 1; - user.achievements.rebirths = 1; - user.achievements.rebirthLevel = MAX_LEVEL + 2; // this value is not actually possible (actually capped at MAX_LEVEL) but makes a good test - - rebirth(user); - - expect(user.achievements.rebirths).to.equal(2); - expect(user.achievements.rebirthLevel).to.equal(MAX_LEVEL); - }); -}); diff --git a/test/common/ops/releaseBoth.js b/test/common/ops/releaseBoth.js deleted file mode 100644 index 41e1bf6efb..0000000000 --- a/test/common/ops/releaseBoth.js +++ /dev/null @@ -1,98 +0,0 @@ -import releaseBoth from '../../../common/script/ops/releaseBoth'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.releaseBoth', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(() => { - user = generateUser(); - user.items.currentMount = animal; - user.items.currentPet = animal; - user.items.pets[animal] = 5; - user.items.mounts[animal] = true; - user.balance = 1.5; - }); - - it('returns an error when user balance is too low and user does not have triadBingo', (done) => { - user.balance = 0; - - try { - releaseBoth(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('grants triad bingo with gems', () => { - let [, message] = releaseBoth(user); - - expect(message).to.equal(i18n.t('mountsAndPetsReleased')); - expect(user.achievements.triadBingoCount).to.equal(1); - }); - - it('grants triad bingo without gems', () => { - user.balance = 0; - user.achievements.triadBingo = 1; - user.achievements.triadBingoCount = 1; - - let [, message] = releaseBoth(user); - - expect(message).to.equal(i18n.t('mountsAndPetsReleased')); - expect(user.achievements.triadBingoCount).to.equal(2); - }); - - it('releases pets', () => { - let [, message] = releaseBoth(user); - - expect(message).to.equal(i18n.t('mountsAndPetsReleased')); - expect(user.items.pets[animal]).to.be.empty; - expect(user.items.mounts[animal]).to.equal(null); - }); - - it('releases mounts', () => { - let [, message] = releaseBoth(user); - - expect(message).to.equal(i18n.t('mountsAndPetsReleased')); - expect(user.items.mounts[animal]).to.equal(null); - }); - - it('removes currentPet', () => { - releaseBoth(user); - - expect(user.items.currentMount).to.be.empty; - expect(user.items.currentPet).to.be.empty; - }); - - it('removes currentMount', () => { - releaseBoth(user); - - expect(user.items.currentMount).to.be.empty; - }); - - it('decreases user\'s balance', () => { - releaseBoth(user); - - expect(user.balance).to.equal(0); - }); - - it('incremenets beastMasterCount', () => { - releaseBoth(user); - - expect(user.achievements.beastMasterCount).to.equal(1); - }); - - it('incremenets mountMasterCount', () => { - releaseBoth(user); - - expect(user.achievements.mountMasterCount).to.equal(1); - }); -}); diff --git a/test/common/ops/releaseMounts.js b/test/common/ops/releaseMounts.js deleted file mode 100644 index 29cb3cf6ac..0000000000 --- a/test/common/ops/releaseMounts.js +++ /dev/null @@ -1,57 +0,0 @@ -import releaseMounts from '../../../common/script/ops/releaseMounts'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.releaseMounts', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(() => { - user = generateUser(); - user.items.currentMount = animal; - user.items.mounts[animal] = true; - user.balance = 1; - }); - - it('returns an error when user balance is too low', (done) => { - user.balance = 0; - - try { - releaseMounts(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('releases mounts', () => { - let [, message] = releaseMounts(user); - - expect(message).to.equal(i18n.t('mountsReleased')); - expect(user.items.mounts[animal]).to.equal(null); - }); - - it('removes currentMount', () => { - releaseMounts(user); - - expect(user.items.currentMount).to.be.empty; - }); - - it('increases mountMasterCount achievement', () => { - releaseMounts(user); - - expect(user.achievements.mountMasterCount).to.equal(1); - }); - - it('subtracts gems from balance', () => { - releaseMounts(user); - - expect(user.balance).to.equal(0); - }); -}); diff --git a/test/common/ops/releasePets.js b/test/common/ops/releasePets.js deleted file mode 100644 index af175736cf..0000000000 --- a/test/common/ops/releasePets.js +++ /dev/null @@ -1,57 +0,0 @@ -import releasePets from '../../../common/script/ops/releasePets'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.releasePets', () => { - let user; - let animal = 'Wolf-Base'; - - beforeEach(() => { - user = generateUser(); - user.items.currentPet = animal; - user.items.pets[animal] = 5; - user.balance = 1; - }); - - it('returns an error when user balance is too low', (done) => { - user.balance = 0; - - try { - releasePets(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('releases pets', () => { - let [, message] = releasePets(user); - - expect(message).to.equal(i18n.t('petsReleased')); - expect(user.items.pets[animal]).to.equal(0); - }); - - it('removes currentPet', () => { - releasePets(user); - - expect(user.items.currentPet).to.be.empty; - }); - - it('decreases user\'s balance', () => { - releasePets(user); - - expect(user.balance).to.equal(0); - }); - - it('incremenets beastMasterCount', () => { - releasePets(user); - - expect(user.achievements.beastMasterCount).to.equal(1); - }); -}); diff --git a/test/common/ops/reroll.js b/test/common/ops/reroll.js deleted file mode 100644 index 4dc5da70c1..0000000000 --- a/test/common/ops/reroll.js +++ /dev/null @@ -1,63 +0,0 @@ -import reroll from '../../../common/script/ops/reroll'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, - generateDaily, - generateReward, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.reroll', () => { - let user; - let tasks = []; - - beforeEach(() => { - user = generateUser(); - user.balance = 1; - tasks = [generateDaily(), generateReward()]; - }); - - it('returns an error when user balance is too low', (done) => { - user.balance = 0; - - try { - reroll(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('rerolls a user with enough gems', () => { - let [, message] = reroll(user); - - expect(message).to.equal(i18n.t('fortifyComplete')); - }); - - it('reduces a user\'s balance', () => { - reroll(user); - - expect(user.balance).to.equal(0); - }); - - it('resets a user\'s health points', () => { - user.stats.hp = 40; - - reroll(user); - - expect(user.stats.hp).to.equal(50); - }); - - it('resets user\'s taks values except for rewards to 0', () => { - tasks[0].value = 1; - tasks[1].value = 1; - - reroll(user, tasks); - - expect(tasks[0].value).to.equal(0); - expect(tasks[1].value).to.equal(1); - }); -}); diff --git a/test/common/ops/reset.js b/test/common/ops/reset.js deleted file mode 100644 index 50ebf90cb5..0000000000 --- a/test/common/ops/reset.js +++ /dev/null @@ -1,79 +0,0 @@ -import reset from '../../../common/script/ops/reset'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, - generateDaily, - generateHabit, - generateReward, - generateTodo, -} from '../../helpers/common.helper'; - -describe('shared.ops.reset', () => { - let user; - let tasksToRemove; - - beforeEach(() => { - user = generateUser(); - user.balance = 2; - - let habit = generateHabit(); - let todo = generateTodo(); - let daily = generateDaily(); - let reward = generateReward(); - - user.tasksOrder.habits = [habit._id]; - user.tasksOrder.todos = [todo._id]; - user.tasksOrder.dailys = [daily._id]; - user.tasksOrder.rewards = [reward._id]; - - tasksToRemove = [habit, todo, daily, reward]; - }); - - - it('resets a user', () => { - let [, message] = reset(user); - - expect(message).to.equal(i18n.t('resetComplete')); - }); - - it('resets user\'s health', () => { - user.stats.hp = 40; - - reset(user); - - expect(user.stats.hp).to.equal(50); - }); - - it('resets user\'s level', () => { - user.stats.lvl = 2; - - reset(user); - - expect(user.stats.lvl).to.equal(1); - }); - - it('resets user\'s gold', () => { - user.stats.gp = 20; - - reset(user); - - expect(user.stats.gp).to.equal(0); - }); - - it('resets user\'s exp', () => { - user.stats.exp = 20; - - reset(user); - - expect(user.stats.exp).to.equal(0); - }); - - it('resets user\'s tasksOrder', () => { - reset(user, tasksToRemove); - - expect(user.tasksOrder.habits).to.be.empty; - expect(user.tasksOrder.todos).to.be.empty; - expect(user.tasksOrder.dailys).to.be.empty; - expect(user.tasksOrder.rewards).to.be.empty; - }); -}); diff --git a/test/common/ops/revive.js b/test/common/ops/revive.js deleted file mode 100644 index efd6968b42..0000000000 --- a/test/common/ops/revive.js +++ /dev/null @@ -1,91 +0,0 @@ -import revive from '../../../common/script/ops/revive'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; -import content from '../../../common/script/content/index'; - -describe('shared.ops.revive', () => { - let user; - - beforeEach(() => { - user = generateUser(); - user.stats.hp = 0; - }); - - it('returns an error when user is not dead', (done) => { - user.stats.hp = 10; - - try { - revive(user); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('cannotRevive')); - done(); - } - }); - - it('resets user\'s hp, exp and gp', () => { - user.stats.exp = 100; - user.stats.gp = 100; - - revive(user); - - expect(user.stats.hp).to.equal(50); - expect(user.stats.exp).to.equal(0); - expect(user.stats.gp).to.equal(0); - }); - - it('decreases user\'s level', () => { - user.stats.lvl = 2; - revive(user); - - expect(user.stats.lvl).to.equal(1); - }); - - it('decreases a stat', () => { - user.stats.str = 2; - revive(user); - - expect(user.stats.str).to.equal(1); - }); - - it('removes a random item from user gear owned', () => { - let weaponKey = 'weapon_warrior_0'; - user.items.gear.owned[weaponKey] = true; - - let [, message] = revive(user); - - expect(message).to.equal(i18n.t('messageLostItem', { itemText: content.gear.flat[weaponKey].text()})); - expect(user.items.gear.owned[weaponKey]).to.be.false; - }); - - it('removes a random item from user gear equipped', () => { - let weaponKey = 'weapon_warrior_0'; - let itemToLose = content.gear.flat[weaponKey]; - - user.items.gear.owned[weaponKey] = true; - user.items.gear.equipped[itemToLose.type] = itemToLose.key; - - let [, message] = revive(user); - - expect(message).to.equal(i18n.t('messageLostItem', { itemText: itemToLose.text()})); - expect(user.items.gear.equipped[itemToLose.type]).to.equal(`${itemToLose.type}_base_0`); - }); - - it('removes a random item from user gear costume', () => { - let weaponKey = 'weapon_warrior_0'; - let itemToLose = content.gear.flat[weaponKey]; - - user.items.gear.owned[weaponKey] = true; - user.items.gear.costume[itemToLose.type] = itemToLose.key; - - let [, message] = revive(user); - - expect(message).to.equal(i18n.t('messageLostItem', { itemText: itemToLose.text()})); - expect(user.items.gear.costume[itemToLose.type]).to.equal(`${itemToLose.type}_base_0`); - }); -}); diff --git a/test/common/ops/scoreTask.test.js b/test/common/ops/scoreTask.test.js deleted file mode 100644 index 727b316d62..0000000000 --- a/test/common/ops/scoreTask.test.js +++ /dev/null @@ -1,203 +0,0 @@ -import scoreTask from '../../../common/script/ops/scoreTask'; -import { - generateUser, - generateDaily, - generateHabit, - generateTodo, - generateReward, -} from '../../helpers/common.helper'; -import common from '../../../common'; -import i18n from '../../../common/script/i18n'; -import { - NotAuthorized, -} from '../../../common/script/libs/errors'; - -let EPSILON = 0.0001; // negligible distance between datapoints - -/* Helper Functions */ -let rewrapUser = (user) => { - user._wrapped = false; - common.wrap(user); - return user; -}; - -let beforeAfter = () => { - let beforeUser = generateUser(); - let afterUser = _.cloneDeep(beforeUser); - rewrapUser(afterUser); - - return { - beforeUser, - afterUser, - }; -}; - -let expectGainedPoints = (beforeUser, afterUser, beforeTask, afterTask) => { - expect(afterUser.stats.hp).to.eql(50); - expect(afterUser.stats.exp).to.be.greaterThan(beforeUser.stats.exp); - expect(afterUser.stats.gp).to.be.greaterThan(beforeUser.stats.gp); - expect(afterTask.value).to.be.greaterThan(beforeTask.value); - if (afterTask.type === 'habit') { - expect(afterTask.history).to.have.length(1); - } -}; - -let expectClosePoints = (beforeUser, afterUser, beforeTask, task) => { - expect(Math.abs(afterUser.stats.exp - beforeUser.stats.exp)).to.be.lessThan(EPSILON); - expect(Math.abs(afterUser.stats.gp - beforeUser.stats.gp)).to.be.lessThan(EPSILON); - expect(Math.abs(task.value - beforeTask.value)).to.be.lessThan(EPSILON); -}; - -let _expectRoughlyEqualDates = (date1, date2) => { - expect(date1.toString()).to.eql(date2.toString()); -}; - -describe('shared.ops.scoreTask', () => { - let ref; - - beforeEach(() => { - ref = beforeAfter(); - }); - - it('throws an error when scoring a reward if user does not have enough gold', (done) => { - let reward = generateReward({ userId: ref.afterUser._id, text: 'some reward', value: 100 }); - try { - scoreTask({ user: ref.afterUser, task: reward }); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.eql(i18n.t('messageNotEnoughGold')); - done(); - } - }); - - it('checks that the streak parameters affects the score', () => { - let task = generateDaily({ userId: ref.afterUser._id, text: 'task to check streak' }); - scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); - scoreTask({ user: ref.afterUser, task, direction: 'up', cron: false }); - expect(task.streak).to.eql(2); - }); - - it('completes when the task direction is up', () => { - let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false }); - scoreTask({ user: ref.afterUser, task, direction: 'up' }); - expect(task.completed).to.eql(true); - _expectRoughlyEqualDates(task.dateCompleted, new Date()); - }); - - it('uncompletes when the task direction is down', () => { - let task = generateTodo({ userId: ref.afterUser._id, text: 'todo to complete', cron: false }); - scoreTask({ user: ref.afterUser, task, direction: 'down' }); - expect(task.completed).to.eql(false); - expect(task.dateCompleted).to.not.exist; - }); - - describe('verifies that times parameter in scoring works', () => { - let habit; - - beforeEach(() => { - ref = beforeAfter(); - habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); - }); - - it('works', () => { - let delta1, delta2, delta3; - - delta1 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }); - - ref = beforeAfter(); - habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); - - delta2 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 4, cron: false }); - - ref = beforeAfter(); - habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); - - delta3 = scoreTask({ user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }); - - expect(Math.abs(delta1 - delta2)).to.be.greaterThan(EPSILON); - expect(Math.abs(delta1 - delta3)).to.be.lessThan(EPSILON); - }); - }); - - describe('scores', () => { - let options = {}; - let habit; - let freshDaily, daily; - let freshTodo, todo; - - beforeEach(() => { - ref = beforeAfter(options); - habit = generateHabit({ userId: ref.afterUser._id, text: 'some habit' }); - freshDaily = generateDaily({ userId: ref.afterUser._id, text: 'some daily' }); - daily = generateDaily({ userId: ref.afterUser._id, text: 'some daily' }); - freshTodo = generateTodo({ userId: ref.afterUser._id, text: 'some todo' }); - todo = generateTodo({ userId: ref.afterUser._id, text: 'some todo' }); - - expect(habit.history.length).to.eql(0); - - // before and after are the same user - expect(ref.beforeUser._id).to.exist; - expect(ref.beforeUser._id).to.eql(ref.afterUser._id); - }); - - context('habits', () => { - it('up', () => { - options = { user: ref.afterUser, task: habit, direction: 'up', times: 5, cron: false }; - scoreTask(options); - - expect(habit.history.length).to.eql(1); - expect(habit.value).to.be.greaterThan(0); - - expect(ref.afterUser.stats.hp).to.eql(50); - expect(ref.afterUser.stats.exp).to.be.greaterThan(ref.beforeUser.stats.exp); - expect(ref.afterUser.stats.gp).to.be.greaterThan(ref.beforeUser.stats.gp); - }); - - it('down', () => { - scoreTask({user: ref.afterUser, task: habit, direction: 'down', times: 5, cron: false}, {}); - - expect(habit.history.length).to.eql(1); - expect(habit.value).to.be.lessThan(0); - - expect(ref.afterUser.stats.hp).to.be.lessThan(ref.beforeUser.stats.hp); - expect(ref.afterUser.stats.exp).to.eql(0); - expect(ref.afterUser.stats.gp).to.eql(0); - }); - }); - - context('dailys', () => { - it('up', () => { - expect(daily.completed).to.not.eql(true); - scoreTask({user: ref.afterUser, task: daily, direction: 'up'}); - expectGainedPoints(ref.beforeUser, ref.afterUser, freshDaily, daily); - expect(daily.completed).to.eql(true); - }); - - it('up, down', () => { - scoreTask({user: ref.afterUser, task: daily, direction: 'up'}); - scoreTask({user: ref.afterUser, task: daily, direction: 'down'}); - expectClosePoints(ref.beforeUser, ref.afterUser, freshDaily, daily); - }); - - it('sets completed = false on direction = down', () => { - daily.completed = true; - expect(daily.completed).to.not.eql(false); - scoreTask({user: ref.afterUser, task: daily, direction: 'down'}); - expect(daily.completed).to.eql(false); - }); - }); - - context('todos', () => { - it('up', () => { - scoreTask({user: ref.afterUser, task: todo, direction: 'up'}); - expectGainedPoints(ref.beforeUser, ref.afterUser, freshTodo, todo); - }); - - it('up, down', () => { - scoreTask({user: ref.afterUser, task: todo, direction: 'up'}); - scoreTask({user: ref.afterUser, task: todo, direction: 'down'}); - expectClosePoints(ref.beforeUser, ref.afterUser, freshTodo, todo); - }); - }); - }); -}); diff --git a/test/common/ops/sell.js b/test/common/ops/sell.js deleted file mode 100644 index 727302ad9c..0000000000 --- a/test/common/ops/sell.js +++ /dev/null @@ -1,79 +0,0 @@ -import sell from '../../../common/script/ops/sell'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, - BadRequest, - NotFound, -} from '../../../common/script/libs/errors'; -import content from '../../../common/script/content/index'; - -describe('shared.ops.sell', () => { - let user; - let type = 'eggs'; - let key = 'Wolf'; - let acceptedTypes = ['eggs', 'hatchingPotions', 'food']; - - beforeEach(() => { - user = generateUser(); - user.items[type][key] = 1; - }); - - it('returns an error when type is not provided', (done) => { - try { - sell(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('typeRequired')); - done(); - } - }); - - it('returns an error when key is not provided', (done) => { - try { - sell(user, {params: { type } }); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('keyRequired')); - done(); - } - }); - - it('returns an error when non-sellable type is provided', (done) => { - let nonSellableType = 'nonSellableType'; - - try { - sell(user, {params: { type: nonSellableType, key } }); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('typeNotSellable', {acceptedTypes: acceptedTypes.join(', ')})); - done(); - } - }); - - it('returns an error when key is not found with type provided', (done) => { - let fakeKey = 'fakeKey'; - - try { - sell(user, {params: { type, key: fakeKey } }); - } catch (err) { - expect(err).to.be.an.instanceof(NotFound); - expect(err.message).to.equal(i18n.t('userItemsKeyNotFound', {type})); - done(); - } - }); - - it('reduces item count from user', () => { - sell(user, {params: { type, key } }); - - expect(user.items[type][key]).to.equal(0); - }); - - it('increases user\'s gold', () => { - sell(user, {params: { type, key } }); - - expect(user.stats.gp).to.equal(content[type][key].value); - }); -}); diff --git a/test/common/ops/sleep.js b/test/common/ops/sleep.js deleted file mode 100644 index f1e15625c9..0000000000 --- a/test/common/ops/sleep.js +++ /dev/null @@ -1,18 +0,0 @@ -import sleep from '../../../common/script/ops/sleep'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.sleep', () => { - it('toggles user.preferences.sleep', () => { - let user = generateUser(); - - let [res] = sleep(user); - expect(res).to.eql(true); - expect(user.preferences.sleep).to.equal(true); - - let [res2] = sleep(user); - expect(res2).to.eql(false); - expect(user.preferences.sleep).to.equal(false); - }); -}); diff --git a/test/common/ops/unlock.js b/test/common/ops/unlock.js deleted file mode 100644 index ede5fe1d31..0000000000 --- a/test/common/ops/unlock.js +++ /dev/null @@ -1,121 +0,0 @@ -import unlock from '../../../common/script/ops/unlock'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; -import { - NotAuthorized, - BadRequest, -} from '../../../common/script/libs/errors'; - -describe('shared.ops.unlock', () => { - let user; - let unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; - let unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'; - let backgroundUnlockPath = 'background.giant_florals'; - let unlockCost = 1.25; - let usersStartingGems = 5; - - beforeEach(() => { - user = generateUser(); - user.balance = usersStartingGems; - }); - - it('returns an error when path is not provided', (done) => { - try { - unlock(user); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('pathRequired')); - done(); - } - }); - - it('returns an error when user balance is too low', (done) => { - user.balance = 0; - - try { - unlock(user, {query: {path: unlockPath}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('notEnoughGems')); - done(); - } - }); - - it('returns an error when user already owns a full set', (done) => { - try { - unlock(user, {query: {path: unlockPath}}); - unlock(user, {query: {path: unlockPath}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('alreadyUnlocked')); - done(); - } - }); - - // disabled untill fully implemente - xit('returns an error when user already owns items in a full set', (done) => { - try { - unlock(user, {query: {path: unlockPath}}); - unlock(user, {query: {path: unlockPath}}); - } catch (err) { - expect(err).to.be.an.instanceof(NotAuthorized); - expect(err.message).to.equal(i18n.t('alreadyUnlocked')); - done(); - } - }); - - it('equips an item already owned', () => { - expect(user.purchased.background.giant_florals).to.not.exists; - - unlock(user, {query: {path: backgroundUnlockPath}}); - let afterBalance = user.balance; - let response = unlock(user, {query: {path: backgroundUnlockPath}}); - expect(user.balance).to.equal(afterBalance); // do not bill twice - - expect(response.message).to.not.exists; - expect(user.preferences.background).to.equal('giant_florals'); - }); - - it('un-equips an item already equipped', () => { - expect(user.purchased.background.giant_florals).to.not.exists; - - unlock(user, {query: {path: backgroundUnlockPath}}); // unlock - let afterBalance = user.balance; - unlock(user, {query: {path: backgroundUnlockPath}}); // equip - let response = unlock(user, {query: {path: backgroundUnlockPath}}); - expect(user.balance).to.equal(afterBalance); // do not bill twice - - expect(response.message).to.not.exists; - expect(user.preferences.background).to.equal(''); - }); - - it('unlocks a full set', () => { - let [, message] = unlock(user, {query: {path: unlockPath}}); - - expect(message).to.equal(i18n.t('unlocked')); - expect(user.purchased.shirt.convict).to.be.true; - }); - - it('unlocks a full set of gear', () => { - let [, message] = unlock(user, {query: {path: unlockGearSetPath}}); - - expect(message).to.equal(i18n.t('unlocked')); - expect(user.items.gear.owned.headAccessory_special_wolfEars).to.be.true; - }); - - it('unlocks a an item', () => { - let [, message] = unlock(user, {query: {path: backgroundUnlockPath}}); - - expect(message).to.equal(i18n.t('unlocked')); - expect(user.purchased.background.giant_florals).to.be.true; - }); - - it('reduces a user\'s balance', () => { - let [, message] = unlock(user, {query: {path: unlockPath}}); - - expect(message).to.equal(i18n.t('unlocked')); - expect(user.balance).to.equal(usersStartingGems - unlockCost); - }); -}); diff --git a/test/common/ops/updateTask.js b/test/common/ops/updateTask.js deleted file mode 100644 index 99e8d80b22..0000000000 --- a/test/common/ops/updateTask.js +++ /dev/null @@ -1,53 +0,0 @@ -import updateTask from '../../../common/script/ops/updateTask'; -import { - generateHabit, -} from '../../helpers/common.helper'; - -describe('shared.ops.updateTask', () => { - it('updates a task', () => { - let now = new Date(); - let habit = generateHabit({ - tags: [ - '123', - '456', - ], - - reminders: [{ - id: '123', - startDate: now, - time: now, - }], - }); - - let [res] = updateTask(habit, { - body: { - text: 'updated', - id: '123', - _id: '123', - type: 'todo', - tags: ['678'], - checklist: [{ - completed: false, - text: 'item', - id: '123', - }], - }, - }); - - expect(res.id).to.not.equal('123'); - expect(res._id).to.not.equal('123'); - expect(res.type).to.equal('habit'); - expect(res.text).to.equal('updated'); - expect(res.checklist).to.eql([{ - completed: false, - text: 'item', - id: '123', - }]); - expect(res.reminders).to.eql([{ - id: '123', - startDate: now, - time: now, - }]); - expect(res.tags).to.eql(['678']); - }); -}); diff --git a/test/common/ops/updateWebhook.test.js b/test/common/ops/updateWebhook.test.js deleted file mode 100644 index 43c353626e..0000000000 --- a/test/common/ops/updateWebhook.test.js +++ /dev/null @@ -1,42 +0,0 @@ -import updateWebhook from '../../../common/script/ops/updateWebhook'; -import { - BadRequest, -} from '../../../common/script/libs/errors'; -import i18n from '../../../common/script/i18n'; -import { - generateUser, -} from '../../helpers/common.helper'; - -describe('shared.ops.updateWebhook', () => { - let user; - let req; - let newUrl = 'http://new-url.com'; - - beforeEach(() => { - user = generateUser(); - req = { params: { - id: 'this-id', - }, body: { - url: newUrl, - enabled: true, - } }; - }); - - it('validates body', (done) => { - delete req.body.url; - try { - updateWebhook(user, req); - } catch (err) { - expect(err).to.be.an.instanceof(BadRequest); - expect(err.message).to.equal(i18n.t('invalidUrl')); - done(); - } - }); - - it('succeeds', () => { - let url = 'http://existing-url.com'; - user.preferences.webhooks = { 'this-id': { url } }; - updateWebhook(user, req); - expect(user.preferences.webhooks['this-id'].url).to.eql(newUrl); - }); -}); diff --git a/test/common/preenTodos.test.js b/test/common/preenTodos.test.js new file mode 100644 index 0000000000..c9f9a45028 --- /dev/null +++ b/test/common/preenTodos.test.js @@ -0,0 +1,76 @@ +import moment from 'moment'; +import { generateTodo } from '../helpers/common.helper'; +import { preenTodos } from '../../common/script/index.js'; + +describe('#preenTodos', () => { + let todos, uncompletedTodo, completedChallengeTodo, newlyCompletedTodo, completedTodoFromTwoDaysAgo, completedTodoFromThreeDaysAgo, completedTodoFromTenDaysAgo; + + beforeEach(() => { + uncompletedTodo = generateTodo({ completed: false }); + completedChallengeTodo = generateTodo({ + completed: true, + challenge: { id: 'some-challenge' }, + }); + newlyCompletedTodo = generateTodo({ + completed: true, + dateCompleted: moment(), + }); + completedTodoFromTwoDaysAgo = generateTodo({ + completed: true, + dateCompleted: moment().subtract({ days: 2 }), + }); + completedTodoFromThreeDaysAgo = generateTodo({ + completed: true, + dateCompleted: moment().subtract({ days: 3 }), + }); + completedTodoFromTenDaysAgo = generateTodo({ + completed: true, + dateCompleted: moment().subtract({ days: 10 }), + }); + + todos = [ + uncompletedTodo, + completedChallengeTodo, + newlyCompletedTodo, + completedTodoFromTwoDaysAgo, + completedTodoFromThreeDaysAgo, + completedTodoFromTenDaysAgo, + ]; + }); + + it('includes uncompleted todos', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.include(uncompletedTodo); + }); + + it('includes completed challenge todos', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.include(completedChallengeTodo); + }); + + it('includes recently completed todos', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.include(newlyCompletedTodo); + }); + + it('includes todos completed two days ago', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.include(completedTodoFromTwoDaysAgo); + }); + + it('does not include todos completed three days ago', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.not.include(completedTodoFromThreeDaysAgo); + }); + + it('does not include todos completed more than three days ago', () => { + let preenedTodos = preenTodos(todos); + + expect(preenedTodos).to.not.include(completedTodoFromTenDaysAgo); + }); +}); diff --git a/test/common/shared.spells.test.js b/test/common/shared.spells.test.js new file mode 100644 index 0000000000..931f9b9991 --- /dev/null +++ b/test/common/shared.spells.test.js @@ -0,0 +1,103 @@ +import shared from '../../common/script/index.js'; +import { + generateUser, + generateTodo, +} from '../helpers/common.helper'; + + +describe('Spells', () => { + let user; + + beforeEach(() => { + let todo = generateTodo(); + + user = generateUser({ + stats: { + int: 20, + str: 20, + con: 20, + per: 20, + lvl: 20, + }, + }); + user.todos.push(todo); + }); + + context('Rogue Spells', () => { + beforeEach(() => { + user.stats.class = 'rogue'; + }); + + describe('#backstab', () => { + it('adds exp to user', () => { + const PREVIOUS_EXP = user.stats.exp; + + shared.content.spells.rogue.backStab.cast(user, user.todos[0]); + + expect(user.stats.exp).to.be.greaterThan(PREVIOUS_EXP); + }); + + it('adds gp to user', () => { + const PREVIOUS_GP = user.stats.gp; + + shared.content.spells.rogue.backStab.cast(user, user.todos[0]); + + expect(user.stats.gp).to.be.greaterThan(PREVIOUS_GP); + }); + + it('levels up user if the gain in experience will level up the user', () => { + user.stats.exp = 399; + user.stats.lvl = 17; + + shared.content.spells.rogue.backStab.cast(user, user.todos[0]); + expect(user.stats.lvl).to.eql(18); + }); + + it('adds quest scroll to inventory when passing level milestone', () => { + user.stats.exp = 329; + user.stats.lvl = 14; + + expect(user.items.quests).to.not.have.property('atom1'); + + shared.content.spells.rogue.backStab.cast(user, user.todos[0]); + + expect(user.items.quests).to.have.property('atom1', 1); + }); + }); + }); + + context('Wizard Spells', () => { + beforeEach(() => { + user.stats.class = 'wizard'; + }); + + describe('#fireball (Burst of flames)', () => { + it('adds exp to user', () => { + const PREVIOUS_EXP = user.stats.exp; + + shared.content.spells.wizard.fireball.cast(user, user.todos[0]); + + expect(user.stats.exp).to.be.greaterThan(PREVIOUS_EXP); + }); + + it('levels up user if the gain in experience will level up the user', () => { + user.stats.exp = 399; + user.stats.lvl = 17; + + shared.content.spells.wizard.fireball.cast(user, user.todos[0]); + expect(user.stats.lvl).to.eql(18); + }); + + it('adds quest scroll to inventory when passing level milestone', () => { + user.stats.exp = 329; + user.stats.lvl = 14; + + expect(user.items.quests).to.not.have.property('atom1'); + + shared.content.spells.wizard.fireball.cast(user, user.todos[0]); + + expect(user.items.quests).to.have.property('atom1', 1); + }); + }); + }); +}); diff --git a/test/common/simulations/autoAllocate.js b/test/common/simulations/autoAllocate.js new file mode 100644 index 0000000000..0b0348efee --- /dev/null +++ b/test/common/simulations/autoAllocate.js @@ -0,0 +1,161 @@ +var $w, _, id, modes, shared, user; + +shared = require('../../../common/script/index.js'); + +_ = require('lodash'); + +$w = function(s) { + return s.split(' '); +}; + +id = shared.uuid(); + +user = { + stats: { + "class": 'warrior', + lvl: 1, + hp: 50, + gp: 0, + exp: 10, + per: 0, + int: 0, + con: 0, + str: 0, + buffs: { + per: 0, + int: 0, + con: 0, + str: 0 + }, + training: { + int: 0, + con: 0, + per: 0, + str: 0 + } + }, + preferences: { + automaticAllocation: false + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + achievements: {}, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + { + id: 'a', + value: 1, + type: 'habit', + attribute: 'str' + } + ], + dailys: [ + { + id: 'b', + value: 1, + type: 'daily', + attribute: 'str' + } + ], + todos: [ + { + id: 'c', + value: 1, + type: 'todo', + attribute: 'con' + }, { + id: 'd', + value: 1, + type: 'todo', + attribute: 'per' + }, { + id: 'e', + value: 1, + type: 'todo', + attribute: 'int' + } + ], + rewards: [] +}; + +modes = { + flat: _.cloneDeep(user), + classbased_warrior: _.cloneDeep(user), + classbased_rogue: _.cloneDeep(user), + classbased_wizard: _.cloneDeep(user), + classbased_healer: _.cloneDeep(user), + taskbased: _.cloneDeep(user) +}; + +modes.classbased_warrior.stats["class"] = 'warrior'; + +modes.classbased_rogue.stats["class"] = 'rogue'; + +modes.classbased_wizard.stats["class"] = 'wizard'; + +modes.classbased_healer.stats["class"] = 'healer'; + +_.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + _.merge(modes[mode].preferences, { + automaticAllocation: true, + allocationMode: mode.indexOf('classbased') === 0 ? 'classbased' : mode + }); + return shared.wrap(modes[mode]); +}); + +console.log("\n\n================================================"); + +console.log("New Simulation"); + +console.log("================================================\n\n"); + +_.times([20], function(lvl) { + console.log("[lvl " + lvl + "]\n--------------\n"); + return _.each($w('flat classbased_warrior classbased_rogue classbased_wizard classbased_healer taskbased'), function(mode) { + var str, u; + u = modes[mode]; + u.stats.exp = shared.tnl(lvl) + 1; + if (mode === 'taskbased') { + _.merge(u.stats, { + per: 0, + con: 0, + int: 0, + str: 0 + }); + } + u.habits[0].attribute = u.fns.randomVal({ + str: 'str', + int: 'int', + per: 'per', + con: 'con' + }); + u.ops.score({ + params: { + id: u.habits[0].id + }, + direction: 'up' + }); + u.fns.updateStats(u.stats); + str = mode + (mode === 'taskbased' ? " (" + u.habits[0].attribute + ")" : ""); + return console.log(str, _.pick(u.stats, $w('per int con str'))); + }); +}); diff --git a/test/common/simulations/passive_active_attrs.js b/test/common/simulations/passive_active_attrs.js new file mode 100644 index 0000000000..f69a2cafc2 --- /dev/null +++ b/test/common/simulations/passive_active_attrs.js @@ -0,0 +1,291 @@ +var _, clearUser, id, party, s, shared, task, user; + +shared = require('../../../common/script/index.js'); + +_ = require('lodash'); + +id = shared.uuid(); + +user = { + stats: { + "class": 'warrior', + buffs: { + per: 0, + int: 0, + con: 0, + str: 0 + } + }, + party: { + quest: { + key: 'evilsanta', + progress: { + up: 0, + down: 0 + } + } + }, + preferences: { + automaticAllocation: false + }, + achievements: {}, + flags: { + levelDrops: {} + }, + items: { + eggs: {}, + hatchingPotions: {}, + food: {}, + quests: {}, + gear: { + equipped: { + weapon: 'weapon_warrior_4', + armor: 'armor_warrior_4', + shield: 'shield_warrior_4', + head: 'head_warrior_4' + } + } + }, + habits: [ + shared.taskDefaults({ + id: id, + value: 0 + }) + ], + dailys: [ + { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + }, { + "text": "1" + } + ], + todos: [], + rewards: [] +}; + +shared.wrap(user); + +s = user.stats; + +task = user.tasks[id]; + +party = [user]; + +console.log("\n\n================================================"); + +console.log("New Simulation"); + +console.log("================================================\n\n"); + +clearUser = function(lvl) { + if (lvl == null) { + lvl = 1; + } + _.merge(user.stats, { + exp: 0, + gp: 0, + hp: 50, + lvl: lvl, + str: lvl * 1.5, + con: lvl * 1.5, + per: lvl * 1.5, + int: lvl * 1.5, + mp: 100 + }); + _.merge(s.buffs, { + str: 0, + con: 0, + int: 0, + per: 0 + }); + _.merge(user.party.quest.progress, { + up: 0, + down: 0 + }); + return user.items.lastDrop = { + count: 0 + }; +}; + +_.each([1, 25, 50, 75, 100], function(lvl) { + console.log("[LEVEL " + lvl + "] (" + (lvl * 2) + " points total in every attr)\n\n"); + _.each({ + red: -25, + yellow: 0, + green: 35 + }, function(taskVal, color) { + var _party, b4, str; + console.log("[task.value = " + taskVal + " (" + color + ")]"); + console.log("direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"); + _.each(['up', 'down'], function(direction) { + var b4, delta; + clearUser(lvl); + b4 = { + hp: s.hp, + taskVal: taskVal + }; + task.value = taskVal; + if (direction === 'up') { + task.type = 'daily'; + } + delta = user.ops.score({ + params: { + id: id, + direction: direction + } + }); + return console.log((direction === 'up' ? '↑' : '↓') + "\t\t" + s.exp + "/" + (shared.tnl(s.lvl)) + "\t\t" + ((b4.hp - s.hp).toFixed(1)) + "\t" + (s.gp.toFixed(1)) + "\t" + (delta.toFixed(1)) + "\t\t" + ((task.value - b4.taskVal - delta).toFixed(1)) + "\t\t\t" + (user.party.quest.progress.up.toFixed(1))); + }); + str = '- [Wizard]'; + task.value = taskVal; + clearUser(lvl); + b4 = { + taskVal: taskVal + }; + shared.content.spells.wizard.fireball.cast(user, task); + str += "\tfireball(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " bossHit:" + (user.party.quest.progress.up.toFixed(2)) + ")"; + task.value = taskVal; + clearUser(lvl); + _party = [ + user, { + stats: { + mp: 0 + } + } + ]; + shared.content.spells.wizard.mpheal.cast(user, _party); + str += "\t| mpheal(mp:" + _party[1].stats.mp + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.earth.cast(user, party); + str += "\t\t\t\t| earth(buffs.int:" + s.buffs.int + ")"; + s.buffs.int = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.wizard.frost.cast(user, {}); + str += "\t\t\t| frost(N/A)"; + console.log(str); + str = '- [Warrior]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.smash.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\tsmash(task.valΔ:" + ((task.value - taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.defensiveStance.cast(user, {}); + str += "\t\t| defensiveStance(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.valorousPresence.cast(user, party); + str += "\t\t\t| valorousPresence(buffs.str:" + s.buffs.str + ")"; + s.buffs.str = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.warrior.intimidate.cast(user, party); + str += "\t\t| intimidate(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + console.log(str); + str = '- [Rogue]'; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.pickPocket.cast(user, task); + str += "\tpickPocket(gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.backStab.cast(user, task); + b4 = { + taskVal: taskVal + }; + str += "\t\t| backStab(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + " exp:" + (s.exp.toFixed(1)) + " gp:" + (s.gp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.toolsOfTrade.cast(user, party); + str += "\t| toolsOfTrade(buffs.per:" + s.buffs.per + ")"; + s.buffs.per = 0; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.rogue.stealth.cast(user, {}); + str += "\t\t| stealth(avoiding " + user.stats.buffs.stealth + " tasks)"; + user.stats.buffs.stealth = 0; + console.log(str); + str = '- [Healer]'; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heal.cast(user, {}); + str += "\theal(hp:" + (s.hp.toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.brightness.cast(user, {}); + b4 = { + taskVal: taskVal + }; + str += "\t\t\t| brightness(task.valΔ:" + ((task.value - b4.taskVal).toFixed(1)) + ")"; + task.value = taskVal; + clearUser(lvl); + shared.content.spells.healer.protectAura.cast(user, party); + str += "\t\t\t| protectAura(buffs.con:" + s.buffs.con + ")"; + s.buffs.con = 0; + task.value = taskVal; + clearUser(lvl); + s.hp = 0; + shared.content.spells.healer.heallAll.cast(user, party); + str += "\t\t| heallAll(hp:" + (s.hp.toFixed(1)) + ")"; + console.log(str); + return console.log('\n'); + }); + return console.log('------------------------------------------------------------'); +}); + + +/* +_.each [1,25,50,75,100,125], (lvl) -> + console.log "[LEVEL #{lvl}] (#{lvl*2} points in every attr)\n\n" + _.each {red:-25,yellow:0,green:35}, (taskVal, color) -> + console.log "[task.value = #{taskVal} (#{color})]" + console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit" + _.each ['up','down'], (direction) -> + clearUser(lvl) + b4 = {hp:s.hp, taskVal} + task.value = taskVal + task.type = 'daily' if direction is 'up' + delta = user.ops.score params:{id, direction} + console.log "#{if direction is 'up' then '↑' else '↓'}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}\t\t\t#{user.party.quest.progress.up.toFixed(1)}" + + task.value = taskVal;clearUser(lvl) + shared.content.spells.rogue.stealth.cast(user,{}) + console.log "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)" + user.stats.buffs.stealth = 0 + + console.log user.dailys.length + */ diff --git a/test/common/user.fns.buy.test.js b/test/common/user.fns.buy.test.js new file mode 100644 index 0000000000..0cf39c1eb0 --- /dev/null +++ b/test/common/user.fns.buy.test.js @@ -0,0 +1,288 @@ +/* eslint-disable camelcase */ + +import sinon from 'sinon'; // eslint-disable-line no-shadow + +let shared = require('../../common/script/index.js'); + +describe('user.fns.buy', () => { + let user; + + beforeEach(() => { + user = { + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + equipped: { + weapon_warrior_0: true, + }, + }, + }, + preferences: {}, + stats: { gp: 200 }, + achievements: { }, + flags: { }, + }; + + shared.wrap(user); + + sinon.stub(user.fns, 'randomVal'); + sinon.stub(user.fns, 'predictableRandom'); + }); + + afterEach(() => { + user.fns.randomVal.restore(); + user.fns.predictableRandom.restore(); + }); + + context('Potion', () => { + it('recovers 15 hp', () => { + user.stats.hp = 30; + user.ops.buy({params: {key: 'potion'}}); + expect(user.stats.hp).to.eql(45); + }); + + it('does not increase hp above 50', () => { + user.stats.hp = 45; + user.ops.buy({params: {key: 'potion'}}); + expect(user.stats.hp).to.eql(50); + }); + + it('deducts 25 gp', () => { + user.stats.hp = 45; + user.ops.buy({params: {key: 'potion'}}); + + expect(user.stats.gp).to.eql(175); + }); + + it('does not purchase if not enough gp', () => { + user.stats.hp = 45; + user.stats.gp = 5; + user.ops.buy({params: {key: 'potion'}}); + + expect(user.stats.hp).to.eql(45); + expect(user.stats.gp).to.eql(5); + }); + }); + + context('Gear', () => { + it('adds equipment to inventory', () => { + user.stats.gp = 31; + + user.ops.buy({params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true }); + }); + + it('deducts gold from user', () => { + user.stats.gp = 31; + + user.ops.buy({params: {key: 'armor_warrior_1'}}); + + expect(user.stats.gp).to.eql(1); + }); + + it('auto equips equipment if user has auto-equip preference turned on', () => { + user.stats.gp = 31; + user.preferences.autoEquip = true; + + user.ops.buy({params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1'); + }); + + it('buys equipment but does not auto-equip', () => { + user.stats.gp = 31; + user.preferences.autoEquip = false; + + user.ops.buy({params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.equipped).to.not.have.property('armor'); + }); + + it('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => { + user.stats.gp = 100; + user.preferences.autoEquip = true; + user.ops.buy({params: {key: 'shield_warrior_1'}}); + user.ops.equip({params: {key: 'shield_warrior_1'}}); + user.ops.buy({params: {key: 'weapon_warrior_1'}}); + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + + user.ops.buy({params: {key: 'weapon_wizard_1'}}); + + expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0'); + expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1'); + }); + + it('buys two-handed equipment but does not automatically remove sword or shield', () => { + user.stats.gp = 100; + user.preferences.autoEquip = false; + user.ops.buy({params: {key: 'shield_warrior_1'}}); + user.ops.equip({params: {key: 'shield_warrior_1'}}); + user.ops.buy({params: {key: 'weapon_warrior_1'}}); + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + + user.ops.buy({params: {key: 'weapon_wizard_1'}}); + + expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1'); + expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1'); + }); + + it('does not buy equipment without enough Gold', () => { + user.stats.gp = 20; + + user.ops.buy({params: {key: 'armor_warrior_1'}}); + + expect(user.items.gear.owned).to.not.have.property('armor_warrior_1'); + }); + }); + + context('Quests', () => { + it('buys a Quest scroll'); + + it('does not buy Quests without enough Gold'); + + it('does not buy nonexistent Quests'); + + it('does not buy Gem-premium Quests'); + }); + + context('Enchanted Armoire', () => { + let YIELD_EQUIPMENT = 0.5; + let YIELD_FOOD = 0.7; + let YIELD_EXP = 0.9; + + let fullArmoire = {}; + + _(shared.content.gearTypes).each((type) => { + _(shared.content.gear.tree[type].armoire).each((gearObject) => { + let armoireKey = gearObject.key; + + fullArmoire[armoireKey] = true; + }).value(); + }).value(); + + beforeEach(() => { + user.achievements.ultimateGearSets = { rogue: true }; + user.flags.armoireOpened = true; + user.stats.exp = 0; + user.items.food = {}; + }); + + context('failure conditions', () => { + it('does not open if user does not have enough gold', (done) => { + user.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.stats.gp = 50; + + user.ops.buy({params: {key: 'armoire'}}, (response) => { + expect(response.message).to.eql('Not Enough Gold'); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + done(); + }); + }); + + it('does not open without Ultimate Gear achievement', (done) => { + user.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false}; + + user.ops.buy({params: {key: 'armoire'}}, (response) => { + expect(response.message).to.eql('You can\'t buy this item'); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + done(); + }); + }); + }); + + context('non-gear awards', () => { + it('gives Experience', () => { + user.fns.predictableRandom.returns(YIELD_EXP); + + user.ops.buy({params: {key: 'armoire'}}); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(46); + expect(user.stats.gp).to.eql(100); + }); + + it('gives food', () => { + let honey = shared.content.food.Honey; + + user.fns.randomVal.returns(honey); + user.fns.predictableRandom.returns(YIELD_FOOD); + + user.ops.buy({params: {key: 'armoire'}}); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + expect(user.items.food).to.eql({Honey: 1}); + expect(user.stats.exp).to.eql(0); + expect(user.stats.gp).to.eql(100); + }); + + it('does not give equipment if all equipment has been found', () => { + user.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.items.gear.owned = fullArmoire; + user.stats.gp = 150; + + user.ops.buy({params: {key: 'armoire'}}); + + expect(user.items.gear.owned).to.eql(fullArmoire); + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(0); + + expect(user.stats.exp).to.eql(30); + expect(user.stats.gp).to.eql(50); + }); + }); + + context('gear awards', () => { + beforeEach(() => { + let shield = shared.content.gear.tree.shield.armoire.gladiatorShield; + + user.fns.randomVal.returns(shield); + }); + + it('always drops equipment the first time', () => { + delete user.flags.armoireOpened; + user.fns.predictableRandom.returns(YIELD_EXP); + + user.ops.buy({params: {key: 'armoire'}}); + + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + shield_armoire_gladiatorShield: true, + }); + + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 1); + expect(user.items.food).to.be.empty; + expect(user.stats.exp).to.eql(0); + expect(user.stats.gp).to.eql(100); + }); + + it('gives more equipment', () => { + user.fns.predictableRandom.returns(YIELD_EQUIPMENT); + user.items.gear.owned = { + weapon_warrior_0: true, + head_armoire_hornedIronHelm: true, + }; + user.stats.gp = 200; + + user.ops.buy({params: {key: 'armoire'}}); + + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true}); + let armoireCount = shared.count.remainingGearInSet(user.items.gear.owned, 'armoire'); + + expect(armoireCount).to.eql(_.size(fullArmoire) - 2); + expect(user.stats.gp).to.eql(100); + }); + }); + }); +}); diff --git a/test/common/user.fns.ultimateGear.test.js b/test/common/user.fns.ultimateGear.test.js new file mode 100644 index 0000000000..a95cb403d9 --- /dev/null +++ b/test/common/user.fns.ultimateGear.test.js @@ -0,0 +1,37 @@ +/* eslint-disable camelcase */ + +let shared = require('../../common/script/index.js'); + +shared.i18n.translations = require('../../website/src/libs/i18n.js').translations; + +require('./test_helper'); + +describe('User.fns.ultimateGear', () => { + it('sets armoirEnabled when partial achievement already achieved', () => { + let items = { + gear: { + owned: { + toObject: () => { + return { + armor_warrior_5: true, + shield_warrior_5: true, + head_warrior_5: true, + weapon_warrior_6: true, + }; + }, + }, + }, + }; + + let user = shared.wrap({ + items, + achievements: { + ultimateGearSets: {}, + }, + flags: {}, + }); + + user.fns.ultimateGear(); + expect(user.flags.armoireEnabled).to.equal(true); + }); +}); diff --git a/test/common/user.fns.updateStats.test.js b/test/common/user.fns.updateStats.test.js new file mode 100644 index 0000000000..fbad57e531 --- /dev/null +++ b/test/common/user.fns.updateStats.test.js @@ -0,0 +1,134 @@ +import { + generateUser, +} from '../helpers/common.helper'; + +describe('user.fns.updateStats', () => { + let user; + + beforeEach(() => { + user = generateUser({}); + }); + + context('No Hp', () => { + it('returns 0 if user\'s hp is 0', () => { + let stats = { + hp: 0, + }; + + expect(user.fns.updateStats(stats)).to.eql(0); + }); + + it('returns 0 if user\'s hp is less than 0', () => { + let stats = { + hp: -5, + }; + + expect(user.fns.updateStats(stats)).to.eql(0); + }); + + it('sets user\'s hp to 0 if it is less than 0', () => { + let stats = { + hp: -5, + }; + + user.fns.updateStats(stats); + + expect(user.stats.hp).to.eql(0); + }); + }); + + context('Stat Allocation', () => { + it('adds only attribute points up to user\'s level', () => { + let stats = { + exp: 261, + }; + + user.stats.lvl = 10; + + user.fns.updateStats(stats); + + expect(user.stats.points).to.eql(11); + }); + + it('adds an attibute point when user\'s stat points are less than max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 24; + + user.fns.updateStats(stats); + + expect(user.stats.points).to.eql(1); + }); + + it('does not add an attibute point when user\'s stat points are equal to max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 25; + + user.fns.updateStats(stats); + + expect(user.stats.points).to.eql(0); + }); + + it('does not add an attibute point when user\'s stat points + unallocated points are equal to max level', () => { + let stats = { + exp: 3581, + }; + + user.stats.lvl = 99; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 15; + user.stats.points = 10; + + user.fns.updateStats(stats); + + expect(user.stats.points).to.eql(10); + }); + + it('only awards stat points up to level 100 if user is missing unallocated stat points and is over level 100', () => { + let stats = { + exp: 5581, + }; + + user.stats.lvl = 104; + user.stats.str = 25; + user.stats.int = 25; + user.stats.con = 25; + user.stats.per = 15; + user.stats.points = 0; + + user.fns.updateStats(stats); + + expect(user.stats.points).to.eql(10); + }); + + // @TODO: Set up sinon sandbox + xit('auto allocates stats if automaticAllocation is turned on', () => { + sandbox.stub(user.fns, 'autoAllocate'); + + let stats = { + exp: 261, + }; + + user.stats.lvl = 10; + + user.fns.updateStats(stats); + + expect(user.fns.autoAllocate).to.be.calledOnce; + }); + }); +}); diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js new file mode 100644 index 0000000000..8c7899cf50 --- /dev/null +++ b/test/common/user.ops.buyMysterySet.test.js @@ -0,0 +1,77 @@ +/* eslint-disable camelcase */ + +let shared = require('../../common/script/index.js'); + +describe('user.ops.buyMysterySet', () => { + let user; + + beforeEach(() => { + user = { + items: { + gear: { + owned: { + weapon_warrior_0: true, + }, + }, + }, + purchased: { + plan: { + consecutive: { + trinkets: 0, + }, + }, + }, + }; + + shared.wrap(user); + }); + + context('Mystery Sets', () => { + context('failure conditions', () => { + it('does not grant mystery sets without Mystic Hourglasses', (done) => { + user.ops.buyMysterySet({params: {key: '201501'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); + expect(user.items.gear.owned).to.eql({weapon_warrior_0: true}); + done(); + }); + }); + + it('does not grant mystery set that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.gear.owned = { + weapon_warrior_0: true, + weapon_mystery_301404: true, + armor_mystery_301404: true, + head_mystery_301404: true, + eyewear_mystery_301404: true, + }; + + user.ops.buyMysterySet({params: {key: '301404'}}, (response) => { + expect(response.message).to.eql('Mystery set not found, or set already owned'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + }); + + context('successful purchases', () => { + it('buys Steampunk Accessories Set', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.buyMysterySet({params: {key: '301404'}}, () => { + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + weapon_mystery_301404: true, + armor_mystery_301404: true, + head_mystery_301404: true, + eyewear_mystery_301404: true, + }); + + done(); + }); + }); + }); + }); +}); + diff --git a/test/common/user.ops.equip.test.js b/test/common/user.ops.equip.test.js new file mode 100644 index 0000000000..62f7956e02 --- /dev/null +++ b/test/common/user.ops.equip.test.js @@ -0,0 +1,102 @@ +/* eslint-disable camelcase */ + +import sinon from 'sinon'; // eslint-disable-line no-shadow +import {assert} from 'sinon'; +import i18n from '../../common/script/i18n'; +import shared from '../../common/script/index.js'; +import content from '../../common/script/content/index'; + +describe('user.ops.equip', () => { + let user; + let spy; + + beforeEach(() => { + user = { + items: { + gear: { + owned: { + weapon_warrior_0: true, + weapon_warrior_1: true, + weapon_warrior_2: true, + weapon_wizard_1: true, + weapon_wizard_2: true, + shield_base_0: true, + shield_warrior_1: true, + }, + equipped: { + weapon: 'weapon_warrior_0', + shield: 'shield_base_0', + }, + }, + }, + preferences: {}, + stats: {gp: 200}, + achievements: {}, + flags: {}, + }; + + shared.wrap(user); + spy = sinon.spy(); + }); + + context('Gear', () => { + it('should not send a message if a weapon is equipped while only having zero or one weapons equipped', () => { + // user.ops.equip always calls the callback, even if it isn't sending a message + // so we need to check to see if a single null message was sent. + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + + // one-handed to one-handed + user.ops.equip({params: {key: 'weapon_warrior_2'}}, spy); + + assert.calledOnce(spy); + assert.calledWith(spy, null); + spy.reset(); + + // one-handed to two-handed + user.ops.equip({params: {key: 'weapon_wizard_1'}}, spy); + assert.calledOnce(spy); + assert.calledWith(spy, null); + spy.reset(); + + // two-handed to two-handed + user.ops.equip({params: {key: 'weapon_wizard_2'}}, spy); + assert.calledOnce(spy); + assert.calledWith(spy, null); + spy.reset(); + + // two-handed to one-handed + user.ops.equip({params: {key: 'weapon_warrior_2'}}, spy); + assert.calledOnce(spy); + assert.calledWith(spy, null); + spy.reset(); + }); + + it('should send messages if equipping a two-hander causes the off-hander to be unequipped', () => { + user.ops.equip({params: {key: 'weapon_warrior_1'}}); + user.ops.equip({params: {key: 'shield_warrior_1'}}); + + // equipping two-hander + user.ops.equip({params: {key: 'weapon_wizard_1'}}, spy); + let weapon = content.gear.flat.weapon_wizard_1; + let item = content.gear.flat.shield_warrior_1; + let message = i18n.t('messageTwoHandedEquip', {twoHandedText: weapon.text(null), offHandedText: item.text(null)}); + + assert.calledOnce(spy); + assert.calledWith(spy, {code: 200, message}); + }); + + it('should send messages if equipping an off-hand item causes a two-handed weapon to be unequipped', () => { + // equipping two-hander + user.ops.equip({params: {key: 'weapon_wizard_1'}}); + let weapon = content.gear.flat.weapon_wizard_1; + let shield = content.gear.flat.shield_warrior_1; + + user.ops.equip({params: {key: 'shield_warrior_1'}}, spy); + + let message = i18n.t('messageTwoHandedUnequip', {twoHandedText: weapon.text(null), offHandedText: shield.text(null)}); + + assert.calledOnce(spy); + assert.calledWith(spy, {code: 200, message}); + }); + }); +}); diff --git a/test/common/user.ops.hatch.js b/test/common/user.ops.hatch.js new file mode 100644 index 0000000000..573103a360 --- /dev/null +++ b/test/common/user.ops.hatch.js @@ -0,0 +1,129 @@ +let shared = require('../../common/script/index.js'); + +describe('user.ops.hatch', () => { + let user; + + beforeEach(() => { + user = { + items: { + eggs: {}, + hatchingPotions: {}, + pets: {}, + }, + }; + + shared.wrap(user); + }); + + context('Pet Hatching', () => { + context('failure conditions', () => { + it('does not allow hatching without specifying egg and potion', (done) => { + user.ops.hatch({params: {}}, (response) => { + expect(response.message).to.eql('Please specify query.egg & query.hatchingPotion'); + expect(user.items.pets).to.be.empty; + done(); + }); + }); + + it('does not allow hatching if user lacks specified egg', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Dragon', hatchingPotion: 'Base'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); + expect(user.items.pets).to.be.empty; + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); + done(); + }); + }); + + it('does not allow hatching if user lacks specified hatching potion', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Golden'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageMissingEggPotion')); + expect(user.items.pets).to.be.empty; + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); + done(); + }); + }); + + it('does not allow hatching if user already owns target pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': 10}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageAlreadyPet')); + expect(user.items.pets).to.eql({'Wolf-Base': 10}); + expect(user.items.eggs).to.eql({Wolf: 1}); + expect(user.items.hatchingPotions).to.eql({Base: 1}); + done(); + }); + }); + + it('does not allow hatching quest pet egg using premium potion', (done) => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Spooky'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageInvalidEggPotionCombo')); + expect(user.items.pets).to.be.empty; + expect(user.items.eggs).to.eql({Cheetah: 1}); + expect(user.items.hatchingPotions).to.eql({Spooky: 1}); + done(); + }); + }); + }); + + context('successful hatching', () => { + it('hatches a basic pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageHatched')); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + done(); + }); + }); + + it('hatches a quest pet', (done) => { + user.items.eggs = {Cheetah: 1}; + user.items.hatchingPotions = {Base: 1}; + user.ops.hatch({params: {egg: 'Cheetah', hatchingPotion: 'Base'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageHatched')); + expect(user.items.pets).to.eql({'Cheetah-Base': 5}); + expect(user.items.eggs).to.eql({Cheetah: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + done(); + }); + }); + + it('hatches a premium pet', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Spooky: 1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Spooky'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageHatched')); + expect(user.items.pets).to.eql({'Wolf-Spooky': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Spooky: 0}); + done(); + }); + }); + + it('hatches a pet previously raised to a mount', (done) => { + user.items.eggs = {Wolf: 1}; + user.items.hatchingPotions = {Base: 1}; + user.items.pets = {'Wolf-Base': -1}; + user.ops.hatch({params: {egg: 'Wolf', hatchingPotion: 'Base'}}, (response) => { + expect(response.message).to.eql(shared.i18n.t('messageHatched')); + expect(user.items.pets).to.eql({'Wolf-Base': 5}); + expect(user.items.eggs).to.eql({Wolf: 0}); + expect(user.items.hatchingPotions).to.eql({Base: 0}); + done(); + }); + }); + }); + }); +}); diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js new file mode 100644 index 0000000000..dbb6f877e2 --- /dev/null +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -0,0 +1,122 @@ +let shared = require('../../common/script/index.js'); + +describe('user.ops.hourglassPurchase', () => { + let user; + + beforeEach(() => { + user = { + items: { + pets: {}, + mounts: {}, + hatchingPotions: {}, + }, + purchased: { + plan: { + consecutive: { + trinkets: 0, + }, + }, + }, + }; + + shared.wrap(user); + }); + + context('Time Travel Stable', () => { + context('failure conditions', () => { + it('does not allow purchase of unsupported item types', (done) => { + user.ops.hourglassPurchase({params: {type: 'hatchingPotions', key: 'Base'}}, (response) => { + expect(response.message).to.eql('Item type not supported for purchase with Mystic Hourglass. Allowed types: pets,mounts'); + expect(user.items.hatchingPotions).to.eql({}); + done(); + }); + }); + + it('does not grant pets without Mystic Hourglasses', (done) => { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); + expect(user.items.pets).to.eql({}); + done(); + }); + }); + + it('does not grant mounts without Mystic Hourglasses', (done) => { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('You don\'t have enough Mystic Hourglasses.'); + expect(user.items.mounts).to.eql({}); + done(); + }); + }); + + it('does not grant pet that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.pets = { + 'MantisShrimp-Base': true, + }; + + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Pet already owned.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant mount that has already been purchased', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + user.items.mounts = { + 'MantisShrimp-Base': true, + }; + + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Mount already owned.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant pet that is not part of the Time Travel Stable', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.hourglassPurchase({params: {type: 'pets', key: 'Wolf-Veteran'}}, (response) => { + expect(response.message).to.eql('Pet not available for purchase with Mystic Hourglass.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant mount that is not part of the Time Travel Stable', (done) => { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'Orca-Base'}}, (response) => { + expect(response.message).to.eql('Mount not available for purchase with Mystic Hourglass.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + }); + + context('successful purchases', () => { + it('buys a pet', (done) => { + user.purchased.plan.consecutive.trinkets = 2; + + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.pets).to.eql({'MantisShrimp-Base': 5}); + done(); + }); + }); + + it('buys a mount', (done) => { + user.purchased.plan.consecutive.trinkets = 2; + + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => { + expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); + done(); + }); + }); + }); + }); +}); diff --git a/test/common/user.ops.test.js b/test/common/user.ops.test.js new file mode 100644 index 0000000000..f4e4bb82c9 --- /dev/null +++ b/test/common/user.ops.test.js @@ -0,0 +1,34 @@ +let shared = require('../../common/script/index.js'); + +describe('user.ops', () => { + let user; + + beforeEach(() => { + user = { + items: { + gear: { }, + special: { }, + }, + achievements: { }, + flags: { }, + }; + + shared.wrap(user); + }); + + describe('readCard', () => { + it('removes card from invitation array', () => { + user.items.special.valentineReceived = ['Leslie']; + user.ops.readCard({ params: { cardType: 'valentine' } }); + + expect(user.items.special.valentineReceived).to.be.empty; + }); + + it('removes the first card from invitation array', () => { + user.items.special.valentineReceived = ['Leslie', 'Vicky']; + user.ops.readCard({ params: { cardType: 'valentine' } }); + + expect(user.items.special.valentineReceived).to.eql(['Vicky']); + }); + }); +}); diff --git a/test/helpers/api-integration/api-classes.js b/test/helpers/api-integration/api-classes.js index f584826520..3fe7adbca7 100644 --- a/test/helpers/api-integration/api-classes.js +++ b/test/helpers/api-integration/api-classes.js @@ -4,7 +4,7 @@ import { requester } from './requester'; import { getDocument as getDocumentFromMongo, updateDocument as updateDocumentInMongo, -} from '../mongo'; +} from './mongo'; import { assign, each, @@ -59,29 +59,6 @@ export class ApiGroup extends ApiObject { this._docType = 'groups'; } - - async addChat (chat) { - let group = this; - - if (!chat) { - chat = { - id: 'Test_ID', - text: 'Test message', - flagCount: 0, - timestamp: Date(), - likes: {}, - flags: {}, - uuid: group.leader, - contributor: {}, - backer: {}, - user: group.leader, - }; - } - - let update = { chat }; - - return await this.update(update); - } } export class ApiChallenge extends ApiObject { diff --git a/test/helpers/api-integration/mongo.js b/test/helpers/api-integration/mongo.js new file mode 100644 index 0000000000..52be5263b4 --- /dev/null +++ b/test/helpers/api-integration/mongo.js @@ -0,0 +1,92 @@ +/* eslint-disable no-use-before-define */ + +import { MongoClient as mongo } from 'mongodb'; + +const DB_URI = 'mongodb://localhost/habitrpg_test'; + +// Useful for checking things that have been deleted, +// but you no longer have access to, +// like private parties or users +export async function checkExistence (collectionName, id) { + let db = await connectToMongo(); + + return new Promise((resolve, reject) => { + let collection = db.collection(collectionName); + + collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { + if (findError) return reject(findError); + + let exists = docs.length > 0; + + db.close(); + resolve(exists); + }); + }); +} + +// Specifically helpful for the GET /groups tests, +// resets the db to an empty state and creates a tavern document +export async function resetHabiticaDB () { + let db = await connectToMongo(); + + return new Promise((resolve, reject) => { + db.dropDatabase((dbErr) => { + if (dbErr) return reject(dbErr); + let groups = db.collection('groups'); + + groups.insertOne({ + _id: 'habitrpg', + chat: [], + leader: '9', + name: 'HabitRPG', + type: 'guild', + privacy: 'public', + members: [], + }, (insertErr) => { + if (insertErr) return reject(insertErr); + + db.close(); + resolve(); + }); + }); + }); +} + +export async function updateDocument (collectionName, doc, update) { + let db = await connectToMongo(); + + let collection = db.collection(collectionName); + + return new Promise((resolve) => { + collection.updateOne({ _id: doc._id }, { $set: update }, (updateErr) => { + if (updateErr) throw new Error(`Error updating ${collectionName}: ${updateErr}`); + db.close(); + resolve(); + }); + }); +} + +export async function getDocument (collectionName, doc) { + let db = await connectToMongo(); + + let collection = db.collection(collectionName); + + return new Promise((resolve) => { + collection.findOne({ _id: doc._id }, (lookupErr, found) => { + if (lookupErr) throw new Error(`Error looking up ${collectionName}: ${lookupErr}`); + db.close(); + resolve(found); + }); + }); +} + +export function connectToMongo () { + return new Promise((resolve, reject) => { + mongo.connect(DB_URI, (err, db) => { + if (err) return reject(err); + + resolve(db); + }); + }); +} + diff --git a/test/helpers/api-integration/requester.js b/test/helpers/api-integration/requester.js index 312942c194..209d312ba1 100644 --- a/test/helpers/api-integration/requester.js +++ b/test/helpers/api-integration/requester.js @@ -1,18 +1,14 @@ /* eslint-disable no-use-before-define */ import superagent from 'superagent'; -import nconf from 'nconf'; -import { isEmpty, cloneDeep } from 'lodash'; -const API_TEST_SERVER_PORT = nconf.get('PORT'); +const API_TEST_SERVER_PORT = 3003; let apiVersion; -// Sets up an object that can make all REST requests +// Sets up an abject that can make all REST requests // If a user is passed in, the uuid and api token of // the user are used to make the requests -export function requester (user = {}, additionalSets = {}) { - additionalSets = cloneDeep(additionalSets); // cloning because it could be modified later to set cookie - +export function requester (user = {}, additionalSets) { return { get: _requestMaker(user, 'get', additionalSets), post: _requestMaker(user, 'post', additionalSets), @@ -25,21 +21,12 @@ requester.setApiVersion = (version) => { apiVersion = version; }; -function _requestMaker (user, method, additionalSets = {}) { +function _requestMaker (user, method, additionalSets) { if (!apiVersion) throw new Error('apiVersion not set'); return (route, send, query) => { return new Promise((resolve, reject) => { - let url = `http://localhost:${API_TEST_SERVER_PORT}`; - - // do not prefix with api/apiVersion requests to top level routes like dataexport, payments and emails - if (route.indexOf('/email') === 0 || route.indexOf('/export') === 0 || route.indexOf('/paypal') === 0 || route.indexOf('/amazon') === 0 || route.indexOf('/stripe') === 0) { - url += `${route}`; - } else { - url += `/api/${apiVersion}${route}`; - } - - let request = superagent[method](url) + let request = superagent[method](`http://localhost:${API_TEST_SERVER_PORT}/api/${apiVersion}${route}`) .accept('application/json'); if (user && user._id && user.apiToken) { @@ -48,7 +35,7 @@ function _requestMaker (user, method, additionalSets = {}) { .set('x-api-key', user.apiToken); } - if (!isEmpty(additionalSets)) { + if (additionalSets) { request.set(additionalSets); } @@ -59,58 +46,14 @@ function _requestMaker (user, method, additionalSets = {}) { if (err) { if (!err.response) return reject(err); - let parsedError = _parseError(err); - - return reject(parsedError); + return reject({ + code: err.status, + text: err.response.body.err, + }); } - resolve(_parseRes(response)); + resolve(response.body); }); }); }; } - -function _parseRes (res) { - let contentType = res.headers['content-type'] || ''; - let contentDisposition = res.headers['content-disposition'] || ''; - - if (contentType.indexOf('json') === -1) { // not a json response - return res.text; - } - - if (contentDisposition.indexOf('attachment') !== -1) { - return res.body; - } - - if (apiVersion === 'v2') { - return res.body; - } else if (apiVersion === 'v3') { - if (res.body.message) { - return { - data: res.body.data, - message: res.body.message, - }; - } else { - return res.body.data; - } - } -} - -function _parseError (err) { - let parsedError; - - if (apiVersion === 'v2') { - parsedError = { - code: err.status, - text: err.response.body.err, - }; - } else if (apiVersion === 'v3') { - parsedError = { - code: err.status, - error: err.response.body.error, - message: err.response.body.message, - }; - } - - return parsedError; -} diff --git a/test/helpers/api-integration/translate.js b/test/helpers/api-integration/translate.js index 3ef7d68541..4507d8fceb 100644 --- a/test/helpers/api-integration/translate.js +++ b/test/helpers/api-integration/translate.js @@ -1,5 +1,5 @@ import i18n from '../../../common/script/i18n'; -i18n.translations = require('../../../website/server/libs/api-v3/i18n').translations; +i18n.translations = require('../../../website/src/libs/i18n.js').translations; // Use this to verify error messages returned by the server // That way, if the translated string changes, the test @@ -16,3 +16,4 @@ export function translate (key, variables) { return translatedString; } + diff --git a/test/helpers/api-integration/v2/index.js b/test/helpers/api-integration/v2/index.js index 8828b9b8ed..1d0baeea4b 100644 --- a/test/helpers/api-integration/v2/index.js +++ b/test/helpers/api-integration/v2/index.js @@ -4,5 +4,5 @@ requester.setApiVersion('v2'); export { requester }; export { translate } from '../translate'; -export { checkExistence, resetHabiticaDB } from '../../mongo'; +export { checkExistence, resetHabiticaDB } from '../mongo'; export * from './object-generators'; diff --git a/test/helpers/api-integration/v2/object-generators.js b/test/helpers/api-integration/v2/object-generators.js index 3cd3c7d1d7..e10a4daa9a 100644 --- a/test/helpers/api-integration/v2/object-generators.js +++ b/test/helpers/api-integration/v2/object-generators.js @@ -1,8 +1,7 @@ import { times, - map, } from 'lodash'; -import Bluebird from 'bluebird'; +import Q from 'q'; import { v4 as generateUUID } from 'uuid'; import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; import { requester } from '../requester'; @@ -42,29 +41,11 @@ export async function generateGroup (leader, details = {}, update = {}) { details.privacy = details.privacy || 'private'; details.name = details.name || 'test group'; - let members; - - if (details.members) { - members = details.members; - delete details.members; - } - let group = await leader.post('/groups', details); let apiGroup = new ApiGroup(group); - const groupMembershipTypes = { - party: { 'party._id': group._id}, - guild: { guilds: [group._id] }, - }; - - await Bluebird.all( - map(members, (member) => { - return member.update(groupMembershipTypes[group.type]); - }) - ); - await apiGroup.update(update); - await apiGroup.sync(); + return apiGroup; } @@ -91,20 +72,20 @@ export async function createAndPopulateGroup (settings = {}) { let groupLeader = await generateUser(leaderDetails); let group = await generateGroup(groupLeader, groupDetails); - const groupMembershipTypes = { - party: { 'party._id': group._id}, - guild: { guilds: [group._id] }, - }; - - let members = await Bluebird.all( + let members = await Q.all( times(numberOfMembers, () => { - return generateUser(groupMembershipTypes[group.type]); + return generateUser(); }) ); - await group.update({ memberCount: numberOfMembers + 1}); + let memberIds = members.map((member) => { + return member._id; + }); + memberIds.push(groupLeader._id); - let invitees = await Bluebird.all( + await group.update({ members: memberIds }); + + let invitees = await Q.all( times(numberOfInvites, () => { return generateUser(); }) @@ -116,7 +97,7 @@ export async function createAndPopulateGroup (settings = {}) { }); }); - await Bluebird.all(invitationPromises); + await Q.all(invitationPromises); return { groupLeader, diff --git a/test/helpers/api-integration/v3/index.js b/test/helpers/api-integration/v3/index.js deleted file mode 100644 index 6ae15d7ca6..0000000000 --- a/test/helpers/api-integration/v3/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/* eslint-disable no-use-before-define */ - -// Import requester function, set it up for v2, export it -import { requester } from '../requester'; -requester.setApiVersion('v3'); -export { requester }; - -export { translate } from '../translate'; -export { checkExistence, resetHabiticaDB } from '../../mongo'; -export * from './object-generators'; -export { sleep } from '../../sleep'; diff --git a/test/helpers/api-integration/v3/object-generators.js b/test/helpers/api-integration/v3/object-generators.js deleted file mode 100644 index f257c3543f..0000000000 --- a/test/helpers/api-integration/v3/object-generators.js +++ /dev/null @@ -1,157 +0,0 @@ -import { - times, -} from 'lodash'; -import Bluebird from 'bluebird'; -import { v4 as generateUUID } from 'uuid'; -import { ApiUser, ApiGroup, ApiChallenge } from '../api-classes'; -import { requester } from '../requester'; -import * as Tasks from '../../../../website/server/models/task'; - -// Creates a new user and returns it -// If you need the user to have specific requirements, -// such as a balance > 0, just pass in the adjustment -// to the update object. If you want to adjust a nested -// paramter, 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 = generateUUID(); - let password = 'password'; - let email = `${username}@example.com`; - - let user = await requester().post('/user/auth/local/register', { - username, - email, - password, - confirmPassword: password, - }); - - let apiUser = new ApiUser(user); - - await apiUser.update(update); - - return apiUser; -} - -export async function generateHabit (update = {}) { - let type = 'habit'; - let task = new Tasks[type](update); - await task.save({ validateBeforeSave: false }); - return task; -} - -export async function generateDaily (update = {}) { - let type = 'daily'; - let task = new Tasks[type](update); - await task.save({ validateBeforeSave: false }); - return task; -} - -export async function generateReward (update = {}) { - let type = 'reward'; - let task = new Tasks[type](update); - await task.save({ validateBeforeSave: false }); - return task; -} - -export async function generateTodo (update = {}) { - let type = 'todo'; - let task = new Tasks[type](update); - await task.save({ validateBeforeSave: false }); - return task; -} - -// Generates a new group. Requires a user object, which -// will will become the groups leader. Takes a details argument -// for the initial group creation and an update argument which -// will update the group via the db -export async function generateGroup (leader, details = {}, update = {}) { - details.type = details.type || 'party'; - details.privacy = details.privacy || 'private'; - details.name = details.name || 'test group'; - - let group = await leader.post('/groups', details); - let apiGroup = new ApiGroup(group); - - await apiGroup.update(update); - - return apiGroup; -} - -// This is generate group + the ability to create -// real users to populate it. The settings object -// takes in: -// members: Number - the number of group members to create. Defaults to 0. -// inivtes: Number - the number of users to create and invite to the group. Defaults to 0. -// groupDetails: Object - how to initialize the group -// leaderDetails: Object - defaults for the leader, defaults with a gem balance so the user -// can create the group -// -// Returns an object with -// members: an array of user objects that correspond to the members of the group -// invitees: an array of user objects that correspond to the invitees of the group -// leader: the leader user object -// group: the group object -export async function createAndPopulateGroup (settings = {}) { - let numberOfMembers = settings.members || 0; - let numberOfInvites = settings.invites || 0; - let groupDetails = settings.groupDetails; - let leaderDetails = settings.leaderDetails || { balance: 10 }; - - let groupLeader = await generateUser(leaderDetails); - let group = await generateGroup(groupLeader, groupDetails); - - const groupMembershipTypes = { - party: { 'party._id': group._id}, - guild: { guilds: [group._id] }, - }; - - let members = await Bluebird.all( - times(numberOfMembers, () => { - return generateUser(groupMembershipTypes[group.type]); - }) - ); - - await group.update({ memberCount: numberOfMembers + 1}); - - let invitees = await Bluebird.all( - times(numberOfInvites, () => { - return generateUser(); - }) - ); - - let invitationPromises = invitees.map((invitee) => { - return groupLeader.post(`/groups/${group._id}/invite`, { - uuids: [invitee._id], - }); - }); - - await Bluebird.all(invitationPromises); - - return { - groupLeader, - group, - members, - invitees, - }; -} - -// Generates a new challenge. Requires an ApiUser object and a -// group-like object (can just be {_id: 'your-group-id'}). The group -// will will become the group that owns the challenge. It takes an -// optional details argument for the initial challenge creation and an -// optional update argument which will update the challenge via the db -export async function generateChallenge (challengeCreator, group, details = {}, update = {}) { - details.group = group._id; - details.name = details.name || 'a challenge'; - details.shortName = details.shortName || 'aChallenge'; - details.prize = details.prize || 0; - details.official = details.official || false; - - let challenge = await challengeCreator.post('/challenges', details); - let apiChallenge = new ApiChallenge(challenge); - - await apiChallenge.update(update); - - return apiChallenge; -} diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js deleted file mode 100644 index ec20ae763e..0000000000 --- a/test/helpers/api-unit.helper.js +++ /dev/null @@ -1,104 +0,0 @@ -import '../../website/server/libs/api-v3/i18n'; -import mongoose from 'mongoose'; -import { defaultsDeep as defaults } from 'lodash'; -import { model as User } from '../../website/server/models/user'; -import { model as Group } from '../../website/server/models/group'; -import mongo from './mongo'; // eslint-disable-line -import moment from 'moment'; -import i18n from '../../common/script/i18n'; -import * as Tasks from '../../website/server/models/task'; - -afterEach((done) => { - sandbox.restore(); - mongoose.connection.db.dropDatabase(done); -}); - -export { sleep } from './sleep'; - -export function generateUser (options = {}) { - return new User(options).toObject(); -} - -export function generateGroup (options = {}) { - return new Group(options).toObject(); -} - -export function generateRes (options = {}) { - let defaultRes = { - render: sandbox.stub(), - send: sandbox.stub(), - status: sandbox.stub().returnsThis(), - sendStatus: sandbox.stub().returnsThis(), - json: sandbox.stub(), - locals: { - user: generateUser(options.localsUser), - group: generateGroup(options.localsGroup), - }, - set: sandbox.stub(), - t (string) { - return i18n.t(string); - }, - }; - - return defaults(options, defaultRes); -} - -export function generateReq (options = {}) { - let defaultReq = { - body: {}, - query: {}, - headers: {}, - header: sandbox.stub().returns(null), - }; - - return defaults(options, defaultReq); -} - -export function generateNext (func) { - return func || sandbox.stub(); -} - -export function generateHistory (days) { - let history = []; - let now = Number(moment().toDate()); - - while (days > 0) { - history.push({ - value: days, - date: Number(moment(now).subtract(days, 'days').toDate()), - }); - days--; - } - - return history; -} - -export function generateTodo (user) { - let todo = { - text: 'test todo', - type: 'todo', - value: 0, - completed: false, - }; - - let task = new Tasks.todo(Tasks.Task.sanitize(todo)); // eslint-disable-line babel/new-cap - task.userId = user._id; - task.save(); - - return task; -} - -export function generateDaily (user) { - let daily = { - text: 'test daily', - type: 'daily', - value: 0, - completed: false, - }; - - let task = new Tasks.daily(Tasks.Task.sanitize(daily)); // eslint-disable-line babel/new-cap - task.userId = user._id; - task.save(); - - return task; -} diff --git a/test/helpers/api-v3-integration.helper.js b/test/helpers/api-v3-integration.helper.js deleted file mode 100644 index 4f703efdd6..0000000000 --- a/test/helpers/api-v3-integration.helper.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./api-integration/v3'); diff --git a/test/helpers/common.helper.js b/test/helpers/common.helper.js index 4cd82ca4b4..96064b8142 100644 --- a/test/helpers/common.helper.js +++ b/test/helpers/common.helper.js @@ -1,13 +1,13 @@ import mongoose from 'mongoose'; import { wrap as wrapUser } from '../../common/script/index'; -import { model as User } from '../../website/server/models/user'; +import { model as User } from '../../website/src/models/user'; import { DailySchema, HabitSchema, RewardSchema, TodoSchema, -} from '../../website/server/models/task'; +} from '../../website/src/models/task'; export function generateUser (options = {}) { let user = new User(options).toObject(); diff --git a/test/helpers/content.helper.js b/test/helpers/content.helper.js index c1ac3c5657..be87f06696 100644 --- a/test/helpers/content.helper.js +++ b/test/helpers/content.helper.js @@ -1,6 +1,7 @@ require('./globals.helper'); + import i18n from '../../common/script/i18n'; -i18n.translations = require('../../website/server/libs/api-v3/i18n').translations; +i18n.translations = require('../../website/src/libs/i18n.js').translations; export const STRING_ERROR_MSG = 'Error processing the string. Please see Help > Report a Bug.'; export const STRING_DOES_NOT_EXIST_MSG = /^String '.*' not found.$/; diff --git a/test/helpers/globals.helper.js b/test/helpers/globals.helper.js index 253d07e1d4..21f60b7f46 100644 --- a/test/helpers/globals.helper.js +++ b/test/helpers/globals.helper.js @@ -1,39 +1,10 @@ /* eslint-disable no-undef */ -/* eslint-disable global-require */ -/* eslint-disable no-process-env */ - -import Bluebird from 'bluebird'; - //------------------------------ // Global modules //------------------------------ + global._ = require('lodash'); global.chai = require('chai'); chai.use(require('sinon-chai')); chai.use(require('chai-as-promised')); global.expect = chai.expect; -global.sinon = require('sinon'); -global.sandbox = sinon.sandbox.create(); -global.Promise = Bluebird; - -import nconf from 'nconf'; -import mongoose from 'mongoose'; - -//------------------------------ -// Load nconf for unit tests -//------------------------------ -if (process.env.LOAD_SERVER === '0') { // when the server is in a different process we simply connect to mongoose - require('../../website/server/libs/api-v3/setupNconf')('./config.json'); - // Use Q promises instead of mpromise in mongoose - mongoose.Promise = Bluebird; - mongoose.connect(nconf.get('TEST_DB_URI')); -} else { // When running tests and the server in the same process - require('../../website/server/libs/api-v3/setupNconf')('./config.json.example'); - nconf.set('NODE_DB_URI', nconf.get('TEST_DB_URI')); - nconf.set('NODE_ENV', 'test'); - nconf.set('IS_TEST', true); - // We require src/server and npt src/index because - // 1. nconf is already setup - // 2. we don't need clustering - require('../../website/server/server'); -} diff --git a/test/helpers/mongo.js b/test/helpers/mongo.js deleted file mode 100644 index 9ad5cc8700..0000000000 --- a/test/helpers/mongo.js +++ /dev/null @@ -1,110 +0,0 @@ -import mongoose from 'mongoose'; -import { TAVERN_ID } from '../../website/server/models/group'; - -// Useful for checking things that have been deleted, -// but you no longer have access to, -// like private parties or users -export async function checkExistence (collectionName, id) { - return new Promise((resolve, reject) => { - let collection = mongoose.connection.db.collection(collectionName); - - collection.find({_id: id}, {_id: 1}).limit(1).toArray((findError, docs) => { - if (findError) return reject(findError); - - let exists = docs.length > 0; - - resolve(exists); - }); - }); -} - -// Specifically helpful for the GET /groups tests, -// resets the db to an empty state and creates a tavern document -export async function resetHabiticaDB () { - return new Promise((resolve, reject) => { - mongoose.connection.db.dropDatabase((dbErr) => { - if (dbErr) return reject(dbErr); - let groups = mongoose.connection.db.collection('groups'); - let users = mongoose.connection.db.collection('users'); - - users.count({_id: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0'}, (err, count) => { - if (err) return reject(err); - if (count > 0) return resolve(); - - // create the leader for the tavern - users.insertOne({ - _id: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', - apiToken: TAVERN_ID, - auth: { - local: { - username: 'username', - lowerCaseUsername: 'username', - email: 'username@email.com', - salt: 'salt', - hashed_password: 'hashed_password', // eslint-disable-line camelcase - }, - }, - }, (insertErr) => { - if (insertErr) return reject(insertErr); - - // For some mysterious reason after a dropDatabase there can still be a group... - groups.count({_id: TAVERN_ID}, (err2, count2) => { - if (err2) return reject(err2); - if (count2 > 0) return resolve(); - - groups.insertOne({ - _id: TAVERN_ID, - chat: [], - leader: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', // Siena Leslie - name: 'HabitRPG', - type: 'guild', - privacy: 'public', - }, (insertErr2) => { - if (insertErr2) return reject(insertErr2); - - resolve(); - }); - }); - }); - }); - }); - }); -} - -export async function updateDocument (collectionName, doc, update) { - let collection = mongoose.connection.db.collection(collectionName); - - return new Promise((resolve) => { - collection.updateOne({ _id: doc._id }, { $set: 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); - - return new Promise((resolve) => { - collection.findOne({ _id: doc._id }, (lookupErr, found) => { - if (lookupErr) throw new Error(`Error looking up ${collectionName}: ${lookupErr}`); - resolve(found); - }); - }); -} - -before((done) => { - mongoose.connection.on('open', (err) => { - if (err) return done(err); - resetHabiticaDB() - .then(() => done()) - .catch(done); - }); -}); - -after((done) => { - mongoose.connection.db.dropDatabase((err) => { - if (err) return done(err); - mongoose.connection.close(done); - }); -}); diff --git a/test/helpers/sleep.js b/test/helpers/sleep.js deleted file mode 100644 index f8dd9ab165..0000000000 --- a/test/helpers/sleep.js +++ /dev/null @@ -1,7 +0,0 @@ -export async function sleep (seconds) { - let milliseconds = seconds * 1000; - - return new Promise((resolve) => { - setTimeout(resolve, milliseconds); - }); -} diff --git a/test/migrations/20150605_ultimate_achievement_backfill.coffee b/test/migrations/20150605_ultimate_achievement_backfill.coffee index 689b49cb67..b5646020b0 100644 --- a/test/migrations/20150605_ultimate_achievement_backfill.coffee +++ b/test/migrations/20150605_ultimate_achievement_backfill.coffee @@ -2,7 +2,7 @@ TEST_DB = process.env.DB_NAME = 'habitrpg_migration_test' process.env.NODE_DB_URI = 'mongodb://localhost/' + TEST_DB -app = require('../../website/server/server') +app = require('../../website/src/server') sh = require('shelljs') runMigration = -> diff --git a/test/mocha.opts b/test/mocha.opts index 69ce4ab86d..a2323400d5 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -6,4 +6,5 @@ --globals io -r babel-polyfill --compilers js:babel-register +--require test/api-legacy/api-helper --require ./test/helpers/globals.helper diff --git a/test/server_side/analytics.test.js b/test/server_side/analytics.test.js index 5257303242..7cde7aede8 100644 --- a/test/server_side/analytics.test.js +++ b/test/server_side/analytics.test.js @@ -30,7 +30,7 @@ describe('analytics', function() { }); describe('init', function() { - var analytics = rewire('../../website/server/libs/api-v2/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); it('throws an error if no options are passed in', function() { expect(analytics).to.throw('No options provided'); @@ -62,7 +62,7 @@ describe('analytics', function() { describe('track', function() { var analyticsData, event_type; - var analytics = rewire('../../website/server/libs/api-v2/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); var initializedAnalytics; beforeEach(function() { @@ -370,7 +370,7 @@ describe('analytics', function() { var purchaseData; - var analytics = rewire('../../website/server/libs/api-v2/analytics'); + var analytics = rewire('../../website/src/libs/analytics'); var initializedAnalytics; beforeEach(function() { diff --git a/test/server_side/controllers/groups.test.js b/test/server_side/controllers/groups.test.js index bf12df321b..353fb581fe 100644 --- a/test/server_side/controllers/groups.test.js +++ b/test/server_side/controllers/groups.test.js @@ -3,12 +3,12 @@ var chai = require("chai"); chai.use(require("sinon-chai")); var expect = chai.expect; -var Bluebird = require('bluebird'); -var Group = require('../../../website/server/models/group').model; -var groupsController = require('../../../website/server/controllers/api-v2/groups'); +var Q = require('q'); +var Group = require('../../../website/src/models/group').model; +var groupsController = require('../../../website/src/controllers/api-v2/groups'); describe('Groups Controller', function() { - var utils = require('../../../website/server/libs/api-v2/utils'); + var utils = require('../../../website/src/libs/utils'); describe('#invite', function() { var res, req, user, group; @@ -69,7 +69,7 @@ describe('Groups Controller', function() { }); context('emails', function() { - var EmailUnsubscription = require('../../../website/server/models/emailUnsubscription').model; + var EmailUnsubscription = require('../../../website/src/models/emailUnsubscription').model; var execStub, selectStub; beforeEach(function() { @@ -301,7 +301,7 @@ describe('Groups Controller', function() { }); afterEach(function() { - Promise.all.restore(); + Q.all.restore(); }); context('error conditions', function() { @@ -342,7 +342,7 @@ describe('Groups Controller', function() { }); it('sends 500 if group cannot save', function() { - Promise.all.returns({ + Q.all.returns({ done: sinon.stub().callsArgWith(1, {err: 'save error'}) }); var nextSpy = sinon.spy(); diff --git a/test/server_side/controllers/user.test.js b/test/server_side/controllers/user.test.js index 59589df511..2be7f7215a 100644 --- a/test/server_side/controllers/user.test.js +++ b/test/server_side/controllers/user.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var userController = rewire('../../../website/server/controllers/api-v2/user'); +var userController = rewire('../../../website/src/controllers/api-v2/user'); describe('User Controller', function() { @@ -359,7 +359,7 @@ describe('User Controller', function() { }); it('sends webhooks', function() { - var webhook = require('../../../website/server/libs/webhook'); + var webhook = require('../../../website/src/libs/webhook'); sinon.spy(webhook, 'sendTaskWebhook'); userController.score(req, res); @@ -384,7 +384,7 @@ describe('User Controller', function() { }); context('save callback dealing with non challenge tasks', function() { - var Challenge = require('../../../website/server/models/challenge').model; + var Challenge = require('../../../website/src/models/challenge').model; beforeEach(function() { user.save.yields(null, user); @@ -446,7 +446,7 @@ describe('User Controller', function() { }); context('save callback dealing with challenge tasks', function() { - var Challenge = require('../../../website/server/models/challenge').model; + var Challenge = require('../../../website/src/models/challenge').model; var chal; beforeEach(function() { diff --git a/test/server_side/webhooks.test.js b/test/server_side/webhooks.test.js index 621d0daba3..ef6636bf93 100644 --- a/test/server_side/webhooks.test.js +++ b/test/server_side/webhooks.test.js @@ -4,7 +4,7 @@ chai.use(require("sinon-chai")) var expect = chai.expect var rewire = require('rewire'); -var webhook = rewire('../../website/server/libs/api-v2/webhook'); +var webhook = rewire('../../website/src/libs/webhook'); describe('webhooks', function() { var postSpy; diff --git a/test/spec/chatServicesSpec.js b/test/spec/chatServicesSpec.js new file mode 100644 index 0000000000..e65c36be68 --- /dev/null +++ b/test/spec/chatServicesSpec.js @@ -0,0 +1,90 @@ +'use strict'; + +describe('Chat Service', function() { + var $httpBackend, $http, chat, user; + + beforeEach(function() { + module(function($provide) { + var usr = specHelper.newUser(); + $provide.value('User', {user:usr}); + }); + + inject(function(_$httpBackend_, Chat, User) { + $httpBackend = _$httpBackend_; + chat = Chat; + user = User; + }); + }); + + describe('utils', function() { + it('calls post chat endpoint', function() { + var payload = { + gid: 'habitrpg', + message: 'Chat', + previousMsg: 'previous-msg-id' + } + + $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat?message=Chat&previousMsg=previous-msg-id').respond(); + chat.utils.postChat(payload, undefined); + $httpBackend.flush(); + }); + + it('calls like chat endpoint', function() { + var payload = { + gid: 'habitrpg', + messageId: 'msg-id' + } + + $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/like').respond(); + chat.utils.like(payload, undefined); + $httpBackend.flush(); + }); + + it('calls delete chat endpoint', function() { + var payload = { + gid: 'habitrpg', + messageId: 'msg-id' + } + + $httpBackend.expectDELETE('/api/v2/groups/habitrpg/chat/msg-id').respond(); + chat.utils.deleteChatMessage(payload, undefined); + $httpBackend.flush(); + }); + + it('calls flag chat endpoint', function() { + var payload = { + gid: 'habitrpg', + messageId: 'msg-id' + } + + $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/flag').respond(); + chat.utils.flagChatMessage(payload, undefined); + $httpBackend.flush(); + }); + + it('calls clear flags endpoint', function() { + var payload = { + gid: 'habitrpg', + messageId: 'msg-id' + } + + $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/msg-id/clearflags').respond(); + chat.utils.clearFlagCount(payload, undefined); + $httpBackend.flush(); + }); + }); + + describe('seenMessage(gid)', function() { + it('calls chat seen endpoint', function() { + $httpBackend.expectPOST('/api/v2/groups/habitrpg/chat/seen').respond(); + chat.seenMessage('habitrpg'); + $httpBackend.flush(); + }); + + it('removes newMessages for a specific guild from user object', function() { + user.user.newMessages = {habitrpg: "foo"}; + chat.seenMessage('habitrpg'); + expect(user.user.newMessages.habitrpg).to.not.exist; + }); + }); +}); diff --git a/test/spec/controllers/authCtrlSpec.js b/test/spec/controllers/authCtrlSpec.js index 75ea898fca..b1f87d4d91 100644 --- a/test/spec/controllers/authCtrlSpec.js +++ b/test/spec/controllers/authCtrlSpec.js @@ -25,7 +25,7 @@ describe('Auth Controller', function() { describe('logging in', function() { it('should log in users with correct uname / pass', function() { - $httpBackend.expectPOST('/api/v3/user/auth/local/login').respond({data: {id: 'abc', apiToken: 'abc'}}); + $httpBackend.expectPOST('/api/v2/user/auth/local').respond({id: 'abc', token: 'abc'}); scope.auth(); $httpBackend.flush(); expect(user.authenticate).to.be.calledOnce; @@ -33,7 +33,7 @@ describe('Auth Controller', function() { }); it('should not log in users with incorrect uname / pass', function() { - $httpBackend.expectPOST('/api/v3/user/auth/local/login').respond(404, ''); + $httpBackend.expectPOST('/api/v2/user/auth/local').respond(404, ''); scope.auth(); $httpBackend.flush(); expect(user.authenticate).to.not.be.called; diff --git a/test/spec/controllers/challengesCtrlSpec.js b/test/spec/controllers/challengesCtrlSpec.js index ec63ee20c4..0ebebeba44 100644 --- a/test/spec/controllers/challengesCtrlSpec.js +++ b/test/spec/controllers/challengesCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Challenges Controller', function() { - var rootScope, scope, user, User, ctrl, groups, members, notification, state, challenges, tasks, tavernId; + var rootScope, scope, user, User, ctrl, groups, members, notification, state; beforeEach(function() { module(function($provide) { @@ -14,7 +14,7 @@ describe('Challenges Controller', function() { $provide.value('User', User); }); - inject(function($rootScope, $controller, _$state_, _Groups_, _Members_, _Notification_, _Challenges_, _Tasks_, _TAVERN_ID_){ + inject(function($rootScope, $controller, _$state_, _Groups_, _Members_, _Notification_){ scope = $rootScope.$new(); rootScope = $rootScope; @@ -23,13 +23,10 @@ describe('Challenges Controller', function() { ctrl = $controller('ChallengesCtrl', {$scope: scope, User: User}); - challenges = _Challenges_; - tasks = _Tasks_; groups = _Groups_; members = _Members_; notification = _Notification_; state = _$state_; - tavernId = _TAVERN_ID_; }); }); @@ -42,36 +39,30 @@ describe('Challenges Controller', function() { description: 'You are the owner and member', leader: user._id, members: [user], - _isMember: true, - _id: 'ownMem-id', + _isMember: true }); ownNotMem = specHelper.newChallenge({ description: 'You are the owner, but not a member', leader: user._id, members: [], - _isMember: false, - _id: 'ownNotMem-id', + _isMember: false }); notOwnMem = specHelper.newChallenge({ description: 'Not owner but a member', leader: {_id:"test"}, members: [user], - _isMember: true, - _id: 'notOwnMem-id', + _isMember: true }); notOwnNotMem = specHelper.newChallenge({ description: 'Not owner or member', leader: {_id:"test"}, members: [], - _isMember: false, - _id: 'notOwnNotMem-id', + _isMember: false }); - user.challenges = [ownMem._id, notOwnMem._id]; - scope.search = { group: _.transform(groups, function(m,g){m[g._id]=true;}) }; @@ -218,17 +209,6 @@ describe('Challenges Controller', function() { }); describe('addTask', function() { - var challenge; - - beforeEach(function () { - challenge = specHelper.newChallenge({ - description: 'You are the owner and member', - leader: user._id, - members: [user], - _isMember: true - }); - }); - it('adds default task to array', function() { var taskArray = []; var listDef = { @@ -236,27 +216,26 @@ describe('Challenges Controller', function() { type: 'todo' } - scope.addTask(taskArray, listDef, challenge); + scope.addTask(taskArray, listDef); - expect(challenge['todos'].length).to.eql(1); - expect(challenge['todos'][0].text).to.eql('new todo text'); - expect(challenge['todos'][0].type).to.eql('todo'); + expect(taskArray.length).to.eql(1); + expect(taskArray[0].text).to.eql('new todo text'); + expect(taskArray[0].type).to.eql('todo'); }); it('adds the task to the front of the array', function() { var previousTask = specHelper.newTodo({ text: 'previous task' }); - var taskArray = []; - challenge['todos'] = [previousTask]; + var taskArray = [previousTask]; var listDef = { newTask: 'new todo', type: 'todo' } - scope.addTask(taskArray, listDef, challenge); + scope.addTask(taskArray, listDef); - expect(challenge['todos'].length).to.eql(2); - expect(challenge['todos'][0].text).to.eql('new todo'); - expect(challenge['todos'][1].text).to.eql('previous task'); + expect(taskArray.length).to.eql(2); + expect(taskArray[0].text).to.eql('new todo'); + expect(taskArray[1].text).to.eql('previous task'); }); it('removes text from new task input box', function() { @@ -266,7 +245,7 @@ describe('Challenges Controller', function() { type: 'todo' } - scope.addTask(taskArray, listDef, challenge); + scope.addTask(taskArray, listDef); expect(listDef.newTask).to.not.exist; }); @@ -281,37 +260,31 @@ describe('Challenges Controller', function() { }); describe('removeTask', function() { - var task, challenge; + var task, list; beforeEach(function() { sandbox.stub(window, 'confirm'); task = specHelper.newTodo(); - challenge = specHelper.newChallenge({ - description: 'You are the owner and member', - leader: user._id, - members: [user], - _isMember: true - }); - challenge['todos'] = [task]; + list = [task]; }); it('asks user to confirm deletion', function() { - scope.removeTask(task, challenge); + scope.removeTask(task, list); expect(window.confirm).to.be.calledOnce; }); it('does not remove task from list if not confirmed', function() { window.confirm.returns(false); - scope.removeTask(task, challenge); + scope.removeTask(task, list); - expect(challenge['todos']).to.include(task); + expect(list).to.include(task); }); it('removes task from list', function() { window.confirm.returns(true); - scope.removeTask(task, challenge); + scope.removeTask(task, list); - expect(challenge['todos']).to.not.include(task); + expect(list).to.not.include(task); }); }); @@ -328,23 +301,16 @@ describe('Challenges Controller', function() { context('challenge owner interactions', function() { describe("save challenge", function() { - var alert, createChallengeSpy, challengeResponse, taskChallengeCreateSpy; + var alert; beforeEach(function(){ alert = sandbox.stub(window, "alert"); - createChallengeSpy = sinon.stub(challenges, 'createChallenge'); - challengeResponse = {data: {data: {_id: 'new-challenge'}}}; - createChallengeSpy.returns(Promise.resolve(challengeResponse)); - - taskChallengeCreateSpy = sinon.stub(tasks, 'createChallengeTasks'); - var taskResponse = {data: {data: []}}; - taskChallengeCreateSpy.returns(Promise.resolve(taskResponse)); }); - it("opens an alert box if challenge.group is not specified", function() { + it("opens an alert box if challenge.group is not specified", function() + { var challenge = specHelper.newChallenge({ name: 'Challenge without a group', - shortName: 'chal without group', group: null }); @@ -357,7 +323,6 @@ describe('Challenges Controller', function() { it("opens an alert box if isNew and user does not have enough gems", function() { var challenge = specHelper.newChallenge({ name: 'Challenge without enough gems', - shortName: 'chal without gem', prize: 5 }); @@ -369,84 +334,81 @@ describe('Challenges Controller', function() { }); it("saves the challenge if user does not have enough gems, but the challenge is not new", function() { - var updateChallengeSpy = sinon.spy(challenges, 'updateChallenge'); - var challenge = specHelper.newChallenge({ _id: 'challenge-has-id-so-its-not-new', name: 'Challenge without enough gems', - shortName: 'chal without gem', prize: 5, + $save: sandbox.spy() // stub $save }); scope.maxPrize = 0; scope.save(challenge); - expect(updateChallengeSpy).to.be.calledOnce; + expect(challenge.$save).to.be.calledOnce; expect(alert).to.not.be.called; }); it("saves the challenge if user has enough gems and challenge is new", function() { var challenge = specHelper.newChallenge({ name: 'Challenge without enough gems', - shortName: 'chal without gem', prize: 5, + $save: sandbox.spy() // stub $save }); scope.maxPrize = 5; scope.save(challenge); - expect(createChallengeSpy).to.be.calledOnce; + expect(challenge.$save).to.be.calledOnce; expect(alert).to.not.be.called; }); - it('saves challenge and then proceeds to detail page', function(done) { + it('saves challenge and then proceeds to detail page', function() { + var saveSpy = sandbox.stub(); + saveSpy.yields({_id: 'challenge-id'}); sandbox.stub(state, 'transitionTo'); var challenge = specHelper.newChallenge({ - name: 'Challenge', - shortName: 'chal', + $save: saveSpy // stub $save }); - setTimeout(function() { - expect(createChallengeSpy).to.be.calledOnce; - expect(state.transitionTo).to.be.calledWith( - 'options.social.challenges.detail', - { cid: 'new-challenge' }, - { - reload: true, inherit: false, notify: true - } - ); - done(); - }, 1000); - scope.save(challenge); + + expect(state.transitionTo).to.be.calledOnce; + expect(state.transitionTo).to.be.calledWith( + 'options.social.challenges.detail', + { cid: 'challenge-id' }, + { + reload: true, inherit: false, notify: true + } + ); }); - it('saves new challenge and syncs User', function(done) { - var challenge = specHelper.newChallenge(); - challenge.shortName = 'chal'; + it('saves new challenge and syncs User', function() { + var saveSpy = sandbox.stub(); + saveSpy.yields({_id: 'new-challenge'}); - setTimeout(function() { - expect(User.sync).to.be.calledOnce; - done(); - }, 1000); + var challenge = specHelper.newChallenge({ + $save: saveSpy // stub $save + }); scope.save(challenge); + + expect(User.sync).to.be.calledOnce; }); - it('saves new challenge and syncs User', function(done) { + it('saves new challenge and syncs User', function() { + var saveSpy = sandbox.stub(); + saveSpy.yields({_id: 'new-challenge'}); sinon.stub(notification, 'text'); - var challenge = specHelper.newChallenge(); - challenge.shortName = 'chal'; - - setTimeout(function() { - expect(notification.text).to.be.calledOnce; - expect(notification.text).to.be.calledWith(window.env.t('challengeCreated')); - done(); - }, 1000); + var challenge = specHelper.newChallenge({ + $save: saveSpy // stub $save + }); scope.save(challenge); + + expect(notification.text).to.be.calledOnce; + expect(notification.text).to.be.calledWith(window.env.t('challengeCreated')); }); }); @@ -494,7 +456,7 @@ describe('Challenges Controller', function() { it('defaults to tavern if no group can be set as default', function() { scope.create(); - expect(scope.newChallenge.group).to.eql(tavernId); + expect(scope.newChallenge.group).to.eql('habitrpg'); }); it('calculates maxPrize', function() { @@ -516,7 +478,7 @@ describe('Challenges Controller', function() { expect(chal.todos).to.eql([]); expect(chal.rewards).to.eql([]); expect(chal.leader).to.eql('unique-user-id'); - expect(chal.group).to.eql(tavernId); + expect(chal.group).to.eql('habitrpg'); expect(chal.timestamp).to.be.greaterThan(0); expect(chal.official).to.eql(false); }); @@ -527,7 +489,7 @@ describe('Challenges Controller', function() { it('returns true if user has no gems', function() { User.user.balance = 0; scope.newChallenge = specHelper.newChallenge({ - group: tavernId + group: 'habitrpg' }); var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge(); @@ -537,7 +499,7 @@ describe('Challenges Controller', function() { it('returns false if user has gems', function() { User.user.balance = .25; scope.newChallenge = specHelper.newChallenge({ - group: tavernId + group: 'habitrpg' }); var cannotCreateTavernChallenge = scope.insufficientGemsForTavernChallenge(); @@ -665,16 +627,15 @@ describe('Challenges Controller', function() { context('User interactions', function() { describe('join', function() { - it('calls challenge join', function(){ - var joinChallengeSpy = sinon.spy(challenges, 'joinChallenge'); - + it('calls challenge.$join', function(){ var challenge = specHelper.newChallenge({ _id: 'challenge-to-join', + $join: sandbox.spy() }); scope.join(challenge); - expect(joinChallengeSpy).to.be.calledOnce; + expect(challenge.$join).to.be.calledOnce; }); }); @@ -708,6 +669,7 @@ describe('Challenges Controller', function() { describe('leave', function() { var challenge = specHelper.newChallenge({ _id: 'challenge-to-leave', + $leave: sandbox.spy() }); var clickEvent = { @@ -723,12 +685,11 @@ describe('Challenges Controller', function() { expect(scope.selectedChal).to.not.exist; }); - it('calls challenge leave when anything but cancel is chosen', function() { - var leaveChallengeSpy = sinon.spy(challenges, 'leaveChallenge'); + it('calls challenge.$leave when anything but cancel is chosen', function() { scope.clickLeave(challenge, clickEvent); - scope.leave('not-cancel', challenge); - expect(leaveChallengeSpy).to.be.calledOnce; + scope.leave('not-cancel'); + expect(challenge.$leave).to.be.calledOnce; }); }); }); @@ -737,36 +698,31 @@ describe('Challenges Controller', function() { beforeEach(function() { sandbox.stub(members, 'selectMember'); sandbox.stub(rootScope, 'openModal'); - members.selectMember.returns(Promise.resolve()); }); describe('sendMessageToChallengeParticipant', function() { - it('opens private-message modal', function(done) { + it('opens private-message modal', function() { + members.selectMember.yields(); scope.sendMessageToChallengeParticipant(user._id); - setTimeout(function() { - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith( - 'private-message', - { controller: 'MemberModalCtrl' } - ); - done(); - }, 1000); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith( + 'private-message', + { controller: 'MemberModalCtrl' } + ); }); }); describe('sendGiftToChallengeParticipant', function() { - it('opens send-gift modal', function(done) { + it('opens send-gift modal', function() { + members.selectMember.yields(); scope.sendGiftToChallengeParticipant(user._id); - setTimeout(function() { - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith( - 'send-gift', - { controller: 'MemberModalCtrl' } - ); - done(); - }, 1000); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith( + 'send-gift', + { controller: 'MemberModalCtrl' } + ); }); }); }); diff --git a/test/spec/controllers/copyMessageModalControllerSpec.js b/test/spec/controllers/copyMessageModalControllerSpec.js index 419fb2341e..bb25b6447e 100644 --- a/test/spec/controllers/copyMessageModalControllerSpec.js +++ b/test/spec/controllers/copyMessageModalControllerSpec.js @@ -4,9 +4,11 @@ describe("CopyMessageModal controller", function() { var scope, ctrl, user, Notification, $rootScope, $controller; beforeEach(function() { - module(function($provide) {}); + module(function($provide) { + $provide.value('User', {}); + }); - inject(function($rootScope, _$controller_, _Notification_, User){ + inject(function($rootScope, _$controller_, _Notification_){ user = specHelper.newUser(); user._id = "unique-user-id"; user.ops = { @@ -18,12 +20,10 @@ describe("CopyMessageModal controller", function() { $controller = _$controller_; - User.setUser(user); - // Load RootCtrl to ensure shared behaviors are loaded - $controller('RootCtrl', {$scope: scope, User: User}); + $controller('RootCtrl', {$scope: scope, User: {user: user}}); - ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: User}); + ctrl = $controller('CopyMessageModalCtrl', {$scope: scope, User: {user: user}}); Notification = _Notification_; Notification.text = sandbox.spy(); diff --git a/test/spec/controllers/filtersCtrlSpec.js b/test/spec/controllers/filtersCtrlSpec.js index b2adac581d..bbebce3cfd 100644 --- a/test/spec/controllers/filtersCtrlSpec.js +++ b/test/spec/controllers/filtersCtrlSpec.js @@ -1,16 +1,13 @@ 'use strict'; describe('Filters Controller', function() { - var scope, user, userService; + var scope, user; - beforeEach(inject(function($rootScope, $controller, Shared, User) { + beforeEach(inject(function($rootScope, $controller, Shared) { user = specHelper.newUser(); Shared.wrap(user); scope = $rootScope.$new(); - // user.filters = {}; - User.setUser(user); - userService = User; - $controller('FiltersCtrl', {$scope: scope, User: User}); + $controller('FiltersCtrl', {$scope: scope, User: {user: user}}); })); describe('tags', function(){ @@ -25,9 +22,9 @@ describe('Filters Controller', function() { it('toggles tag filtering', inject(function(Shared){ var tag = {id: Shared.uuid(), name: 'myTag'}; scope.toggleFilter(tag); - expect(userService.user.filters[tag.id]).to.eql(true); + expect(user.filters[tag.id]).to.eql(true); scope.toggleFilter(tag); - expect(userService.user.filters[tag.id]).to.eql(false); + expect(user.filters[tag.id]).to.eql(false); })); }); @@ -36,7 +33,7 @@ describe('Filters Controller', function() { scope.filterQuery = 'task'; scope.updateTaskFilter(); - expect(userService.user.filterQuery).to.eql(scope.filterQuery); + expect(user.filterQuery).to.eql(scope.filterQuery); }); }); }); diff --git a/test/spec/controllers/footerCtrlSpec.js b/test/spec/controllers/footerCtrlSpec.js index ce5245d099..7b12bc2875 100644 --- a/test/spec/controllers/footerCtrlSpec.js +++ b/test/spec/controllers/footerCtrlSpec.js @@ -1,17 +1,12 @@ 'use strict'; describe('Footer Controller', function() { - var scope, user, User; + var scope, user; beforeEach(inject(function($rootScope, $controller) { + console.log(window.env.NODE_ENV); user = specHelper.newUser(); - User = { - log: sandbox.stub(), - set: sandbox.stub(), - addTenGems: sandbox.stub(), - addHourglass: sandbox.stub(), - user: user - }; + var User = {log: sandbox.stub(), set: sandbox.stub(), user: user}; scope = $rootScope.$new(); $controller('FooterCtrl', {$scope: scope, User: User}); })); @@ -45,17 +40,21 @@ describe('Footer Controller', function() { describe('#addTenGems', function() { it('posts to /user/addTenGems', inject(function($httpBackend) { + $httpBackend.expectPOST('/api/v2/user/addTenGems').respond({}); + scope.addTenGems(); - expect(User.addTenGems).to.have.been.called; + $httpBackend.flush(); })); }); describe('#addHourglass', function() { it('posts to /user/addHourglass', inject(function($httpBackend) { + $httpBackend.expectPOST('/api/v2/user/addHourglass').respond({}); + scope.addHourglass(); - expect(User.addHourglass).to.have.been.called; + $httpBackend.flush(); })); }); diff --git a/test/spec/controllers/groupCtrlSpec.js b/test/spec/controllers/groupCtrlSpec.js index 6a5819ec67..dce054bef6 100644 --- a/test/spec/controllers/groupCtrlSpec.js +++ b/test/spec/controllers/groupCtrlSpec.js @@ -23,53 +23,6 @@ describe('Groups Controller', function() { }); }); - describe("isMemberOfPendingQuest", function() { - var party; - var partyStub; - - beforeEach(function () { - party = specHelper.newGroup({ - _id: "unique-party-id", - type: 'party', - members: ['leader-id'] // Ensure we wouldn't pass automatically. - }); - - partyStub = sandbox.stub(groups, "party", function() { - return party; - }); - }); - - it("returns false if group is does not have a quest", function() { - expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; - }); - - it("returns false if group quest has not members", function() { - party.quest = { - 'key': 'random-key', - }; - expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; - }); - - it("returns false if group quest is active", function() { - party.quest = { - 'key': 'random-key', - 'members': {}, - 'active': true, - }; - party.quest.members[user._id] = true; - expect(scope.isMemberOfPendingQuest(user._id, party)).to.not.be.ok; - }); - - it("returns true if user is a member of a pending quest", function() { - party.quest = { - 'key': 'random-key', - 'members': {}, - }; - party.quest.members[user._id] = true; - expect(scope.isMemberOfPendingQuest(user._id, party)).to.be.ok; - }); - }); - describe("isMemberOfGroup", function() { it("returns true if group is the user's party retrieved from groups service", function() { var party = specHelper.newGroup({ @@ -78,7 +31,7 @@ describe('Groups Controller', function() { members: ['leader-id'] // Ensure we wouldn't pass automatically. }); - var partyStub = sandbox.stub(groups, "party", function() { + var partyStub = sandbox.stub(groups,"party", function() { return party; }); @@ -93,9 +46,12 @@ describe('Groups Controller', function() { members: [user._id] }); - user.guilds = [guild._id]; + var myGuilds = sandbox.stub(groups,"myGuilds", function() { + return [guild]; + }); expect(scope.isMemberOfGroup(user._id, guild)).to.be.ok; + expect(myGuilds).to.be.called; }); it('does not return true if guild is not included in myGuilds call', function(){ @@ -106,9 +62,12 @@ describe('Groups Controller', function() { members: ['not-user-id'] }); - user.guilds = []; + var myGuilds = sandbox.stub(groups,"myGuilds", function() { + return []; + }); expect(scope.isMemberOfGroup(user._id, guild)).to.not.be.ok; + expect(myGuilds).to.be.calledOnce; }); }); @@ -163,12 +122,12 @@ describe('Groups Controller', function() { scope.editGroup(guild); }); - it('calls group update', () => { - let guildUpdate = sandbox.spy(groups.Group, 'update'); + it('calls group.save', () => { + let guildSave = sandbox.spy(scope.groupCopy, '$save'); scope.saveEdit(guild); - expect(guildUpdate).to.be.calledOnce; + expect(guildSave).to.be.calledOnce; }); it('calls cancelEdit', () => { diff --git a/test/spec/controllers/headerCtrlSpec.js b/test/spec/controllers/headerCtrlSpec.js index b32c5ccbd5..ab37d11205 100644 --- a/test/spec/controllers/headerCtrlSpec.js +++ b/test/spec/controllers/headerCtrlSpec.js @@ -5,12 +5,13 @@ describe('Header Controller', function() { beforeEach(function() { module(function($provide) { - user = specHelper.newUser(); - user._id = "unique-user-id" - $provide.value('User', {user: user}); + $provide.value('User', {}); }); inject(function(_$rootScope_, _$controller_, _$location_){ + user = specHelper.newUser(); + user._id = "unique-user-id" + scope = _$rootScope_.$new(); $rootScope = _$rootScope_; diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index 4000f80a33..2553acbcf3 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -4,9 +4,11 @@ describe('Inventory Controller', function() { var scope, ctrl, user, rootScope; beforeEach(function() { - module(function($provide) {}); + module(function($provide) { + $provide.value('User', {}); + }); - inject(function($rootScope, $controller, Shared, User, $location, $window) { + inject(function($rootScope, $controller, Shared){ user = specHelper.newUser({ balance: 4, items: { @@ -24,21 +26,17 @@ describe('Inventory Controller', function() { Shared.wrap(user); var mockWindow = { - confirm: function(msg) { + confirm: function(msg){ return true; - }, + } }; - scope = $rootScope.$new(); rootScope = $rootScope; - User.user = user; - User.setUser(user); - // Load RootCtrl to ensure shared behaviors are loaded - $controller('RootCtrl', {$scope: scope, User: User, $window: mockWindow}); + $controller('RootCtrl', {$scope: scope, User: {user: user}, $window: mockWindow}); - ctrl = $controller('InventoryCtrl', {$scope: scope, User: User, $window: mockWindow}); + ctrl = $controller('InventoryCtrl', {$scope: scope, User: {user: user}, $window: mockWindow}); }); }); @@ -90,16 +88,14 @@ describe('Inventory Controller', function() { expect(rootScope.openModal).to.have.been.calledWith('hatchPet'); }); - //@TODO: Fix Common hatch - xit('does not show modal if user tries to hatch a pet they own', function(){ + it('does not show modal if user tries to hatch a pet they own', function(){ user.items.pets['Cactus-Base'] = 5; scope.chooseEgg('Cactus'); scope.choosePotion('Base'); expect(rootScope.openModal).to.not.have.been.called; }); - //@TODO: Fix Common hatch - xit('does not show modal if user tries to hatch a premium quest pet', function(){ + it('does not show modal if user tries to hatch a premium quest pet', function(){ user.items.eggs = {Snake: 1}; user.items.hatchingPotions = {Peppermint: 1}; scope.chooseEgg('Snake'); diff --git a/test/spec/controllers/inviteToGroupCtrlSpec.js b/test/spec/controllers/inviteToGroupCtrlSpec.js index 5c316855e9..385f42dda1 100644 --- a/test/spec/controllers/inviteToGroupCtrlSpec.js +++ b/test/spec/controllers/inviteToGroupCtrlSpec.js @@ -1,7 +1,7 @@ 'use strict'; describe('Invite to Group Controller', function() { - var scope, ctrl, groups, user, guild, rootScope, $controller; + var scope, ctrl, groups, user, guild, $rootScope; beforeEach(function() { user = specHelper.newUser({ @@ -13,12 +13,8 @@ describe('Invite to Group Controller', function() { $provide.value('injectedGroup', { user: user }); }); - inject(function(_$rootScope_, _$controller_, Groups) { - rootScope = _$rootScope_; - - scope = _$rootScope_.$new(); - - $controller = _$controller_; + inject(function($rootScope, $controller, Groups){ + scope = $rootScope.$new(); // Load RootCtrl to ensure shared behaviors are loaded $controller('RootCtrl', {$scope: scope, User: {user: user}}); @@ -48,101 +44,69 @@ describe('Invite to Group Controller', function() { }); describe('inviteNewUsers', function() { - var groupInvite, groupCreate; - beforeEach(function() { scope.group = specHelper.newGroup({ type: 'party', + $save: sinon.stub().returns({ + then: function(cb) { cb(); } + }) }); - groupCreate = sandbox.stub(groups.Group, 'create'); - groupInvite = sandbox.stub(groups.Group, 'invite'); + sandbox.stub(groups.Group, 'invite'); }); context('if the party does not already exist', function() { - var groupResponse; - beforeEach(function() { delete scope.group._id; - groupResponse = {data: {data: scope.group}} }); it('saves the group if a new group is being created', function() { - groupCreate.returns(Promise.resolve(groupResponse)); scope.inviteNewUsers('uuid'); - expect(groupCreate).to.be.calledOnce; + expect(scope.group.$save).to.be.calledOnce; }); it('uses provided name', function() { scope.group.name = 'test party'; - - groupCreate.returns(Promise.resolve(groupResponse)); - scope.inviteNewUsers('uuid'); - - expect(groupCreate).to.be.calledWith(scope.group); expect(scope.group.name).to.eql('test party'); }); it('names the group if no name is provided', function() { scope.group.name = ''; - - groupCreate.returns(Promise.resolve(groupResponse)); - scope.inviteNewUsers('uuid'); - - expect(groupCreate).to.be.calledWith(scope.group); expect(scope.group.name).to.eql(env.t('possessiveParty', {name: user.profile.name})); }); }); context('email', function() { - beforeEach(function () { - sandbox.stub(rootScope, 'hardRedirect'); - }); - - it('invites user with emails', function(done) { + it('invites user with emails', function() { scope.emails = [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ]; - var inviteDetails = { + scope.inviteNewUsers('email'); + expect(groups.Group.invite).to.be.calledOnce; + expect(groups.Group.invite).to.be.calledWith({ + gid: scope.group._id, + }, { inviter: user.profile.name, emails: [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ] - }; - groupInvite.returns( - Promise.resolve() - .then(function () { - expect(groupInvite).to.be.calledOnce; - expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails); - done(); - }) - ); - - scope.inviteNewUsers('email'); + }); }); - it('resets email list after sending', function(done) { + it('resets email list after sending', function() { + groups.Group.invite.yields(); scope.emails[0].name = 'Luigi'; scope.emails[0].email = 'mario_bro@themushroomkingdom.com'; - groupInvite.returns( - Promise.resolve() - .then(function () { - //We use a timeout to test items that happen after the promise is resolved - setTimeout(function(){ - expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]); - done(); - }, 1000); - }) - ); - scope.inviteNewUsers('email'); + + expect(scope.emails).to.eql([{name:'', email: ''},{name:'', email: ''}]); }); it('filters out blank email inputs', function() { @@ -152,93 +116,66 @@ describe('Invite to Group Controller', function() { {name: 'Mario', email: 'mario@tmk.com'} ]; - var inviteDetails = { + scope.inviteNewUsers('email'); + expect(groups.Group.invite).to.be.calledOnce; + expect(groups.Group.invite).to.be.calledWith({ + gid: scope.group._id, + }, { inviter: user.profile.name, emails: [ {name: 'Luigi', email: 'mario_bro@themushroomkingdom.com'}, {name: 'Mario', email: 'mario@tmk.com'} ] - }; - - groupInvite.returns( - Promise.resolve() - .then(function () { - expect(groupInvite).to.be.calledOnce; - expect(groupInvite).to.be.calledWith(scope.group._id, inviteDetails); - done(); - }) - ); - - scope.inviteNewUsers('email'); + }); }); }); context('uuid', function() { - beforeEach(function () { - sandbox.stub(rootScope, 'hardRedirect'); - }); - - it('invites user with uuid', function(done) { + it('invites user with uuid', function() { scope.invitees = [{uuid: '1234'}]; - groupInvite.returns( - Promise.resolve() - .then(function () { - expect(groupInvite).to.be.calledOnce; - expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['1234'] }); - done(); - }) - ); - scope.inviteNewUsers('uuid'); + expect(groups.Group.invite).to.be.calledOnce; + expect(groups.Group.invite).to.be.calledWith({ + gid: scope.group._id, + }, { + uuids: ['1234'] + }); }); - it('invites users with uuids', function(done) { + it('invites users with uuids', function() { scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}]; - groupInvite.returns( - Promise.resolve() - .then(function () { - expect(groupInvite).to.be.calledOnce; - expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user2', 'user3'] }); - done(); - }) - ); - scope.inviteNewUsers('uuid'); + expect(groups.Group.invite).to.be.calledOnce; + expect(groups.Group.invite).to.be.calledWith({ + gid: scope.group._id, + }, { + uuids: ['user1', 'user2', 'user3'] + }); }); - it('resets invitee list after sending', function(done) { + it('resets invitee list after sending', function() { + groups.Group.invite.yields(); scope.invitees = [{uuid: 'user1'}, {uuid: 'user2'}, {uuid: 'user3'}]; - groupInvite.returns( - Promise.resolve() - .then(function () { - //We use a timeout to test items that happen after the promise is resolved - setTimeout(function(){ - expect(scope.invitees).to.eql([{uuid: ''}]); - done(); - }, 1000); - done(); - }) - ); - scope.inviteNewUsers('uuid'); + + expect(scope.invitees).to.eql([{uuid: ''}]); }); it('removes blank fields from being sent', function() { + groups.Group.invite.yields(); scope.invitees = [{uuid: 'user1'}, {uuid: ''}, {uuid: 'user3'}]; - groupInvite.returns( - Promise.resolve() - .then(function () { - expect(groupInvite).to.be.calledOnce; - expect(groupInvite).to.be.calledWith(scope.group._id, { uuids: ['user1', 'user3'] }); - done(); - }) - ); - scope.inviteNewUsers('uuid'); + + expect(groups.Group.invite).to.be.calledOnce; + expect(groups.Group.invite).to.be.calledWith({ + gid: scope.group._id, + }, { + uuids: ['user1', 'user3'] + }); }); }); diff --git a/test/spec/controllers/menuCtrlSpec.js b/test/spec/controllers/menuCtrlSpec.js index 1e98595105..eaa642370b 100644 --- a/test/spec/controllers/menuCtrlSpec.js +++ b/test/spec/controllers/menuCtrlSpec.js @@ -19,7 +19,7 @@ describe('Menu Controller', function() { describe('clearMessage', function() { it('is Chat.seenMessage', inject(function(Chat) { - expect(scope.clearMessages).to.eql(Chat.markChatSeen); + expect(scope.clearMessages).to.eql(Chat.seenMessage); })); }); }); diff --git a/test/spec/controllers/partyCtrlSpec.js b/test/spec/controllers/partyCtrlSpec.js index 00d3e676c5..92401f1987 100644 --- a/test/spec/controllers/partyCtrlSpec.js +++ b/test/spec/controllers/partyCtrlSpec.js @@ -1,8 +1,7 @@ 'use strict'; describe("Party Controller", function() { - var scope, ctrl, user, User, questsService, groups, rootScope, $controller, deferred; - var party; + var scope, ctrl, user, User, questsService, groups, rootScope, $controller; beforeEach(function() { user = specHelper.newUser(), @@ -11,19 +10,13 @@ describe("Party Controller", function() { user: user, sync: sandbox.spy(), set: sandbox.spy() - }; - - party = specHelper.newGroup({ - _id: "unique-party-id", - type: 'party', - members: ['leader-id'] // Ensure we wouldn't pass automatically. - }); + } module(function($provide) { $provide.value('User', User); }); - inject(function(_$rootScope_, _$controller_, Groups, Quests, _$q_){ + inject(function(_$rootScope_, _$controller_, Groups, Quests){ rootScope = _$rootScope_; @@ -42,18 +35,12 @@ describe("Party Controller", function() { }); describe('initialization', function() { - var groupResponse; - function initializeControllerWithStubbedState() { inject(function(_$state_) { var state = _$state_; sandbox.stub(state, 'is').returns(true); - var syncParty = sinon.stub(groups.Group, 'syncParty') - syncParty.returns(Promise.resolve(groupResponse)); - $controller('PartyCtrl', { $scope: scope, $state: state, User: User }); - // @TODO: I have update the party ctrl to sync the user whenever it is called rather than only on the party page - // Since I have cached the promise, this should not be a performance issue, but let's keep this test here in case anything breaks. - // expect(state.is).to.be.calledOnce; // ensure initialization worked as desired + $controller('PartyCtrl', { $scope: scope, $state: state }); + expect(state.is).to.be.calledOnce; // ensure initialization worked as desired }); }; @@ -63,7 +50,10 @@ describe("Party Controller", function() { context('party has 1 member', function() { it('awards no new achievements', function() { - groupResponse = {_id: "test", type: "party", memberCount: 1}; + sandbox.stub(groups, 'party').returns({ + $syncParty: function() {}, + memberCount: 1 + }); initializeControllerWithStubbedState(); @@ -74,65 +64,61 @@ describe("Party Controller", function() { context('party has 2 members', function() { context('user does not have "Party Up" achievement', function() { - it('awards "Party Up" achievement', function(done) { - groupResponse = {_id: "test", type: "party", memberCount: 2}; + it('awards "Party Up" achievement', function() { + sandbox.stub(groups, 'party').returns({ + $syncParty: function() {}, + memberCount: 2 + }); initializeControllerWithStubbedState(); - setTimeout(function() { - expect(User.set).to.be.calledOnce; - expect(User.set).to.be.calledWith( - { 'achievements.partyUp': true } - ); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); - done(); - }, 1000); + expect(User.set).to.be.calledOnce; + expect(User.set).to.be.calledWith( + { 'achievements.partyUp': true } + ); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); }); }); }); context('party has 4 members', function() { - beforeEach(function() { - groupResponse = {_id: "test", type: "party", memberCount: 4}; + sandbox.stub(groups, 'party').returns({ + $syncParty: function() {}, + memberCount: 4 + }); }); context('user has "Party Up" but not "Party On" achievement', function() { - it('awards "Party On" achievement', function(done) { + it('awards "Party On" achievement', function() { user.achievements.partyUp = true; initializeControllerWithStubbedState(); - setTimeout(function(){ - expect(User.set).to.be.calledOnce; - expect(User.set).to.be.calledWith( - { 'achievements.partyOn': true } - ); - expect(rootScope.openModal).to.be.calledOnce; - expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); - done(); - }, 1000); + expect(User.set).to.be.calledOnce; + expect(User.set).to.be.calledWith( + { 'achievements.partyOn': true } + ); + expect(rootScope.openModal).to.be.calledOnce; + expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); }); }); context('user has neither "Party Up" nor "Party On" achievements', function() { - it('awards "Party Up" and "Party On" achievements', function(done) { + it('awards "Party Up" and "Party On" achievements', function() { initializeControllerWithStubbedState(); - setTimeout(function(){ - expect(User.set).to.have.been.called; - expect(User.set).to.be.calledWith( - { 'achievements.partyUp': true} - ); - expect(User.set).to.be.calledWith( - { 'achievements.partyOn': true} - ); - expect(rootScope.openModal).to.have.been.called; - expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); - expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); - done(); - }, 1000); + expect(User.set).to.be.calledTwice; + expect(User.set).to.be.calledWith( + { 'achievements.partyUp': true} + ); + expect(User.set).to.be.calledWith( + { 'achievements.partyOn': true} + ); + expect(rootScope.openModal).to.be.calledTwice; + expect(rootScope.openModal).to.be.calledWith('achievements/partyUp'); + expect(rootScope.openModal).to.be.calledWith('achievements/partyOn'); }); }); @@ -150,107 +136,70 @@ describe("Party Controller", function() { }); }); - describe("create", function() { - var partyStub; - - beforeEach(function () { - partyStub = sinon.stub(groups.Group, "create"); - partyStub.returns(Promise.resolve(party)); - sinon.stub(rootScope, 'hardRedirect'); - }); - - it("creates a new party", function() { - var group = { - type: 'party', - }; - scope.create(group); - expect(partyStub).to.be.calledOnce; - //@TODO: Check user party console.log(User.user.party.id) - }); - }); - describe('questAccept', function() { - var sendAction; - var memberResponse; - beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - - memberResponse = {members: {another: true}}; - sinon.stub(questsService, 'sendAction') - questsService.sendAction.returns(Promise.resolve(memberResponse)); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); it('calls Quests.sendAction', function() { scope.questAccept(); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('quests/accept'); + expect(questsService.sendAction).to.be.calledWith('questAccept'); }); - it('updates quest object with new participants list', function(done) { + it('updates quest object with new participants list', function() { scope.group.quest = { members: { user: true, another: true } }; - setTimeout(function(){ - expect(scope.group.quest).to.eql(memberResponse); - done(); - }, 1000); - scope.questAccept(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); describe('questReject', function() { - var memberResponse; - beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - - memberResponse = {members: {another: true}}; - var sendAction = sinon.stub(questsService, 'sendAction') - sendAction.returns(Promise.resolve(memberResponse)); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); it('calls Quests.sendAction', function() { scope.questReject(); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('quests/reject'); + expect(questsService.sendAction).to.be.calledWith('questReject'); }); - it('updates quest object with new participants list', function(done) { + it('updates quest object with new participants list', function() { scope.group.quest = { members: { user: true, another: true } }; - setTimeout(function(){ - expect(scope.group.quest).to.eql(memberResponse); - done(); - }, 1000); - scope.questReject(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); describe('questCancel', function() { - var party, cancelSpy, windowSpy, memberResponse; - + var party, cancelSpy, windowSpy; beforeEach(function() { - scope.group = { - quest: { members: { 'user-id': true } } - }; - - memberResponse = {members: {another: true}}; - sinon.stub(questsService, 'sendAction') - questsService.sendAction.returns(Promise.resolve(memberResponse)); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); it('calls Quests.sendAction when alert box is confirmed', function() { @@ -261,7 +210,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledOnce; expect(window.confirm).to.be.calledWith(window.env.t('sureCancel')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('quests/cancel'); + expect(questsService.sendAction).to.be.calledWith('questCancel'); }); it('does not call Quests.sendAction when alert box is not confirmed', function() { @@ -275,16 +224,10 @@ describe("Party Controller", function() { }); describe('questAbort', function() { - var memberResponse; - beforeEach(function() { - scope.group = { - quest: { members: { 'user-id': true } } - }; - - memberResponse = {members: {another: true}}; - sinon.stub(questsService, 'sendAction') - questsService.sendAction.returns(Promise.resolve(memberResponse)); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); it('calls Quests.sendAction when two alert boxes are confirmed', function() { @@ -296,7 +239,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledWith(window.env.t('doubleSureAbort')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('quests/abort'); + expect(questsService.sendAction).to.be.calledWith('questAbort'); }); it('does not call Quests.sendAction when first alert box is not confirmed', function() { @@ -330,16 +273,13 @@ describe("Party Controller", function() { }); describe('#questLeave', function() { - var memberResponse; - beforeEach(function() { scope.group = { quest: { members: { 'user-id': true } } }; - - memberResponse = {members: {another: true}}; - sinon.stub(questsService, 'sendAction') - questsService.sendAction.returns(Promise.resolve(memberResponse)); + sandbox.stub(questsService, 'sendAction').returns({ + then: sandbox.stub().yields({members: {another: true}}) + }); }); it('calls Quests.sendAction when alert box is confirmed', function() { @@ -350,7 +290,7 @@ describe("Party Controller", function() { expect(window.confirm).to.be.calledOnce; expect(window.confirm).to.be.calledWith(window.env.t('sureLeave')); expect(questsService.sendAction).to.be.calledOnce; - expect(questsService.sendAction).to.be.calledWith('quests/leave'); + expect(questsService.sendAction).to.be.calledWith('questLeave'); }); it('does not call Quests.sendAction when alert box is not confirmed', function() { @@ -362,18 +302,15 @@ describe("Party Controller", function() { questsService.sendAction.should.not.have.been.calledOnce; }); - it('updates quest object with new participants list', function(done) { + it('updates quest object with new participants list', function() { scope.group.quest = { members: { user: true, another: true } }; sandbox.stub(window, "confirm").returns(true); - setTimeout(function(){ - expect(scope.group.quest).to.eql(memberResponse); - done(); - }, 1000); - scope.questLeave(); + + expect(scope.group.quest).to.eql({members: { another: true }}); }); }); @@ -425,9 +362,7 @@ describe("Party Controller", function() { describe('#leaveOldPartyAndJoinNewParty', function() { beforeEach(function() { sandbox.stub(scope, 'join'); - groups.data.party = { _id: 'old-party' }; - var groupLeave = sandbox.stub(groups.Group, 'leave'); - groupLeave.returns(Promise.resolve({})); + sandbox.stub(groups.Group, 'leave').yields(); sandbox.stub(groups, 'party').returns({ _id: 'old-party' }); @@ -445,17 +380,20 @@ describe("Party Controller", function() { scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name'); expect(groups.Group.leave).to.be.calledOnce; - expect(groups.Group.leave).to.be.calledWith('old-party', false); + expect(groups.Group.leave).to.be.calledWith({ + gid: 'old-party', + keep: false + }); }); - it('joins the new party', function(done) { + it('joins the new party', function() { scope.leaveOldPartyAndJoinNewParty('some-id', 'some-name'); - setTimeout(function() { - expect(scope.join).to.be.calledOnce; - expect(scope.join).to.be.calledWith({id: 'some-id', name: 'some-name'}); - done(); - }, 1000); + expect(scope.join).to.be.calledOnce; + expect(scope.join).to.be.calledWith({ + id: 'some-id', + name: 'some-name' + }); }); }); @@ -468,7 +406,6 @@ describe("Party Controller", function() { leader: {}, quest: {} }); - scope.group = party; }); it('returns false if user is not the quest leader', function() { diff --git a/test/spec/controllers/settingsCtrlSpec.js b/test/spec/controllers/settingsCtrlSpec.js index ef960ae204..527600b29e 100644 --- a/test/spec/controllers/settingsCtrlSpec.js +++ b/test/spec/controllers/settingsCtrlSpec.js @@ -12,12 +12,6 @@ describe('Settings Controller', function () { user = specHelper.newUser(); User = { set: sandbox.stub(), - reroll: sandbox.stub(), - rebirth: sandbox.stub(), - releasePets: sandbox.stub(), - releaseMounts: sandbox.stub(), - releaseBoth: sandbox.stub(), - setCustomDayStart: sandbox.stub(), user: user }; @@ -87,11 +81,19 @@ describe('Settings Controller', function () { }); describe('#saveDayStart', function () { - it('updates user\'s custom day start', function () { + + it('updates user\'s custom day start and last cron', function () { + var fakeCurrentTime = new Date(2013, 3, 1, 8, 12).getTime(); + var expectedTime = fakeCurrentTime; + sandbox.useFakeTimers(fakeCurrentTime); scope.dayStart = 5; scope.saveDayStart(); - expect(User.setCustomDayStart).to.be.calledWith(5); + expect(User.set).to.be.calledOnce; + expect(User.set).to.be.calledWith({ + 'preferences.dayStart': 5, + 'lastCron': expectedTime + }); }); }); @@ -121,7 +123,7 @@ describe('Settings Controller', function () { scope.reroll(true); - expect(User.reroll).to.be.calledWith({}); + expect(user.ops.reroll).to.be.calledWith({}); }); it('navigates to the tasks page when confirmed', function () { @@ -171,7 +173,7 @@ describe('Settings Controller', function () { scope.rebirth(true); - expect(User.rebirth).to.be.calledWith({}); + expect(user.ops.rebirth).to.be.calledWith({}); }); it('navigates to tasks page when confirmed', function () { @@ -214,9 +216,9 @@ describe('Settings Controller', function () { it('doesn\'t call any release method if type is not provided', function () { scope.releaseAnimals(); - expect(User.releasePets).to.not.be.called; - expect(User.releaseMounts).to.not.be.called; - expect(User.releaseBoth).to.not.be.called; + expect(User.user.ops.releasePets).to.not.be.called; + expect(User.user.ops.releaseMounts).to.not.be.called; + expect(User.user.ops.releaseBoth).to.not.be.called; }); it('doesn\'t redirect to tasks page if type is not provided', function () { @@ -228,7 +230,7 @@ describe('Settings Controller', function () { it('calls releasePets when "pets" is provided', function () { scope.releaseAnimals('pets'); - expect(User.releasePets).to.be.calledOnce; + expect(User.user.ops.releasePets).to.be.calledOnce; }); it('navigates to the tasks page when "pets" is provided', function () { @@ -240,7 +242,7 @@ describe('Settings Controller', function () { it('calls releaseMounts when "mounts" is provided', function () { scope.releaseAnimals('mounts'); - expect(User.releaseMounts).to.be.calledOnce; + expect(User.user.ops.releaseMounts).to.be.calledOnce; }); it('navigates to the tasks page when "mounts" is provided', function () { @@ -252,7 +254,7 @@ describe('Settings Controller', function () { it('calls releaseBoth when "both" is provided', function () { scope.releaseAnimals('both'); - expect(User.releaseBoth).to.be.calledOnce; + expect(User.user.ops.releaseBoth).to.be.calledOnce; }); it('navigates to the tasks page when "both" is provided', function () { @@ -264,9 +266,9 @@ describe('Settings Controller', function () { it('does not call release functions when non-applicable argument is passed in', function () { scope.releaseAnimals('dummy'); - expect(User.releasePets).to.not.be.called; - expect(User.releaseMounts).to.not.be.called; - expect(User.releaseBoth).to.not.be.called; + expect(User.user.ops.releasePets).to.not.be.called; + expect(User.user.ops.releaseMounts).to.not.be.called; + expect(User.user.ops.releaseBoth).to.not.be.called; }); }); diff --git a/test/spec/controllers/tasksCtrlSpec.js b/test/spec/controllers/tasksCtrlSpec.js index 7d02a6edbb..ea6da32897 100644 --- a/test/spec/controllers/tasksCtrlSpec.js +++ b/test/spec/controllers/tasksCtrlSpec.js @@ -8,8 +8,6 @@ describe('Tasks Controller', function() { User = { user: user }; - - User.deleteTask = sandbox.stub(); User.user.ops = { deleteTask: sandbox.stub(), }; @@ -53,13 +51,13 @@ describe('Tasks Controller', function() { it('does not remove task if not confirmed', function() { window.confirm.returns(false); scope.removeTask(task); - expect(User.deleteTask).to.not.be.called; + expect(user.ops.deleteTask).to.not.be.called; }); it('removes task', function() { window.confirm.returns(true); scope.removeTask(task); - expect(User.deleteTask).to.be.calledOnce; + expect(user.ops.deleteTask).to.be.calledOnce; }); }); diff --git a/test/spec/services/challengeServicesSpec.js b/test/spec/services/challengeServicesSpec.js deleted file mode 100644 index 9bed72db31..0000000000 --- a/test/spec/services/challengeServicesSpec.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; - -describe('challengeServices', function() { - var $httpBackend, $http, challenges, user; - var apiV3Prefix = '/api/v3'; - - beforeEach(function() { - module(function($provide) { - $provide.value('User', {user:user}); - }); - - inject(function(_$httpBackend_, Challenges, User) { - $httpBackend = _$httpBackend_; - challenges = Challenges; - user = User; - user.sync = function(){}; - }); - }); - - it('calls create challenge endpoint', function() { - $httpBackend.expectPOST(apiV3Prefix + '/challenges').respond({}); - challenges.createChallenge(); - $httpBackend.flush(); - }); - - it('calls join challenge endpoint', function() { - var challengeId = 1; - $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/join').respond({}); - challenges.joinChallenge(challengeId); - $httpBackend.flush(); - }); - - it('calls leave challenge endpoint', function() { - var challengeId = 1; - $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/leave').respond({}); - challenges.leaveChallenge(challengeId); - $httpBackend.flush(); - }); - - it('calls get user challenges endpoint', function() { - $httpBackend.expectGET(apiV3Prefix + '/challenges/user').respond({}); - challenges.getUserChallenges(); - $httpBackend.flush(); - }); - - it('calls get group challenges endpoint', function() { - var groupId = 1; - $httpBackend.expectGET(apiV3Prefix + '/challenges/groups/' + groupId).respond({}); - challenges.getGroupChallenges(groupId); - $httpBackend.flush(); - }); - - it('calls get challenge endpoint', function() { - var challengeId = 1; - $httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId).respond({}); - challenges.getChallenge(challengeId); - $httpBackend.flush(); - }); - - it('calls export challenge to csv endpoint', function() { - var challengeId = 1; - $httpBackend.expectGET(apiV3Prefix + '/challenges/' + challengeId + '/export/csv').respond({}); - challenges.exportChallengeCsv(challengeId); - $httpBackend.flush(); - }); - - it('calls update challenge endpoint', function() { - var challengeId = 1; - $httpBackend.expectPUT(apiV3Prefix + '/challenges/' + challengeId).respond({}); - challenges.updateChallenge(challengeId); - $httpBackend.flush(); - }); - - it('calls delete challenge endpoint', function() { - var challengeId = 1; - $httpBackend.expectDELETE(apiV3Prefix + '/challenges/' + challengeId).respond({}); - challenges.deleteChallenge(challengeId); - $httpBackend.flush(); - }); - - it('calls select challenge winner endpoint', function() { - var challengeId = 1; - var winnerId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId).respond({}); - challenges.selectChallengeWinner(challengeId, winnerId); - $httpBackend.flush(); - }); -}); diff --git a/test/spec/services/chatServicesSpec.js b/test/spec/services/chatServicesSpec.js deleted file mode 100644 index 2d1c101df8..0000000000 --- a/test/spec/services/chatServicesSpec.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -describe('chatServices', function() { - var $httpBackend, $http, chat, user; - var apiV3Prefix = '/api/v3'; - - beforeEach(function() { - module(function($provide) { - $provide.value('User', {user:user}); - }); - - inject(function(_$httpBackend_, Chat, User) { - $httpBackend = _$httpBackend_; - chat = Chat; - user = User; - user.sync = function(){}; - }); - }); - - it('calls get chat endpoint', function() { - var groupId = 1; - $httpBackend.expectGET(apiV3Prefix + '/groups/' + groupId + '/chat').respond({}); - chat.getChat(groupId); - $httpBackend.flush(); - }); - - it('calls get chat endpoint', function() { - var groupId = 1; - var message = "test message"; - $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat').respond({}); - chat.postChat(groupId, message); - $httpBackend.flush(); - }); - - it('calls delete chat endpoint', function() { - var groupId = 1; - var chatId = 2; - $httpBackend.expectDELETE(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId).respond({}); - chat.deleteChat(groupId, chatId); - $httpBackend.flush(); - }); - - it('calls like chat endpoint', function() { - var groupId = 1; - var chatId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like').respond({}); - chat.like(groupId, chatId); - $httpBackend.flush(); - }); - - it('calls flag chat endpoint', function() { - var groupId = 1; - var chatId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag').respond({}); - chat.flagChatMessage(groupId, chatId); - $httpBackend.flush(); - }); - - it('calls clearflags chat endpoint', function() { - var groupId = 1; - var chatId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags').respond({}); - chat.clearFlagCount(groupId, chatId); - $httpBackend.flush(); - }); - - it('calls chat seen endpoint', function() { - var groupId = 1; - $httpBackend.expectPOST(apiV3Prefix + '/groups/' + groupId + '/chat/seen').respond({}); - chat.markChatSeen(groupId); - $httpBackend.flush(); - }); -}); diff --git a/test/spec/services/groupServicesSpec.js b/test/spec/services/groupServicesSpec.js index 5b4a88c996..e9a9285265 100644 --- a/test/spec/services/groupServicesSpec.js +++ b/test/spec/services/groupServicesSpec.js @@ -2,168 +2,41 @@ describe('groupServices', function() { var $httpBackend, $http, groups, user; - var groupApiUrlPrefix = '/api/v3/groups'; beforeEach(function() { module(function($provide) { - user = specHelper.newUser(); - user._id = "unique-user-id" - user.party._id = 'unique-party-id'; - user.sync = function(){}; - $provide.value('User', {user: user}); + $provide.value('User', {user:user}); }); inject(function(_$httpBackend_, Groups, User) { $httpBackend = _$httpBackend_; groups = Groups; + user = User; + user.sync = function(){}; }); }); - it('calls get groups', function() { - $httpBackend.expectGET(groupApiUrlPrefix).respond({}); - groups.Group.getGroups(); - $httpBackend.flush(); - }); - - it('calls get group', function() { - var gid = 1; - $httpBackend.expectGET(groupApiUrlPrefix + '/' + gid).respond({}); - groups.Group.get(gid); - $httpBackend.flush(); - }); - it('calls party endpoint', function() { - var groupId = '1234'; - var groupResponse = {data: {_id: groupId}}; - $httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse); - $httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({}); - $httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({}); - $httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({}); - groups.Group.syncParty(); - $httpBackend.flush(); - }); - - it('calls create endpoint', function() { - $httpBackend.expectPOST(groupApiUrlPrefix).respond({}); - groups.Group.create({}); - $httpBackend.flush(); - }); - - it('calls update group', function() { - var gid = 1; - var groupDetails = { _id: gid }; - $httpBackend.expectPUT(groupApiUrlPrefix + '/' + gid).respond({}); - groups.Group.update(groupDetails); - $httpBackend.flush(); - }); - - it('calls join group', function() { - var gid = 1; - $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/join').respond({}); - groups.Group.join(gid); - $httpBackend.flush(); - }); - - it('calls reject invite group', function() { - var gid = 1; - $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/reject-invite').respond({}); - groups.Group.rejectInvite(gid); - $httpBackend.flush(); - }); - - it('calls invite group', function() { - var gid = 1; - $httpBackend.expectPOST(groupApiUrlPrefix + '/' + gid + '/invite').respond({}); - groups.Group.invite(gid, [], []); - $httpBackend.flush(); - }); - - it('calls party endpoint when party is not cached', function() { - var groupId = '1234'; - var groupResponse = {data: {_id: groupId}}; - $httpBackend.expectGET(groupApiUrlPrefix + '/party').respond(groupResponse); - $httpBackend.expectGET('/api/v3/groups/' + groupId + '/members?includeAllPublicFields=true').respond({}); - $httpBackend.expectGET('/api/v3/groups/' + groupId + '/invites').respond({}); - $httpBackend.expectGET('/api/v3/challenges/groups/' + groupId).respond({}); + $httpBackend.expectGET('/api/v2/groups/party').respond({}); groups.party(); $httpBackend.flush(); }); - it('returns party if cached', function (done) { - var uid = 'abc'; - var party = { - _id: uid, - }; - groups.data.party = party; - groups.party() - .then(function (result) { - expect(result).to.eql(party); - done(); - }); - $httpBackend.flush(); - }); - - it('calls tavern endpoint when tavern is not cached', function() { - $httpBackend.expectGET(groupApiUrlPrefix + '/habitrpg').respond({}); + it('calls tavern endpoint', function() { + $httpBackend.expectGET('/api/v2/groups/habitrpg').respond({}); groups.tavern(); $httpBackend.flush(); }); - it('returns tavern if cached', function (done) { - var uid = 'abc'; - var tavern = { - _id: uid, - }; - groups.data.tavern = tavern; - groups.tavern() - .then(function (result) { - expect(result).to.eql(tavern); - done(); - }); - $httpBackend.flush(); - }); - it('calls public guilds endpoint', function() { - $httpBackend.expectGET(groupApiUrlPrefix + '?type=publicGuilds').respond([]); + $httpBackend.expectGET('/api/v2/groups?type=public').respond([]); groups.publicGuilds(); $httpBackend.flush(); }); - it('returns public guilds if cached', function (done) { - var uid = 'abc'; - var publicGuilds = [ - {_id: uid}, - ]; - groups.data.publicGuilds = publicGuilds; - - groups.publicGuilds() - .then(function (result) { - expect(result).to.eql(publicGuilds); - done(); - }); - - $httpBackend.flush(); - }); - it('calls my guilds endpoint', function() { - $httpBackend.expectGET(groupApiUrlPrefix + '?type=guilds').respond([]); + $httpBackend.expectGET('/api/v2/groups?type=guilds').respond([]); groups.myGuilds(); $httpBackend.flush(); }); - - it('returns my guilds if cached', function (done) { - var uid = 'abc'; - var myGuilds = [ - {_id: uid}, - ]; - groups.data.myGuilds = myGuilds; - - groups.myGuilds() - .then(function (myGuilds) { - expect(myGuilds).to.eql(myGuilds); - done(); - }); - - $httpBackend.flush() - }); }); diff --git a/test/spec/services/memberServicesSpec.js b/test/spec/services/memberServicesSpec.js index 1344bf96e7..d33ccd8c98 100644 --- a/test/spec/services/memberServicesSpec.js +++ b/test/spec/services/memberServicesSpec.js @@ -2,7 +2,6 @@ describe('memberServices', function() { var $httpBackend, members; - var apiV3Prefix = '/api/v3'; beforeEach(inject(function (_$httpBackend_, Members) { $httpBackend = _$httpBackend_; @@ -21,51 +20,10 @@ describe('memberServices', function() { expect(members.selectedMember).to.be.undefined; }); - it('calls fetch member', function() { - var memberId = 1; - var memberUrl = apiV3Prefix + '/members/' + memberId; - $httpBackend.expectGET(memberUrl).respond({}); - members.fetchMember(memberId); - $httpBackend.flush(); - }); - - it('calls get group members', function() { - var groupId = 1; - var memberUrl = apiV3Prefix + '/groups/' + groupId + '/members'; - $httpBackend.expectGET(memberUrl).respond({}); - members.getGroupMembers(groupId); - $httpBackend.flush(); - }); - - it('calls get group invites', function() { - var groupId = 1; - var memberUrl = apiV3Prefix + '/groups/' + groupId + '/invites'; - $httpBackend.expectGET(memberUrl).respond({}); - members.getGroupInvites(groupId); - $httpBackend.flush(); - }); - - it('calls get challenge members', function() { - var challengeId = 1; - var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members'; - $httpBackend.expectGET(memberUrl).respond({}); - members.getChallengeMembers(challengeId); - $httpBackend.flush(); - }); - - it('calls get challenge members progress', function() { - var challengeId = 1; - var memberId = 2; - var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId; - $httpBackend.expectGET(memberUrl).respond({}); - members.getChallengeMemberProgress(challengeId, memberId); - $httpBackend.flush(); - }); - describe('addToMembersList', function() { it('adds member to members object', function() { var member = { _id: 'user_id' }; - members.addToMembersList(member, members); + members.addToMembersList(member); expect(members.members).to.eql({ user_id: { _id: 'user_id' } }); @@ -73,37 +31,27 @@ describe('memberServices', function() { }); describe('selectMember', function() { - it('fetches member if not already in cache', function(done) { + it('fetches member if not already in cache', function() { var uid = 'abc'; - var memberResponse = { - data: {_id: uid}, - } - $httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse); - members.selectMember(uid) - .then(function () { - expect(members.selectedMember._id).to.eql(uid); - expect(members.members).to.have.property(uid); - done(); - }); + $httpBackend.expectGET('/api/v2/members/' + uid).respond({ _id: uid }); + members.selectMember(uid, function(){}); $httpBackend.flush(); + + expect(members.selectedMember._id).to.eql(uid); + expect(members.members).to.have.property(uid); }); - it('fetches member if member data in cache is incomplete', function(done) { + it('fetches member if member data in cache is incomplete', function() { var uid = 'abc'; members.members = { abc: { _id: 'abc', items: {} } } - var memberResponse = { - data: {_id: uid}, - } - $httpBackend.expectGET(apiV3Prefix + '/members/' + uid).respond(memberResponse); - members.selectMember(uid) - .then(function () { - expect(members.selectedMember._id).to.eql(uid); - expect(members.members).to.have.property(uid); - done(); - }); + $httpBackend.expectGET('/api/v2/members/' + uid).respond({ _id: uid }); + members.selectMember(uid, function(){}); $httpBackend.flush(); + + expect(members.selectedMember._id).to.eql(uid); + expect(members.members).to.have.property(uid); }); it('gets member from cache if member has a weapons object', function() { diff --git a/test/spec/services/questServicesSpec.js b/test/spec/services/questServicesSpec.js index e71dc6f3ef..e7f8c638b2 100644 --- a/test/spec/services/questServicesSpec.js +++ b/test/spec/services/questServicesSpec.js @@ -1,14 +1,13 @@ 'use strict'; describe('Quests Service', function() { - var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy, state; + var groupsService, quest, questsService, user, content, resolveSpy, rejectSpy; beforeEach(function() { user = specHelper.newUser(); user.ops = { buyQuest: sandbox.spy() }; - user.party._id = 'unique-party-id'; user.achievements.quests = {}; quest = {lvl:20}; @@ -17,11 +16,10 @@ describe('Quests Service', function() { $provide.value('User', {sync: sinon.stub(), user: user}); }); - inject(function(Quests, Groups, Content, _$state_) { + inject(function(Quests, Groups, Content) { questsService = Quests; groupsService = Groups; content = Content; - state = _$state_; }); sandbox.stub(groupsService, 'inviteOrStartParty'); @@ -74,8 +72,7 @@ describe('Quests Service', function() { scope = $rootScope.$new(); })); - //@TODO: This is fixed in a Quest Service PR port - xit('returns a promise', function() { + it('returns a promise', function() { var promise = questsService.buyQuest('whale'); expect(promise).to.respondTo('then'); }); @@ -228,7 +225,7 @@ describe('Quests Service', function() { scope = $rootScope.$new(); })); - xit('returns a promise', function() { + it('returns a promise', function() { var promise = questsService.showQuest('whale'); expect(promise).to.respondTo('then'); }); @@ -338,67 +335,39 @@ describe('Quests Service', function() { }); describe('#initQuest', function() { - var fakeBackend, scope, key = 'whale'; - - beforeEach(inject(function($httpBackend, $rootScope) { - scope = $rootScope.$new(); - fakeBackend = $httpBackend; - var partyResponse = {data:{_id: 'party-id'}}; - - fakeBackend.when('GET', 'partials/main.html').respond({}); - fakeBackend.when('GET', 'partials/main.html').respond({}); - fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse); - fakeBackend.when('GET', '/api/v3/groups/party-id/members?includeAllPublicFields=true').respond({}); - fakeBackend.when('GET', '/api/v3/groups/party-id/invites').respond({}); - fakeBackend.when('GET', '/api/v3/challenges/groups/party-id').respond({}); - fakeBackend.when('POST', '/api/v3/groups/party-id/quests/invite/' + key).respond({quest: { key: 'whale' } }); - fakeBackend.flush(); - })); it('returns a promise', function() { - var promise = questsService.initQuest(key); + var promise = questsService.initQuest('whale'); expect(promise).to.respondTo('then'); }); - it('starts a quest', function(done) { - fakeBackend.expectPOST( '/api/v3/groups/party-id/quests/invite/' + key); - - questsService.initQuest(key) - .then(function(res) { - done(); - }); - - fakeBackend.flush(); - scope.$apply(); - }); + it('accepts quest'); it('brings user to party page'); }); - //@TODO: This is fixed in a Quest Service PR port - xdescribe('#sendAction', function() { + describe('#sendAction', function() { var fakeBackend, scope; beforeEach(inject(function($httpBackend, $rootScope) { scope = $rootScope.$new(); fakeBackend = $httpBackend; - var partyResponse = {data:{_id: 'party-id'}}; fakeBackend.when('GET', 'partials/main.html').respond({}); - fakeBackend.when('GET', '/api/v3/groups/party').respond(partyResponse); - fakeBackend.when('POST', '/api/v3/groups/party-id/quests/reject').respond({quest: { key: 'whale' } }); + fakeBackend.when('GET', '/api/v2/groups/party').respond({_id: 'party-id'}); + fakeBackend.when('POST', '/api/v2/groups/party-id/questReject').respond({quest: { key: 'whale' } }); fakeBackend.flush(); })); it('returns a promise', function() { - var promise = questsService.sendAction('quests/reject'); + var promise = questsService.sendAction('questReject'); expect(promise).to.respondTo('then'); }); it('calls specified quest endpoint', function(done) { - fakeBackend.expectPOST('/api/v3/groups/party-id/quests/reject'); + fakeBackend.expectPOST('/api/v2/groups/party-id/questReject'); - questsService.sendAction('quests/reject') + questsService.sendAction('questReject') .then(function(res) { expect(res.key).to.eql('whale'); done(); @@ -409,7 +378,7 @@ describe('Quests Service', function() { }); it('syncs User', function() { - questsService.sendAction('quests/reject') + questsService.sendAction('questReject') .then(function(res) { expect(User.sync).to.be.calledOnce; done(); diff --git a/test/spec/services/statServicesSpec.js b/test/spec/services/statServicesSpec.js index a1d99809c2..81e13645de 100644 --- a/test/spec/services/statServicesSpec.js +++ b/test/spec/services/statServicesSpec.js @@ -76,11 +76,7 @@ describe('Stats Service', function() { "armor" : "armor_warrior_1" }; var user = { - fns: { - statsComputed: function () { - return { str: 50 }; - }, - }, + _statsComputed: { str: 50 }, stats: { lvl: 10, buffs: { str: 10 }, @@ -256,8 +252,7 @@ describe('Stats Service', function() { describe('mpDisplay', function() { it('displays mp as "mp / totalMP"', function() { - user.fns = {}; - user.fns.statsComputed = function () { return { maxMP: 100 } }; + user._statsComputed = { maxMP: 100 }; user.stats.mp = 30; var mpDisplay = statCalc.mpDisplay(user); @@ -265,8 +260,7 @@ describe('Stats Service', function() { }); it('Rounds mp down when given a decimal', function() { - user.fns = {}; - user.fns.statsComputed = function () { return { maxMP: 100 } }; + user._statsComputed = { maxMP: 100 }; user.stats.mp = 30.99; var mpDisplay = statCalc.mpDisplay(user); diff --git a/test/spec/services/tagServicesSpec.js b/test/spec/services/tagServicesSpec.js deleted file mode 100644 index 119c814e09..0000000000 --- a/test/spec/services/tagServicesSpec.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -describe('Tags Service', function() { - var rootScope, tags, user, $httpBackend; - var apiV3Prefix = 'api/v3/tags'; - - beforeEach(function() { - module(function($provide) { - user = specHelper.newUser(); - $provide.value('User', {user: user}); - }); - - inject(function(_$httpBackend_, _$rootScope_, Tags, User) { - $httpBackend = _$httpBackend_; - rootScope = _$rootScope_; - tags = Tags; - }); - }); - - it('calls get tags endpoint', function() { - $httpBackend.expectGET(apiV3Prefix).respond({}); - tags.getTags(); - $httpBackend.flush(); - }); - - it('calls post tags endpoint', function() { - $httpBackend.expectPOST(apiV3Prefix).respond({}); - tags.createTag(); - $httpBackend.flush(); - }); - - it('calls get tag endpoint', function() { - var tagId = 1; - $httpBackend.expectGET(apiV3Prefix + '/' + tagId).respond({}); - tags.getTag(tagId); - $httpBackend.flush(); - }); - - it('calls update tag endpoint', function() { - var tagId = 1; - $httpBackend.expectPUT(apiV3Prefix + '/' + tagId).respond({}); - tags.updateTag(tagId, {}); - $httpBackend.flush(); - }); - - it('calls delete tag endpoint', function() { - var tagId = 1; - $httpBackend.expectDELETE(apiV3Prefix + '/' + tagId).respond({}); - tags.deleteTag(tagId); - $httpBackend.flush(); - }); -}); diff --git a/test/spec/services/taskServicesSpec.js b/test/spec/services/taskServicesSpec.js index 59d1a5d49e..212ba816f9 100644 --- a/test/spec/services/taskServicesSpec.js +++ b/test/spec/services/taskServicesSpec.js @@ -1,155 +1,22 @@ 'use strict'; describe('Tasks Service', function() { - var rootScope, tasks, user, $httpBackend; - var apiV3Prefix = '/api/v3/tasks'; + var rootScope, tasks, user; beforeEach(function() { + module(function($provide) { user = specHelper.newUser(); $provide.value('User', {user: user}); }); - inject(function(_$httpBackend_, _$rootScope_, Tasks, User) { - $httpBackend = _$httpBackend_; + inject(function(_$rootScope_, Tasks, User) { rootScope = _$rootScope_; rootScope.charts = {}; tasks = Tasks; }); }); - it('calls get user tasks endpoint', function() { - $httpBackend.expectGET(apiV3Prefix + '/user').respond({}); - tasks.getUserTasks(); - $httpBackend.flush(); - }); - - it('calls post user tasks endpoint', function() { - $httpBackend.expectPOST(apiV3Prefix + '/user').respond({}); - tasks.createUserTasks(); - $httpBackend.flush(); - }); - - it('calls get challenge tasks endpoint', function() { - var challengeId = 1; - $httpBackend.expectGET(apiV3Prefix + '/challenge/' + challengeId).respond({}); - tasks.getChallengeTasks(challengeId); - $httpBackend.flush(); - }); - - it('calls create challenge tasks endpoint', function() { - var challengeId = 1; - $httpBackend.expectPOST(apiV3Prefix + '/challenge/' + challengeId).respond({}); - tasks.createChallengeTasks(challengeId, {}); - $httpBackend.flush(); - }); - - it('calls get task endpoint', function() { - var taskId = 1; - $httpBackend.expectGET(apiV3Prefix + '/' + taskId).respond({}); - tasks.getTask(taskId); - $httpBackend.flush(); - }); - - it('calls update task endpoint', function() { - var taskId = 1; - $httpBackend.expectPUT(apiV3Prefix + '/' + taskId).respond({}); - tasks.updateTask(taskId, {}); - $httpBackend.flush(); - }); - - it('calls delete task endpoint', function() { - var taskId = 1; - $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId).respond({}); - tasks.deleteTask(taskId); - $httpBackend.flush(); - }); - - it('calls score task endpoint', function() { - var taskId = 1; - var direction = "down"; - $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/score/' + direction).respond({}); - tasks.scoreTask(taskId, direction); - $httpBackend.flush(); - }); - - it('calls move task endpoint', function() { - var taskId = 1; - var position = 0; - $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/move/to/' + position).respond({}); - tasks.moveTask(taskId, position); - $httpBackend.flush(); - }); - - it('calls add check list item endpoint', function() { - var taskId = 1; - $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist').respond({}); - tasks.addChecklistItem(taskId, {}); - $httpBackend.flush(); - }); - - it('calls score check list item endpoint', function() { - var taskId = 1; - var itemId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/checklist/' + itemId + '/score').respond({}); - tasks.scoreCheckListItem(taskId, itemId); - $httpBackend.flush(); - }); - - it('calls update check list item endpoint', function() { - var taskId = 1; - var itemId = 2; - $httpBackend.expectPUT(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({}); - tasks.updateChecklistItem(taskId, itemId, {}); - $httpBackend.flush(); - }); - - it('calls remove check list item endpoint', function() { - var taskId = 1; - var itemId = 2; - $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/checklist/' + itemId).respond({}); - tasks.removeChecklistItem(taskId, itemId); - $httpBackend.flush(); - }); - - it('calls add tag to list item endpoint', function() { - var taskId = 1; - var tagId = 2; - $httpBackend.expectPOST(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({}); - tasks.addTagToTask(taskId, tagId); - $httpBackend.flush(); - }); - - it('calls remove tag to list item endpoint', function() { - var taskId = 1; - var tagId = 2; - $httpBackend.expectDELETE(apiV3Prefix + '/' + taskId + '/tags/' + tagId).respond({}); - tasks.removeTagFromTask(taskId, tagId); - $httpBackend.flush(); - }); - - it('calls unlinkOneTask endpoint', function() { - var taskId = 1; - var keep = "keep"; - $httpBackend.expectPOST(apiV3Prefix + '/unlink-one/' + taskId + '?keep=' + keep).respond({}); - tasks.unlinkOneTask(taskId); - $httpBackend.flush(); - }); - - it('calls unlinkAllTasks endpoint', function() { - var challengeId = 1; - var keep = "keep-all"; - $httpBackend.expectPOST(apiV3Prefix + '/unlink-all/' + challengeId + '?keep=' + keep).respond({}); - tasks.unlinkAllTasks(challengeId); - $httpBackend.flush(); - }); - - it('calls clear completed todo task endpoint', function() { - $httpBackend.expectPOST(apiV3Prefix + '/clearCompletedTodos').respond({}); - tasks.clearCompletedTodos(); - $httpBackend.flush(); - }); - describe('editTask', function() { var task; @@ -159,35 +26,35 @@ describe('Tasks Service', function() { }); it('toggles the _editing property', function() { - tasks.editTask(task, user); + tasks.editTask(task); expect(task._editing).to.eql(true); - tasks.editTask(task, user); + tasks.editTask(task); expect(task._editing).to.eql(false); }); it('sets _tags to true by default', function() { - tasks.editTask(task, user); + tasks.editTask(task); expect(task._tags).to.eql(true); }); it('sets _tags to false if preference for collapsed tags is turned on', function() { user.preferences.tagsCollapsed = true; - tasks.editTask(task, user); + tasks.editTask(task); expect(task._tags).to.eql(false); }); it('sets _advanced to true by default', function(){ user.preferences.advancedCollapsed = true; - tasks.editTask(task, user); + tasks.editTask(task); expect(task._advanced).to.eql(false); }); it('sets _advanced to false if preference for collapsed advance menu is turned on', function() { user.preferences.advancedCollapsed = false; - tasks.editTask(task, user); + tasks.editTask(task); expect(task._advanced).to.eql(true); }); @@ -195,7 +62,7 @@ describe('Tasks Service', function() { it('closes task chart if it exists', function() { rootScope.charts[task.id] = true; - tasks.editTask(task, user); + tasks.editTask(task); expect(rootScope.charts[task.id]).to.eql(false); }); }); @@ -215,22 +82,24 @@ describe('Tasks Service', function() { expect(clonedTask.attribute).to.eql(task.attribute); }); - it('does not clone original task\'s _id', function() { + it('does not clone original task\'s id or _id', function() { var task = specHelper.newTask(); var clonedTask = tasks.cloneTask(task); + expect(clonedTask.id).to.exist; + expect(clonedTask.id).to.not.eql(task.id); expect(clonedTask._id).to.exist; expect(clonedTask._id).to.not.eql(task._id); }); it('does not clone original task\'s dateCreated attribute', function() { var task = specHelper.newTask({ - createdAt: new Date(2014, 5, 1, 1, 1, 1, 1), + dateCreated: new Date(2014, 5, 1, 1, 1, 1, 1), }); var clonedTask = tasks.cloneTask(task); - expect(clonedTask.createdAt).to.exist; - expect(clonedTask.createdAt).to.not.eql(task.createdAt); + expect(clonedTask.dateCreated).to.exist; + expect(clonedTask.dateCreated).to.not.eql(task.dateCreated); }); it('does not clone original task\'s value', function() { diff --git a/test/spec/services/userServicesSpec.js b/test/spec/services/userServicesSpec.js index 7bb5b7aac9..2f34507e32 100644 --- a/test/spec/services/userServicesSpec.js +++ b/test/spec/services/userServicesSpec.js @@ -36,12 +36,12 @@ describe('userServices', function() { expect(user_id).to.eql(user.user); }); - xit('alerts when not authenticated', function(){ + it('alerts when not authenticated', function(){ user.log(); expect($window.alert).to.have.been.calledWith("Not authenticated, can't sync, go to settings first."); }); - xit('puts items in que queue', function(){ + it('puts items in que queue', function(){ user.log({}); //TODO where does that null comes from? expect(user.settings.sync.queue).to.eql([null, {}]); diff --git a/test/spec/specHelper.js b/test/spec/specHelper.js index cbacac1988..9324fa5d8b 100644 --- a/test/spec/specHelper.js +++ b/test/spec/specHelper.js @@ -28,9 +28,6 @@ var specHelper = {}; var user = { _id: 'unique-user-id', - profile: { - name: 'dummy-name', - }, auth: { timestamps: {} }, stats: stats, items: items, diff --git a/website/client/js/controllers/guildsCtrl.js b/website/client/js/controllers/guildsCtrl.js deleted file mode 100644 index 821b9aebba..0000000000 --- a/website/client/js/controllers/guildsCtrl.js +++ /dev/null @@ -1,127 +0,0 @@ -'use strict'; - -habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', - function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { - $scope.groups = { - guilds: [], - public: [], - }; - - Groups.myGuilds() - .then(function (guilds) { - $scope.groups.guilds = guilds; - }); - - Groups.publicGuilds() - .then(function (guilds) { - $scope.groups.public = guilds; - }); - - $scope.type = 'guild'; - $scope.text = window.env.t('guild'); - - var newGroup = function(){ - return {type:'guild', privacy:'private'}; - } - $scope.newGroup = newGroup() - - $scope.create = function(group){ - if (User.user.balance < 1) { - return $rootScope.openModal('buyGems', {track:"Gems > Create Group"}); - } - - if (confirm(window.env.t('confirmGuild'))) { - Groups.Group.create(group) - .then(function (response) { - var createdGroup = response.data.data; - if (createdGroup.privacy == 'public') { - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy, 'groupName':createdGroup.name}) - } else { - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'guild', 'privacy': createdGroup.privacy}) - } - $rootScope.hardRedirect('/#/options/groups/guilds/' + createdGroup._id); - }); - } - } - - $scope.join = function (group) { - var groupId = group._id; - - // If we don't have the _id property, we are joining from an invitation - // which contains a id property of the group - if (group.id && !group._id) { - groupId = group.id; - } - - Groups.Group.join(groupId) - .then(function (response) { - var joinedGroup = response.data.data; - - User.user.guilds.push(joinedGroup._id); - - if (joinedGroup.privacy == 'public') { - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy, 'groupName': joinedGroup.name}) - } else { - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':false, 'groupType':'guild','privacy': joinedGroup.privacy}) - } - - $location.path('/options/groups/guilds/' + joinedGroup._id); - }); - } - - $scope.reject = function(invitationToReject) { - var index = _.findIndex(User.user.invitations.guilds, function(invite) { return invite.id === invitationToReject.id; }); - User.user.invitations.guilds = User.user.invitations.guilds.splice(0, index); - Groups.Group.rejectInvite(invitationToReject.id); - } - - $scope.leave = function(keep) { - if (keep == 'cancel') { - $scope.selectedGroup = undefined; - $scope.popoverEl.popover('destroy'); - } else { - Groups.Group.leave($scope.selectedGroup._id, keep) - .success(function (data) { - var index = User.user.guilds.indexOf($scope.selectedGroup._id); - delete User.user.guilds[index]; - $scope.selectedGroup = undefined; - $location.path('/options/groups/guilds'); - }); - } - } - - $scope.clickLeave = function(group, $event){ - $scope.selectedGroup = group; - $scope.popoverEl = $($event.target).closest('.btn'); - - var html, title; - - Challenges.getGroupChallenges(group._id) - .then(function(response) { - var challenges = _.pluck(_.filter(response.data.data, function(c) { - return c.group._id == group._id; - }), '_id'); - - if (_.intersection(challenges, User.user.challenges).length > 0) { - html = $compile( - '
' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveGroupCha'); - } else { - html = $compile( - '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveGroup') - } - - $scope.popoverEl.popover('destroy').popover({ - html: true, - placement: 'top', - trigger: 'manual', - title: title, - content: html - }).popover('show'); - }); - } - } - ]); diff --git a/website/client/js/controllers/partyCtrl.js b/website/client/js/controllers/partyCtrl.js deleted file mode 100644 index 64070734bf..0000000000 --- a/website/client/js/controllers/partyCtrl.js +++ /dev/null @@ -1,213 +0,0 @@ -'use strict'; - -habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', - function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social) { - - var user = User.user; - - $scope.type = 'party'; - $scope.text = window.env.t('party'); - - $scope.inviteOrStartParty = Groups.inviteOrStartParty; - $scope.loadWidgets = Social.loadWidgets; - - Groups.Group.syncParty() - .then(function successCallback(group) { - $rootScope.party = $scope.group = group; - checkForNotifications(); - }, function errorCallback(response) { - $rootScope.party = $scope.group = $scope.newGroup = { type: 'party' }; - }); - - function checkForNotifications () { - // Checks if user's party has reached 2 players for the first time. - if(!user.achievements.partyUp - && $scope.group.memberCount >= 2) { - User.set({'achievements.partyUp':true}); - $rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'}); - } - - // Checks if user's party has reached 4 players for the first time. - if(!user.achievements.partyOn - && $scope.group.memberCount >= 4) { - User.set({'achievements.partyOn':true}); - $rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'}); - } - } - - if ($scope.group && $scope.group._id) { - Chat.markChatSeen($scope.group._id); - } - - $scope.create = function(group) { - if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name}); - Groups.Group.create(group) - .then(function(response) { - $rootScope.party = $scope.group = response.data.data; - User.sync(); - Groups.data.party = $scope.group; - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'join group', 'owner':true, 'groupType':'party', 'privacy':'private'}); - Analytics.updateUser({'party.id': $scope.group ._id, 'partySize': 1}); - }); - }; - - $scope.join = function (party) { - Groups.Group.join(party.id) - .then(function (response) { - $rootScope.party = $scope.group = response.data.data; - User.sync(); - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); - Analytics.updateUser({'partyID': party.id}); - $rootScope.hardRedirect('/#/options/groups/party'); - }); - }; - - // TODO: refactor guild and party leave into one function - $scope.leave = function (keep) { - if (keep == 'cancel') { - $scope.selectedGroup = undefined; - $scope.popoverEl.popover('destroy'); - } else { - Groups.Group.leave($scope.selectedGroup._id, keep) - .then(function (response) { - Analytics.updateUser({'partySize':null,'partyID':null}); - User.sync().then(function () { - $rootScope.hardRedirect('/#/options/groups/party'); - }); - }); - } - }; - - // TODO: refactor guild and party clickLeave into one function - $scope.clickLeave = function(group, $event){ - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'}); - $scope.selectedGroup = group; - $scope.popoverEl = $($event.target).closest('.btn'); - var html, title; - html = $compile('' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
')($scope); - title = window.env.t('leavePartyCha'); - - //TODO: Move this to challenge service - Challenges.getGroupChallenges(group._id) - .then(function(response) { - var challenges = _.pluck(_.filter(response.data.data, function(c) { - return c.group._id == group._id; - }), '_id'); - - if (_.intersection(challenges, User.user.challenges).length > 0) { - html = $compile( - '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leavePartyCha'); - } else { - html = $compile( - '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' - )($scope); - title = window.env.t('leaveParty'); - } - - $scope.popoverEl.popover('destroy').popover({ - html: true, - placement: 'top', - trigger: 'manual', - title: title, - content: html - }).popover('show'); - }); - }; - - $scope.clickStartQuest = function () { - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'}); - var hasQuests = _.find(User.user.items.quests, function(quest) { - return quest > 0; - }); - - if (hasQuests){ - $rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" }); - } else { - $rootScope.$state.go('options.inventory.quests'); - } - }; - - $scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) { - if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) { - Groups.Group.leave(Groups.data.party._id, false) - .then(function() { - $rootScope.party = $scope.group = { - loadingNewParty: true - }; - $scope.join({ id: newPartyId, name: newPartyName }); - }); - } - } - - $scope.reject = function(party) { - Groups.Group.rejectInvite(party.id); - User.set({'invitations.party':{}}); - } - - $scope.questInit = function() { - var key = $rootScope.selectedQuest.key; - - Quests.initQuest(key).then(function() { - $rootScope.selectedQuest = undefined; - $scope.$close(); - }); - }; - - $scope.questCancel = function(){ - if (!confirm(window.env.t('sureCancel'))) return; - - Quests.sendAction('quests/cancel') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questAbort = function(){ - if (!confirm(window.env.t('sureAbort'))) return; - if (!confirm(window.env.t('doubleSureAbort'))) return; - - Quests.sendAction('quests/abort') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questLeave = function(){ - if (!confirm(window.env.t('sureLeave'))) return; - - Quests.sendAction('quests/leave') - .then(function(quest) { - $scope.group.quest = quest; - }); - } - - $scope.questAccept = function(){ - Quests.sendAction('quests/accept') - .then(function(quest) { - $scope.group.quest = quest; - }); - }; - - $scope.questForceStart = function(){ - Quests.sendAction('quests/force-start') - .then(function(quest) { - $scope.group.quest = quest; - }); - }; - - $scope.questReject = function(){ - Quests.sendAction('quests/reject') - .then(function(quest) { - $scope.group.quest = quest; - }); - }; - - $scope.canEditQuest = function() { - var isQuestLeader = $scope.group.quest && $scope.group.quest.leader === User.user._id; - - return isQuestLeader; - }; - } - ]); diff --git a/website/client/js/controllers/tavernCtrl.js b/website/client/js/controllers/tavernCtrl.js deleted file mode 100644 index 77056e07a2..0000000000 --- a/website/client/js/controllers/tavernCtrl.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User', 'Challenges', - function($scope, Groups, User, Challenges) { - Groups.tavern() - .then(function (tavern) { - $scope.group = tavern; - Challenges.getGroupChallenges($scope.group._id) - .then(function (response) { - $scope.group.challenges = response.data.data; - }); - }) - - $scope.toggleUserTier = function($event) { - $($event.target).next().toggle(); - } - } - ]); diff --git a/website/client/js/services/challengeServices.js b/website/client/js/services/challengeServices.js deleted file mode 100644 index e5f4cfa2b8..0000000000 --- a/website/client/js/services/challengeServices.js +++ /dev/null @@ -1,99 +0,0 @@ -'use strict'; - -angular.module('habitrpg') -.factory('Challenges', ['ApiUrl', '$resource', '$http', - function(ApiUrl, $resource, $http) { - var apiV3Prefix = '/api/v3'; - - function createChallenge (challengeData) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/challenges', - data: challengeData, - }); - } - - function joinChallenge (challengeId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/challenges/' + challengeId + '/join', - }); - } - - function leaveChallenge (challengeId, keep) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/challenges/' + challengeId + '/leave', - data: { - keep: keep, - } - }); - } - - function getUserChallenges () { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/user', - }); - } - - function getGroupChallenges (groupId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/groups/' + groupId, - }); - } - - function getChallenge (challengeId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/' + challengeId, - }); - } - - function exportChallengeCsv (challengeId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/' + challengeId + '/export/csv', - }); - } - - function updateChallenge (challengeId, updateData) { - - var challengeDataToSend = _.omit(updateData, ['tasks', 'habits', 'todos', 'rewards', 'group']); - if (challengeDataToSend.leader && challengeDataToSend.leader._id) challengeDataToSend.leader = challengeDataToSend.leader._id; - - return $http({ - method: 'PUT', - url: apiV3Prefix + '/challenges/' + challengeId, - data: challengeDataToSend, - }); - } - - function deleteChallenge (challengeId) { - return $http({ - method: 'DELETE', - url: apiV3Prefix + '/challenges/' + challengeId, - }); - } - - function selectChallengeWinner (challengeId, winnerId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/challenges/' + challengeId + '/selectWinner/' + winnerId, - }); - } - - return { - createChallenge: createChallenge, - joinChallenge: joinChallenge, - leaveChallenge: leaveChallenge, - getUserChallenges: getUserChallenges, - getGroupChallenges: getGroupChallenges, - getChallenge: getChallenge, - exportChallengeCsv: exportChallengeCsv, - updateChallenge: updateChallenge, - deleteChallenge: deleteChallenge, - selectChallengeWinner: selectChallengeWinner, - } - }]); diff --git a/website/client/js/services/chatServices.js b/website/client/js/services/chatServices.js deleted file mode 100644 index 4020c9dd92..0000000000 --- a/website/client/js/services/chatServices.js +++ /dev/null @@ -1,87 +0,0 @@ -'use strict'; - -angular.module('habitrpg') -.factory('Chat', ['$http', 'ApiUrl', 'User', - function($http, ApiUrl, User) { - var apiV3Prefix = '/api/v3'; - - function getChat (groupId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/groups/' + groupId + '/chat', - }); - } - - function postChat (groupId, message, previousMsg) { - var url = apiV3Prefix + '/groups/' + groupId + '/chat'; - - if (previousMsg) { - url += '?previousMsg=' + previousMsg; - } - - return $http({ - method: 'POST', - url: url, - data: { - message: message, - } - }); - } - - function deleteChat (groupId, chatId, previousMsg) { - var url = apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId; - - if (previousMsg) { - url += '?previousMsg=' + previousMsg; - } - - return $http({ - method: 'DELETE', - url: url, - }); - } - - function like (groupId, chatId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/like', - }); - } - - function flagChatMessage (groupId, chatId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/flag', - }); - } - - function clearFlagCount (groupId, chatId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/groups/' + groupId + '/chat/' + chatId + '/clearflags', - }); - } - - function markChatSeen (groupId) { - if (User.user.newMessages) delete User.user.newMessages[groupId]; - return $http({ - method: 'POST', - url: apiV3Prefix + '/groups/' + groupId + '/chat/seen', - }); - } - - function clearCards () { - User.user._wrapped && User.set({'flags.cardReceived':false}); - } - - return { - getChat: getChat, - postChat: postChat, - deleteChat: deleteChat, - like: like, - flagChatMessage: flagChatMessage, - clearFlagCount: clearFlagCount, - markChatSeen: markChatSeen, - clearCards: clearCards, - } - }]); diff --git a/website/client/js/services/groupServices.js b/website/client/js/services/groupServices.js deleted file mode 100644 index 916efe50b7..0000000000 --- a/website/client/js/services/groupServices.js +++ /dev/null @@ -1,234 +0,0 @@ -'use strict'; - -angular.module('habitrpg') -.factory('Groups', [ '$location', '$rootScope', '$http', 'Analytics', 'ApiUrl', 'Challenges', '$q', 'User', 'Members', - function($location, $rootScope, $http, Analytics, ApiUrl, Challenges, $q, User, Members) { - var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined }; - var groupApiURLPrefix = "/api/v3/groups"; - var TAVERN_NAME = 'HabitRPG'; - - var Group = {}; - - //@TODO: Add paging - Group.getGroups = function(type) { - var url = groupApiURLPrefix; - if (type) { - url += '?type=' + type; - } - - return $http({ - method: 'GET', - url: url, - }); - }; - - Group.get = function(gid) { - return $http({ - method: 'GET', - url: groupApiURLPrefix + '/' + gid, - }); - }; - - Group.syncParty = function() { - return party(); - }; - - Group.create = function(groupDetails) { - return $http({ - method: "POST", - url: groupApiURLPrefix, - data: groupDetails, - }); - }; - - Group.update = function(groupDetails) { - //@TODO: Check for what has changed? - - //Remove populated fields - var groupDetailsToSend = _.omit(groupDetails, ['challenges', 'members', 'invites']); - if (groupDetailsToSend.leader && groupDetailsToSend.leader._id) groupDetailsToSend.leader = groupDetailsToSend.leader._id; - - return $http({ - method: "PUT", - url: groupApiURLPrefix + '/' + groupDetailsToSend._id, - data: groupDetailsToSend, - }); - }; - - Group.join = function(gid) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/join', - }); - }; - - Group.rejectInvite = function(gid) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/reject-invite', - }); - }; - - Group.leave = function(gid, keep) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/leave', - data: { - keep: keep, - } - }); - }; - - Group.removeMember = function(gid, memberId, message) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/removeMember/' + memberId, - data: { - message: message, - }, - }); - }; - - Group.invite = function(gid, invitationDetails) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/invite', - data: { - uuids: invitationDetails.uuids, - emails: invitationDetails.emails, - }, - }); - }; - - Group.inviteToQuest = function(gid, key) { - return $http({ - method: "POST", - url: groupApiURLPrefix + '/' + gid + '/quests/invite/' + key, - }); - }; - - //On page load, multiple controller request the party. - //So, we cache the promise until the first result is returned - var _cachedPartyPromise; - function party (forceUpdate) { - if (_cachedPartyPromise && !forceUpdate) return _cachedPartyPromise.promise; - _cachedPartyPromise = $q.defer(); - - if (!User.user.party._id) { - data.party = { type: 'party' }; - _cachedPartyPromise.reject(data.party); - } - - if (!data.party || forceUpdate) { - Group.get('party') - .then(function (response) { - data.party = response.data.data; - Members.getGroupMembers(data.party._id, true) - .then(function (response) { - data.party.members = response.data.data; - return Members.getGroupInvites(data.party._id); - }) - .then(function (response) { - data.party.invites = response.data.data; - return Challenges.getGroupChallenges(data.party._id) - }) - .then(function (response) { - data.party.challenges = response.data.data; - _cachedPartyPromise.resolve(data.party); - }); - }, function (response) { - data.party = { type: 'party' }; - _cachedPartyPromise.reject(data.party); - }) - .finally(function() { - _cachePartyPromise = null; - }); - } else { - _cachedPartyPromise.resolve(data.party); - } - - return _cachedPartyPromise.promise; - } - - function publicGuilds () { - var deferred = $q.defer(); - - if (!data.publicGuilds) { - Group.getGroups('publicGuilds') - .then(function (response) { - data.publicGuilds = response.data.data; - deferred.resolve(data.publicGuilds); - }, function (response) { - deferred.reject(response); - }); - } else { - deferred.resolve(data.publicGuilds); - } - - return deferred.promise; - //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them - } - - function myGuilds () { - var deferred = $q.defer(); - - if (!data.myGuilds) { - Group.getGroups('guilds') - .then(function (response) { - data.myGuilds = response.data.data; - deferred.resolve(data.myGuilds); - }, function (response) { - deferred.reject(response); - }); - } else { - deferred.resolve(data.myGuilds); - } - - return deferred.promise; - } - - function tavern (forceUpdate) { - var deferred = $q.defer(); - - if (!data.tavern || forceUpdate) { - Group.get('habitrpg') - .then(function (response) { - data.tavern = response.data.data; - deferred.resolve(data.tavern); - }, function (response) { - deferred.reject(response); - }); - } else { - deferred.resolve(data.tavern); - } - - return deferred.promise; - } - - function inviteOrStartParty (group) { - Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'}); - if (group.type === "party" || $location.$$path === "/options/groups/party") { - group.type = 'party'; - $rootScope.openModal('invite-party', { - controller:'InviteToGroupCtrl', - resolve: { - injectedGroup: function(){ return group; } - } - }); - } else { - $location.path("/options/groups/party"); - } - } - - return { - TAVERN_NAME: TAVERN_NAME, - party: party, - publicGuilds: publicGuilds, - myGuilds: myGuilds, - tavern: tavern, - inviteOrStartParty: inviteOrStartParty, - - data: data, - Group: Group, - }; - }]); diff --git a/website/client/js/services/memberServices.js b/website/client/js/services/memberServices.js deleted file mode 100644 index be1eab4841..0000000000 --- a/website/client/js/services/memberServices.js +++ /dev/null @@ -1,128 +0,0 @@ -'use strict'; - -angular.module('habitrpg') -.factory('Members', [ '$rootScope', 'Shared', 'ApiUrl', '$http', '$q', - function($rootScope, Shared, ApiUrl, $http, $q) { - var members = {}; - var selectedMember = {}; - var apiV3Prefix = '/api/v3'; - - function fetchMember (memberId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/members/' + memberId, - }); - } - - //@TODO: Add paging - function getGroupMembers (groupId, includeAllPublicFields) { - var url = apiV3Prefix + '/groups/' + groupId + '/members'; - - if (includeAllPublicFields) { - url += '?includeAllPublicFields=true'; - } - - return $http({ - method: 'GET', - url: url, - }); - } - - function getGroupInvites (groupId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/groups/' + groupId + '/invites', - }); - } - - function getChallengeMembers (challengeId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/' + challengeId + '/members', - }); - } - - function getChallengeMemberProgress (challengeId, memberId) { - return $http({ - method: 'GET', - url: apiV3Prefix + '/challenges/' + challengeId + '/members/' + memberId, - }); - } - - function sendPrivateMessage (message, toUserId) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/members/send-private-message', - data: { - message: message, - toUserId: toUserId, - } - }); - } - - function transferGems (message, toUserId, gemAmount) { - return $http({ - method: 'POST', - url: apiV3Prefix + '/members/transfer-gems', - data: { - message: message, - toUserId: toUserId, - gemAmount: gemAmount, - } - }); - } - - function selectMember (uid) { - var self = this; - var deferred = $q.defer(); - var memberIsReady = _checkIfMemberIsReady(members[uid]); - - if (memberIsReady) { - _prepareMember(members[uid], self); - deferred.resolve(); - } else { - fetchMember(uid) - .then(function (response) { - var member = response.data.data; - addToMembersList(member); // lazy load for later - _prepareMember(member, self); - deferred.resolve(); - }); - } - - return deferred.promise; - } - - function addToMembersList (member) { - if (member._id) { - members[member._id] = member; - } - } - - function _checkIfMemberIsReady (member) { - return member && member.items && member.items.weapon; - } - - function _prepareMember(member, self) { - Shared.wrap(member, false); - self.selectedMember = members[member._id]; - } - - $rootScope.$on('userUpdated', function(event, user){ - addToMembersList(user); - }) - - return { - members: members, - addToMembersList: addToMembersList, - selectedMember: undefined, - selectMember: selectMember, - fetchMember: fetchMember, - getGroupMembers: getGroupMembers, - getGroupInvites: getGroupInvites, - getChallengeMembers: getChallengeMembers, - getChallengeMemberProgress: getChallengeMemberProgress, - sendPrivateMessage: sendPrivateMessage, - transferGems: transferGems, - } - }]); diff --git a/website/client/js/services/tagsServices.js b/website/client/js/services/tagsServices.js deleted file mode 100644 index 2a430282b6..0000000000 --- a/website/client/js/services/tagsServices.js +++ /dev/null @@ -1,60 +0,0 @@ -'use strict'; - -angular.module('habitrpg') -.factory('Tags', ['$rootScope', '$http', - function tagsFactory($rootScope, $http) { - - function getTags () { - return $http({ - method: 'GET', - url: 'api/v3/tags', - }); - }; - - function createTag (tagDetails) { - return $http({ - method: 'POST', - url: 'api/v3/tags', - data: tagDetails, - }); - }; - - function getTag (tagId) { - return $http({ - method: 'GET', - url: 'api/v3/tags/' + tagId, - }); - }; - - function updateTag (tagId, tagDetails) { - return $http({ - method: 'PUT', - url: 'api/v3/tags/' + tagId, - data: tagDetails, - }); - }; - - function sortTag (tagId, to) { - return $http({ - method: 'POST', - url: 'api/v3/reorder-tags', - data: {tagId: tagId, to: to}, - }); - }; - - function deleteTag (tagId) { - return $http({ - method: 'DELETE', - url: 'api/v3/tags/' + tagId, - }); - }; - - return { - getTags: getTags, - createTag: createTag, - getTag: getTag, - updateTag: updateTag, - sortTag: sortTag, - deleteTag: deleteTag, - }; - }]); diff --git a/website/client/js/services/taskServices.js b/website/client/js/services/taskServices.js deleted file mode 100644 index e7eeef1a93..0000000000 --- a/website/client/js/services/taskServices.js +++ /dev/null @@ -1,205 +0,0 @@ -'use strict'; - -var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt']; - -angular.module('habitrpg') -.factory('Tasks', ['$rootScope', 'Shared', '$http', - function tasksFactory($rootScope, Shared, $http) { - - function getUserTasks (getCompletedTodos) { - var url = '/api/v3/tasks/user'; - - if (getCompletedTodos) url += '?type=completedTodos'; - - return $http({ - method: 'GET', - url: url, - }); - }; - - function createUserTasks (taskDetails) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/user', - data: taskDetails, - }); - }; - - function getChallengeTasks (challengeId) { - return $http({ - method: 'GET', - url: '/api/v3/tasks/challenge/' + challengeId, - }); - }; - - function createChallengeTasks (challengeId, taskDetails) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/challenge/' + challengeId, - data: taskDetails, - }); - }; - - function getTask (taskId) { - return $http({ - method: 'GET', - url: '/api/v3/tasks/' + taskId, - }); - }; - - function updateTask (taskId, taskDetails) { - return $http({ - method: 'PUT', - url: '/api/v3/tasks/' + taskId, - data: taskDetails, - }); - }; - - function deleteTask (taskId) { - return $http({ - method: 'DELETE', - url: '/api/v3/tasks/' + taskId, - }); - }; - - function scoreTask (taskId, direction) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/' + taskId + '/score/' + direction, - }); - }; - - function moveTask (taskId, position) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/' + taskId + '/move/to/' + position, - }); - }; - - function addChecklistItem (taskId, checkListItem) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/' + taskId + '/checklist', - data: checkListItem, - }); - }; - - function scoreCheckListItem (taskId, itemId) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId + '/score', - }); - }; - - function updateChecklistItem (taskId, itemId, itemDetails) { - return $http({ - method: 'PUT', - url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId, - data: itemDetails, - }); - }; - - function removeChecklistItem (taskId, itemId) { - return $http({ - method: 'DELETE', - url: '/api/v3/tasks/' + taskId + '/checklist/' + itemId, - }); - }; - - function addTagToTask (taskId, tagId) { - return $http({ - method: 'POST', - url: '/api/v3/tasks/' + taskId + '/tags/' + tagId, - }); - }; - - function removeTagFromTask (taskId, tagId) { - return $http({ - method: 'DELETE', - url: '/api/v3/tasks/' + taskId + '/tags/' + tagId, - }); - }; - - function unlinkOneTask (taskId, keep) { // single task - if (!keep) { - keep = "keep"; - } - - return $http({ - method: 'POST', - url: '/api/v3/tasks/unlink-one/' + taskId + '?keep=' + keep, - }); - }; - - function unlinkAllTasks (challengeId, keep) { // all tasks - if (!keep) { - keep = "keep-all"; - } - - return $http({ - method: 'POST', - url: '/api/v3/tasks/unlink-all/' + challengeId + '?keep=' + keep, - }); - }; - - function clearCompletedTodos () { - return $http({ - method: 'POST', - url: '/api/v3/tasks/clearCompletedTodos', - }); - }; - - function editTask(task, user) { - task._editing = !task._editing; - task._tags = !user.preferences.tagsCollapsed; - task._advanced = !user.preferences.advancedCollapsed; - if($rootScope.charts[task._id]) $rootScope.charts[task.id] = false; - } - - function cloneTask(task) { - var clonedTask = _.cloneDeep(task); - clonedTask = _cleanUpTask(clonedTask); - - return Shared.taskDefaults(clonedTask); - } - - function _cleanUpTask(task) { - var cleansedTask = _.omit(task, TASK_KEYS_TO_REMOVE); - - // Copy checklists but reset to uncomplete and assign new id - _(cleansedTask.checklist).forEach(function(item) { - item.completed = false; - item.id = Shared.uuid(); - }).value(); - - if (cleansedTask.type !== 'reward') { - delete cleansedTask.value; - } - - return cleansedTask; - } - - return { - getUserTasks: getUserTasks, - loadedCompletedTodos: false, - createUserTasks: createUserTasks, - getChallengeTasks: getChallengeTasks, - createChallengeTasks: createChallengeTasks, - getTask: getTask, - updateTask: updateTask, - deleteTask: deleteTask, - scoreTask: scoreTask, - moveTask: moveTask, - addChecklistItem: addChecklistItem, - scoreCheckListItem: scoreCheckListItem, - updateChecklistItem: updateChecklistItem, - removeChecklistItem: removeChecklistItem, - addTagToTask: addTagToTask, - removeTagFromTask: removeTagFromTask, - unlinkOneTask: unlinkOneTask, - unlinkAllTasks: unlinkAllTasks, - clearCompletedTodos: clearCompletedTodos, - editTask: editTask, - cloneTask: cloneTask - }; - }]); diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js deleted file mode 100644 index 70b8821c6d..0000000000 --- a/website/client/js/services/userServices.js +++ /dev/null @@ -1,583 +0,0 @@ -'use strict'; - -angular.module('habitrpg') - .service('ApiUrl', ['API_URL', function(currentApiUrl) { - this.setApiUrl = function(newUrl){ - currentApiUrl = newUrl; - }; - - this.get = function(){ - return currentApiUrl; - }; - }]) - -/** - * Services that persists and retrieves user from localStorage. - */ - .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Notification', 'ApiUrl', 'Tasks', 'Tags', - function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Notification, ApiUrl, Tasks, Tags) { - var authenticated = false; - var defaultSettings = { - auth: { apiId: '', apiToken: ''}, - sync: { - queue: [], //here OT will be queued up, this is NOT call-back queue! - sent: [] //here will be OT which have been sent, but we have not got reply from server yet. - }, - fetching: false, // whether fetch() was called or no. this is to avoid race conditions - online: false - }; - var settings = {}; //habit mobile settings (like auth etc.) to be stored here - var user = {}; // this is stored as a reference accessible to all controllers, that way updates propagate - - var userNotifications = { - // "party.order" : env.t("updatedParty"), - // "party.orderAscending" : env.t("updatedParty") - // party.order notifications are not currently needed because the party avatars are resorted immediately now - }; // this is a list of notifications to send to the user when changes are made, along with the message. - - //first we populate user with schema - user.apiToken = user._id = ''; // we use id / apitoken to determine if registered - - //than we try to load localStorage - if (localStorage.getItem(STORAGE_USER_ID)) { - _.extend(user, JSON.parse(localStorage.getItem(STORAGE_USER_ID))); - } - - user._wrapped = false; - - function syncUserTasks (tasks) { - user.habits = []; - user.todos = []; - user.dailys = []; - user.rewards = []; - - // Order tasks based on tasksOrder - var groupedTasks = _(tasks) - .groupBy('type') - .forEach(function (tasksOfType, type) { - var order = user.tasksOrder[type + 's']; - var orderedTasks = new Array(tasksOfType.length); - var unorderedTasks = []; // what we want to add later - - tasksOfType.forEach(function (task, index) { - var taskId = task._id; - var i = order[index] === taskId ? index : order.indexOf(taskId); - if (i === -1) { - unorderedTasks.push(task); - } else { - orderedTasks[i] = task; - } - }); - - // Remove empty values from the array and add any unordered task - user[type + 's'] = _.compact(orderedTasks).concat(unorderedTasks); - }).value(); - } - - function sync() { - return $http({ - method: "GET", - url: '/api/v3/user/', - }) - .then(function (response) { - if (response.data.message) Notification.text(response.data.message); - - _.extend(user, response.data.data); - - $rootScope.$emit('userUpdated', user); - - if (!user._wrapped) { - // This wraps user with `ops`, which are functions shared both on client and mobile. When performed on client, - // they update the user in the browser and then send the request to the server, where the same operation is - // replicated. We need to wrap each op to provide a callback to send that operation - $window.habitrpgShared.wrap(user); - _.each(user.ops, function(op,k){ - user.ops[k] = function(req){ - try { - op(req); - } catch (err) { - Notification.text(err.message); - return; - } - } - }); - } - - return Tasks.getUserTasks(); - }) - .then(function (response) { - var tasks = response.data.data; - syncUserTasks(tasks); - save(); - $rootScope.$emit('userSynced'); - }); - } - sync(); - - var save = function () { - localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); - localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(settings)); - }; - - function callOpsFunctionAndRequest (opName, endPoint, method, paramString, opData) { - if (!opData) opData = {}; - - var clientResponse; - - try { - var args = [user]; - if (opName === 'rebirth' || opName === 'reroll' || opName === 'reset') { - args.push(user.habits.concat(user.dailys).concat(user.rewards).concat(user.todos)); - } - - args.push(opData); - clientResponse = $window.habitrpgShared.ops[opName].apply(null, args); - } catch (err) { - Notification.text(err.message); - return; - } - - var clientMessage = clientResponse[1]; - - if (clientMessage) { - Notification.text(clientMessage); - } - - var url = '/api/v3/user/' + endPoint; - if (paramString) { - url += '/' + paramString - } - - var body = {}; - if (opData.body) body = opData.body; - - var queryString = ''; - if (opData.query) queryString = '?' + $.param(opData.query) - - $http({ - method: method, - url: url + queryString, - body: body, - }) - .then(function (response) { - if (response.data.message && response.data.message !== clientMessage) { - Notification.text(response.data.message); - } - - save(); - }) - } - - function setUser(updates) { - for (var key in updates) { - _.set(user, key, updates[key]); - } - } - - var userServices = { - user: user, - - //@TODO: WE need a new way to set the user from tests - setUser: function (userInc) { - user = userInc; - }, - - allocate: function (data) { - callOpsFunctionAndRequest('allocate', 'allocate', "POST",'', data); - }, - - allocateNow: function () { - callOpsFunctionAndRequest('allocateNow', 'allocate-now', "POST"); - }, - - changeClass: function (data) { - callOpsFunctionAndRequest('changeClass', 'change-class', "POST",'', data); - }, - - disableClasses: function () { - callOpsFunctionAndRequest('disableClasses', 'disable-classes', "POST"); - }, - - revive: function (data) { - callOpsFunctionAndRequest('revive', 'revive', "POST"); - }, - - addTask: function (data) { - if (_.isArray(data.body)) { - data.body.forEach(function (task) { - user.ops.addTask({body: task}); - }); - } else { - user.ops.addTask(data); - } - save(); - Tasks.createUserTasks(data.body); - }, - - score: function (data) { - try { - $window.habitrpgShared.ops.scoreTask({user: user, task: data.params.task, direction: data.params.direction}, data.params); - } catch (err) { - Notification.text(err.message); - return; - } - save(); - - Tasks.scoreTask(data.params.task._id, data.params.direction).then(function (res) { - var tmp = res.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses - var drop = tmp.drop; - - if (drop) user._tmp.drop = drop; - }); - }, - - sortTask: function (data) { - user.ops.sortTask(data); - save(); - Tasks.moveTask(data.params.id, data.query.to); - }, - - updateTask: function (task, data) { - $window.habitrpgShared.ops.updateTask(task, data); - save(); - Tasks.updateTask(task._id, data.body); - }, - - deleteTask: function (data) { - user.ops.deleteTask(data); - save(); - Tasks.deleteTask(data.params.id); - }, - - clearCompleted: function () { - user.ops.clearCompleted(user.todos); - save(); - Tasks.clearCompletedTodos(); - }, - - addTag: function(data) { - user.ops.addTag(data); - save(); - Tags.createTag(data.body); - }, - - updateTag: function(data) { - user.ops.updateTag(data); - save(); - Tags.updateTag(data.params.id, data.body); - }, - - sortTag: function (data) { - user.ops.sortTag(data); - Tags.sortTag(user.tags[data.query.from].id, data.query.to); - }, - - deleteTag: function(data) { - user.ops.deleteTag(data); - save(); - Tags.deleteTag(data.params.id); - }, - - addTenGems: function () { - $http({ - method: "POST", - url: 'api/v3/debug/add-ten-gems', - }) - .then(function (response) { - Notification.text('+10 Gems!'); - sync(); - }) - }, - - addHourglass: function () { - $http({ - method: "POST", - url: 'api/v3/debug/add-hourglass', - }) - .then(function (response) { - sync(); - }) - }, - - setCron: function (numberOfDays) { - var date = moment(user.lastCron).subtract(numberOfDays, 'days').toDate(); - - $http({ - method: "POST", - url: 'api/v3/debug/set-cron', - data: { - lastCron: date - } - }) - .then(function (response) { - Notification.text('-' + numberOfDays + ' day(s), remember to refresh'); - }); - }, - - setCustomDayStart: function (dayStart) { - $http({ - method: "POST", - url: 'api/v3/user/custom-day-start', - data: { - dayStart: dayStart - } - }) - .then(function (response) { - Notification.text(response.data.data.message); - sync(); - }); - }, - - makeAdmin: function () { - $http({ - method: "POST", - url: 'api/v3/debug/make-admin' - }) - .then(function (response) { - Notification.text('You are now an admin! Go to the Hall of Heroes to change your contributor level.'); - sync() - }); - }, - - clearNewMessages: function () { - callOpsFunctionAndRequest('markPmsRead', 'mark-pms-read', "POST"); - }, - - clearPMs: function () { - callOpsFunctionAndRequest('clearPMs', 'messages', "DELETE"); - }, - - deletePM: function (data) { - callOpsFunctionAndRequest('deletePM', 'messages', "DELETE", data.params.id, data); - }, - - buy: function (data) { - callOpsFunctionAndRequest('buy', 'buy', "POST", data.params.key, data); - }, - - buyQuest: function (data) { - callOpsFunctionAndRequest('buyQuest', 'buy-quest', "POST", data.params.key, data); - }, - - purchase: function (data) { - var type = data.params.type; - var key = data.params.key; - callOpsFunctionAndRequest('purchase', 'purchase', "POST", type + '/' + key, data); - }, - - buySpecialSpell: function (data) { - $window.habitrpgShared.ops['buySpecialSpell'](user, data); - var key = data.params.key; - - $http({ - method: "POST", - url: '/api/v3/user/' + 'buy-special-spell/' + key, - }) - .then(function (response) { - Notification.text(response.data.message); - }) - }, - - buyMysterySet: function (data) { - callOpsFunctionAndRequest('buyMysterySet', 'buy-mystery-set', "POST", data.params.key, data); - }, - - readCard: function (data) { - callOpsFunctionAndRequest('readCard', 'read-card', "POST", data.params.cardType, data); - }, - - openMysteryItem: function (data) { - callOpsFunctionAndRequest('openMysteryItem', 'open-mystery-item', "POST"); - }, - - sell: function (data) { - var type = data.params.type; - var key = data.params.key; - callOpsFunctionAndRequest('sell', 'sell', "POST", type + '/' + key, data); - }, - - hatch: function (data) { - var egg = data.params.egg; - var hatchingPotion = data.params.hatchingPotion; - callOpsFunctionAndRequest('hatch', 'hatch', "POST", egg + '/' + hatchingPotion, data); - }, - - feed: function (data) { - var pet = data.params.pet; - var food = data.params.food; - callOpsFunctionAndRequest('feed', 'feed', "POST", pet + '/' + food, data); - }, - - equip: function (data) { - var type = data.params.type; - var key = data.params.key; - callOpsFunctionAndRequest('equip', 'equip', "POST", type + '/' + key, data); - }, - - hourglassPurchase: function (data) { - var type = data.params.type; - var key = data.params.key; - callOpsFunctionAndRequest('purchaseHourglass', 'purchase-hourglass', "POST", type + '/' + key, data); - }, - - unlock: function (data) { - callOpsFunctionAndRequest('unlock', 'unlock', "POST", '', data); - }, - - set: function(updates) { - setUser(updates); - $http({ - method: "PUT", - url: '/api/v3/user', - data: updates, - }) - .then(function () { - save(); - $rootScope.$emit('userSynced'); - }) - }, - - reroll: function () { - callOpsFunctionAndRequest('reroll', 'reroll', "POST"); - }, - - rebirth: function () { - callOpsFunctionAndRequest('rebirth', 'rebirth', "POST"); - }, - - reset: function () { - callOpsFunctionAndRequest('reset', 'reset', "POST"); - }, - - releaseBoth: function () { - callOpsFunctionAndRequest('releaseBoth', 'release-both', "POST"); - }, - - releaseMounts: function () { - callOpsFunctionAndRequest('releaseMounts', 'release-mounts', "POST"); - }, - - releasePets: function () { - callOpsFunctionAndRequest('releasePets', 'release-pets', "POST"); - }, - - addWebhook: function (data) { - callOpsFunctionAndRequest('addWebhook', 'webhook', "POST", '', data, data.body); - }, - - updateWebhook: function (data) { - callOpsFunctionAndRequest('updateWebhook', 'webhook', "PUT", data.params.id, data, data.body); - }, - - deleteWebhook: function (data) { - callOpsFunctionAndRequest('deleteWebhook', 'webhook', "DELETE", data.params.id, data, data.body); - }, - - sleep: function () { - callOpsFunctionAndRequest('sleep', 'sleep', "POST"); - }, - - blockUser: function (data) { - callOpsFunctionAndRequest('blockUser', 'block', "POST", data.params.uuid, data); - }, - - online: function (status) { - if (status===true) { - settings.online = true; - // syncQueue(); - } else { - settings.online = false; - }; - }, - - authenticate: function (uuid, token, cb) { - if (uuid && token) { - var offset = moment().zone(); // eg, 240 - this will be converted on server as -(offset/60) - $http.defaults.headers.common['x-api-user'] = uuid; - $http.defaults.headers.common['x-api-key'] = token; - $http.defaults.headers.common['x-user-timezoneOffset'] = offset; - authenticated = true; - settings.auth.apiId = uuid; - settings.auth.apiToken = token; - settings.online = true; - save(); - sync().then(function () { - if (user.preferences.timezoneOffset !== offset) - userServices.set({'preferences.timezoneOffset': offset}); - if (cb) cb(); - }); - } else { - alert('Please enter your ID and Token in settings.') - } - }, - - authenticated: function(){ - return this.settings.auth.apiId !== ""; - }, - - getBalanceInGems: function() { - var balance = user.balance || 0; - return balance * 4; - }, - - log: function (action, cb) { - //push by one buy one if an array passed in. - if (_.isArray(action)) { - action.forEach(function (a) { - settings.sync.queue.push(a); - }); - } else { - settings.sync.queue.push(action); - } - - save(); - }, - - sync: function(){ - userServices.log({}); - return sync(); - }, - - syncUserTasks: syncUserTasks, - - save: save, - - settings: settings - }; - - //load settings if we have them - if (localStorage.getItem(STORAGE_SETTINGS_ID)) { - //use extend here to make sure we keep object reference in other angular controllers - _.extend(settings, JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID))); - - //if settings were saved while fetch was in process reset the flag. - settings.fetching = false; - //create and load if not - } else { - localStorage.setItem(STORAGE_SETTINGS_ID, JSON.stringify(defaultSettings)); - _.extend(settings, defaultSettings); - } - - //If user does not have ApiID that forward him to settings. - if (!settings.auth.apiId || !settings.auth.apiToken) { - //var search = $location.search(); // FIXME this should be working, but it's returning an empty object when at a root url /?_id=... - var search = $location.search($window.location.search.substring(1)).$$search; // so we use this fugly hack instead - if (search.err) return alert(search.err); - if (search._id && search.apiToken) { - userServices.authenticate(search._id, search.apiToken, function(){ - $window.location.href = '/'; - }); - } else { - var isStaticOrSocial = $window.location.pathname.match(/^\/(static|social)/); - if (!isStaticOrSocial){ - localStorage.clear(); - $location.path('/logout'); - } - } - } else { - userServices.authenticate(settings.auth.apiId, settings.auth.apiToken) - } - - return userServices; - } -]); diff --git a/website/client/500.html b/website/public/500.html similarity index 100% rename from website/client/500.html rename to website/public/500.html diff --git a/website/client/apple-touch-icon-114-precomposed.png b/website/public/apple-touch-icon-114-precomposed.png similarity index 100% rename from website/client/apple-touch-icon-114-precomposed.png rename to website/public/apple-touch-icon-114-precomposed.png diff --git a/website/client/apple-touch-icon-144-precomposed.png b/website/public/apple-touch-icon-144-precomposed.png similarity index 100% rename from website/client/apple-touch-icon-144-precomposed.png rename to website/public/apple-touch-icon-144-precomposed.png diff --git a/website/client/apple-touch-icon-57-precomposed.png b/website/public/apple-touch-icon-57-precomposed.png similarity index 100% rename from website/client/apple-touch-icon-57-precomposed.png rename to website/public/apple-touch-icon-57-precomposed.png diff --git a/website/client/apple-touch-icon-72-precomposed.png b/website/public/apple-touch-icon-72-precomposed.png similarity index 100% rename from website/client/apple-touch-icon-72-precomposed.png rename to website/public/apple-touch-icon-72-precomposed.png diff --git a/website/client/apple-touch-icon-precomposed.png b/website/public/apple-touch-icon-precomposed.png similarity index 100% rename from website/client/apple-touch-icon-precomposed.png rename to website/public/apple-touch-icon-precomposed.png diff --git a/website/client/cake.png b/website/public/cake.png similarity index 100% rename from website/client/cake.png rename to website/public/cake.png diff --git a/website/client/community-guidelines-images/backCorner.png b/website/public/community-guidelines-images/backCorner.png similarity index 100% rename from website/client/community-guidelines-images/backCorner.png rename to website/public/community-guidelines-images/backCorner.png diff --git a/website/client/community-guidelines-images/beingHabitican.png b/website/public/community-guidelines-images/beingHabitican.png similarity index 100% rename from website/client/community-guidelines-images/beingHabitican.png rename to website/public/community-guidelines-images/beingHabitican.png diff --git a/website/client/community-guidelines-images/consequences.png b/website/public/community-guidelines-images/consequences.png similarity index 100% rename from website/client/community-guidelines-images/consequences.png rename to website/public/community-guidelines-images/consequences.png diff --git a/website/client/community-guidelines-images/contributing.png b/website/public/community-guidelines-images/contributing.png similarity index 100% rename from website/client/community-guidelines-images/contributing.png rename to website/public/community-guidelines-images/contributing.png diff --git a/website/client/community-guidelines-images/github.gif b/website/public/community-guidelines-images/github.gif similarity index 100% rename from website/client/community-guidelines-images/github.gif rename to website/public/community-guidelines-images/github.gif diff --git a/website/client/community-guidelines-images/infractions.png b/website/public/community-guidelines-images/infractions.png similarity index 100% rename from website/client/community-guidelines-images/infractions.png rename to website/public/community-guidelines-images/infractions.png diff --git a/website/client/community-guidelines-images/intro.png b/website/public/community-guidelines-images/intro.png similarity index 100% rename from website/client/community-guidelines-images/intro.png rename to website/public/community-guidelines-images/intro.png diff --git a/website/client/community-guidelines-images/moderators.png b/website/public/community-guidelines-images/moderators.png similarity index 100% rename from website/client/community-guidelines-images/moderators.png rename to website/public/community-guidelines-images/moderators.png diff --git a/website/client/community-guidelines-images/publicGuilds.png b/website/public/community-guidelines-images/publicGuilds.png similarity index 100% rename from website/client/community-guidelines-images/publicGuilds.png rename to website/public/community-guidelines-images/publicGuilds.png diff --git a/website/client/community-guidelines-images/publicSpaces.png b/website/public/community-guidelines-images/publicSpaces.png similarity index 100% rename from website/client/community-guidelines-images/publicSpaces.png rename to website/public/community-guidelines-images/publicSpaces.png diff --git a/website/client/community-guidelines-images/restoration.png b/website/public/community-guidelines-images/restoration.png similarity index 100% rename from website/client/community-guidelines-images/restoration.png rename to website/public/community-guidelines-images/restoration.png diff --git a/website/client/community-guidelines-images/staff.png b/website/public/community-guidelines-images/staff.png similarity index 100% rename from website/client/community-guidelines-images/staff.png rename to website/public/community-guidelines-images/staff.png diff --git a/website/client/community-guidelines-images/tavern.png b/website/public/community-guidelines-images/tavern.png similarity index 100% rename from website/client/community-guidelines-images/tavern.png rename to website/public/community-guidelines-images/tavern.png diff --git a/website/client/community-guidelines-images/trello.png b/website/public/community-guidelines-images/trello.png similarity index 100% rename from website/client/community-guidelines-images/trello.png rename to website/public/community-guidelines-images/trello.png diff --git a/website/client/community-guidelines-images/wiki.png b/website/public/community-guidelines-images/wiki.png similarity index 100% rename from website/client/community-guidelines-images/wiki.png rename to website/public/community-guidelines-images/wiki.png diff --git a/website/client/css/README.md b/website/public/css/README.md similarity index 100% rename from website/client/css/README.md rename to website/public/css/README.md diff --git a/website/client/css/alerts.styl b/website/public/css/alerts.styl similarity index 100% rename from website/client/css/alerts.styl rename to website/public/css/alerts.styl diff --git a/website/client/css/avatar.styl b/website/public/css/avatar.styl similarity index 100% rename from website/client/css/avatar.styl rename to website/public/css/avatar.styl diff --git a/website/client/css/challenges.styl b/website/public/css/challenges.styl similarity index 100% rename from website/client/css/challenges.styl rename to website/public/css/challenges.styl diff --git a/website/client/css/classes.styl b/website/public/css/classes.styl similarity index 100% rename from website/client/css/classes.styl rename to website/public/css/classes.styl diff --git a/website/client/css/customizer.styl b/website/public/css/customizer.styl similarity index 100% rename from website/client/css/customizer.styl rename to website/public/css/customizer.styl diff --git a/website/client/css/filters.styl b/website/public/css/filters.styl similarity index 100% rename from website/client/css/filters.styl rename to website/public/css/filters.styl diff --git a/website/client/css/footer.styl b/website/public/css/footer.styl similarity index 100% rename from website/client/css/footer.styl rename to website/public/css/footer.styl diff --git a/website/client/css/game-pane.styl b/website/public/css/game-pane.styl similarity index 100% rename from website/client/css/game-pane.styl rename to website/public/css/game-pane.styl diff --git a/website/client/css/global-colors.styl b/website/public/css/global-colors.styl similarity index 100% rename from website/client/css/global-colors.styl rename to website/public/css/global-colors.styl diff --git a/website/client/css/global-modules.styl b/website/public/css/global-modules.styl similarity index 100% rename from website/client/css/global-modules.styl rename to website/public/css/global-modules.styl diff --git a/website/client/css/header.styl b/website/public/css/header.styl similarity index 100% rename from website/client/css/header.styl rename to website/public/css/header.styl diff --git a/website/client/css/helpers.styl b/website/public/css/helpers.styl similarity index 100% rename from website/client/css/helpers.styl rename to website/public/css/helpers.styl diff --git a/website/client/css/index.styl b/website/public/css/index.styl similarity index 100% rename from website/client/css/index.styl rename to website/public/css/index.styl diff --git a/website/client/css/inventory.styl b/website/public/css/inventory.styl similarity index 100% rename from website/client/css/inventory.styl rename to website/public/css/inventory.styl diff --git a/website/client/css/items.styl b/website/public/css/items.styl similarity index 100% rename from website/client/css/items.styl rename to website/public/css/items.styl diff --git a/website/client/css/menu.styl b/website/public/css/menu.styl similarity index 100% rename from website/client/css/menu.styl rename to website/public/css/menu.styl diff --git a/website/client/css/no-script.styl b/website/public/css/no-script.styl similarity index 100% rename from website/client/css/no-script.styl rename to website/public/css/no-script.styl diff --git a/website/client/css/npcs.styl b/website/public/css/npcs.styl similarity index 100% rename from website/client/css/npcs.styl rename to website/public/css/npcs.styl diff --git a/website/client/css/options.styl b/website/public/css/options.styl similarity index 100% rename from website/client/css/options.styl rename to website/public/css/options.styl diff --git a/website/client/css/quests.styl b/website/public/css/quests.styl similarity index 100% rename from website/client/css/quests.styl rename to website/public/css/quests.styl diff --git a/website/client/css/scrollbars.styl b/website/public/css/scrollbars.styl similarity index 100% rename from website/client/css/scrollbars.styl rename to website/public/css/scrollbars.styl diff --git a/website/client/css/shared.styl b/website/public/css/shared.styl similarity index 100% rename from website/client/css/shared.styl rename to website/public/css/shared.styl diff --git a/website/client/css/static.styl b/website/public/css/static.styl similarity index 100% rename from website/client/css/static.styl rename to website/public/css/static.styl diff --git a/website/client/css/tasks.styl b/website/public/css/tasks.styl similarity index 100% rename from website/client/css/tasks.styl rename to website/public/css/tasks.styl diff --git a/website/client/css/variables/screen-size.styl b/website/public/css/variables/screen-size.styl similarity index 100% rename from website/client/css/variables/screen-size.styl rename to website/public/css/variables/screen-size.styl diff --git a/website/client/emails/images/10-days-recapture-v1.png b/website/public/emails/images/10-days-recapture-v1.png similarity index 100% rename from website/client/emails/images/10-days-recapture-v1.png rename to website/public/emails/images/10-days-recapture-v1.png diff --git a/website/client/emails/images/3-days-1-month-recapture-v1.png b/website/public/emails/images/3-days-1-month-recapture-v1.png similarity index 100% rename from website/client/emails/images/3-days-1-month-recapture-v1.png rename to website/public/emails/images/3-days-1-month-recapture-v1.png diff --git a/website/client/emails/images/PROMO-Enchanted-Armoire-v1.png b/website/public/emails/images/PROMO-Enchanted-Armoire-v1.png similarity index 100% rename from website/client/emails/images/PROMO-Enchanted-Armoire-v1.png rename to website/public/emails/images/PROMO-Enchanted-Armoire-v1.png diff --git a/website/client/emails/images/android-promo-v1.png b/website/public/emails/images/android-promo-v1.png similarity index 100% rename from website/client/emails/images/android-promo-v1.png rename to website/public/emails/images/android-promo-v1.png diff --git a/website/client/emails/images/iphone-promo-v1.png b/website/public/emails/images/iphone-promo-v1.png similarity index 100% rename from website/client/emails/images/iphone-promo-v1.png rename to website/public/emails/images/iphone-promo-v1.png diff --git a/website/client/emails/images/one-day-v1.png b/website/public/emails/images/one-day-v1.png similarity index 100% rename from website/client/emails/images/one-day-v1.png rename to website/public/emails/images/one-day-v1.png diff --git a/website/client/emails/images/spring-2015-00-v1.png b/website/public/emails/images/spring-2015-00-v1.png similarity index 100% rename from website/client/emails/images/spring-2015-00-v1.png rename to website/public/emails/images/spring-2015-00-v1.png diff --git a/website/client/emails/images/spring-2015-01-v1.png b/website/public/emails/images/spring-2015-01-v1.png similarity index 100% rename from website/client/emails/images/spring-2015-01-v1.png rename to website/public/emails/images/spring-2015-01-v1.png diff --git a/website/client/emails/images/subscription-begins-time-travelers-v1.png b/website/public/emails/images/subscription-begins-time-travelers-v1.png similarity index 100% rename from website/client/emails/images/subscription-begins-time-travelers-v1.png rename to website/public/emails/images/subscription-begins-time-travelers-v1.png diff --git a/website/client/emails/images/subscription-begins-v1.png b/website/public/emails/images/subscription-begins-v1.png similarity index 100% rename from website/client/emails/images/subscription-begins-v1.png rename to website/public/emails/images/subscription-begins-v1.png diff --git a/website/client/favicon.ico b/website/public/favicon.ico similarity index 100% rename from website/client/favicon.ico rename to website/public/favicon.ico diff --git a/website/client/favicon_192x192.png b/website/public/favicon_192x192.png similarity index 100% rename from website/client/favicon_192x192.png rename to website/public/favicon_192x192.png diff --git a/website/client/fontello/LICENSE.txt b/website/public/fontello/LICENSE.txt similarity index 100% rename from website/client/fontello/LICENSE.txt rename to website/public/fontello/LICENSE.txt diff --git a/website/client/fontello/README.txt b/website/public/fontello/README.txt similarity index 100% rename from website/client/fontello/README.txt rename to website/public/fontello/README.txt diff --git a/website/client/fontello/css/animation.css b/website/public/fontello/css/animation.css similarity index 100% rename from website/client/fontello/css/animation.css rename to website/public/fontello/css/animation.css diff --git a/website/client/fontello/css/fontelico-codes.css b/website/public/fontello/css/fontelico-codes.css similarity index 100% rename from website/client/fontello/css/fontelico-codes.css rename to website/public/fontello/css/fontelico-codes.css diff --git a/website/client/fontello/css/fontelico-embedded.css b/website/public/fontello/css/fontelico-embedded.css similarity index 100% rename from website/client/fontello/css/fontelico-embedded.css rename to website/public/fontello/css/fontelico-embedded.css diff --git a/website/client/fontello/css/fontelico-ie7-codes.css b/website/public/fontello/css/fontelico-ie7-codes.css similarity index 100% rename from website/client/fontello/css/fontelico-ie7-codes.css rename to website/public/fontello/css/fontelico-ie7-codes.css diff --git a/website/client/fontello/css/fontelico-ie7.css b/website/public/fontello/css/fontelico-ie7.css similarity index 100% rename from website/client/fontello/css/fontelico-ie7.css rename to website/public/fontello/css/fontelico-ie7.css diff --git a/website/client/fontello/css/fontelico.css b/website/public/fontello/css/fontelico.css similarity index 100% rename from website/client/fontello/css/fontelico.css rename to website/public/fontello/css/fontelico.css diff --git a/website/client/fontello/demo.html b/website/public/fontello/demo.html similarity index 100% rename from website/client/fontello/demo.html rename to website/public/fontello/demo.html diff --git a/website/client/fontello/font/fontelico.eot b/website/public/fontello/font/fontelico.eot similarity index 100% rename from website/client/fontello/font/fontelico.eot rename to website/public/fontello/font/fontelico.eot diff --git a/website/client/fontello/font/fontelico.svg b/website/public/fontello/font/fontelico.svg similarity index 100% rename from website/client/fontello/font/fontelico.svg rename to website/public/fontello/font/fontelico.svg diff --git a/website/client/fontello/font/fontelico.ttf b/website/public/fontello/font/fontelico.ttf similarity index 100% rename from website/client/fontello/font/fontelico.ttf rename to website/public/fontello/font/fontelico.ttf diff --git a/website/client/fontello/font/fontelico.woff b/website/public/fontello/font/fontelico.woff similarity index 100% rename from website/client/fontello/font/fontelico.woff rename to website/public/fontello/font/fontelico.woff diff --git a/website/client/front/README.md b/website/public/front/README.md similarity index 100% rename from website/client/front/README.md rename to website/public/front/README.md diff --git a/website/client/front/css/blockScroll.css b/website/public/front/css/blockScroll.css similarity index 100% rename from website/client/front/css/blockScroll.css rename to website/public/front/css/blockScroll.css diff --git a/website/client/front/css/bootstrap.min.css b/website/public/front/css/bootstrap.min.css similarity index 100% rename from website/client/front/css/bootstrap.min.css rename to website/public/front/css/bootstrap.min.css diff --git a/website/client/front/css/fixed-positioning.css b/website/public/front/css/fixed-positioning.css similarity index 100% rename from website/client/front/css/fixed-positioning.css rename to website/public/front/css/fixed-positioning.css diff --git a/website/client/front/fonts/glyphicons-halflings-regular.eot b/website/public/front/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from website/client/front/fonts/glyphicons-halflings-regular.eot rename to website/public/front/fonts/glyphicons-halflings-regular.eot diff --git a/website/client/front/fonts/glyphicons-halflings-regular.svg b/website/public/front/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from website/client/front/fonts/glyphicons-halflings-regular.svg rename to website/public/front/fonts/glyphicons-halflings-regular.svg diff --git a/website/client/front/fonts/glyphicons-halflings-regular.ttf b/website/public/front/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from website/client/front/fonts/glyphicons-halflings-regular.ttf rename to website/public/front/fonts/glyphicons-halflings-regular.ttf diff --git a/website/client/front/fonts/glyphicons-halflings-regular.woff b/website/public/front/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from website/client/front/fonts/glyphicons-halflings-regular.woff rename to website/public/front/fonts/glyphicons-halflings-regular.woff diff --git a/website/client/front/fonts/glyphicons-halflings-regular.woff2 b/website/public/front/fonts/glyphicons-halflings-regular.woff2 similarity index 100% rename from website/client/front/fonts/glyphicons-halflings-regular.woff2 rename to website/public/front/fonts/glyphicons-halflings-regular.woff2 diff --git a/website/client/front/images/Feeding_Time.png b/website/public/front/images/Feeding_Time.png similarity index 100% rename from website/client/front/images/Feeding_Time.png rename to website/public/front/images/Feeding_Time.png diff --git a/website/client/front/images/Guilds Sample Screen.png b/website/public/front/images/Guilds Sample Screen.png similarity index 100% rename from website/client/front/images/Guilds Sample Screen.png rename to website/public/front/images/Guilds Sample Screen.png diff --git a/website/client/front/images/HabitRPGPromoPostCard6.png b/website/public/front/images/HabitRPGPromoPostCard6.png similarity index 100% rename from website/client/front/images/HabitRPGPromoPostCard6.png rename to website/public/front/images/HabitRPGPromoPostCard6.png diff --git a/website/client/front/images/HabitRPGPromoThin.png b/website/public/front/images/HabitRPGPromoThin.png similarity index 100% rename from website/client/front/images/HabitRPGPromoThin.png rename to website/public/front/images/HabitRPGPromoThin.png diff --git a/website/client/front/images/Habitica_banner_by_uncommoncriminal.png b/website/public/front/images/Habitica_banner_by_uncommoncriminal.png similarity index 100% rename from website/client/front/images/Habitica_banner_by_uncommoncriminal.png rename to website/public/front/images/Habitica_banner_by_uncommoncriminal.png diff --git a/website/client/front/images/Habitica_map_by_uncommoncriminal.png b/website/public/front/images/Habitica_map_by_uncommoncriminal.png similarity index 100% rename from website/client/front/images/Habitica_map_by_uncommoncriminal.png rename to website/public/front/images/Habitica_map_by_uncommoncriminal.png diff --git a/website/client/front/images/Healer.png b/website/public/front/images/Healer.png similarity index 100% rename from website/client/front/images/Healer.png rename to website/public/front/images/Healer.png diff --git a/website/client/front/images/Mount.png b/website/public/front/images/Mount.png similarity index 100% rename from website/client/front/images/Mount.png rename to website/public/front/images/Mount.png diff --git a/website/client/front/images/Mount_Body_Dragon-Golden.png b/website/public/front/images/Mount_Body_Dragon-Golden.png similarity index 100% rename from website/client/front/images/Mount_Body_Dragon-Golden.png rename to website/public/front/images/Mount_Body_Dragon-Golden.png diff --git a/website/client/front/images/Mount_Body_Dragon-Red.png b/website/public/front/images/Mount_Body_Dragon-Red.png similarity index 100% rename from website/client/front/images/Mount_Body_Dragon-Red.png rename to website/public/front/images/Mount_Body_Dragon-Red.png diff --git a/website/client/front/images/Mount_Body_Wolf-Base.png b/website/public/front/images/Mount_Body_Wolf-Base.png similarity index 100% rename from website/client/front/images/Mount_Body_Wolf-Base.png rename to website/public/front/images/Mount_Body_Wolf-Base.png diff --git a/website/client/front/images/Mount_Head_Dragon-Golden.png b/website/public/front/images/Mount_Head_Dragon-Golden.png similarity index 100% rename from website/client/front/images/Mount_Head_Dragon-Golden.png rename to website/public/front/images/Mount_Head_Dragon-Golden.png diff --git a/website/client/front/images/Mount_Head_Dragon-Red.png b/website/public/front/images/Mount_Head_Dragon-Red.png similarity index 100% rename from website/client/front/images/Mount_Head_Dragon-Red.png rename to website/public/front/images/Mount_Head_Dragon-Red.png diff --git a/website/client/front/images/Mount_Head_Wolf-Base.png b/website/public/front/images/Mount_Head_Wolf-Base.png similarity index 100% rename from website/client/front/images/Mount_Head_Wolf-Base.png rename to website/public/front/images/Mount_Head_Wolf-Base.png diff --git a/website/client/front/images/Party-Header.png b/website/public/front/images/Party-Header.png similarity index 100% rename from website/client/front/images/Party-Header.png rename to website/public/front/images/Party-Header.png diff --git a/website/client/front/images/Pet-Dragon-Red.png b/website/public/front/images/Pet-Dragon-Red.png similarity index 100% rename from website/client/front/images/Pet-Dragon-Red.png rename to website/public/front/images/Pet-Dragon-Red.png diff --git a/website/client/front/images/Pet-Fox-Red.png b/website/public/front/images/Pet-Fox-Red.png similarity index 100% rename from website/client/front/images/Pet-Fox-Red.png rename to website/public/front/images/Pet-Fox-Red.png diff --git a/website/client/front/images/Promo_springclasses2015.png b/website/public/front/images/Promo_springclasses2015.png similarity index 100% rename from website/client/front/images/Promo_springclasses2015.png rename to website/public/front/images/Promo_springclasses2015.png diff --git a/website/client/front/images/Quest_dilatory_drag'on.png b/website/public/front/images/Quest_dilatory_drag'on.png similarity index 100% rename from website/client/front/images/Quest_dilatory_drag'on.png rename to website/public/front/images/Quest_dilatory_drag'on.png diff --git a/website/client/front/images/Quest_dilatory_drag'onSmall.png b/website/public/front/images/Quest_dilatory_drag'onSmall.png similarity index 100% rename from website/client/front/images/Quest_dilatory_drag'onSmall.png rename to website/public/front/images/Quest_dilatory_drag'onSmall.png diff --git a/website/client/front/images/Rogue.png b/website/public/front/images/Rogue.png similarity index 100% rename from website/client/front/images/Rogue.png rename to website/public/front/images/Rogue.png diff --git a/website/client/front/images/SAMPLEadventurers.png b/website/public/front/images/SAMPLEadventurers.png similarity index 100% rename from website/client/front/images/SAMPLEadventurers.png rename to website/public/front/images/SAMPLEadventurers.png diff --git a/website/client/front/images/TVreward.png b/website/public/front/images/TVreward.png similarity index 100% rename from website/client/front/images/TVreward.png rename to website/public/front/images/TVreward.png diff --git a/website/client/front/images/VICE_by_Baconsaur.png b/website/public/front/images/VICE_by_Baconsaur.png similarity index 100% rename from website/client/front/images/VICE_by_Baconsaur.png rename to website/public/front/images/VICE_by_Baconsaur.png diff --git a/website/client/front/images/Warrior.png b/website/public/front/images/Warrior.png similarity index 100% rename from website/client/front/images/Warrior.png rename to website/public/front/images/Warrior.png diff --git a/website/client/front/images/Wizard.png b/website/public/front/images/Wizard.png similarity index 100% rename from website/client/front/images/Wizard.png rename to website/public/front/images/Wizard.png diff --git a/website/client/front/images/achievement-perfect.png b/website/public/front/images/achievement-perfect.png similarity index 100% rename from website/client/front/images/achievement-perfect.png rename to website/public/front/images/achievement-perfect.png diff --git a/website/client/front/images/achievement-triadbingo.png b/website/public/front/images/achievement-triadbingo.png similarity index 100% rename from website/client/front/images/achievement-triadbingo.png rename to website/public/front/images/achievement-triadbingo.png diff --git a/website/client/front/images/avatar/Warrior.png b/website/public/front/images/avatar/Warrior.png similarity index 100% rename from website/client/front/images/avatar/Warrior.png rename to website/public/front/images/avatar/Warrior.png diff --git a/website/client/front/images/avatar/avatar.png b/website/public/front/images/avatar/avatar.png similarity index 100% rename from website/client/front/images/avatar/avatar.png rename to website/public/front/images/avatar/avatar.png diff --git a/website/client/front/images/avatar/avatarstatic.png b/website/public/front/images/avatar/avatarstatic.png similarity index 100% rename from website/client/front/images/avatar/avatarstatic.png rename to website/public/front/images/avatar/avatarstatic.png diff --git a/website/client/front/images/avatar/hair_bangs_1_brown.png b/website/public/front/images/avatar/hair_bangs_1_brown.png similarity index 100% rename from website/client/front/images/avatar/hair_bangs_1_brown.png rename to website/public/front/images/avatar/hair_bangs_1_brown.png diff --git a/website/client/front/images/avatar/head_0.png b/website/public/front/images/avatar/head_0.png similarity index 100% rename from website/client/front/images/avatar/head_0.png rename to website/public/front/images/avatar/head_0.png diff --git a/website/client/front/images/avatar/head_warrior_3.png b/website/public/front/images/avatar/head_warrior_3.png similarity index 100% rename from website/client/front/images/avatar/head_warrior_3.png rename to website/public/front/images/avatar/head_warrior_3.png diff --git a/website/client/front/images/avatar/head_warrior_5.png b/website/public/front/images/avatar/head_warrior_5.png similarity index 100% rename from website/client/front/images/avatar/head_warrior_5.png rename to website/public/front/images/avatar/head_warrior_5.png diff --git a/website/client/front/images/avatar/shield_warrior_3.png b/website/public/front/images/avatar/shield_warrior_3.png similarity index 100% rename from website/client/front/images/avatar/shield_warrior_3.png rename to website/public/front/images/avatar/shield_warrior_3.png diff --git a/website/client/front/images/avatar/shield_warrior_5.png b/website/public/front/images/avatar/shield_warrior_5.png similarity index 100% rename from website/client/front/images/avatar/shield_warrior_5.png rename to website/public/front/images/avatar/shield_warrior_5.png diff --git a/website/client/front/images/avatar/skin_f5a76e.png b/website/public/front/images/avatar/skin_f5a76e.png similarity index 100% rename from website/client/front/images/avatar/skin_f5a76e.png rename to website/public/front/images/avatar/skin_f5a76e.png diff --git a/website/client/front/images/avatar/slim_armor_warrior_3.png b/website/public/front/images/avatar/slim_armor_warrior_3.png similarity index 100% rename from website/client/front/images/avatar/slim_armor_warrior_3.png rename to website/public/front/images/avatar/slim_armor_warrior_3.png diff --git a/website/client/front/images/avatar/slim_armor_warrior_5.png b/website/public/front/images/avatar/slim_armor_warrior_5.png similarity index 100% rename from website/client/front/images/avatar/slim_armor_warrior_5.png rename to website/public/front/images/avatar/slim_armor_warrior_5.png diff --git a/website/client/front/images/avatar/slim_shirt_black.png b/website/public/front/images/avatar/slim_shirt_black.png similarity index 100% rename from website/client/front/images/avatar/slim_shirt_black.png rename to website/public/front/images/avatar/slim_shirt_black.png diff --git a/website/client/front/images/avatar/weapon_healer_6.png b/website/public/front/images/avatar/weapon_healer_6.png similarity index 100% rename from website/client/front/images/avatar/weapon_healer_6.png rename to website/public/front/images/avatar/weapon_healer_6.png diff --git a/website/client/front/images/avatar/weapon_warrior_3.png b/website/public/front/images/avatar/weapon_warrior_3.png similarity index 100% rename from website/client/front/images/avatar/weapon_warrior_3.png rename to website/public/front/images/avatar/weapon_warrior_3.png diff --git a/website/client/front/images/avatar/weapon_warrior_5.png b/website/public/front/images/avatar/weapon_warrior_5.png similarity index 100% rename from website/client/front/images/avatar/weapon_warrior_5.png rename to website/public/front/images/avatar/weapon_warrior_5.png diff --git a/website/client/front/images/blackish_fox_by_kellllly-d7pzd46.png b/website/public/front/images/blackish_fox_by_kellllly-d7pzd46.png similarity index 100% rename from website/client/front/images/blackish_fox_by_kellllly-d7pzd46.png rename to website/public/front/images/blackish_fox_by_kellllly-d7pzd46.png diff --git a/website/client/front/images/coding_by_phoneix_faerie.png b/website/public/front/images/coding_by_phoneix_faerie.png similarity index 100% rename from website/client/front/images/coding_by_phoneix_faerie.png rename to website/public/front/images/coding_by_phoneix_faerie.png diff --git a/website/client/front/images/devices.png b/website/public/front/images/devices.png similarity index 100% rename from website/client/front/images/devices.png rename to website/public/front/images/devices.png diff --git a/website/client/front/images/explosion.jpg b/website/public/front/images/explosion.jpg similarity index 100% rename from website/client/front/images/explosion.jpg rename to website/public/front/images/explosion.jpg diff --git a/website/client/front/images/explosion.png b/website/public/front/images/explosion.png similarity index 100% rename from website/client/front/images/explosion.png rename to website/public/front/images/explosion.png diff --git a/website/client/front/images/habitrpg_pixel.png b/website/public/front/images/habitrpg_pixel.png similarity index 100% rename from website/client/front/images/habitrpg_pixel.png rename to website/public/front/images/habitrpg_pixel.png diff --git a/website/client/front/images/icon175x175.png b/website/public/front/images/icon175x175.png similarity index 100% rename from website/client/front/images/icon175x175.png rename to website/public/front/images/icon175x175.png diff --git a/website/client/front/images/intro.jpg b/website/public/front/images/intro.jpg similarity index 100% rename from website/client/front/images/intro.jpg rename to website/public/front/images/intro.jpg diff --git a/website/client/front/images/intro.psd b/website/public/front/images/intro.psd similarity index 100% rename from website/client/front/images/intro.psd rename to website/public/front/images/intro.psd diff --git a/website/client/front/images/misc/Pet_Food_Cake_Base.png b/website/public/front/images/misc/Pet_Food_Cake_Base.png similarity index 100% rename from website/client/front/images/misc/Pet_Food_Cake_Base.png rename to website/public/front/images/misc/Pet_Food_Cake_Base.png diff --git a/website/client/front/images/misc/inventory_quest_scroll_harpy.png b/website/public/front/images/misc/inventory_quest_scroll_harpy.png similarity index 100% rename from website/client/front/images/misc/inventory_quest_scroll_harpy.png rename to website/public/front/images/misc/inventory_quest_scroll_harpy.png diff --git a/website/client/front/images/misc/rebirth_orb.png b/website/public/front/images/misc/rebirth_orb.png similarity index 100% rename from website/client/front/images/misc/rebirth_orb.png rename to website/public/front/images/misc/rebirth_orb.png diff --git a/website/client/front/images/misc/shop_gold.png b/website/public/front/images/misc/shop_gold.png similarity index 100% rename from website/client/front/images/misc/shop_gold.png rename to website/public/front/images/misc/shop_gold.png diff --git a/website/client/front/images/misc/shop_potion.png b/website/public/front/images/misc/shop_potion.png similarity index 100% rename from website/client/front/images/misc/shop_potion.png rename to website/public/front/images/misc/shop_potion.png diff --git a/website/client/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png b/website/public/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png similarity index 100% rename from website/client/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png rename to website/public/front/images/mockup_for_habit_by_cosmic_caterpillar-d8mf5mb.png diff --git a/website/client/front/images/party/AnnaCosplay.png b/website/public/front/images/party/AnnaCosplay.png similarity index 100% rename from website/client/front/images/party/AnnaCosplay.png rename to website/public/front/images/party/AnnaCosplay.png diff --git a/website/client/front/images/party/Ariel_cosplay.png b/website/public/front/images/party/Ariel_cosplay.png similarity index 100% rename from website/client/front/images/party/Ariel_cosplay.png rename to website/public/front/images/party/Ariel_cosplay.png diff --git a/website/client/front/images/party/Big_Daddy_(BioShock).png b/website/public/front/images/party/Big_Daddy_(BioShock).png similarity index 100% rename from website/client/front/images/party/Big_Daddy_(BioShock).png rename to website/public/front/images/party/Big_Daddy_(BioShock).png diff --git a/website/client/front/images/party/Cosplay_Daenerys_Targaryen.png b/website/public/front/images/party/Cosplay_Daenerys_Targaryen.png similarity index 100% rename from website/client/front/images/party/Cosplay_Daenerys_Targaryen.png rename to website/public/front/images/party/Cosplay_Daenerys_Targaryen.png diff --git a/website/client/front/images/party/GrimReaper.png b/website/public/front/images/party/GrimReaper.png similarity index 100% rename from website/client/front/images/party/GrimReaper.png rename to website/public/front/images/party/GrimReaper.png diff --git a/website/client/front/images/party/HomeStuckLusus.png b/website/public/front/images/party/HomeStuckLusus.png similarity index 100% rename from website/client/front/images/party/HomeStuckLusus.png rename to website/public/front/images/party/HomeStuckLusus.png diff --git a/website/client/front/images/presslogos/Cnetlogo.png b/website/public/front/images/presslogos/Cnetlogo.png similarity index 100% rename from website/client/front/images/presslogos/Cnetlogo.png rename to website/public/front/images/presslogos/Cnetlogo.png diff --git a/website/client/front/images/presslogos/Fast-Company-logo.png b/website/public/front/images/presslogos/Fast-Company-logo.png similarity index 100% rename from website/client/front/images/presslogos/Fast-Company-logo.png rename to website/public/front/images/presslogos/Fast-Company-logo.png diff --git a/website/client/front/images/presslogos/Forbes_logo.png b/website/public/front/images/presslogos/Forbes_logo.png similarity index 100% rename from website/client/front/images/presslogos/Forbes_logo.png rename to website/public/front/images/presslogos/Forbes_logo.png diff --git a/website/client/front/images/presslogos/GitHub_Logo.png b/website/public/front/images/presslogos/GitHub_Logo.png similarity index 100% rename from website/client/front/images/presslogos/GitHub_Logo.png rename to website/public/front/images/presslogos/GitHub_Logo.png diff --git a/website/client/front/images/presslogos/discover_logo.png b/website/public/front/images/presslogos/discover_logo.png similarity index 100% rename from website/client/front/images/presslogos/discover_logo.png rename to website/public/front/images/presslogos/discover_logo.png diff --git a/website/client/front/images/presslogos/ionic-logo-blog.png b/website/public/front/images/presslogos/ionic-logo-blog.png similarity index 100% rename from website/client/front/images/presslogos/ionic-logo-blog.png rename to website/public/front/images/presslogos/ionic-logo-blog.png diff --git a/website/client/front/images/presslogos/ionic-logo-horizontal-transparent.png b/website/public/front/images/presslogos/ionic-logo-horizontal-transparent.png similarity index 100% rename from website/client/front/images/presslogos/ionic-logo-horizontal-transparent.png rename to website/public/front/images/presslogos/ionic-logo-horizontal-transparent.png diff --git a/website/client/front/images/presslogos/kickstarter-logo.png b/website/public/front/images/presslogos/kickstarter-logo.png similarity index 100% rename from website/client/front/images/presslogos/kickstarter-logo.png rename to website/public/front/images/presslogos/kickstarter-logo.png diff --git a/website/client/front/images/presslogos/landing_slack_hash_wordmark_logo.png b/website/public/front/images/presslogos/landing_slack_hash_wordmark_logo.png similarity index 100% rename from website/client/front/images/presslogos/landing_slack_hash_wordmark_logo.png rename to website/public/front/images/presslogos/landing_slack_hash_wordmark_logo.png diff --git a/website/client/front/images/presslogos/lifehacker.png b/website/public/front/images/presslogos/lifehacker.png similarity index 100% rename from website/client/front/images/presslogos/lifehacker.png rename to website/public/front/images/presslogos/lifehacker.png diff --git a/website/client/front/images/presslogos/logo_webstorm.png b/website/public/front/images/presslogos/logo_webstorm.png similarity index 100% rename from website/client/front/images/presslogos/logo_webstorm.png rename to website/public/front/images/presslogos/logo_webstorm.png diff --git a/website/client/front/images/presslogos/makeuseof.png b/website/public/front/images/presslogos/makeuseof.png similarity index 100% rename from website/client/front/images/presslogos/makeuseof.png rename to website/public/front/images/presslogos/makeuseof.png diff --git a/website/client/front/images/presslogos/nyt-logo.png b/website/public/front/images/presslogos/nyt-logo.png similarity index 100% rename from website/client/front/images/presslogos/nyt-logo.png rename to website/public/front/images/presslogos/nyt-logo.png diff --git a/website/client/front/images/presslogos/slack.png b/website/public/front/images/presslogos/slack.png similarity index 100% rename from website/client/front/images/presslogos/slack.png rename to website/public/front/images/presslogos/slack.png diff --git a/website/client/front/images/presslogos/trello-logo-blue.png b/website/public/front/images/presslogos/trello-logo-blue.png similarity index 100% rename from website/client/front/images/presslogos/trello-logo-blue.png rename to website/public/front/images/presslogos/trello-logo-blue.png diff --git a/website/client/front/images/quest_vice3.png b/website/public/front/images/quest_vice3.png similarity index 100% rename from website/client/front/images/quest_vice3.png rename to website/public/front/images/quest_vice3.png diff --git a/website/client/front/images/screenshot.png b/website/public/front/images/screenshot.png similarity index 100% rename from website/client/front/images/screenshot.png rename to website/public/front/images/screenshot.png diff --git a/website/client/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png b/website/public/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png similarity index 100% rename from website/client/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png rename to website/public/front/images/t_bone_fight_2_by_mortquitue-d8dtxbl.png diff --git a/website/client/front/images/testimonial_by_Streak.png b/website/public/front/images/testimonial_by_Streak.png similarity index 100% rename from website/client/front/images/testimonial_by_Streak.png rename to website/public/front/images/testimonial_by_Streak.png diff --git a/website/client/front/images/testimonials/16bitFil.png b/website/public/front/images/testimonials/16bitFil.png similarity index 100% rename from website/client/front/images/testimonials/16bitFil.png rename to website/public/front/images/testimonials/16bitFil.png diff --git a/website/client/front/images/testimonials/AlexandraSo.png b/website/public/front/images/testimonials/AlexandraSo.png similarity index 100% rename from website/client/front/images/testimonials/AlexandraSo.png rename to website/public/front/images/testimonials/AlexandraSo.png diff --git a/website/client/front/images/testimonials/Althaire.png b/website/public/front/images/testimonials/Althaire.png similarity index 100% rename from website/client/front/images/testimonials/Althaire.png rename to website/public/front/images/testimonials/Althaire.png diff --git a/website/client/front/images/testimonials/AndeeLiao.png b/website/public/front/images/testimonials/AndeeLiao.png similarity index 100% rename from website/client/front/images/testimonials/AndeeLiao.png rename to website/public/front/images/testimonials/AndeeLiao.png diff --git a/website/client/front/images/testimonials/Brenna.png b/website/public/front/images/testimonials/Brenna.png similarity index 100% rename from website/client/front/images/testimonials/Brenna.png rename to website/public/front/images/testimonials/Brenna.png diff --git a/website/client/front/images/testimonials/Drag0nsilver.png b/website/public/front/images/testimonials/Drag0nsilver.png similarity index 100% rename from website/client/front/images/testimonials/Drag0nsilver.png rename to website/public/front/images/testimonials/Drag0nsilver.png diff --git a/website/client/front/images/testimonials/Drei-M.png b/website/public/front/images/testimonials/Drei-M.png similarity index 100% rename from website/client/front/images/testimonials/Drei-M.png rename to website/public/front/images/testimonials/Drei-M.png diff --git a/website/client/front/images/testimonials/Elmi.png b/website/public/front/images/testimonials/Elmi.png similarity index 100% rename from website/client/front/images/testimonials/Elmi.png rename to website/public/front/images/testimonials/Elmi.png diff --git a/website/client/front/images/testimonials/EvaGantz.png b/website/public/front/images/testimonials/EvaGantz.png similarity index 100% rename from website/client/front/images/testimonials/EvaGantz.png rename to website/public/front/images/testimonials/EvaGantz.png diff --git a/website/client/front/images/testimonials/Helcura.png b/website/public/front/images/testimonials/Helcura.png similarity index 100% rename from website/client/front/images/testimonials/Helcura.png rename to website/public/front/images/testimonials/Helcura.png diff --git a/website/client/front/images/testimonials/InfH.png b/website/public/front/images/testimonials/InfH.png similarity index 100% rename from website/client/front/images/testimonials/InfH.png rename to website/public/front/images/testimonials/InfH.png diff --git a/website/client/front/images/testimonials/Kai.png b/website/public/front/images/testimonials/Kai.png similarity index 100% rename from website/client/front/images/testimonials/Kai.png rename to website/public/front/images/testimonials/Kai.png diff --git a/website/client/front/images/testimonials/Kazui.png b/website/public/front/images/testimonials/Kazui.png similarity index 100% rename from website/client/front/images/testimonials/Kazui.png rename to website/public/front/images/testimonials/Kazui.png diff --git a/website/client/front/images/testimonials/Zelah_Meyer.png b/website/public/front/images/testimonials/Zelah_Meyer.png similarity index 100% rename from website/client/front/images/testimonials/Zelah_Meyer.png rename to website/public/front/images/testimonials/Zelah_Meyer.png diff --git a/website/client/front/images/testimonials/autumnesquirrel.png b/website/public/front/images/testimonials/autumnesquirrel.png similarity index 100% rename from website/client/front/images/testimonials/autumnesquirrel.png rename to website/public/front/images/testimonials/autumnesquirrel.png diff --git a/website/client/front/images/testimonials/frabjabulous.png b/website/public/front/images/testimonials/frabjabulous.png similarity index 100% rename from website/client/front/images/testimonials/frabjabulous.png rename to website/public/front/images/testimonials/frabjabulous.png diff --git a/website/client/front/images/testimonials/galarix.png b/website/public/front/images/testimonials/galarix.png similarity index 100% rename from website/client/front/images/testimonials/galarix.png rename to website/public/front/images/testimonials/galarix.png diff --git a/website/client/front/images/testimonials/gwyn.blath.png b/website/public/front/images/testimonials/gwyn.blath.png similarity index 100% rename from website/client/front/images/testimonials/gwyn.blath.png rename to website/public/front/images/testimonials/gwyn.blath.png diff --git a/website/client/front/images/testimonials/irishfeet123.png b/website/public/front/images/testimonials/irishfeet123.png similarity index 100% rename from website/client/front/images/testimonials/irishfeet123.png rename to website/public/front/images/testimonials/irishfeet123.png diff --git a/website/client/front/images/testimonials/skysailor.png b/website/public/front/images/testimonials/skysailor.png similarity index 100% rename from website/client/front/images/testimonials/skysailor.png rename to website/public/front/images/testimonials/skysailor.png diff --git a/website/client/front/images/testimonials/supermouse35.png b/website/public/front/images/testimonials/supermouse35.png similarity index 100% rename from website/client/front/images/testimonials/supermouse35.png rename to website/public/front/images/testimonials/supermouse35.png diff --git a/website/client/front/images/testimonials/tonitonirocca.png b/website/public/front/images/testimonials/tonitonirocca.png similarity index 100% rename from website/client/front/images/testimonials/tonitonirocca.png rename to website/public/front/images/testimonials/tonitonirocca.png diff --git a/website/client/front/images/uses/achievement-bkgd.png b/website/public/front/images/uses/achievement-bkgd.png similarity index 100% rename from website/client/front/images/uses/achievement-bkgd.png rename to website/public/front/images/uses/achievement-bkgd.png diff --git a/website/client/front/images/uses/clipart-rosemonkeyct-meditation.png b/website/public/front/images/uses/clipart-rosemonkeyct-meditation.png similarity index 100% rename from website/client/front/images/uses/clipart-rosemonkeyct-meditation.png rename to website/public/front/images/uses/clipart-rosemonkeyct-meditation.png diff --git a/website/client/front/images/uses/clipart-rosemonkeyct-meditation.psd b/website/public/front/images/uses/clipart-rosemonkeyct-meditation.psd similarity index 100% rename from website/client/front/images/uses/clipart-rosemonkeyct-meditation.psd rename to website/public/front/images/uses/clipart-rosemonkeyct-meditation.psd diff --git a/website/client/front/images/uses/clipart-rosemonkeyct-reading.png b/website/public/front/images/uses/clipart-rosemonkeyct-reading.png similarity index 100% rename from website/client/front/images/uses/clipart-rosemonkeyct-reading.png rename to website/public/front/images/uses/clipart-rosemonkeyct-reading.png diff --git a/website/client/front/images/uses/coding.png b/website/public/front/images/uses/coding.png similarity index 100% rename from website/client/front/images/uses/coding.png rename to website/public/front/images/uses/coding.png diff --git a/website/client/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png b/website/public/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png similarity index 100% rename from website/client/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png rename to website/public/front/images/uses/coding_3_by_phoneix_faerie-d7idtti.png diff --git a/website/client/front/images/uses/consequences.png b/website/public/front/images/uses/consequences.png similarity index 100% rename from website/client/front/images/uses/consequences.png rename to website/public/front/images/uses/consequences.png diff --git a/website/client/front/images/uses/dusting-bkgd.png b/website/public/front/images/uses/dusting-bkgd.png similarity index 100% rename from website/client/front/images/uses/dusting-bkgd.png rename to website/public/front/images/uses/dusting-bkgd.png diff --git a/website/client/front/images/uses/dusting_by_leephon.png b/website/public/front/images/uses/dusting_by_leephon.png similarity index 100% rename from website/client/front/images/uses/dusting_by_leephon.png rename to website/public/front/images/uses/dusting_by_leephon.png diff --git a/website/client/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png b/website/public/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png similarity index 100% rename from website/client/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png rename to website/public/front/images/uses/gaining_an_achievement_by_cosmic_caterpillar-d7uyv5z.png diff --git a/website/client/front/images/uses/meditation-bkgd.png b/website/public/front/images/uses/meditation-bkgd.png similarity index 100% rename from website/client/front/images/uses/meditation-bkgd.png rename to website/public/front/images/uses/meditation-bkgd.png diff --git a/website/client/front/images/uses/publicSpaces.png b/website/public/front/images/uses/publicSpaces.png similarity index 100% rename from website/client/front/images/uses/publicSpaces.png rename to website/public/front/images/uses/publicSpaces.png diff --git a/website/client/front/images/uses/reading.png b/website/public/front/images/uses/reading.png similarity index 100% rename from website/client/front/images/uses/reading.png rename to website/public/front/images/uses/reading.png diff --git a/website/client/front/js/blockScroll.js b/website/public/front/js/blockScroll.js similarity index 100% rename from website/client/front/js/blockScroll.js rename to website/public/front/js/blockScroll.js diff --git a/website/client/front/js/bootstrap.min.js b/website/public/front/js/bootstrap.min.js similarity index 100% rename from website/client/front/js/bootstrap.min.js rename to website/public/front/js/bootstrap.min.js diff --git a/website/client/front/js/skrollr.min.js b/website/public/front/js/skrollr.min.js similarity index 100% rename from website/client/front/js/skrollr.min.js rename to website/public/front/js/skrollr.min.js diff --git a/website/client/front/landingv1Wireframe.jpg b/website/public/front/landingv1Wireframe.jpg similarity index 100% rename from website/client/front/landingv1Wireframe.jpg rename to website/public/front/landingv1Wireframe.jpg diff --git a/website/client/front/staticstyle.css b/website/public/front/staticstyle.css similarity index 100% rename from website/client/front/staticstyle.css rename to website/public/front/staticstyle.css diff --git a/website/client/front/style.css b/website/public/front/style.css similarity index 100% rename from website/client/front/style.css rename to website/public/front/style.css diff --git a/website/client/google280633b772b94345.html b/website/public/google280633b772b94345.html similarity index 100% rename from website/client/google280633b772b94345.html rename to website/public/google280633b772b94345.html diff --git a/website/client/google8ca65b6ff3506fb8.html b/website/public/google8ca65b6ff3506fb8.html similarity index 100% rename from website/client/google8ca65b6ff3506fb8.html rename to website/public/google8ca65b6ff3506fb8.html diff --git a/website/client/googlef3b1402b0e28338a.html b/website/public/googlef3b1402b0e28338a.html similarity index 100% rename from website/client/googlef3b1402b0e28338a.html rename to website/public/googlef3b1402b0e28338a.html diff --git a/website/client/js/.eslintrc b/website/public/js/.eslintrc similarity index 100% rename from website/client/js/.eslintrc rename to website/public/js/.eslintrc diff --git a/website/client/js/app.js b/website/public/js/app.js similarity index 73% rename from website/client/js/app.js rename to website/public/js/app.js index 86801e6777..6b39f45d63 100644 --- a/website/client/js/app.js +++ b/website/public/js/app.js @@ -26,7 +26,6 @@ window.habitrpg = angular.module('habitrpg', .constant("STORAGE_USER_ID", 'habitrpg-user') .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) - .constant("TAVERN_ID", window.habitrpgShared.TAVERN_ID) //.constant("STORAGE_GROUPS_ID", "") // if we decide to take groups offline .config(['$stateProvider', '$urlRouterProvider', '$httpProvider', 'STORAGE_SETTINGS_ID', @@ -151,25 +150,12 @@ window.habitrpg = angular.module('habitrpg', url: '/:gid', templateUrl: 'partials/options.social.guilds.detail.html', title: env.t('titleGuilds'), - controller: ['$scope', 'Groups', 'Chat', '$stateParams', 'Members', 'Challenges', - function($scope, Groups, Chat, $stateParams, Members, Challenges){ - Groups.Group.get($stateParams.gid) - .then(function (response) { - $scope.group = response.data.data; - Chat.markChatSeen($scope.group._id); - Members.getGroupMembers($scope.group._id) - .then(function (response) { - $scope.group.members = response.data.data; - }); - Members.getGroupInvites($scope.group._id) - .then(function (response) { - $scope.group.invites = response.data.data; - }); - Challenges.getGroupChallenges($scope.group._id) - .then(function (response) { - $scope.group.challenges = response.data.data; - }); - }); + controller: ['$scope', 'Groups', 'Chat', '$stateParams', + function($scope, Groups, Chat, $stateParams){ + Groups.Group.get({gid:$stateParams.gid}, function(group){ + $scope.group = group; + Chat.seenMessage(group._id); + }); }] }) @@ -185,69 +171,33 @@ window.habitrpg = angular.module('habitrpg', url: '/:cid', templateUrl: 'partials/options.social.challenges.detail.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Challenges', '$stateParams', 'Tasks', 'Members', - function ($scope, Challenges, $stateParams, Tasks, Members) { - Challenges.getChallenge($stateParams.cid) - .then(function (response) { - $scope.obj = $scope.challenge = response.data.data; - $scope.challenge._locked = true; - return Tasks.getChallengeTasks($scope.challenge._id); - }) - .then(function (response) { - var tasks = response.data.data; - tasks.forEach(function (element, index, array) { - if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = []; - $scope.challenge[element.type + 's'].push(element); - }) - - return Members.getChallengeMembers($scope.challenge._id); - }) - .then(function (response) { - $scope.challenge.members = response.data.data; - }); + controller: ['$scope', 'Challenges', '$stateParams', + function($scope, Challenges, $stateParams){ + $scope.obj = $scope.challenge = Challenges.Challenge.get({cid:$stateParams.cid}, function(){ + $scope.challenge._locked = true; + }); }] }) .state('options.social.challenges.edit', { url: '/:cid/edit', templateUrl: 'partials/options.social.challenges.detail.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Challenges', '$stateParams', 'Tasks', - function ($scope, Challenges, $stateParams, Tasks) { - Challenges.getChallenge($stateParams.cid) - .then(function (response) { - $scope.obj = $scope.challenge = response.data.data; - $scope.challenge._locked = false; - return Tasks.getChallengeTasks($scope.challenge._id); - }) - .then(function (response) { - var tasks = response.data.data; - tasks.forEach(function (element, index, array) { - if (!$scope.challenge[element.type + 's']) $scope.challenge[element.type + 's'] = []; - $scope.challenge[element.type + 's'].push(element); - }) - }); + controller: ['$scope', 'Challenges', '$stateParams', + function($scope, Challenges, $stateParams){ + $scope.obj = $scope.challenge = Challenges.Challenge.get({cid:$stateParams.cid}, function(){ + $scope.challenge._locked = false; + }); }] }) .state('options.social.challenges.detail.member', { url: '/:uid', templateUrl: 'partials/options.social.challenges.detail.member.html', title: env.t('titleChallenges'), - controller: ['$scope', 'Members', '$stateParams', - function($scope, Members, $stateParams){ - Members.getChallengeMemberProgress($stateParams.cid, $stateParams.uid) - .then(function(response) { - $scope.obj = response.data.data; - - $scope.obj.habits = []; - $scope.obj.todos = []; - $scope.obj.dailys = []; - $scope.obj.rewards = []; - $scope.obj.tasks.forEach(function (element, index, array) { - $scope.obj[element.type + 's'].push(element) - }); - - $scope.obj._locked = true; - }); + controller: ['$scope', 'Challenges', '$stateParams', + function($scope, Challenges, $stateParams){ + $scope.obj = Challenges.Challenge.getMember({cid:$stateParams.cid, uid:$stateParams.uid}, function(){ + $scope.obj._locked = true; + }); }] }) @@ -331,7 +281,6 @@ window.habitrpg = angular.module('habitrpg', }); var settings = JSON.parse(localStorage.getItem(STORAGE_SETTINGS_ID)); - if (settings && settings.auth) { $httpProvider.defaults.headers.common['Content-Type'] = 'application/json;charset=utf-8'; $httpProvider.defaults.headers.common['x-api-user'] = settings.auth.apiId; diff --git a/website/client/js/controllers/authCtrl.js b/website/public/js/controllers/authCtrl.js similarity index 71% rename from website/client/js/controllers/authCtrl.js rename to website/public/js/controllers/authCtrl.js index 13bc8ca1e6..5b486677f5 100644 --- a/website/client/js/controllers/authCtrl.js +++ b/website/public/js/controllers/authCtrl.js @@ -17,10 +17,10 @@ angular.module('habitrpg') var runAuth = function(id, token) { User.authenticate(id, token, function(err) { if(!err) $scope.registrationInProgress = false; + $window.location.href = ('/' + window.location.hash); Analytics.login(); Analytics.updateUser(); Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'login'}); - $window.location.href = ('/' + window.location.hash); }); }; @@ -28,12 +28,8 @@ angular.module('habitrpg') $scope.registrationInProgress = false; if (status === 0) { $window.alert(window.env.t('noReachServer')); - } else if (status === 400 && data.errors && _.isArray(data.errors)) { // bad requests - data.errors.forEach(function (err) { - $window.alert(err.message); - }); - } else if (!!data && !!data.error) { - $window.alert(data.message); + } else if (!!data && !!data.err) { + $window.alert(data.err); } else { $window.alert(window.env.t('errorUpCase') + ' ' + status); } @@ -50,18 +46,10 @@ angular.module('habitrpg') $scope.registrationInProgress = true; - var url = ApiUrl.get() + "/api/v3/user/auth/local/register"; - if (location.search && location.search.indexOf('Invite=') !== -1) { // matches groupInvite and partyInvite - url += location.search; - } - - if($rootScope.selectedLanguage) { - var toAppend = url.indexOf('?') !== -1 ? '&' : '?'; - url = url + toAppend + 'lang=' + $rootScope.selectedLanguage.code; - } - - $http.post(url, scope.registerVals).success(function(res, status, headers, config) { - runAuth(res.data._id, res.data.apiToken); + var url = ApiUrl.get() + "/api/v2/register"; + if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code; + $http.post(url, scope.registerVals).success(function(data, status, headers, config) { + runAuth(data.id, data.apiToken); }).error(errorAlert); }; @@ -70,14 +58,13 @@ angular.module('habitrpg') username: $scope.loginUsername || $('#loginForm input[name="username"]').val(), password: $scope.loginPassword || $('#loginForm input[name="password"]').val() }; - //@TODO: Move all the $http methods to a service - $http.post(ApiUrl.get() + "/api/v3/user/auth/local/login", data) - .success(function(res, status, headers, config) { - runAuth(res.data.id, res.data.apiToken); + $http.post(ApiUrl.get() + "/api/v2/user/auth/local", data) + .success(function(data, status, headers, config) { + runAuth(data.id, data.token); }).error(errorAlert); }; - $scope.playButtonClick = function() { + $scope.playButtonClick = function(){ Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Play'}) if (User.authenticated()) { window.location.href = ('/' + window.location.hash); @@ -93,7 +80,7 @@ angular.module('habitrpg') if(email == null || email.length == 0) { alert(window.env.t('invalidEmail')); } else { - $http.post(ApiUrl.get() + '/api/v3/user/reset-password', {email:email}) + $http.post(ApiUrl.get() + '/api/v2/user/reset-password', {email:email}) .success(function(){ alert(window.env.t('newPassSent')); }) @@ -111,12 +98,12 @@ angular.module('habitrpg') $scope.socialLogin = function(network){ hello(network).login({scope:'email'}).then(function(auth){ - $http.post(ApiUrl.get() + "/api/v3/user/auth/social", auth) - .success(function(res, status, headers, config) { - runAuth(res.data.id, res.data.apiToken); + $http.post(ApiUrl.get() + "/api/v2/user/auth/social", auth) + .success(function(data, status, headers, config) { + runAuth(data.id, data.token); }).error(errorAlert); }, function( e ){ - alert("Signin error: " + e.message ); + alert("Signin error: " + e.error.message ); }); }; diff --git a/website/client/js/controllers/autoCompleteCtrl.js b/website/public/js/controllers/autoCompleteCtrl.js similarity index 100% rename from website/client/js/controllers/autoCompleteCtrl.js rename to website/public/js/controllers/autoCompleteCtrl.js diff --git a/website/client/js/controllers/challengesCtrl.js b/website/public/js/controllers/challengesCtrl.js similarity index 61% rename from website/client/js/controllers/challengesCtrl.js rename to website/public/js/controllers/challengesCtrl.js index bcc62eca72..30aac589d5 100644 --- a/website/client/js/controllers/challengesCtrl.js +++ b/website/public/js/controllers/challengesCtrl.js @@ -1,5 +1,5 @@ -habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', '$stateParams', 'Members', 'Tasks', 'TAVERN_ID', - function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state, $stateParams, Members, Tasks, TAVERN_ID) { +habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state', '$stateParams', 'Members', 'Tasks', + function($rootScope, $scope, Shared, User, Challenges, Notification, $compile, Groups, $state, $stateParams, Members, Tasks) { // Use presence of cid to determine whether to show a list or a single // challenge @@ -10,11 +10,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', _getChallenges(); // FIXME $scope.challenges needs to be resolved first (see app.js) - $scope.groups = []; - Groups.Group.getGroups('party,guilds,tavern') - .then(function (response) { - $scope.groups = response.data.data; - }); + $scope.groups = Groups.Group.query({type:'party,guilds,tavern'}); // override score() for tasks listed in challenges-editing pages, so that nothing happens $scope.score = function(){} @@ -30,16 +26,13 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', }); }; - $scope.isUserMemberOf = function (challenge) { - return User.user.challenges.indexOf(challenge._id) !== -1; - } - $scope.editTask = Tasks.editTask; /** * Create */ $scope.create = function() { + //If the user has one filter selected, assume that the user wants to default to that group var defaultGroup; //Our filters contain all groups, but we only want groups that have atleast one challenge @@ -48,19 +41,19 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', var filterCount = 0; for ( var i = 0; i < len; i += 1 ) { - if ($scope.search.group[groupsWithChallenges[i]] === true) { + if ( $scope.search.group[groupsWithChallenges[i]] == true ) { filterCount += 1; defaultGroup = groupsWithChallenges[i]; } - - if (filterCount >= 1 && defaultGroup) { + if (filterCount > 1) { + defaultGroup = $scope.groups[0]._id break; } } - if(!defaultGroup) defaultGroup = TAVERN_ID; + if(!defaultGroup) defaultGroup = 'habitrpg'; - $scope.obj = $scope.newChallenge = { + $scope.obj = $scope.newChallenge = new Challenges.Challenge({ name: '', description: '', habits: [], @@ -72,7 +65,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', timestamp: +(new Date), members: [], official: false - }; + }); _calculateMaxPrize(defaultGroup); }; @@ -89,12 +82,10 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', }; _(clonedTasks).each(function(val, type) { - if (challenge[type + 's']) { - challenge[type + 's'].forEach(_cloneTaskAndPush); - } + challenge[type + 's'].forEach(_cloneTaskAndPush); }).value(); - $scope.obj = $scope.newChallenge = { + $scope.obj = $scope.newChallenge = new Challenges.Challenge({ name: challenge.name, shortName: challenge.shortName, description: challenge.description, @@ -106,7 +97,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', group: challenge.group._id, official: challenge.official, prize: challenge.prize - }; + }); function _cloneTaskAndPush(taskToClone) { var task = Tasks.cloneTask(taskToClone); @@ -120,45 +111,22 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.save = function(challenge) { if (!challenge.group) return alert(window.env.t('selectGroup')); - if (!challenge.shortName || challenge.shortName.length < 3) return alert(window.env.t('shortNameTooShort')); - var isNew = !challenge._id; if(isNew && challenge.prize > $scope.maxPrize) { return alert(window.env.t('challengeNotEnoughGems')); } - if (isNew) { - var _challenge; - Challenges.createChallenge(challenge) - .then(function (response) { - _challenge = response.data.data; - Notification.text(window.env.t('challengeCreated')); + challenge.$save(function(_challenge){ + if (isNew) { + Notification.text(window.env.t('challengeCreated')); + User.sync(); + } - var challengeTasks = []; - challengeTasks = challengeTasks.concat(challenge.todos); - challengeTasks = challengeTasks.concat(challenge.habits); - challengeTasks = challengeTasks.concat(challenge.dailys); - challengeTasks = challengeTasks.concat(challenge.rewards); - - return Tasks.createChallengeTasks(_challenge._id, challengeTasks); - }) - .then(function (response) { - $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { - reload: true, inherit: false, notify: true - }); - User.sync(); - }); - } else { - Challenges.updateChallenge(challenge._id, challenge) - .then(function (response) { - var _challenge = response.data.data; - $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { - reload: true, inherit: false, notify: true - }); - User.sync(); - }); - } + $state.transitionTo('options.social.challenges.detail', { cid: _challenge._id }, { + reload: true, inherit: false, notify: true + }); + }); }; /** @@ -168,6 +136,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.newChallenge = null; }; + /** * Close Challenge * ------------------ @@ -179,34 +148,27 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', challenge.winner = undefined; }; - //@TODO: change to $scope.remove $scope["delete"] = function(challenge) { var warningMsg; - - if(challenge.group._id == TAVERN_ID) { + if(challenge.group._id == 'habitrpg') { warningMsg = window.env.t('sureDelChaTavern'); } else { warningMsg = window.env.t('sureDelCha'); } - if (!confirm(warningMsg)) return; - - Challenges.deleteChallenge(challenge._id) - .then(function (response) { - $scope.popoverEl.popover('destroy'); - _backToChallenges(); - }); + challenge.$delete(function(){ + $scope.popoverEl.popover('destroy'); + _backToChallenges(); + }); }; $scope.selectWinner = function(challenge) { if (!challenge.winner) return; if (!confirm(window.env.t('youSure'))) return; - - Challenges.selectChallengeWinner(challenge._id, challenge.winner) - .then(function (response) { - $scope.popoverEl.popover('destroy'); - _backToChallenges(); - }); + challenge.$close({uid:challenge.winner}, function(){ + $scope.popoverEl.popover('destroy'); + _backToChallenges(); + }) } $scope.close = function(challenge, $event) { @@ -241,36 +203,19 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', //------------------------------------------------------------ // Tasks //------------------------------------------------------------ - function addTask (addTo, listDef, challenge) { + + $scope.addTask = function(addTo, listDef) { var task = Shared.taskDefaults({text: listDef.newTask, type: listDef.type}); - //If the challenge has not been created, we bulk add tasks on save - if (challenge._id) Tasks.createChallengeTasks(challenge._id, task); - if (!challenge[task.type + 's']) challenge[task.type + 's'] = []; - challenge[task.type + 's'].unshift(task); + addTo.unshift(task); + //User.log({op: "addTask", data: task}); //TODO persist delete listDef.newTask; }; - $scope.addTask = function(addTo, listDef, challenge) { - if (listDef.bulk) { - var tasks = listDef.newTask.split(/[\n\r]+/); - //Reverse the order of tasks so the tasks will appear in the order the user entered them - tasks.reverse(); - _.each(tasks, function(t) { - listDef.newTask = t; - addTask(addTo, listDef, challenge); - }); - listDef.bulk = false; - } else { - addTask(addTo, listDef, challenge); - } - } - - $scope.removeTask = function(task, challenge) { + $scope.removeTask = function(task, list) { if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return; - //We only pass to the api if the challenge exists, otherwise, the tasks only exist on the client - if (challenge._id) Tasks.deleteTask(task._id); - var index = challenge[task.type + 's'].indexOf(task); - challenge[task.type + 's'].splice(index, 1); + //TODO persist + // User.log({op: "delTask", data: task}); + _.remove(list, task); }; $scope.saveTask = function(task){ @@ -278,48 +223,28 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', // TODO persist } - $scope.toggleBulk = function(list) { - if (typeof list.bulk === 'undefined') { - list.bulk = false; - } - list.bulk = !list.bulk; - list.focus = true; - }; - /* -------------------------- Subscription -------------------------- */ - $scope.join = function (challenge) { - Challenges.joinChallenge(challenge._id) - .then(function (response) { - User.user.challenges.push(challenge._id); - _getChallenges(); - return Tasks.getUserTasks(); - }) - .then(function (response) { - var tasks = response.data.data; - User.syncUserTasks(tasks); - }); + $scope.join = function(challenge){ + challenge.$join(function(){ + _getChallenges() + User.log({}); + }); + } - $scope.leave = function(keep, challenge) { + $scope.leave = function(keep) { if (keep == 'cancel') { $scope.selectedChal = undefined; } else { - Challenges.leaveChallenge($scope.selectedChal._id, keep) - .then(function (response) { - var index = User.user.challenges.indexOf($scope.selectedChal._id); - delete User.user.challenges[index]; - _getChallenges(); - return Tasks.getUserTasks(); - }) - .then(function (response) { - var tasks = response.data.data; - User.syncUserTasks(tasks); - }); + $scope.selectedChal.$leave({keep:keep}, function(){ + _getChallenges() + User.log({}); + }); } $scope.popoverEl.popover('destroy'); } @@ -358,7 +283,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', _calculateMaxPrize(gid); - if (gid == TAVERN_ID) { + if (gid == 'habitrpg') { $scope.newChallenge.prize = 1; } }) @@ -381,7 +306,7 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.insufficientGemsForTavernChallenge = function() { var balance = User.user.balance || 0; - var isForTavern = $scope.newChallenge.group == TAVERN_ID; + var isForTavern = $scope.newChallenge.group == 'habitrpg'; if (isForTavern) { return balance <= 0; @@ -391,24 +316,21 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', } $scope.sendMessageToChallengeParticipant = function(uid) { - Members.selectMember(uid) - .then(function () { - $rootScope.openModal('private-message', {controller:'MemberModalCtrl'}); - }); + Members.selectMember(uid, function(){ + $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); + }); }; $scope.sendGiftToChallengeParticipant = function(uid) { - Members.selectMember(uid) - .then(function () { - $rootScope.openModal('send-gift', {controller:'MemberModalCtrl'}); - }); + Members.selectMember(uid, function(){ + $rootScope.openModal('send-gift',{controller:'MemberModalCtrl'}) + }); }; $scope.filterInitialChallenges = function() { - $scope.groupsFilter = _.uniq(_.compact(_.pluck($scope.challenges, 'group')), function(g) {return g._id}); - + $scope.groupsFilter = _.uniq(_.pluck($scope.challenges, 'group'), function(g){return g._id}); $scope.search = { - group: _.transform($scope.groups, function(m,g) { m[g._id] = true;}), + group: _.transform($scope.groups, function(m,g){m[g._id]=true;}), _isMember: "either", _isOwner: "either" }; @@ -439,14 +361,14 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', return groupBalance; } - function _shouldShowChallenge (chal) { + function _shouldShowChallenge(chal) { // Have to check that the leader object exists first in the // case where a challenge's leader deletes their account var userIsOwner = (chal.leader && chal.leader._id) === User.user.id; - var groupSelected = $scope.search.group[chal.group ? chal.group._id : null]; + var groupSelected = $scope.search.group[chal.group._id]; var checkOwner = $scope.search._isOwner === 'either' || (userIsOwner === $scope.search._isOwner); - var checkMember = $scope.search._isMember === 'either' || ($scope.isUserMemberOf(chal) === $scope.search._isMember); + var checkMember = $scope.search._isMember === 'either' || (chal._isMember === $scope.search._isMember); return groupSelected && checkOwner && checkMember; } @@ -455,24 +377,24 @@ habitrpg.controller("ChallengesCtrl", ['$rootScope','$scope', 'Shared', 'User', $scope.popoverEl.popover('destroy'); $scope.cid = null; $state.go('options.social.challenges'); - _getChallenges(); + $scope.challenges = Challenges.Challenge.query(); + User.log({}); } + // Fetch single challenge if a cid is present; fetch multiple challenges // otherwise function _getChallenges() { if ($scope.cid) { - Challenges.getChallenge($scope.cid) - .then(function (response) { - var challenge = response.data.data; - $scope.challenges = [challenge]; - }); + Challenges.Challenge.get({cid: $scope.cid}, function(challenge) { + $scope.challenges = [challenge]; + }); } else { - Challenges.getUserChallenges() - .then(function(response){ - $scope.challenges = response.data.data; - $scope.filterInitialChallenges(); - }); + Challenges.Challenge.query(function(challenges){ + $scope.challenges = challenges; + $scope.filterInitialChallenges(); + }); } }; + }]); diff --git a/website/client/js/controllers/chatCtrl.js b/website/public/js/controllers/chatCtrl.js similarity index 62% rename from website/client/js/controllers/chatCtrl.js rename to website/public/js/controllers/chatCtrl.js index c7c29d6502..8ba5b2a15c 100644 --- a/website/client/js/controllers/chatCtrl.js +++ b/website/public/js/controllers/chatCtrl.js @@ -27,72 +27,66 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A if (_.isEmpty(message) || $scope._sending) return; $scope._sending = true; var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false; - Chat.postChat(group._id, message, previousMsg) - .then(function(response) { - var message = response.data.data.message; - - group.chat.unshift(message); - - $scope.message.content = ''; - $scope._sending = false; - - if (group.type == 'party') { - Analytics.updateUser({'partyID': group.id, 'partySize': group.memberCount}); - } - - if (group.privacy == 'public'){ - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name}); - } else { - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); - } - }, function(err){ - $scope._sending = false; - }); + Chat.utils.postChat({gid: group._id, message:message, previousMsg: previousMsg}, undefined, function(data){ + if(data.chat){ + group.chat = data.chat; + }else if(data.message){ + group.chat.unshift(data.message); + } + $scope.message.content = ''; + $scope._sending = false; + if (group.type == 'party') { + Analytics.updateUser({'partyID':group.id,'partySize':group.memberCount}); + } + if (group.privacy == 'public'){ + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy,'groupName':group.name}); + } else { + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'group chat','groupType':group.type,'privacy':group.privacy}); + } + }, function(err){ + $scope._sending = false; + }); } $scope.deleteChatMessage = function(group, message){ if(message.uuid === User.user.id || (User.user.backer && User.user.contributor.admin)){ var previousMsg = (group.chat && group.chat[0]) ? group.chat[0].id : false; - if (confirm('Are you sure you want to delete this message?')) { - Chat.deleteChat(group._id, message.id, previousMsg) - .then(function (response) { - var i = _.findIndex(group.chat, {id: message.id}); - if(i !== -1) group.chat.splice(i, 1); - }); + if(confirm('Are you sure you want to delete this message?')){ + Chat.utils.deleteChatMessage({gid:group._id, messageId:message.id, previousMsg:previousMsg}, undefined, function(data){ + if(data.chat) group.chat = data.chat; + + var i = _.findIndex(group.chat, {id: message.id}); + if(i !== -1) group.chat.splice(i, 1); + }); } } } - $scope.likeChatMessage = function(group, message) { + $scope.likeChatMessage = function(group,message) { if (message.uuid == User.user._id) return Notification.text(window.env.t('foreverAlone')); - if (!message.likes) message.likes = {}; - if (message.likes[User.user._id]) { delete message.likes[User.user._id]; } else { message.likes[User.user._id] = true; } - - Chat.like(group._id, message.id); + Chat.utils.like({ gid:group._id, messageId:message.id }, undefined); } $scope.flagChatMessage = function(groupId,message) { if(!message.flags) message.flags = {}; - - if (message.flags[User.user._id]) { + if(message.flags[User.user._id]) Notification.text(window.env.t('abuseAlreadyReported')); - } else { + else { $scope.abuseObject = message; $scope.groupId = groupId; - Members.selectMember(message.uuid) - .then(function () { - $rootScope.openModal('abuse-flag',{ - controller:'MemberModalCtrl', - scope: $scope - }); + Members.selectMember(message.uuid, function(){ + $rootScope.openModal('abuse-flag',{ + controller:'MemberModalCtrl', + scope: $scope }); + }); } }; @@ -114,21 +108,15 @@ habitrpg.controller('ChatCtrl', ['$scope', 'Groups', 'Chat', 'User', '$http', 'A }); }; - function handleGroupResponse (response) { - $scope.group = response; - if (!$scope.group._id) $scope.group = response.data.data; - }; - - $scope.sync = function(group) { - if (group.name === Groups.TAVERN_NAME) { - Groups.tavern(true).then(handleGroupResponse); - } else if (group._id === User.user.party._id) { - Groups.party(true).then(handleGroupResponse); + $scope.sync = function(group){ + if(group.type == 'party') { + group.$syncParty(); // Syncs the whole party, not just 15 members } else { - Groups.Group.get(group._id).then(handleGroupResponse); + group.$get(); } - - Chat.markChatSeen(group._id); + // When the user clicks fetch recent messages we need to update + // that the user has seen the new messages + Chat.seenMessage(group._id); } // List of Ordering options for the party members list diff --git a/website/client/js/controllers/copyMessageModalCtrl.js b/website/public/js/controllers/copyMessageModalCtrl.js similarity index 89% rename from website/client/js/controllers/copyMessageModalCtrl.js rename to website/public/js/controllers/copyMessageModalCtrl.js index 1e58237454..60d07ec152 100644 --- a/website/client/js/controllers/copyMessageModalCtrl.js +++ b/website/public/js/controllers/copyMessageModalCtrl.js @@ -9,7 +9,7 @@ habitrpg.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification', notes: $scope.notes }; - User.addTask({body:newTask}); + User.user.ops.addTask({body:newTask}); Notification.text(window.env.t('messageAddedAsToDo')); $scope.$close(); diff --git a/website/client/js/controllers/filtersCtrl.js b/website/public/js/controllers/filtersCtrl.js similarity index 81% rename from website/client/js/controllers/filtersCtrl.js rename to website/public/js/controllers/filtersCtrl.js index 4a3ed6e59c..cfdc45658d 100644 --- a/website/client/js/controllers/filtersCtrl.js +++ b/website/public/js/controllers/filtersCtrl.js @@ -14,7 +14,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', _.each(User.user.tags, function(tag){ // Send an update op for each changed tag (excluding new tags & deleted tags, this if() packs a punch) if (tagsSnap[tag.id] && tagsSnap[tag.id].name != tag.name) - User.updateTag({params:{id:tag.id}, body:{name:tag.name}}); + User.user.ops.updateTag({params:{id:tag.id},body:{name:tag.name}}); }) $scope._editing = false; } else { @@ -25,12 +25,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', }; $scope.toggleFilter = function(tag) { - if (!user.filters[tag.id]) { - user.filters[tag.id] = true; - } else { - user.filters[tag.id] = !user.filters[tag.id]; - } - + user.filters[tag.id] = !user.filters[tag.id]; // no longer persisting this, it was causing a lot of confusion - users thought they'd permanently lost tasks // Note: if we want to persist for just this computer, easy method is: // User.save(); @@ -42,7 +37,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', $scope.updateTaskFilter(); $scope.createTag = function() { - User.addTag({body:{name: $scope._newTag.name, id: Shared.uuid()}}); + User.user.ops.addTag({body:{name:$scope._newTag.name, id:Shared.uuid()}}); $scope._newTag.name = ''; }; }]); diff --git a/website/client/js/controllers/footerCtrl.js b/website/public/js/controllers/footerCtrl.js similarity index 64% rename from website/client/js/controllers/footerCtrl.js rename to website/public/js/controllers/footerCtrl.js index 2d07aff430..cd0b9ac182 100644 --- a/website/client/js/controllers/footerCtrl.js +++ b/website/public/js/controllers/footerCtrl.js @@ -7,8 +7,8 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { $scope.loadWidgets = Social.loadWidgets; if(env.isStaticPage){ - $scope.languages = env.availableLanguages; - $scope.selectedLanguage = _.find(env.availableLanguages, {code: env.language.code}); + $scope.languages = env.avalaibleLanguages; + $scope.selectedLanguage = _.find(env.avalaibleLanguages, {code: env.language.code}); $rootScope.selectedLanguage = $scope.selectedLanguage; @@ -80,16 +80,21 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { $scope.addMissedDay = function(numberOfDays){ if (!confirm("Are you sure you want to reset the day by " + numberOfDays + " day(s)?")) return; - - User.setCron(numberOfDays); + var dayBefore = moment(User.user.lastCron).subtract(numberOfDays, 'days').toDate(); + User.set({'lastCron': dayBefore}); + Notification.text('-' + numberOfDays + ' day(s), remember to refresh'); }; $scope.addTenGems = function(){ - User.addTenGems(); + $http.post(ApiUrl.get() + '/api/v2/user/addTenGems').success(function(){ + User.log({}); + }) }; $scope.addHourglass = function(){ - User.addHourglass(); + $http.post(ApiUrl.get() + '/api/v2/user/addHourglass').success(function(){ + User.log({}); + }) }; $scope.addGold = function(){ @@ -118,63 +123,10 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl, Social) { }); }; - $scope.addQuestProgress = function(){ - $http({ - method: "POST", - url: 'api/v3/debug/quest-progress' - }) - .then(function (response) { - Notification.text('Quest progress increased'); - User.sync(); - }) - }; - - $scope.makeAdmin = function () { - User.makeAdmin(); - }; - - $scope.openModifyInventoryModal = function () { - $rootScope.openModal('modify-inventory', {controller: 'FooterCtrl', scope: $scope }); - $scope.showInv = { }; - $scope.inv = { - gear: {}, - special: {}, - pets: {}, - mounts: {}, - eggs: {}, - hatchingPotions: {}, - food: {}, - quests: {}, - }; - $scope.setAllItems = function (type, value) { - var set = $scope.inv[type]; - - for (var item in set) { - if (set.hasOwnProperty(item)) { - set[item] = value; - } - } - }; - }; - - $scope.modifyInventory = function () { - $http({ - method: "POST", - url: 'api/v3/debug/modify-inventory', - data: { - gear: $scope.showInv.gear ? $scope.inv.gear : null, - special: $scope.showInv.special ? $scope.inv.special : null, - pets: $scope.showInv.pets ? $scope.inv.pets : null, - mounts: $scope.showInv.mounts ? $scope.inv.mounts : null, - eggs: $scope.showInv.eggs ? $scope.inv.eggs : null, - hatchingPotions: $scope.showInv.hatchingPotions ? $scope.inv.hatchingPotions : null, - food: $scope.showInv.food ? $scope.inv.food : null, - quests: $scope.showInv.quests ? $scope.inv.quests : null, - } - }) - .then(function (response) { - Notification.text('Inventory updated. Refresh or sync.'); - }) + $scope.addBossQuestProgressUp = function(){ + User.set({ + 'party.quest.progress.up': User.user.party.quest.progress.up + 1000 + }); }; } }]) diff --git a/website/client/js/controllers/groupsCtrl.js b/website/public/js/controllers/groupsCtrl.js similarity index 72% rename from website/client/js/controllers/groupsCtrl.js rename to website/public/js/controllers/groupsCtrl.js index cafbe3b158..e7865afb55 100644 --- a/website/client/js/controllers/groupsCtrl.js +++ b/website/public/js/controllers/groupsCtrl.js @@ -2,28 +2,30 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', '$q', 'User', 'Members', '$state', 'Notification', function($scope, $rootScope, Shared, Groups, $http, $q, User, Members, $state, Notification) { - $scope.isMemberOfPendingQuest = function (userid, group) { + + $scope.isMemberOfPendingQuest = function(userid, group) { if (!group.quest || !group.quest.members) return false; if (group.quest.active) return false; // quest is started, not pending return userid in group.quest.members && group.quest.members[userid] != false; }; - $scope.isMemberOfRunningQuest = function (userid, group) { + $scope.isMemberOfRunningQuest = function(userid, group) { if (!group.quest || !group.quest.members) return false; if (!group.quest.active) return false; // quest is pending, not started return group.quest.members[userid]; }; - $scope.isMemberOfGroup = function (userid, group) { + $scope.isMemberOfGroup = function(userid, group){ + // If the group is a guild, just check for an intersection with the // current user's guilds, rather than checking the members of the group. if(group.type === 'guild') { - return _.detect(User.user.guilds, function(guildId) { return guildId === group._id }); + return _.detect(Groups.myGuilds(), function(g) { return g._id === group._id }); } // Similarly, if we're dealing with the user's current party, return true. if(group.type === 'party') { - var currentParty = group; + var currentParty = Groups.party(); if(currentParty._id && currentParty._id === group._id) return true; } @@ -32,13 +34,12 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' return ~(memberIds.indexOf(userid)); }; - $scope.isMember = function (user, group) { + $scope.isMember = function(user, group){ return ~(group.members.indexOf(user._id)); }; $scope.Members = Members; - - $scope._editing = {group: false}; + $scope._editing = {group:false}; $scope.groupCopy = {}; $scope.editGroup = function (group) { @@ -46,6 +47,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' group._editing = true; }; + $scope.saveEdit = function (group) { var newLeader = $scope.groupCopy._newLeader && $scope.groupCopy._newLeader._id; @@ -55,7 +57,7 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' angular.copy($scope.groupCopy, group); - Groups.Group.update(group); + group.$save(); $scope.cancelEdit(group); }; @@ -67,13 +69,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.deleteAllMessages = function() { if (confirm(window.env.t('confirmDeleteAllMessages'))) { - User.clearPMs(); + User.user.ops.clearPMs({}); } }; // ------ Modals ------ - $scope.clickMember = function (uid, forceShow) { + $scope.clickMember = function(uid, forceShow) { if (User.user._id == uid && !forceShow) { if ($state.is('tasks')) { $state.go('options.profile.avatar'); @@ -83,14 +85,14 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' } else { // We need the member information up top here, but then we pass it down to the modal controller // down below. Better way of handling this? - Members.selectMember(uid) - .then(function () { - $rootScope.openModal('member', {controller: 'MemberModalCtrl', windowClass: 'profile-modal', size: 'lg'}); - }); + Members.selectMember(uid, function(){ + $rootScope.openModal('member', {controller:'MemberModalCtrl', windowClass:'profile-modal', size:'lg'}); + }); } }; - $scope.removeMember = function (group, member, isMember) { + + $scope.removeMember = function(group, member, isMember){ // TODO find a better way to do this (share data with remove member modal) $scope.removeMemberData = { group: group, @@ -100,13 +102,13 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $rootScope.openModal('remove-member', {scope: $scope}); }; - $scope.confirmRemoveMember = function (confirm) { - if (confirm) { - Groups.Group.removeMember( - $scope.removeMemberData.group._id, - $scope.removeMemberData.member._id, - $scope.removeMemberData.message - ).then(function (response) { + $scope.confirmRemoveMember = function(confirm){ + if(confirm){ + Groups.Group.removeMember({ + gid: $scope.removeMemberData.group._id, + uuid: $scope.removeMemberData.member._id, + message: $scope.removeMemberData.message, + }, undefined, function(){ if($scope.removeMemberData.isMember){ _.pull($scope.removeMemberData.group.members, $scope.removeMemberData.member); }else{ @@ -115,16 +117,15 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' $scope.removeMemberData = undefined; }); - } else { + }else{ $scope.removeMemberData = undefined; } }; - $scope.openInviteModal = function (group) { + $scope.openInviteModal = function(group){ if (group.type !== 'party' && group.type !== 'guild') { return console.log('Invalid group type.') } - $rootScope.openModal('invite-' + group.type, { controller:'InviteToGroupCtrl', resolve: { @@ -135,10 +136,10 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' }); }; - $scope.quickReply = function (uid) { - Members.selectMember(uid) - .then(function (response) { - $rootScope.openModal('private-message', {controller: 'MemberModalCtrl'}); - }); + $scope.quickReply = function(uid) { + Members.selectMember(uid, function(){ + $rootScope.openModal('private-message',{controller:'MemberModalCtrl'}); + }); } + }]); diff --git a/website/public/js/controllers/guildsCtrl.js b/website/public/js/controllers/guildsCtrl.js new file mode 100644 index 0000000000..df6b001448 --- /dev/null +++ b/website/public/js/controllers/guildsCtrl.js @@ -0,0 +1,94 @@ +'use strict'; + +habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$rootScope', '$state', '$location', '$compile', 'Analytics', + function($scope, Groups, User, Challenges, $rootScope, $state, $location, $compile, Analytics) { + $scope.groups = { + guilds: Groups.myGuilds(), + "public": Groups.publicGuilds() + } + + $scope.type = 'guild'; + $scope.text = window.env.t('guild'); + var newGroup = function(){ + return new Groups.Group({type:'guild', privacy:'private'}); + } + $scope.newGroup = newGroup() + $scope.create = function(group){ + if (User.user.balance < 1) + return $rootScope.openModal('buyGems', {track:"Gems > Create Group"}); + + if (confirm(window.env.t('confirmGuild'))) { + group.$save(function(saved){ + if (saved.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy,'groupName':saved.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'guild','privacy':saved.privacy})} + $rootScope.hardRedirect('/#/options/groups/guilds/' + saved._id); + }); + } + } + + $scope.join = function(group){ + // If we're accepting an invitation, we don't have the actual group object, but a faux group object (for performance + // purposes) {id, name}. Let's trick ngResource into thinking we have a group, so we can call the same $join + // function (server calls .attachGroup(), which finds group by _id and handles this properly) + if (group.id && !group._id) { + group = new Groups.Group({_id:group.id}); + } + + group.$join(function(joined){ + if (joined.privacy == 'public') {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy,'groupName':joined.name})} + else {Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'guild','privacy':joined.privacy})} + $rootScope.hardRedirect('/#/options/groups/guilds/' + joined._id); + }) + } + + $scope.leave = function(keep) { + if (keep == 'cancel') { + $scope.selectedGroup = undefined; + $scope.popoverEl.popover('destroy'); + } else { + Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){ + $rootScope.hardRedirect('/#/options/groups/guilds'); + }); + } + } + + $scope.clickLeave = function(group, $event){ + $scope.selectedGroup = group; + $scope.popoverEl = $($event.target).closest('.btn'); + var html, title; + Challenges.Challenge.query(function(challenges) { + challenges = _.pluck(_.filter(challenges, function(c) { + return c.group._id == group._id; + }), '_id'); + + if (_.intersection(challenges, User.user.challenges).length > 0) { + html = $compile( + '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveGroupCha'); + } else { + html = $compile( + '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveGroup') + } + + $scope.popoverEl.popover('destroy').popover({ + html: true, + placement: 'top', + trigger: 'manual', + title: title, + content: html + }).popover('show'); + }); + } + + $scope.reject = function(guild){ + var i = _.findIndex(User.user.invitations.guilds, {id:guild.id}); + if (~i){ + User.user.invitations.guilds.splice(i, 1); + User.set({'invitations.guilds':User.user.invitations.guilds}); + } + } + } + ]); diff --git a/website/client/js/controllers/hallCtrl.js b/website/public/js/controllers/hallCtrl.js similarity index 66% rename from website/client/js/controllers/hallCtrl.js rename to website/public/js/controllers/hallCtrl.js index 3aad20a782..fe385a316c 100644 --- a/website/client/js/controllers/hallCtrl.js +++ b/website/public/js/controllers/hallCtrl.js @@ -2,12 +2,10 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Hero = $resource(ApiUrl.get() + '/api/v3/hall/heroes/:uid', {uid:'@_id'}); + var Hero = $resource(ApiUrl.get() + '/api/v2/hall/heroes/:uid', {uid:'@_id'}); $scope.hero = undefined; $scope.loadHero = function(uuid){ - Hero.query({uid:uuid}, function (heroData) { - $scope.hero = heroData.data; - }); + $scope.hero = Hero.get({uid:uuid}); } $scope.saveHero = function(hero) { $scope.hero.contributor.admin = ($scope.hero.contributor.level > 7) ? true : false; @@ -15,14 +13,10 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notifica Notification.text("User updated"); $scope.hero = undefined; $scope._heroID = undefined; - Hero.query({}, function (heroesData) { - $scope.heroes = heroesData.data; - }); + $scope.heroes = Hero.query(); }) } - Hero.query({}, function (heroesData) { - $scope.heroes = heroesData.data; - }); + $scope.heroes = Hero.query(); $scope.populateContributorInput = function(id) { $scope._heroID = id; @@ -33,14 +27,14 @@ habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notifica habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Patron = $resource(ApiUrl.get() + '/api/v3/hall/patrons/:uid', {uid:'@_id'}); + var Patron = $resource(ApiUrl.get() + '/api/v2/hall/patrons/:uid', {uid:'@_id'}); var page = 0; $scope.patrons = []; $scope.loadMore = function(){ - Patron.query({page: page++}, function(patronsData){ - $scope.patrons = $scope.patrons.concat(patronsData.data); + Patron.query({page: page++}, function(patrons){ + $scope.patrons = $scope.patrons.concat(patrons); }) } $scope.loadMore(); diff --git a/website/client/js/controllers/headerCtrl.js b/website/public/js/controllers/headerCtrl.js similarity index 79% rename from website/client/js/controllers/headerCtrl.js rename to website/public/js/controllers/headerCtrl.js index 390d2c92a9..68000da3b9 100644 --- a/website/client/js/controllers/headerCtrl.js +++ b/website/public/js/controllers/headerCtrl.js @@ -8,19 +8,15 @@ habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User', $scope.inviteOrStartParty = Groups.inviteOrStartParty; - function handlePartyResponse (party) { - $scope.party = party; + $scope.party = Groups.party(function(){ + var triggerResort = function() { + $scope.partyMinusSelf = resortParty(); + }; - var triggerResort = function() { - $scope.partyMinusSelf = resortParty(); - }; - - triggerResort(); - $scope.$watch('user.party.order', triggerResort); - $scope.$watch('user.party.orderAscending', triggerResort); - } - - Groups.party().then(handlePartyResponse, handlePartyResponse); + triggerResort(); + $scope.$watch('user.party.order', triggerResort); + $scope.$watch('user.party.orderAscending', triggerResort); + }); function resortParty() { var result = _.sortBy( diff --git a/website/client/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js similarity index 92% rename from website/client/js/controllers/inventoryCtrl.js rename to website/public/js/controllers/inventoryCtrl.js index 41fa416abf..80c80bf3d4 100644 --- a/website/client/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -99,7 +99,7 @@ habitrpg.controller("InventoryCtrl", var selected = $scope.selectedEgg ? 'selectedEgg' : $scope.selectedPotion ? 'selectedPotion' : $scope.selectedFood ? 'selectedFood' : undefined; if (selected) { var type = $scope.selectedEgg ? 'eggs' : $scope.selectedPotion ? 'hatchingPotions' : $scope.selectedFood ? 'food' : undefined; - User.sell({params:{type:type, key: $scope[selected].key}}); + user.ops.sell({params:{type:type, key: $scope[selected].key}}); if (user.items[type][$scope[selected].key] < 1) { $scope[selected] = null; } @@ -118,7 +118,7 @@ habitrpg.controller("InventoryCtrl", var userHasPet = user.items.pets[egg.key + '-' + potion.key] > 0; var isPremiumPet = Content.hatchingPotions[potion.key].premium && !Content.dropEggs[egg.key]; - User.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); + user.ops.hatch({params:{egg:egg.key, hatchingPotion:potion.key}}); if (!user.preferences.suppressModals.hatchPet && !userHasPet && !isPremiumPet) { $scope.hatchedPet = { @@ -128,13 +128,11 @@ habitrpg.controller("InventoryCtrl", eggKey: egg.key, pet: 'Pet-' + egg.key + '-' + potion.key }; - $rootScope.openModal('hatchPet', { scope: $scope, size: 'sm' }); } - $scope.selectedEgg = null; $scope.selectedPotion = null; @@ -172,7 +170,7 @@ habitrpg.controller("InventoryCtrl", } else if (!$window.confirm(window.env.t('feedPet', {name: petDisplayName, article: food.article, text: food.text()}))) { return; } - User.feed({params:{pet: pet, food: food.key}}); + User.user.ops.feed({params:{pet: pet, food: food.key}}); $scope.selectedFood = null; _updateDropAnimalCount(user.items); @@ -198,12 +196,12 @@ habitrpg.controller("InventoryCtrl", // Selecting Pet } else { - User.equip({params:{type: 'pet', key: pet}}); + User.user.ops.equip({params:{type: 'pet', key: pet}}); } } $scope.chooseMount = function(egg, potion) { - User.equip({params:{type: 'mount', key: egg + '-' + potion}}); + User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}}); } $scope.getSeasonalShopArray = function(set){ @@ -230,7 +228,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.equipped){ var itemKey = user.items.gear.equipped[item]; if (user.items.gear.owned[itemKey]) { - User.equip({params: {type: 'equipped', key: itemKey}}); + user.ops.equip({params: {key: itemKey}}); } } break; @@ -239,7 +237,7 @@ habitrpg.controller("InventoryCtrl", for (item in user.items.gear.costume){ var itemKey = user.items.gear.costume[item]; if (user.items.gear.owned[itemKey]) { - User.equip({params: {type:"costume", key: itemKey}}); + user.ops.equip({params: {type:"costume", key: itemKey}}); } } break; @@ -247,17 +245,17 @@ habitrpg.controller("InventoryCtrl", case "petMountBackground": var pet = user.items.currentPet; if (pet) { - User.equip({params:{type: 'pet', key: pet}}); + user.ops.equip({params:{type: 'pet', key: pet}}); } var mount = user.items.currentMount; if (mount) { - User.equip({params:{type: 'mount', key: mount}}); + user.ops.equip({params:{type: 'mount', key: mount}}); } var background = user.preferences.background; if (background) { - User.unlock({query:{path:"background."+background}}); + User.user.ops.unlock({query:{path:"background."+background}}); } break; @@ -310,9 +308,9 @@ habitrpg.controller("InventoryCtrl", }; $scope.clickTimeTravelItem = function(type,key) { - if (user.purchased.plan.consecutive.trinkets < 1) return User.hourglassPurchase({params:{type:type,key:key}}); + if (user.purchased.plan.consecutive.trinkets < 1) return user.ops.hourglassPurchase({params:{type:type,key:key}}); if (!window.confirm(window.env.t('hourglassBuyItemConfirm'))) return; - User.hourglassPurchase({params:{type:type,key:key}}); + user.ops.hourglassPurchase({params:{type:type,key:key}}); }; function _updateDropAnimalCount(items) { diff --git a/website/client/js/controllers/inviteToGroupCtrl.js b/website/public/js/controllers/inviteToGroupCtrl.js similarity index 63% rename from website/client/js/controllers/inviteToGroupCtrl.js rename to website/public/js/controllers/inviteToGroupCtrl.js index a986cd768c..f4a74dbac9 100644 --- a/website/client/js/controllers/inviteToGroupCtrl.js +++ b/website/public/js/controllers/inviteToGroupCtrl.js @@ -1,7 +1,6 @@ 'use strict'; -habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', - function($scope, $rootScope, User, Groups, injectedGroup, $http, Notification) { +habitrpg.controller('InviteToGroupCtrl', ['$scope', 'User', 'Groups', 'injectedGroup', '$http', 'Notification', function($scope, User, Groups, injectedGroup, $http, Notification) { $scope.group = injectedGroup; $scope.inviter = User.user.profile.name; @@ -18,12 +17,8 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Group $scope.inviteNewUsers = function(inviteMethod) { if (!$scope.group._id) { $scope.group.name = $scope.group.name || env.t('possessiveParty', {name: User.user.profile.name}); - - return Groups.Group.create($scope.group) - .then(function(response) { - $scope.group = response.data.data; - User.sync(); - Groups.data.party = $scope.group; + return $scope.group.$save() + .then(function(res) { _inviteByMethod(inviteMethod); }); } @@ -44,21 +39,12 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Group return console.log('Invalid invite method.') } - Groups.Group.invite($scope.group._id, invitationDetails) - .then(function() { - Notification.text(window.env.t('invitationsSent')); - _resetInvitees(); - var redirectTo = '/#/options/groups/' - if ($scope.group.type === 'party') { - redirectTo += 'party'; - } else { - redirectTo += ('guilds/' + $scope.group._id); - } - - $rootScope.hardRedirect(redirectTo); - }, function(){ - _resetInvitees(); - }); + Groups.Group.invite({gid: $scope.group._id}, invitationDetails, function(){ + Notification.text(window.env.t('invitationsSent')); + _resetInvitees(); + }, function(){ + _resetInvitees(); + }); } function _getOnlyUuids() { @@ -79,6 +65,7 @@ habitrpg.controller('InviteToGroupCtrl', ['$scope', '$rootScope', 'User', 'Group function _resetInvitees() { var emptyEmails = [{name:"",email:""},{name:"",email:""}]; var emptyInvitees = [{uuid: ''}]; + $scope.emails = emptyEmails; $scope.invitees = emptyInvitees; } diff --git a/website/client/js/controllers/memberModalCtrl.js b/website/public/js/controllers/memberModalCtrl.js similarity index 50% rename from website/client/js/controllers/memberModalCtrl.js rename to website/public/js/controllers/memberModalCtrl.js index 6e8b96d0ae..54f0bb054a 100644 --- a/website/client/js/controllers/memberModalCtrl.js +++ b/website/public/js/controllers/memberModalCtrl.js @@ -20,49 +20,45 @@ habitrpg }); $scope.sendPrivateMessage = function(uuid, message){ + // Don't do anything if the user somehow gets here without a message. if (!message) return; - Members.sendPrivateMessage(message, uuid) - .then(function (response) { - Notification.text(window.env.t('messageSentAlert')); - $rootScope.User.sync(); - $scope.$close(); - }); + $http.post('/api/v2/members/'+uuid+'/message',{message:message}).success(function(){ + Notification.text(window.env.t('messageSentAlert')); + $rootScope.User.sync(); + $scope.$close(); + }); }; - //@TODO: We don't send subscriptions so the structure has changed in the back. Update this when we update the views. $scope.gift = { type: 'gems', - gems: {amount: 0, fromBalance: true}, - subscription: {key: ''}, - message: '' + gems: {amount:0, fromBalance:true}, + subscription: {key:''}, + message:'' }; - $scope.sendGift = function (uuid) { - Members.transferGems($scope.gift.message, uuid, $scope.gift.gems.amount) - .then(function (response) { - Notification.text(window.env.t('sentGems')); - $rootScope.User.sync(); - $scope.$close(); - }); + $scope.sendGift = function(uuid, gift){ + $http.post('/api/v2/members/'+uuid+'/gift', gift).success(function(){ + Notification.text('Gift sent!') + $rootScope.User.sync(); + $scope.$close(); + }) }; $scope.reportAbuse = function(reporter, message, groupId) { message.flags[reporter._id] = true; - Chat.flagChatMessage(groupId, message.id) - .then(function(data){ - Notification.text(window.env.t('abuseReported')); - $scope.$close(); - }); + Chat.utils.flagChatMessage({gid: groupId, messageId: message.id}, undefined, function(data){ + Notification.text(window.env.t('abuseReported')); + $scope.$close(); + }); }; $scope.clearFlagCount = function(message, groupId) { - Chat.clearFlagCount(groupId, message.id) - .then(function(data){ - message.flagCount = 0; - Notification.text("Flags cleared"); - $scope.$close(); - }); + Chat.utils.clearFlagCount({gid: groupId, messageId: message.id}, undefined, function(data){ + message.flagCount = 0; + Notification.text("Flags cleared"); + $scope.$close(); + }); } } ]); diff --git a/website/client/js/controllers/menuCtrl.js b/website/public/js/controllers/menuCtrl.js similarity index 97% rename from website/client/js/controllers/menuCtrl.js rename to website/public/js/controllers/menuCtrl.js index 761c118986..5549c527dd 100644 --- a/website/client/js/controllers/menuCtrl.js +++ b/website/public/js/controllers/menuCtrl.js @@ -26,7 +26,7 @@ angular.module('habitrpg') } } - $scope.clearMessages = Chat.markChatSeen; + $scope.clearMessages = Chat.seenMessage; $scope.clearCards = Chat.clearCards; $scope.iconClasses = function() { diff --git a/website/client/js/controllers/notificationCtrl.js b/website/public/js/controllers/notificationCtrl.js similarity index 97% rename from website/client/js/controllers/notificationCtrl.js rename to website/public/js/controllers/notificationCtrl.js index 16a2c3a991..ac3fab9e6a 100644 --- a/website/client/js/controllers/notificationCtrl.js +++ b/website/public/js/controllers/notificationCtrl.js @@ -68,7 +68,7 @@ habitrpg.controller('NotificationCtrl', } $rootScope.$watch('user.stats.lvl', function(after, before) { - if (after <= before) return; + if (after <= before) return; Notification.lvl(); $rootScope.playSound('Level_Up'); if (User.user._tmp && User.user._tmp.drop && (User.user._tmp.drop.type === 'Quest')) return; @@ -127,7 +127,7 @@ habitrpg.controller('NotificationCtrl', Notification.drop(env.t('messageDropFood', {dropArticle: after.article, dropText: text, dropNotes: notes}), after); } else if (after.type === 'Quest') { $rootScope.selectedQuest = Content.quests[after.key]; - $rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'}); + $rootScope.openModal('questDrop', {controller:'PartyCtrl',size:'sm'}); } else if (after.notificationType === 'Mystery') { text = Content.gear.flat[after.key].text(); Notification.drop(env.t('messageDropMysteryItem', {dropText: text}), after); @@ -180,13 +180,9 @@ habitrpg.controller('NotificationCtrl', $rootScope.openModal('questInvitation', {controller:'PartyCtrl'}); }); - $rootScope.$on('responseError500', function(ev, error){ + $rootScope.$on('responseError', function(ev, error){ Notification.error(error); }); - $rootScope.$on('responseError', function(ev, error){ - Notification.error(error, true); - }); - $rootScope.$on('responseText', function(ev, error){ Notification.text(error); }); diff --git a/website/public/js/controllers/partyCtrl.js b/website/public/js/controllers/partyCtrl.js new file mode 100644 index 0000000000..6494679911 --- /dev/null +++ b/website/public/js/controllers/partyCtrl.js @@ -0,0 +1,173 @@ +'use strict'; + +habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', + function($rootScope,$scope,Groups,Chat,User,Challenges,$state,$compile,Analytics,Quests,Social) { + + var user = User.user; + + $scope.type = 'party'; + $scope.text = window.env.t('party'); + $scope.group = $rootScope.party = Groups.party(); + $scope.newGroup = new Groups.Group({type:'party'}); + $scope.inviteOrStartParty = Groups.inviteOrStartParty; + $scope.loadWidgets = Social.loadWidgets; + + if ($state.is('options.social.party')) { + $scope.group.$syncParty(); // Sync party automatically when navigating to party page + + // Checks if user's party has reached 2 players for the first time. + if(!user.achievements.partyUp + && $scope.group.memberCount >= 2) { + User.set({'achievements.partyUp':true}); + $rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'}); + } + + // Checks if user's party has reached 4 players for the first time. + if(!user.achievements.partyOn + && $scope.group.memberCount >= 4) { + User.set({'achievements.partyOn':true}); + $rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'}); + } + } + + Chat.seenMessage($scope.group._id); + + $scope.create = function(group){ + if (!group.name) group.name = env.t('possessiveParty', {name: User.user.profile.name}); + group.$save(function(){ + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':true,'groupType':'party','privacy':'private'}); + Analytics.updateUser({'partyID':group.id,'partySize':1}); + $rootScope.hardRedirect('/#/options/groups/party'); + }); + }; + + $scope.join = function(party){ + var group = new Groups.Group({_id: party.id, name: party.name}); + group.$join(function(){ + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'join group','owner':false,'groupType':'party','privacy':'private'}); + Analytics.updateUser({'partyID':party.id}); + $rootScope.hardRedirect('/#/options/groups/party'); + }); + }; + + // TODO: refactor guild and party leave into one function + $scope.leave = function(keep) { + if (keep == 'cancel') { + $scope.selectedGroup = undefined; + $scope.popoverEl.popover('destroy'); + } else { + Groups.Group.leave({gid: $scope.selectedGroup._id, keep:keep}, undefined, function(){ + Analytics.updateUser({'partySize':null,'partyID':null}); + $rootScope.hardRedirect('/#/options/groups/party'); + }); + } + }; + + // TODO: refactor guild and party clickLeave into one function + $scope.clickLeave = function(group, $event){ + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Leave Party'}); + $scope.selectedGroup = group; + $scope.popoverEl = $($event.target).closest('.btn'); + var html, title; + Challenges.Challenge.query(function(challenges) { + challenges = _.pluck(_.filter(challenges, function(c) { + return c.group._id == group._id; + }), '_id'); + if (_.intersection(challenges, User.user.challenges).length > 0) { + html = $compile( + '' + window.env.t('removeTasks') + '
\n' + window.env.t('keepTasks') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leavePartyCha'); + } else { + html = $compile( + '' + window.env.t('confirm') + '
\n' + window.env.t('cancel') + '
' + )($scope); + title = window.env.t('leaveParty'); + } + $scope.popoverEl.popover('destroy').popover({ + html: true, + placement: 'top', + trigger: 'manual', + title: title, + content: html + }).popover('show'); + }); + }; + + $scope.clickStartQuest = function(){ + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'}); + var hasQuests = _.find(User.user.items.quests, function(quest) { + return quest > 0; + }); + + if (hasQuests){ + $rootScope.openModal("ownedQuests", { controller:"InventoryCtrl" }); + } else { + $rootScope.$state.go('options.inventory.quests'); + } + }; + + $scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) { + if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) { + Groups.Group.leave({gid: Groups.party()._id, keep:false}, undefined, function() { + $scope.group = { + loadingNewParty: true + }; + $scope.join({ id: newPartyId, name: newPartyName }); + }); + } + } + + $scope.reject = function(){ + User.set({'invitations.party':{}}); + } + + $scope.questCancel = function(){ + if (!confirm(window.env.t('sureCancel'))) return; + + Quests.sendAction('questCancel') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questAbort = function(){ + if (!confirm(window.env.t('sureAbort'))) return; + if (!confirm(window.env.t('doubleSureAbort'))) return; + + Quests.sendAction('questAbort') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questLeave = function(){ + if (!confirm(window.env.t('sureLeave'))) return; + + Quests.sendAction('questLeave') + .then(function(quest) { + $scope.group.quest = quest; + }); + } + + $scope.questAccept = function(){ + Quests.sendAction('questAccept') + .then(function(quest) { + $scope.group.quest = quest; + }); + }; + + $scope.questReject = function(){ + Quests.sendAction('questReject') + .then(function(quest) { + $scope.group.quest = quest; + }); + }; + + $scope.canEditQuest = function(party) { + var isQuestLeader = party.quest && party.quest.leader === User.user._id; + + return isQuestLeader; + }; + } + ]); diff --git a/website/client/js/controllers/rootCtrl.js b/website/public/js/controllers/rootCtrl.js similarity index 86% rename from website/client/js/controllers/rootCtrl.js rename to website/public/js/controllers/rootCtrl.js index b4fee4076a..eacc0d2cce 100644 --- a/website/client/js/controllers/rootCtrl.js +++ b/website/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics','TAVERN_ID', - function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics, TAVERN_ID) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics) { var user = User.user; var initSticky = _.once(function(){ @@ -21,11 +21,10 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name}); // clear inbox when entering or exiting inbox tab if (fromState.name=='options.social.inbox' || toState.name=='options.social.inbox') { - User.clearNewMessages(); + User.user.ops.update && User.set({'inbox.newMessages':0}); } }); - $rootScope.TAVERN_ID = TAVERN_ID; $rootScope.User = User; $rootScope.user = user; $rootScope.moment = window.moment; @@ -219,11 +218,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ key: itemKey }; - User.equip({ params: equipParams }); + user.ops.equip({ params: equipParams }); } $rootScope.purchase = function(type, item){ - if (type == 'special') return User.buySpecialSpell({params:{key:item.key}}); + if (type == 'special') return user.ops.buySpecialSpell({params:{key:item.key}}); var gems = user.balance * 4; var price = item.value; @@ -249,7 +248,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ message += window.env.t('buyThis', {text: itemName, price: price, gems: gems}); if ($window.confirm(message)) - User.purchase({params:{type:type,key:item.key}}); + user.ops.purchase({params:{type:type,key:item.key}}); }; function _canBuyEquipment(itemKey) { @@ -278,51 +277,31 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ if (spell.target == 'self') { $scope.castEnd(null, 'self'); } else if (spell.target == 'party') { - Groups.party() - .then(function (party) { - party = (_.isArray(party) ? party : []).concat(User.user); - $scope.castEnd(party, 'party'); - }) - .catch(function (party) { // not in a party, act as a solo party - if (party && party.type === 'party') { - party = [User.user]; - $scope.castEnd(party, 'party'); - } - }); - } else if (spell.target == 'tasks') { - var tasks = User.user.habits.concat(User.user.dailys).concat(User.user.rewards).concat(User.user.todos); - // exclude challenge tasks - tasks = tasks.filter(function (task) { - if (!task.challenge) return true; - return (!task.challenge.id || task.challenge.broken); - }); - $scope.castEnd(tasks, 'tasks'); + var party = Groups.party(); + party = (_.isArray(party) ? party : []).concat(User.user); + $scope.castEnd(party, 'party'); } } $scope.castEnd = function(target, type, $event){ if (!$rootScope.applyingAction) return 'No applying action'; $event && ($event.stopPropagation(),$event.preventDefault()); - if ($scope.spell.target != type) return Notification.text(window.env.t('invalidTarget')); $scope.spell.cast(User.user, target); User.save(); var spell = $scope.spell; - var targetId = target ? target._id : null; + var targetId = (type == 'party' || type == 'self') ? '' : type == 'task' ? target.id : target._id; $scope.spell = null; $rootScope.applyingAction = false; - var spellUrl = ApiUrl.get() + '/api/v3/user/class/cast/' + spell.key; - if (targetId) spellUrl += '?targetId=' + targetId; - - $http.post(spellUrl) - .success(function(){ // TODO response will always include the modified data, no need to sync! + $http.post(ApiUrl.get() + '/api/v2/user/class/cast/'+spell.key+'?targetType='+type+'&targetId='+targetId) + .success(function(){ var msg = window.env.t('youCast', {spell: spell.text()}); switch (type) { - case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break; - case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break; - case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break; + case 'task': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.text});break; + case 'user': msg = window.env.t('youCastTarget', {spell: spell.text(), target: target.profile.name});break; + case 'party': msg = window.env.t('youCastParty', {spell: spell.text()});break; } Notification.markdown(msg); User.sync(); diff --git a/website/client/js/controllers/settingsCtrl.js b/website/public/js/controllers/settingsCtrl.js similarity index 89% rename from website/client/js/controllers/settingsCtrl.js rename to website/public/js/controllers/settingsCtrl.js index 7e78a8b095..3672a4d703 100644 --- a/website/client/js/controllers/settingsCtrl.js +++ b/website/public/js/controllers/settingsCtrl.js @@ -77,11 +77,14 @@ habitrpg.controller('SettingsCtrl', }; $scope.saveDayStart = function() { - User.setCustomDayStart(Math.floor($scope.dayStart)); + User.set({ + 'preferences.dayStart': Math.floor($scope.dayStart), + 'lastCron': +new Date + }); }; $scope.language = window.env.language; - $scope.availableLanguages = window.env.availableLanguages; + $scope.avalaibleLanguages = window.env.avalaibleLanguages; $scope.changeLanguage = function(){ $rootScope.$on('userSynced', function(){ @@ -96,7 +99,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.reroll({}); + User.user.ops.reroll({}); $rootScope.$state.go('tasks'); } } @@ -121,7 +124,7 @@ habitrpg.controller('SettingsCtrl', $scope.popoverEl.popover('destroy'); if (confirm) { - User.rebirth({}); + User.user.ops.rebirth({}); $rootScope.$state.go('tasks'); } } @@ -143,7 +146,7 @@ habitrpg.controller('SettingsCtrl', } $scope.changeUser = function(attr, updates){ - $http.put(ApiUrl.get() + '/api/v3/user/auth/update-'+attr, updates) + $http.post(ApiUrl.get() + '/api/v2/user/change-'+attr, updates) .success(function(){ alert(window.env.t(attr+'Success')); _.each(updates, function(v,k){updates[k]=null;}); @@ -172,25 +175,21 @@ habitrpg.controller('SettingsCtrl', } $scope.reset = function(){ - User.reset({}); - User.sync(); + User.user.ops.reset({}); $rootScope.$state.go('tasks'); } - $scope['delete'] = function(password) { - $http({ - url: ApiUrl.get() + '/api/v3/user', - method: 'DELETE', - data: {password: password}, - }) - .then(function(res, code) { - localStorage.clear(); - window.location.href = '/logout'; - }); + $scope['delete'] = function(){ + $http['delete'](ApiUrl.get() + '/api/v2/user') + .success(function(res, code){ + if (res.err) return alert(res.err); + localStorage.clear(); + window.location.href = '/logout'; + }); } $scope.enterCoupon = function(code) { - $http.post(ApiUrl.get() + '/api/v3/coupons/enter/' + code).success(function(res,code){ + $http.post(ApiUrl.get() + '/api/v2/user/coupon/' + code).success(function(res,code){ if (code!==200) return; User.sync(); Notification.text(env.t('promoCodeApplied')); @@ -202,7 +201,7 @@ habitrpg.controller('SettingsCtrl', .success(function(res,code){ $scope._codes = {}; if (code!==200) return; - window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.settings.auth.apiToken; + window.location.href = '/api/v2/coupons?limit='+codes.count+'&_id='+User.user._id+'&apiToken='+User.user.apiToken; }) } @@ -236,7 +235,7 @@ habitrpg.controller('SettingsCtrl', var releaseFunction = RELEASE_ANIMAL_TYPES[type]; if (releaseFunction) { - User[releaseFunction]({}); + User.user.ops[releaseFunction]({}); $rootScope.$state.go('tasks'); } } @@ -247,19 +246,19 @@ habitrpg.controller('SettingsCtrl', $scope.hasWebhooks = _.size(webhooks); }) $scope.addWebhook = function(url) { - User.addWebhook({body:{url:url, id:Shared.uuid()}}); + User.user.ops.addWebhook({body:{url:url, id:Shared.uuid()}}); $scope._newWebhook.url = ''; } $scope.saveWebhook = function(id,webhook) { delete webhook._editing; - User.updateWebhook({params:{id:id}, body:webhook}); + User.user.ops.updateWebhook({params:{id:id}, body:webhook}); } $scope.deleteWebhook = function(id) { - User.deleteWebhook({params:{id:id}}); + User.user.ops.deleteWebhook({params:{id:id}}); } $scope.applyCoupon = function(coupon){ - $http.get(ApiUrl.get() + '/api/v3/coupons/validate/'+coupon) + $http.get(ApiUrl.get() + '/api/v2/coupons/valid-discount/'+coupon) .success(function(){ Notification.text("Coupon applied!"); var subs = Content.subscriptionBlocks; diff --git a/website/client/js/controllers/sortableInventoryCtrl.js b/website/public/js/controllers/sortableInventoryCtrl.js similarity index 100% rename from website/client/js/controllers/sortableInventoryCtrl.js rename to website/public/js/controllers/sortableInventoryCtrl.js diff --git a/website/client/js/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js similarity index 71% rename from website/client/js/controllers/tasksCtrl.js rename to website/public/js/controllers/tasksCtrl.js index fb44d19cb3..619b0a8b3f 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/public/js/controllers/tasksCtrl.js @@ -24,23 +24,20 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N if (direction === 'down') $rootScope.playSound('Minus_Habit'); else if (direction === 'up') $rootScope.playSound('Plus_Habit'); } - User.score({params:{task: task, direction:direction}}); + User.user.ops.score({params:{id: task.id, direction:direction}}); Analytics.updateUser(); Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'score task','taskType':task.type,'direction':direction}); }; - function addTask(addTo, listDef, tasks) { - tasks = _.isArray(tasks) ? tasks : [tasks]; - - User.addTask({ - body: tasks.map(function (task) { - return { - text: task, - type: listDef.type, - tags: _.keys(User.user.filters), - } - }), - }); + function addTask(addTo, listDef, task) { + var newTask = { + text: task, + type: listDef.type, + tags: _.transform(User.user.filters, function(m,v,k){ + if (v) m[k]=v; + }) + }; + User.user.ops.addTask({body:newTask}); } $scope.addTask = function(addTo, listDef) { @@ -48,7 +45,9 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N var tasks = listDef.newTask.split(/[\n\r]+/); //Reverse the order of tasks so the tasks will appear in the order the user entered them tasks.reverse(); - addTask(addTo, listDef, tasks); + _.each(tasks, function(t) { + addTask(addTo, listDef, t); + }); listDef.bulk = false; } else { addTask(addTo, listDef, listDef.newTask); @@ -71,16 +70,14 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N /** * Add the new task to the actions log */ - $scope.clearDoneTodos = function() { - Tasks.clearCompletedTodos(); - }; + $scope.clearDoneTodos = function() {}; /** * Pushes task to top or bottom of list */ $scope.pushTask = function(task, index, location) { var to = (location === 'bottom' || $scope.ctrlPressed) ? -1 : 0; - User.sortTask({params:{id: task._id, taskType: task.type}, query:{from:index, to:to}}) + User.user.ops.sortTask({params:{id:task.id},query:{from:index, to:to}}) }; /** @@ -96,22 +93,16 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.removeTask = function(task) { if (!confirm(window.env.t('sureDelete', {taskType: window.env.t(task.type), taskText: task.text}))) return; - User.deleteTask({params:{id: task._id, taskType: task.type}}) + User.user.ops.deleteTask({params:{id:task.id}}) }; $scope.saveTask = function(task, stayOpen, isSaveAndClose) { - if (task.checklist) { - task.checklist = _.filter(task.checklist, function (i) { - return !!i.text - }); - } - User.updateTask(task, {body: task}); + if (task.checklist) + task.checklist = _.filter(task.checklist,function(i){return !!i.text}); + User.user.ops.updateTask({params:{id:task.id},body:task}); if (!stayOpen) task._editing = false; - - if (isSaveAndClose) { - $("#task-" + task._id).parent().children('.popover').removeClass('in'); - } - + if (isSaveAndClose) + $("#task-" + task.id).parent().children('.popover').removeClass('in'); if (task.type == 'habit') Guide.goto('intro', 3); }; @@ -129,17 +120,11 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N }; $scope.unlink = function(task, keep) { - if (keep.search('-all') !== -1) { // unlink all tasks - Tasks.unlinkAllTasks(task.challenge.id, keep) - .success(function () { - User.sync({}); - }); - } else { // unlink a task - Tasks.unlinkOneTask(task._id, keep) - .success(function () { - User.sync({}); - }); - } + // TODO move this to userServices, turn userSerivces.user into ng-resource + $http.post(ApiUrl.get() + '/api/v2/user/tasks/' + task.id + '/unlink?keep=' + keep) + .success(function(){ + User.log({}); + }); }; /* @@ -149,16 +134,6 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ $scope._today = moment().add({days: 1}); - $scope.loadedCompletedTodos = function () { - if (Tasks.loadedCompletedTodos === true) return; - - Tasks.getUserTasks(true) - .then(function (response) { - User.user.todos = User.user.todos.concat(response.data.data); - Tasks.loadedCompletedTodos = true; - }); - } - /* ------------------------ Dailies @@ -179,60 +154,53 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N */ function focusChecklist(task,index) { window.setTimeout(function(){ - $('#task-'+task._id+' .checklist-form input[type="text"]')[index].focus(); + $('#task-'+task.id+' .checklist-form input[type="text"]')[index].focus(); }); } - $scope.addChecklist = function(task) { - task.checklist = [{completed:false, text:""}]; + task.checklist = [{completed:false,text:""}]; focusChecklist(task,0); } - - $scope.addChecklistItem = function(task, $event, $index) { + $scope.addChecklistItem = function(task,$event,$index) { if (!task.checklist[$index].text) { // Don't allow creation of an empty checklist item // TODO Provide UI feedback that this item is still blank - } else if ($index == task.checklist.length - 1) { - Tasks.addChecklistItem(task._id, task.checklist[$index]); + } else if ($index == task.checklist.length-1){ + User.user.ops.updateTask({params:{id:task.id},body:task}); // don't preen the new empty item task.checklist.push({completed:false,text:''}); focusChecklist(task,task.checklist.length-1); } else { - $scope.saveTask(task, true); - focusChecklist(task, $index + 1); + $scope.saveTask(task,true); + focusChecklist(task,$index+1); } } - - $scope.removeChecklistItem = function(task, $event, $index, force){ + $scope.removeChecklistItem = function(task,$event,$index,force){ // Remove item if clicked on trash icon if (force) { - Tasks.removeChecklistItem(task._id, task.checklist[$index].id); - task.checklist.splice($index, 1); + task.checklist.splice($index,1); + $scope.saveTask(task,true); } else if (!task.checklist[$index].text) { // User deleted all the text and is now wishing to delete the item // saveTask will prune the empty item - Tasks.removeChecklistItem(task._id, task.checklist[$index].id); + $scope.saveTask(task,true); // Move focus if the list is still non-empty if ($index > 0) - focusChecklist(task, $index-1); + focusChecklist(task,$index-1); // Don't allow the backspace key to navigate back now that the field is gone $event.preventDefault(); } } - $scope.swapChecklistItems = function(task, oldIndex, newIndex) { var toSwap = task.checklist.splice(oldIndex, 1)[0]; task.checklist.splice(newIndex, 0, toSwap); $scope.saveTask(task, true); } - $scope.navigateChecklist = function(task,$index,$event){ focusChecklist(task, $event.keyCode == '40' ? $index+1 : $index-1); } - $scope.checklistCompletion = function(checklist){ return _.reduce(checklist,function(m,i){return m+(i.completed ? 1 : 0);},0) } - $scope.collapseChecklist = function(task) { task.collapseChecklist = !task.collapseChecklist; $scope.saveTask(task,true); @@ -253,9 +221,10 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.buy = function(item) { playRewardSound(item); - User.buy({params:{key:item.key}}); + User.user.ops.buy({params:{key:item.key}}); }; + /* ------------------------ Hiding Tasks @@ -289,21 +258,4 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $rootScope.playSound('Reward'); } } - - /* - ------------------------ - Tags - ------------------------ - */ - - $scope.updateTaskTags = function (tagId, task) { - var tagIndex = task.tags.indexOf(tagId); - if (tagIndex === -1) { - Tasks.addTagToTask(task._id, tagId); - task.tags.push(tagId); - } else { - Tasks.removeTagFromTask(task._id, tagId); - task.tags.splice(tagIndex, 0); - } - } }]); diff --git a/website/public/js/controllers/tavernCtrl.js b/website/public/js/controllers/tavernCtrl.js new file mode 100644 index 0000000000..995fa22ad0 --- /dev/null +++ b/website/public/js/controllers/tavernCtrl.js @@ -0,0 +1,10 @@ +'use strict'; + +habitrpg.controller("TavernCtrl", ['$scope', 'Groups', 'User', + function($scope, Groups, User) { + $scope.group = Groups.tavern(); + $scope.toggleUserTier = function($event) { + $($event.target).next().toggle(); + } + } + ]); diff --git a/website/client/js/controllers/userCtrl.js b/website/public/js/controllers/userCtrl.js similarity index 90% rename from website/client/js/controllers/userCtrl.js rename to website/public/js/controllers/userCtrl.js index b62207b945..e345d8ba2e 100644 --- a/website/client/js/controllers/userCtrl.js +++ b/website/public/js/controllers/userCtrl.js @@ -17,17 +17,17 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ }); $scope.allocate = function(stat){ - User.allocate({query:{stat:stat}}); + User.user.ops.allocate({query:{stat:stat}}); } $scope.changeClass = function(klass){ if (!klass) { if (!confirm(window.env.t('sureReset'))) return; - return User.changeClass({}); + return User.user.ops.changeClass({}); } - User.changeClass({query:{class:klass}}); + User.user.ops.changeClass({query:{class:klass}}); $scope.selectedClass = undefined; Shared.updateStore(User.user); Guide.goto('classes', 0,true); @@ -46,7 +46,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ } $scope.acknowledgeHealthWarning = function(){ - User.set({'flags.warnedLowHealth':true}); + User.user.ops.update && User.set({'flags.warnedLowHealth':true}); } /** @@ -69,7 +69,7 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ if (confirm(window.env.t('purchaseFor',{cost:cost*4})) !== true) return; if (User.user.balance < cost) return $rootScope.openModal('buyGems'); } - User.unlock({query:{path:path}}) + User.user.ops.unlock({query:{path:path}}) } $scope.ownsSet = function(type,_set) { diff --git a/website/client/js/directives/close-menu.directive.js b/website/public/js/directives/close-menu.directive.js similarity index 100% rename from website/client/js/directives/close-menu.directive.js rename to website/public/js/directives/close-menu.directive.js diff --git a/website/client/js/directives/expand-menu.directive.js b/website/public/js/directives/expand-menu.directive.js similarity index 100% rename from website/client/js/directives/expand-menu.directive.js rename to website/public/js/directives/expand-menu.directive.js diff --git a/website/client/js/directives/focus-element.directive.js b/website/public/js/directives/focus-element.directive.js similarity index 100% rename from website/client/js/directives/focus-element.directive.js rename to website/public/js/directives/focus-element.directive.js diff --git a/website/client/js/directives/from-now.directive.js b/website/public/js/directives/from-now.directive.js similarity index 100% rename from website/client/js/directives/from-now.directive.js rename to website/public/js/directives/from-now.directive.js diff --git a/website/client/js/directives/habitrpg-tasks.directive.js b/website/public/js/directives/habitrpg-tasks.directive.js similarity index 100% rename from website/client/js/directives/habitrpg-tasks.directive.js rename to website/public/js/directives/habitrpg-tasks.directive.js diff --git a/website/client/js/directives/hrpg-sort-checklist.directive.js b/website/public/js/directives/hrpg-sort-checklist.directive.js similarity index 100% rename from website/client/js/directives/hrpg-sort-checklist.directive.js rename to website/public/js/directives/hrpg-sort-checklist.directive.js diff --git a/website/client/js/directives/hrpg-sort-tags.directive.js b/website/public/js/directives/hrpg-sort-tags.directive.js similarity index 88% rename from website/client/js/directives/hrpg-sort-tags.directive.js rename to website/public/js/directives/hrpg-sort-tags.directive.js index 9a9e3d49bb..5b42bc778f 100644 --- a/website/client/js/directives/hrpg-sort-tags.directive.js +++ b/website/public/js/directives/hrpg-sort-tags.directive.js @@ -16,10 +16,10 @@ ui.item.data('startIndex', ui.item.index()); }, stop: function (event, ui) { - User.sortTag({ + User.user.ops.sortTag({ query: { from: ui.item.data('startIndex'), - to: ui.item.index() + to:ui.item.index() } }); } diff --git a/website/client/js/directives/hrpg-sort-tasks.directive.js b/website/public/js/directives/hrpg-sort-tasks.directive.js similarity index 89% rename from website/client/js/directives/hrpg-sort-tasks.directive.js rename to website/public/js/directives/hrpg-sort-tasks.directive.js index 0ce42d82eb..820fccfbe8 100644 --- a/website/client/js/directives/hrpg-sort-tasks.directive.js +++ b/website/public/js/directives/hrpg-sort-tasks.directive.js @@ -20,8 +20,8 @@ stop: function (event, ui) { var task = angular.element(ui.item[0]).scope().task; var startIndex = ui.item.data('startIndex'); - User.sortTask({ - params: { id: task._id, taskType: task.type }, + User.user.ops.sortTask({ + params: { id: task.id }, query: { from: startIndex, to: ui.item.index() diff --git a/website/client/js/directives/popover-html-popup.directive.js b/website/public/js/directives/popover-html-popup.directive.js similarity index 100% rename from website/client/js/directives/popover-html-popup.directive.js rename to website/public/js/directives/popover-html-popup.directive.js diff --git a/website/client/js/directives/popover-html.directive.js b/website/public/js/directives/popover-html.directive.js similarity index 100% rename from website/client/js/directives/popover-html.directive.js rename to website/public/js/directives/popover-html.directive.js diff --git a/website/client/js/directives/when-scrolled.directive.js b/website/public/js/directives/when-scrolled.directive.js similarity index 100% rename from website/client/js/directives/when-scrolled.directive.js rename to website/public/js/directives/when-scrolled.directive.js diff --git a/website/client/js/env.js b/website/public/js/env.js similarity index 100% rename from website/client/js/env.js rename to website/public/js/env.js diff --git a/website/client/js/filters/money.js b/website/public/js/filters/money.js similarity index 100% rename from website/client/js/filters/money.js rename to website/public/js/filters/money.js diff --git a/website/client/js/filters/roundLargeNumbers.js b/website/public/js/filters/roundLargeNumbers.js similarity index 100% rename from website/client/js/filters/roundLargeNumbers.js rename to website/public/js/filters/roundLargeNumbers.js diff --git a/website/client/js/filters/taskOrdering.js b/website/public/js/filters/taskOrdering.js similarity index 100% rename from website/client/js/filters/taskOrdering.js rename to website/public/js/filters/taskOrdering.js diff --git a/website/client/js/filters/timezoneOffsetToUtc.js b/website/public/js/filters/timezoneOffsetToUtc.js similarity index 100% rename from website/client/js/filters/timezoneOffsetToUtc.js rename to website/public/js/filters/timezoneOffsetToUtc.js diff --git a/website/client/js/services/analyticsServices.js b/website/public/js/services/analyticsServices.js similarity index 100% rename from website/client/js/services/analyticsServices.js rename to website/public/js/services/analyticsServices.js diff --git a/website/public/js/services/challengeServices.js b/website/public/js/services/challengeServices.js new file mode 100644 index 0000000000..51b91de8c2 --- /dev/null +++ b/website/public/js/services/challengeServices.js @@ -0,0 +1,26 @@ +'use strict'; + +/** + * Services that persists and retrieves user from localStorage. + */ + +angular.module('habitrpg').factory('Challenges', +['ApiUrl', '$resource', +function(ApiUrl, $resource) { + var Challenge = $resource(ApiUrl.get() + '/api/v2/challenges/:cid', + {cid:'@_id'}, + { + //'query': {method: "GET", isArray:false} + join: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/join'}, + leave: {method: "POST", url: ApiUrl.get() + '/api/v2/challenges/:cid/leave'}, + close: {method: "POST", params: {uid:''}, url: ApiUrl.get() + '/api/v2/challenges/:cid/close'}, + getMember: {method: "GET", url: ApiUrl.get() + '/api/v2/challenges/:cid/member/:uid'} + }); + + //var challenges = []; + + return { + Challenge: Challenge + //challenges: challenges + } +}]); diff --git a/website/public/js/services/chatServices.js b/website/public/js/services/chatServices.js new file mode 100644 index 0000000000..7fe2533506 --- /dev/null +++ b/website/public/js/services/chatServices.js @@ -0,0 +1,33 @@ +'use strict'; + +angular.module('habitrpg').factory('Chat', +['$resource', '$http', 'ApiUrl', 'User', +function($resource, $http, ApiUrl, User) { + var utils = $resource(ApiUrl.get() + '/api/v2/groups/:gid', + {gid:'@_id', messageId: '@_messageId'}, + { + postChat: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat'}, + like: {method: 'POST', isArray: true, url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/like'}, + deleteChatMessage: {method: "DELETE", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId'}, + flagChatMessage: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/flag'}, + clearFlagCount: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/chat/:messageId/clearflags'}, + }); + + var chatService = { + seenMessage: seenMessage, + clearCards: clearCards, + utils: utils + }; + + return chatService; + + function clearCards() { + User.user.ops.update && User.set({'flags.cardReceived':false}); + } + + function seenMessage(gid) { + // On enter, set chat message to "seen" + $http.post(ApiUrl.get() + '/api/v2/groups/'+gid+'/chat/seen'); + if (User.user.newMessages) delete User.user.newMessages[gid]; + } +}]); diff --git a/website/public/js/services/groupServices.js b/website/public/js/services/groupServices.js new file mode 100644 index 0000000000..001a137d63 --- /dev/null +++ b/website/public/js/services/groupServices.js @@ -0,0 +1,92 @@ +'use strict'; + +(function() { + angular + .module('habitrpg') + .factory('Groups', groupsFactory); + + groupsFactory.$inject = [ + '$location', + '$resource', + '$rootScope', + 'Analytics', + 'ApiUrl', + 'Challenges', + 'User' + ]; + + function groupsFactory($location, $resource, $rootScope, Analytics, ApiUrl, Challenges, User) { + + var data = {party: undefined, myGuilds: undefined, publicGuilds: undefined, tavern: undefined}; + var Group = $resource(ApiUrl.get() + '/api/v2/groups/:gid', + {gid:'@_id', messageId: '@_messageId'}, + { + get: { + method: "GET", + isArray:false, + // Wrap challenges as ngResource so they have functions like $leave or $join + transformResponse: function(data) { + data = angular.fromJson(data); + _.each(data && data.challenges, function(c) { + angular.extend(c, Challenges.Challenge.prototype); + }); + return data; + } + }, + + syncParty: {method: "GET", url: '/api/v2/groups/party'}, + join: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/join'}, + leave: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/leave'}, + invite: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/invite'}, + removeMember: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/removeMember'}, + startQuest: {method: "POST", url: ApiUrl.get() + '/api/v2/groups/:gid/questAccept'} + }); + + function party(cb) { + if (!data.party) return (data.party = Group.get({gid: 'party'}, cb)); + return (cb) ? cb(party) : data.party; + } + + function publicGuilds() { + //TODO combine these as {type:'guilds,public'} and create a $filter() to separate them + if (!data.publicGuilds) data.publicGuilds = Group.query({type:'public'}); + return data.publicGuilds; + } + + function myGuilds() { + if (!data.myGuilds) data.myGuilds = Group.query({type:'guilds'}); + return data.myGuilds; + } + + function tavern() { + if (!data.tavern) data.tavern = Group.get({gid:'habitrpg'}); + return data.tavern; + } + + function inviteOrStartParty(group) { + Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'}); + if (group.type === "party" || $location.$$path === "/options/groups/party") { + group.type = 'party'; + $rootScope.openModal('invite-party', { + controller:'InviteToGroupCtrl', + resolve: { + injectedGroup: function(){ return group; } + } + }); + } else { + $location.path("/options/groups/party"); + } + } + + return { + party: party, + publicGuilds: publicGuilds, + myGuilds: myGuilds, + tavern: tavern, + inviteOrStartParty: inviteOrStartParty, + + data: data, + Group: Group + } + } +})(); diff --git a/website/client/js/services/guideServices.js b/website/public/js/services/guideServices.js similarity index 97% rename from website/client/js/services/guideServices.js rename to website/public/js/services/guideServices.js index 7f3ce9e499..ee0be9d2c8 100644 --- a/website/client/js/services/guideServices.js +++ b/website/public/js/services/guideServices.js @@ -241,7 +241,7 @@ function($rootScope, User, $timeout, $state, Analytics) { }); var goto = function(chapter, page, force) { - if (chapter == 'intro' && User.user.flags.welcomed != true) User.set({'flags.welcomed': true}); + if (chapter == 'intro') User.set({'flags.welcomed': true}); if (page === -1) page = 0; var curr = User.user.flags.tour[chapter]; if (page != curr+1 && !force) return; @@ -264,8 +264,8 @@ function($rootScope, User, $timeout, $state, Analytics) { } //Init and show the welcome tour (only after user is pulled from server & wrapped). - var watcher = $rootScope.$watch('User.user._wrapped', function(wrapped){ - if (!wrapped) return; // only run after user has been wrapped + var watcher = $rootScope.$watch('User.user.ops.update', function(updateFn){ + if (!updateFn) return; // only run after user has been wrapped watcher(); // deregister watcher if (window.env.IS_MOBILE) return; // Don't show tour immediately on mobile devices if (User.user.flags.welcomed == false) { diff --git a/website/public/js/services/memberServices.js b/website/public/js/services/memberServices.js new file mode 100644 index 0000000000..4146a4ea1f --- /dev/null +++ b/website/public/js/services/memberServices.js @@ -0,0 +1,60 @@ +'use strict'; +(function(){ + angular + .module('habitrpg') + .factory('Members', membersFactory); + + membersFactory.$inject = [ + '$rootScope', + 'Shared', + 'ApiUrl', + '$resource' + ]; + + function membersFactory($rootScope, Shared, ApiUrl, $resource) { + var members = {}; + var fetchMember = $resource(ApiUrl.get() + '/api/v2/members/:uid', { uid: '@_id' }).get; + + function selectMember(uid, cb) { + + var self = this; + var memberIsReady = _checkIfMemberIsReady(members[uid]); + + if (memberIsReady) { + _prepareMember(self, members[uid], cb); + } else { + fetchMember({ uid: uid }, function(member) { + addToMembersList(member); // lazy load for later + _prepareMember(self, member, cb); + }); + } + } + + function addToMembersList(member){ + if (member._id) { + members[member._id] = member; + } + } + + function _checkIfMemberIsReady(member) { + return member && member.items && member.items.weapon; + } + + function _prepareMember(self, member, cb) { + Shared.wrap(member, false); + self.selectedMember = members[member._id]; + cb(); + } + + $rootScope.$on('userUpdated', function(event, user){ + addToMembersList(user); + }) + + return { + members: members, + addToMembersList: addToMembersList, + selectedMember: undefined, + selectMember: selectMember + } + } +}()); diff --git a/website/client/js/services/notificationServices.js b/website/public/js/services/notificationServices.js similarity index 95% rename from website/client/js/services/notificationServices.js rename to website/public/js/services/notificationServices.js index 096c1643af..f4556f6989 100644 --- a/website/client/js/services/notificationServices.js +++ b/website/public/js/services/notificationServices.js @@ -54,8 +54,8 @@ angular.module("habitrpg").factory("Notification", _notify(_sign(val) + " " + _round(val) + " " + window.env.t('experience'), 'xp', 'glyphicon glyphicon-star'); } - function error(error, canHide){ - _notify(error, "danger", 'glyphicon glyphicon-exclamation-sign', canHide); + function error(error){ + _notify(error, "danger", 'glyphicon glyphicon-exclamation-sign'); } function gp(val, bonus) { @@ -107,14 +107,14 @@ angular.module("habitrpg").factory("Notification", // Used to stack notifications, must be outside of _notify var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60}; - function _notify(html, type, icon, canHide) { + function _notify(html, type, icon) { var notice = $.pnotify({ type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit') text: html, opacity: 1, addclass: 'alert-' + type, delay: 7000, - hide: ((type == 'error' || type == 'danger') && !canHide) ? false : true, + hide: (type == 'error' || type == 'danger') ? false : true, mouse_reset: false, width: "250px", stack: stack_topright, diff --git a/website/client/js/services/paymentServices.js b/website/public/js/services/paymentServices.js similarity index 95% rename from website/client/js/services/paymentServices.js rename to website/public/js/services/paymentServices.js index 6797da7bf9..a3fc832d24 100644 --- a/website/client/js/services/paymentServices.js +++ b/website/public/js/services/paymentServices.js @@ -37,7 +37,7 @@ function($rootScope, User, $http, Content) { $http.post(url, res).success(function() { window.location.reload(true); }).error(function(res) { - alert(res.message); + alert(res.err); }); } }); @@ -55,7 +55,7 @@ function($rootScope, User, $http, Content) { $http.post(url, data).success(function() { window.location.reload(true); }).error(function(data) { - alert(data.message); + alert(data.err); }); } }); @@ -127,12 +127,12 @@ function($rootScope, User, $http, Content) { var url = '/amazon/createOrderReferenceId' $http.post(url, { billingAgreementId: Payments.amazonPayments.billingAgreementId - }).success(function(res){ + }).success(function(data){ Payments.amazonPayments.loggedIn = true; - Payments.amazonPayments.orderReferenceId = res.data.orderReferenceId; + Payments.amazonPayments.orderReferenceId = data.orderReferenceId; Payments.amazonPayments.initWidgets(); }).error(function(res){ - alert(res.message); + alert(res.err); }); } }, @@ -146,7 +146,7 @@ function($rootScope, User, $http, Content) { var url = '/amazon/verifyAccessToken' $http.post(url, response).error(function(res){ - alert(res.message); + alert(res.err); }); }); }, @@ -232,7 +232,7 @@ function($rootScope, User, $http, Content) { Payments.amazonPayments.reset(); window.location.reload(true); }).error(function(res){ - alert(res.message); + alert(res.err); Payments.amazonPayments.reset(); }); }else if(Payments.amazonPayments.type === 'subscription'){ @@ -246,7 +246,7 @@ function($rootScope, User, $http, Content) { Payments.amazonPayments.reset(); window.location.reload(true); }).error(function(res){ - alert(res.message); + alert(res.err); Payments.amazonPayments.reset(); }); } @@ -262,7 +262,7 @@ function($rootScope, User, $http, Content) { paymentMethod = paymentMethod.toLowerCase(); } - window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.settings.auth.apiToken; + window.location.href = '/' + paymentMethod + '/subscribe/cancel?_id=' + User.user._id + '&apiToken=' + User.user.apiToken; } Payments.encodeGift = function(uuid, gift){ diff --git a/website/client/js/services/questServices.js b/website/public/js/services/questServices.js similarity index 80% rename from website/client/js/services/questServices.js rename to website/public/js/services/questServices.js index 5033bad33d..a69ae800d8 100644 --- a/website/client/js/services/questServices.js +++ b/website/public/js/services/questServices.js @@ -1,16 +1,25 @@ 'use strict'; -angular.module('habitrpg') -.factory('Quests', ['$http', '$state','$q', 'ApiUrl', 'Content', 'Groups', 'User', 'Analytics', +(function(){ + angular + .module('habitrpg') + .factory('Quests', questsFactory); + + questsFactory.$inject = [ + '$http', + '$state', + '$q', + 'ApiUrl', + 'Content', + 'Groups', + 'User', + 'Analytics' + ]; + function questsFactory($http, $state, $q, ApiUrl, Content, Groups, User, Analytics) { var user = User.user; - var party; - - Groups.party() - .then(function (partyFound) { - party = partyFound; - }); + var party = Groups.party(); function lockQuest(quest,ignoreLevel) { if (!ignoreLevel){ @@ -97,21 +106,20 @@ angular.module('habitrpg') function initQuest(key) { return $q(function(resolve, reject) { - Analytics.track({'hitType':'event', 'eventCategory':'behavior', 'eventAction':'quest', 'owner':true, 'response':'accept', 'questName': key}); - Analytics.updateUser({'partyID': party._id, 'partySize': party.memberCount}); - Groups.Group.inviteToQuest(party._id, key) - .then(function(response) { - party.quest = response.data.data; - Groups.data.party = party; - $state.go('options.social.party'); - resolve(); - }); + Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName': key}); + Analytics.updateUser({'partyID':party._id,'partySize':party.memberCount}); + party.$startQuest({key:key}, function(){ + party.$syncParty(); + $state.go('options.social.party'); + resolve(); + }); }); } function sendAction(action) { return $q(function(resolve, reject) { - $http.post(ApiUrl.get() + '/api/v3/groups/' + party._id + '/' + action) + + $http.post(ApiUrl.get() + '/api/v2/groups/' + party._id + '/' + action) .then(function(response) { User.sync(); @@ -121,7 +129,6 @@ angular.module('habitrpg') }); var quest = response.data.quest; - if (!quest) quest = response.data.data; resolve(quest); });; }); @@ -135,4 +142,5 @@ angular.module('habitrpg') showQuest: showQuest, initQuest: initQuest } - }]); + } +}()); diff --git a/website/client/js/services/sharedServices.js b/website/public/js/services/sharedServices.js similarity index 100% rename from website/client/js/services/sharedServices.js rename to website/public/js/services/sharedServices.js diff --git a/website/client/js/services/socialServices.js b/website/public/js/services/socialServices.js similarity index 100% rename from website/client/js/services/socialServices.js rename to website/public/js/services/socialServices.js diff --git a/website/client/js/services/statServices.js b/website/public/js/services/statServices.js similarity index 94% rename from website/client/js/services/statServices.js rename to website/public/js/services/statServices.js index b716dbcaf7..f2db805fb6 100644 --- a/website/client/js/services/statServices.js +++ b/website/public/js/services/statServices.js @@ -22,7 +22,7 @@ } function classBonus(user, stat) { - var computedStats = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed() : null; + var computedStats = user._statsComputed; if(computedStats) { var bonus = computedStats[stat] @@ -95,7 +95,7 @@ function mpDisplay(user) { var remainingMP = Math.floor(user.stats.mp); - var totalMP = (user.fns && user.fns.statsComputed) ? user.fns.statsComputed().maxMP : null; + var totalMP = user._statsComputed.maxMP; var display = _formatOutOfTotalDisplay(remainingMP, totalMP); return display; diff --git a/website/public/js/services/taskServices.js b/website/public/js/services/taskServices.js new file mode 100644 index 0000000000..bbe7f6f689 --- /dev/null +++ b/website/public/js/services/taskServices.js @@ -0,0 +1,53 @@ +'use strict'; + +(function(){ + var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'dateCreated', 'history', 'id', 'streak']; + + angular + .module('habitrpg') + .factory('Tasks', tasksFactory); + + tasksFactory.$inject = [ + '$rootScope', + 'Shared', + 'User' + ]; + + function tasksFactory($rootScope, Shared, User) { + + function editTask(task) { + task._editing = !task._editing; + task._tags = !User.user.preferences.tagsCollapsed; + task._advanced = !User.user.preferences.advancedCollapsed; + if($rootScope.charts[task.id]) $rootScope.charts[task.id] = false; + } + + function cloneTask(task) { + var clonedTask = _.cloneDeep(task); + clonedTask = _cleanUpTask(clonedTask); + + return Shared.taskDefaults(clonedTask); + } + + function _cleanUpTask(task) { + var cleansedTask = _.omit(task, TASK_KEYS_TO_REMOVE); + + // Copy checklists but reset to uncomplete and assign new id + _(cleansedTask.checklist).forEach(function(item) { + item.completed = false; + item.id = Shared.uuid(); + }).value(); + + if (cleansedTask.type !== 'reward') { + delete cleansedTask.value; + } + + return cleansedTask; + } + + return { + editTask: editTask, + cloneTask: cloneTask + }; + } +})(); diff --git a/website/client/js/static.js b/website/public/js/static.js similarity index 77% rename from website/client/js/static.js rename to website/public/js/static.js index c8af2adf27..67a07df815 100644 --- a/website/client/js/static.js +++ b/website/public/js/static.js @@ -6,21 +6,18 @@ window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui. .constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings') .constant("MOBILE_APP", false) -.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', 'Stats', 'Members', - function($scope, $location, $modal, $http, Stats, Members) { +.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', 'Stats', function($scope, $location, $modal, $http, Stats){ var memberId = $location.search()['memberId']; if (memberId) { - Members.fetchMember(memberId) - .success(function(response) { - $scope.profile = response.data; - - $scope.statCalc = Stats; - $scope.Content = window.habitrpgShared.content; - $modal.open({ - templateUrl: 'modals/member.html', - scope: $scope - }); + $http.get('/api/v2/members/'+memberId).success(function(data, status, headers, config){ + $scope.profile = window.habitrpgShared.wrap(data, false); + $scope.statCalc = Stats; + $scope.Content = window.habitrpgShared.content; + $modal.open({ + templateUrl: 'modals/member.html', + scope: $scope }); + }) } $http.defaults.headers.common['x-client'] = 'habitica-web'; diff --git a/website/client/logo.png b/website/public/logo.png similarity index 100% rename from website/client/logo.png rename to website/public/logo.png diff --git a/website/client/logo/HABITRPG logo version 1.psd b/website/public/logo/HABITRPG logo version 1.psd similarity index 100% rename from website/client/logo/HABITRPG logo version 1.psd rename to website/public/logo/HABITRPG logo version 1.psd diff --git a/website/client/logo/HABITRPG-logo-version-1.gif b/website/public/logo/HABITRPG-logo-version-1.gif similarity index 100% rename from website/client/logo/HABITRPG-logo-version-1.gif rename to website/public/logo/HABITRPG-logo-version-1.gif diff --git a/website/client/logo/habitrpg.jpg b/website/public/logo/habitrpg.jpg similarity index 100% rename from website/client/logo/habitrpg.jpg rename to website/public/logo/habitrpg.jpg diff --git a/website/client/logo/habitrpg_bl.eps b/website/public/logo/habitrpg_bl.eps similarity index 100% rename from website/client/logo/habitrpg_bl.eps rename to website/public/logo/habitrpg_bl.eps diff --git a/website/client/logo/habitrpg_pixel.png b/website/public/logo/habitrpg_pixel.png similarity index 100% rename from website/client/logo/habitrpg_pixel.png rename to website/public/logo/habitrpg_pixel.png diff --git a/website/client/manifest.json b/website/public/manifest.json similarity index 94% rename from website/client/manifest.json rename to website/public/manifest.json index 2c770893f1..20da085a28 100644 --- a/website/client/manifest.json +++ b/website/public/manifest.json @@ -31,6 +31,7 @@ "bower_components/jquery-ui/ui/minified/jquery.ui.widget.min.js", "bower_components/jquery-ui/ui/minified/jquery.ui.mouse.min.js", "bower_components/jquery-ui/ui/minified/jquery.ui.sortable.min.js", + "bower_components/smart-app-banner/smart-app-banner.js", "common/dist/scripts/habitrpg-shared.js", @@ -42,6 +43,7 @@ "js/services/sharedServices.js", "js/services/notificationServices.js", + "common/script/public/userServices.js", "common/script/public/directives.js", "js/services/analyticsServices.js", "js/services/groupServices.js", @@ -49,13 +51,11 @@ "js/services/memberServices.js", "js/services/guideServices.js", "js/services/taskServices.js", - "js/services/tagsServices.js", "js/services/challengeServices.js", "js/services/paymentServices.js", "js/services/questServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "js/services/userServices.js", "js/filters/money.js", "js/filters/roundLargeNumbers.js", @@ -130,13 +130,10 @@ "js/static.js", "js/services/analyticsServices.js", "js/services/notificationServices.js", - "js/services/userServices.js", "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "js/services/taskServices.js", - "js/services/tagsServices.js", - "js/services/memberServices.js", + "common/script/public/userServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], @@ -170,10 +167,7 @@ "js/services/sharedServices.js", "js/services/socialServices.js", "js/services/statServices.js", - "js/services/taskServices.js", - "js/services/tagsServices.js", - "js/services/userServices.js", - "js/services/memberServices.js", + "common/script/public/userServices.js", "js/controllers/authCtrl.js", "js/controllers/footerCtrl.js" ], diff --git a/website/client/marketing/android_iphone.png b/website/public/marketing/android_iphone.png similarity index 100% rename from website/client/marketing/android_iphone.png rename to website/public/marketing/android_iphone.png diff --git a/website/client/marketing/animals.png b/website/public/marketing/animals.png similarity index 100% rename from website/client/marketing/animals.png rename to website/public/marketing/animals.png diff --git a/website/client/marketing/challenge.png b/website/public/marketing/challenge.png similarity index 100% rename from website/client/marketing/challenge.png rename to website/public/marketing/challenge.png diff --git a/website/client/marketing/devices.png b/website/public/marketing/devices.png similarity index 100% rename from website/client/marketing/devices.png rename to website/public/marketing/devices.png diff --git a/website/client/marketing/drops.png b/website/public/marketing/drops.png similarity index 100% rename from website/client/marketing/drops.png rename to website/public/marketing/drops.png diff --git a/website/client/marketing/education.png b/website/public/marketing/education.png similarity index 100% rename from website/client/marketing/education.png rename to website/public/marketing/education.png diff --git a/website/client/marketing/gear.png b/website/public/marketing/gear.png similarity index 100% rename from website/client/marketing/gear.png rename to website/public/marketing/gear.png diff --git a/website/client/marketing/guild.png b/website/public/marketing/guild.png similarity index 100% rename from website/client/marketing/guild.png rename to website/public/marketing/guild.png diff --git a/website/client/marketing/guild_small.png b/website/public/marketing/guild_small.png similarity index 100% rename from website/client/marketing/guild_small.png rename to website/public/marketing/guild_small.png diff --git a/website/client/marketing/integration.png b/website/public/marketing/integration.png similarity index 100% rename from website/client/marketing/integration.png rename to website/public/marketing/integration.png diff --git a/website/client/marketing/lefnire.png b/website/public/marketing/lefnire.png similarity index 100% rename from website/client/marketing/lefnire.png rename to website/public/marketing/lefnire.png diff --git a/website/client/marketing/promos/201403_Forest_Walker.png b/website/public/marketing/promos/201403_Forest_Walker.png similarity index 100% rename from website/client/marketing/promos/201403_Forest_Walker.png rename to website/public/marketing/promos/201403_Forest_Walker.png diff --git a/website/client/marketing/promos/April14SAMPLE2.png b/website/public/marketing/promos/April14SAMPLE2.png similarity index 100% rename from website/client/marketing/promos/April14SAMPLE2.png rename to website/public/marketing/promos/April14SAMPLE2.png diff --git a/website/client/marketing/screenshot.png b/website/public/marketing/screenshot.png similarity index 100% rename from website/client/marketing/screenshot.png rename to website/public/marketing/screenshot.png diff --git a/website/client/marketing/social_competitve.png b/website/public/marketing/social_competitve.png similarity index 100% rename from website/client/marketing/social_competitve.png rename to website/public/marketing/social_competitve.png diff --git a/website/client/marketing/wellness.png b/website/public/marketing/wellness.png similarity index 100% rename from website/client/marketing/wellness.png rename to website/public/marketing/wellness.png diff --git a/website/client/merch/stickermule-logo.png b/website/public/merch/stickermule-logo.png similarity index 100% rename from website/client/merch/stickermule-logo.png rename to website/public/merch/stickermule-logo.png diff --git a/website/client/merch/stickermule-logo.svg b/website/public/merch/stickermule-logo.svg similarity index 100% rename from website/client/merch/stickermule-logo.svg rename to website/public/merch/stickermule-logo.svg diff --git a/website/client/merch/stickermule.png b/website/public/merch/stickermule.png similarity index 100% rename from website/client/merch/stickermule.png rename to website/public/merch/stickermule.png diff --git a/website/client/merch/teespring-eu-logo.png b/website/public/merch/teespring-eu-logo.png similarity index 100% rename from website/client/merch/teespring-eu-logo.png rename to website/public/merch/teespring-eu-logo.png diff --git a/website/client/merch/teespring-eu.png b/website/public/merch/teespring-eu.png similarity index 100% rename from website/client/merch/teespring-eu.png rename to website/public/merch/teespring-eu.png diff --git a/website/client/merch/teespring-logo.png b/website/public/merch/teespring-logo.png similarity index 100% rename from website/client/merch/teespring-logo.png rename to website/public/merch/teespring-logo.png diff --git a/website/client/merch/teespring-logo.svg b/website/public/merch/teespring-logo.svg similarity index 100% rename from website/client/merch/teespring-logo.svg rename to website/public/merch/teespring-logo.svg diff --git a/website/client/merch/teespring.png b/website/public/merch/teespring.png similarity index 100% rename from website/client/merch/teespring.png rename to website/public/merch/teespring.png diff --git a/website/client/page-loader.gif b/website/public/page-loader.gif similarity index 100% rename from website/client/page-loader.gif rename to website/public/page-loader.gif diff --git a/website/client/presskit/Boss - Basi-List.png b/website/public/presskit/Boss - Basi-List.png similarity index 100% rename from website/client/presskit/Boss - Basi-List.png rename to website/public/presskit/Boss - Basi-List.png diff --git a/website/client/presskit/Boss - Battling the Ghost Stag.png b/website/public/presskit/Boss - Battling the Ghost Stag.png similarity index 100% rename from website/client/presskit/Boss - Battling the Ghost Stag.png rename to website/public/presskit/Boss - Battling the Ghost Stag.png diff --git a/website/client/presskit/Boss - Laundromancer.png b/website/public/presskit/Boss - Laundromancer.png similarity index 100% rename from website/client/presskit/Boss - Laundromancer.png rename to website/public/presskit/Boss - Laundromancer.png diff --git a/website/client/presskit/Boss - Necro-Vice.png b/website/public/presskit/Boss - Necro-Vice.png similarity index 100% rename from website/client/presskit/Boss - Necro-Vice.png rename to website/public/presskit/Boss - Necro-Vice.png diff --git a/website/client/presskit/Boss - SnackLess Monster.png b/website/public/presskit/Boss - SnackLess Monster.png similarity index 100% rename from website/client/presskit/Boss - SnackLess Monster.png rename to website/public/presskit/Boss - SnackLess Monster.png diff --git a/website/client/presskit/Boss - Stagnant Dishes.png b/website/public/presskit/Boss - Stagnant Dishes.png similarity index 100% rename from website/client/presskit/Boss - Stagnant Dishes.png rename to website/public/presskit/Boss - Stagnant Dishes.png diff --git a/website/client/presskit/Habitica Gryphon.png b/website/public/presskit/Habitica Gryphon.png similarity index 100% rename from website/client/presskit/Habitica Gryphon.png rename to website/public/presskit/Habitica Gryphon.png diff --git a/website/client/presskit/Habitica Logo - Android.png b/website/public/presskit/Habitica Logo - Android.png similarity index 100% rename from website/client/presskit/Habitica Logo - Android.png rename to website/public/presskit/Habitica Logo - Android.png diff --git a/website/client/presskit/Habitica Logo - Icon with Text.png b/website/public/presskit/Habitica Logo - Icon with Text.png similarity index 100% rename from website/client/presskit/Habitica Logo - Icon with Text.png rename to website/public/presskit/Habitica Logo - Icon with Text.png diff --git a/website/client/presskit/Habitica Logo - Icon.png b/website/public/presskit/Habitica Logo - Icon.png similarity index 100% rename from website/client/presskit/Habitica Logo - Icon.png rename to website/public/presskit/Habitica Logo - Icon.png diff --git a/website/client/presskit/Habitica Logo - Text.png b/website/public/presskit/Habitica Logo - Text.png similarity index 100% rename from website/client/presskit/Habitica Logo - Text.png rename to website/public/presskit/Habitica Logo - Text.png diff --git a/website/client/presskit/Habitica Logo - iOS.png b/website/public/presskit/Habitica Logo - iOS.png similarity index 100% rename from website/client/presskit/Habitica Logo - iOS.png rename to website/public/presskit/Habitica Logo - iOS.png diff --git a/website/client/presskit/Habitica Promo - Thin.png b/website/public/presskit/Habitica Promo - Thin.png similarity index 100% rename from website/client/presskit/Habitica Promo - Thin.png rename to website/public/presskit/Habitica Promo - Thin.png diff --git a/website/client/presskit/Habitica Promo.png b/website/public/presskit/Habitica Promo.png similarity index 100% rename from website/client/presskit/Habitica Promo.png rename to website/public/presskit/Habitica Promo.png diff --git a/website/client/presskit/Sample Screen - Boss (iOS).png b/website/public/presskit/Sample Screen - Boss (iOS).png similarity index 100% rename from website/client/presskit/Sample Screen - Boss (iOS).png rename to website/public/presskit/Sample Screen - Boss (iOS).png diff --git a/website/client/presskit/Sample Screen - Challenges.png b/website/public/presskit/Sample Screen - Challenges.png similarity index 100% rename from website/client/presskit/Sample Screen - Challenges.png rename to website/public/presskit/Sample Screen - Challenges.png diff --git a/website/client/presskit/Sample Screen - Equipment.png b/website/public/presskit/Sample Screen - Equipment.png similarity index 100% rename from website/client/presskit/Sample Screen - Equipment.png rename to website/public/presskit/Sample Screen - Equipment.png diff --git a/website/client/presskit/Sample Screen - Guilds.png b/website/public/presskit/Sample Screen - Guilds.png similarity index 100% rename from website/client/presskit/Sample Screen - Guilds.png rename to website/public/presskit/Sample Screen - Guilds.png diff --git a/website/client/presskit/Sample Screen - Level Up (iOS).png b/website/public/presskit/Sample Screen - Level Up (iOS).png similarity index 100% rename from website/client/presskit/Sample Screen - Level Up (iOS).png rename to website/public/presskit/Sample Screen - Level Up (iOS).png diff --git a/website/client/presskit/Sample Screen - Market.png b/website/public/presskit/Sample Screen - Market.png similarity index 100% rename from website/client/presskit/Sample Screen - Market.png rename to website/public/presskit/Sample Screen - Market.png diff --git a/website/client/presskit/Sample Screen - Party (iOS).png b/website/public/presskit/Sample Screen - Party (iOS).png similarity index 100% rename from website/client/presskit/Sample Screen - Party (iOS).png rename to website/public/presskit/Sample Screen - Party (iOS).png diff --git a/website/client/presskit/Sample Screen - Pets (iOS).png b/website/public/presskit/Sample Screen - Pets (iOS).png similarity index 100% rename from website/client/presskit/Sample Screen - Pets (iOS).png rename to website/public/presskit/Sample Screen - Pets (iOS).png diff --git a/website/client/presskit/Sample Screen - Tasks Page (iOS).png b/website/public/presskit/Sample Screen - Tasks Page (iOS).png similarity index 100% rename from website/client/presskit/Sample Screen - Tasks Page (iOS).png rename to website/public/presskit/Sample Screen - Tasks Page (iOS).png diff --git a/website/client/presskit/Sample Screen - Tasks Page.png b/website/public/presskit/Sample Screen - Tasks Page.png similarity index 100% rename from website/client/presskit/Sample Screen - Tasks Page.png rename to website/public/presskit/Sample Screen - Tasks Page.png diff --git a/website/client/presskit/World Boss - Dread Drag'on of Dilatory.png b/website/public/presskit/World Boss - Dread Drag'on of Dilatory.png similarity index 100% rename from website/client/presskit/World Boss - Dread Drag'on of Dilatory.png rename to website/public/presskit/World Boss - Dread Drag'on of Dilatory.png diff --git a/website/client/presskit/presskit.zip b/website/public/presskit/presskit.zip similarity index 100% rename from website/client/presskit/presskit.zip rename to website/public/presskit/presskit.zip diff --git a/website/client/refresh.png b/website/public/refresh.png similarity index 100% rename from website/client/refresh.png rename to website/public/refresh.png diff --git a/website/server/controllers/api-v2/challenges.js b/website/server/controllers/api-v2/challenges.js deleted file mode 100644 index c5d716d88e..0000000000 --- a/website/server/controllers/api-v2/challenges.js +++ /dev/null @@ -1,428 +0,0 @@ -// @see ../routes for routing - -var _ = require('lodash'); -var nconf = require('nconf'); -var async = require('async'); -var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - model as Group, - basicFields as basicGroupFields, - TAVERN_ID, -} from '../../models/group'; -import { - model as Challenge, -} from '../../models/challenge'; -import * as Tasks from '../../models/task'; -var logging = require('./../../libs/api-v2/logging'); -var csvStringify = require('csv-stringify'); -var utils = require('../../libs/api-v2/utils'); -var api = module.exports; -var pushNotify = require('./pushNotifications'); -import Bluebird from 'bluebird'; -import v3MembersController from '../api-v3/members'; -/* - ------------------------------------------------------------------------ - Challenges - ------------------------------------------------------------------------ -*/ - -var nameFields = 'profile.name'; - -api.list = async function(req, res, next) { - try { - var user = res.locals.user; - - let challenges = await Challenge.find({ - $or: [ - {_id: {$in: user.challenges}}, // Challenges where the user is participating - {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member - {leader: user._id}, // Challenges where I'm the leader - ], - _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit - }) - .sort('-official -timestamp') - // .populate('group', basicGroupFields) - // .populate('leader', nameFields) - .exec(); - - let resChals = challenges.map(challenge => { - let obj = challenge.toJSON(); - - obj._isMember = user.challenges.indexOf(challenge._id) !== -1; - return obj; - }); - - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - await Bluebird.all(resChals.map((chal, index) => { - return Bluebird.all([ - User.findById(chal.leader).select(nameFields).exec(), - Group.findById(chal.group).select(basicGroupFields).exec(), - ]).then(populatedData => { - resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null; - resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null; - }); - })); - - res.json(resChals); - } catch (err) { - next(err); - } -} - -// GET -api.get = async function(req, res, next) { - try { - let user = res.locals.user; - let challengeId = req.params.cid; - - let challenge = await Challenge.findById(challengeId) - // Don't populate the group as we'll fetch it manually later - // .populate('leader', nameFields) - .exec(); - if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); - - // Fetching basic group data - let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); - if (!group || !challenge.canView(user, group)) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); - - let leaderRes = await User.findById(challenge.leader).select('profile.name').exec(); - leaderRes = leaderRes ? leaderRes.toJSON({minimize: true}) : null; - - challenge.getTransformedData({ - populateMembers: 'profile.name', - cb (err, transformedChal) { - transformedChal.group = group.toJSON({minimize: true}); - transformedChal.leader = leaderRes; - transformedChal._isMember = user.challenges.indexOf(transformedChal._id) !== -1; - res.json(transformedChal); - } - }); - } catch (err) { - next(err); - } -} - -api.csv = function(req, res, next) { - var cid = req.params.cid; - req.params.challengeId = cid; - v3MembersController.exportChallengeCsv.handler(req, res, next).catch(next); -} - -api.getMember = function(req, res, next) { - var cid = req.params.cid; - var uid = req.params.uid; - - req.params.memberId = uid; - req.params.challengeId = cid; - v3MembersController.getChallengeMemberProgress.handler(req, res, next) - .then(result => { - let newResult = { - profile: { - name: result.profile.name, - }, - habits: [], - dailys: [], - todos: [], - rewards: [], - }; - - let tasks = result.tasks; - tasks.forEach(task => { - let taskObj = task.toJSONV2(); - newResult[taskObj.type + 's'].push(taskObj); - }); - - res.json(newResult); - }) - .catch(next); -} - -// CREATE -api.create = async function(req, res, next){ - try { - var user = res.locals.user; - - let groupId = req.body.group; - let prize = req.body.prize; - - let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); - if (!group) return res.status(404).json({err:"Group." + req.body.group + " not found"}); - if (!group.isMember(user)) return res.status(404).json({err:"Group." + req.body.group + " not found"}); - - if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { - return res.status(401).json({err:"Only the group leader can create challenges"}); - } - - if (group._id === TAVERN_ID && prize < 1) { - return res.status(401).json({err: 'Prize must be at least 1 Gem for public challenges.'}) - } - - if (prize > 0) { - let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; - let prizeCost = prize / 4; - - if (prizeCost > user.balance + groupBalance) { - return res.status(401).json({err: 'You can\'t afford this prize. Purchase more gems or lower the prize amount.'}); - } - - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - let remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - } - - group.challengeCount += 1; - - req.body.leader = user._id; - req.body.official = user.contributor.admin && req.body.official; - let challenge = new Challenge(Challenge.sanitize(req.body)); - - // First validate challenge so we don't save group if it's invalid (only runs sync validators) - let challengeValidationErrors = challenge.validateSync(); - if (challengeValidationErrors) throw challengeValidationErrors; - - req.body.habits = req.body.habits || []; - req.body.todos = req.body.todos || []; - req.body.dailys = req.body.dailys || []; - req.body.rewards = req.body.rewards || []; - - var chalTasks = req.body.habits.concat(req.body.rewards) - .concat(req.body.dailys).concat(req.body.todos) - .map(v2Task => Tasks.Task.fromJSONV2(v2Task)); - - chalTasks = chalTasks.map(function(task) { - var newTask = new Tasks[task.type](Tasks.Task.sanitize(task)); - newTask.challenge.id = challenge._id; - return newTask.save(); - }); - - let results = await Bluebird.all([challenge.save({ - validateBeforeSave: false, // already validated - }), group.save()].concat(chalTasks)); - let savedChal = results[0]; - - await savedChal.syncToUser(user); // (it also saves the user) - - savedChal.getTransformedData({ - cb (err, transformedChal) { - res.status(201).json(transformedChal); - }, - }); - } catch (err) { - next(err); - } -} - -// UPDATE -api.update = function(req, res, next){ - var cid = req.params.cid; - var user = res.locals.user; - var before; - var updatedTasks; - - async.waterfall([ - function(cb){ - // We first need the original challenge data, since we're going to compare against new & decide to sync users - Challenge.findById(cid, cb); - }, - function(chal, cb){ - if(!chal) return cb({chal: null}); - - chal.getTasks(function(err, tasks){ - cb(err, { - chal: chal, - tasks: tasks - }); - }); - }, - function(_before, cb) { - if (!_before.chal) return cb('Challenge ' + cid + ' not found'); - if (_before.chal.leader != user._id && !user.contributor.admin) return cb({code: 401, err: shared.i18n.t('noPermissionEditChallenge', req.language)}); - // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some - // before-save / after-save comparison to determine if we need to sync to users - before = {chal: _before.chal, tasks: _before.tasks}; - var chalAttrs = _.pick(req.body, 'name shortName description date'.split(' ')); - async.parallel({ - chal: function(cb1){ - Challenge.findByIdAndUpdate(cid, {$set:chalAttrs}, {new: true}, cb1); - }, - tasks: function(cb1) { - // Convert to map of {id: task} so we can easily match them - var _beforeClonedTasks = _before.tasks; - updatedTasks = _.object(_.pluck(_beforeClonedTasks, '_id'), _beforeClonedTasks); - var newTasks = req.body.habits.concat(req.body.dailys) - .concat(req.body.todos).concat(req.body.rewards); - - var newTasksObj = _.object(_.pluck(newTasks, '_id'), newTasks); - async.forEachOf(newTasksObj, function(newTask, taskId, cb2){ - // some properties can't be changed - newTask = Tasks.Task.sanitize(newTask); - // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? - _.assign(updatedTasks[taskId], shared.ops.updateTask(updatedTasks[taskId].toObject(), {body: newTask})); - _before.chal.updateTask(updatedTasks[taskId]).then(cb2).catch(cb2); - }, cb1); - } - }, cb); - }, - ], function(err, saved){ - if(err) { - return err.code ? res.json(err.code, err) : next(err); - } - - saved.chal.getTransformedData({cb: function(err, newChal){ - if(err) return next(err); - res.json(newChal); - }}) - cid = user = before = null; - }); -} - -/** - * Delete & close - */ -api.delete = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - - let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); - if (!challenge) return next('Challenge ' + cid + ' not found'); - if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge')); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_DELETED'}); - res.sendStatus(200); - } catch (err) { - next(err); - } -} - -/** - * Select Winner & Close - */ -api.selectWinner = async function(req, res, next) { - try { - if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'}); - - let challenge = await Challenge.findOne({_id: req.params.cid}).exec(); - if (!challenge) return next('Challenge ' + req.params.cid + ' not found'); - if (!challenge.canModify(res.locals.user)) return next(shared.i18n.t('noPermissionCloseChallenge')); - - let winner = await User.findOne({_id: req.query.uid}).exec(); - if (!winner || winner.challenges.indexOf(challenge._id) === -1) return next('Winner ' + req.query.uid + ' not found.'); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner}); - res.respond(200, {}); - } catch (err) { - next(err); - } -} - -api.join = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - - let challenge = await Challenge.findOne({ _id: cid }); - if (!challenge) return next(shared.i18n.t('challengeNotFound')); - if (challenge.isMember(user)) return next(shared.i18n.t('userAlreadyInChallenge')); - - let group = await Group.getGroup({user, groupId: challenge.group, optionalMembership: true}); - if (!group || !challenge.hasAccess(user, group)) return next(shared.i18n.t('challengeNotFound')); - - challenge.memberCount += 1; - - // Add all challenge's tasks to user's tasks and save the challenge - await Bluebird.all([challenge.syncToUser(user), challenge.save()]); - - challenge.getTransformedData({ - cb (err, transformedChal) { - transformedChal._isMember = true; - res.json(transformedChal); - } - }); - } catch (e) { - next(e); - } -} - -api.leave = async function(req, res, next){ - try { - var user = res.locals.user; - var cid = req.params.cid; - // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided - var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - - let challenge = await Challenge.findOne({ _id: cid }); - if (!challenge) return next(shared.i18n.t('challengeNotFound')); - - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); - if (!group || !challenge.canView(user, group)) return next(shared.i18n.t('challengeNotFound')); - - if (!challenge.isMember(user)) return next(shared.i18n.t('challengeMemberNotFound')); - - challenge.memberCount -= 1; - - // Unlink challenge's tasks from user's tasks and save the challenge - await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]); - - challenge.getTransformedData({ - cb (err, transformedChal) { - transformedChal._isMember = false; - res.json(transformedChal); - } - }); - } catch (e) { - next(e); - } -} - -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; - -api.unlink = async function(req, res, next) { - try { - var user = res.locals.user; - var tid = req.params.id; - var cid; - if (!req.query.keep) - return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'}); - - let keep = req.query.keep; - let task = await Tasks.Task.findOne({ - _id: tid, - userId: user._id, - }).exec(); - - if (!task) return next(shared.i18n.t('taskNotFound')); - if (!task.challenge.id) return next(shared.i18n.t('cantOnlyUnlinkChalTask')); - - cid = task.challenge.id; - if (keep === 'keep') { - task.challenge = {}; - await task.save(); - } else { // remove - if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if - removeFromArray(user.tasksOrder[`${task.type}s`], tid); - await Bluebird.all([user.save(), task.remove()]); - } else { - await task.remove(); - } - } - - res.sendStatus(200); - } catch (e) { - next(e); - } -} diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js deleted file mode 100644 index 656a48c908..0000000000 --- a/website/server/controllers/api-v2/user.js +++ /dev/null @@ -1,1053 +0,0 @@ -var url = require('url'); -var ipn = require('paypal-ipn'); -var _ = require('lodash'); -var validator = require('validator'); -var nconf = require('nconf'); -var asyncM = require('async'); -var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - NotFound, -} from '../../libs/api-v3/errors'; -import { model as Tag } from '../../models/tag'; -import * as Tasks from '../../models/task'; -import Bluebird from 'bluebird'; -import {removeFromArray} from './../../libs/api-v3/collectionManipulators'; -var utils = require('./../../libs/api-v2/utils'); -var analytics = utils.analytics; -import { - basicFields as basicGroupFields, - model as Group, -} from '../../models/group'; -import { - model as Challenge, -} from '../../models/challenge'; -var moment = require('moment'); -var logging = require('./../../libs/api-v2/logging'); -var acceptablePUTPaths; -let restrictedPUTSubPaths; -import v3UserController from '../api-v3/user'; - -let i18n = shared.i18n; - -var api = module.exports; -var firebase = require('../../libs/api-v2/firebase'); -var webhook = require('../../libs/api-v2/webhook'); - -const partyMembersFields = 'profile.name stats achievements items.special'; - -// api.purchase // Shared.ops - -api.getContent = function(req, res, next) { - var language = 'en'; - - if (typeof req.query.language != 'undefined') - language = req.query.language.toString(); //|| 'en' in i18n - - var content = _.cloneDeep(shared.content); - var walk = function(obj, lang){ - _.each(obj, function(item, key, source){ - if (_.isPlainObject(item) || _.isArray(item)) return walk(item, lang); - if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); - }); - } - walk(content, language); - res.json(content); -} - -api.getModelPaths = function(req,res,next){ - res.json(_.reduce(User.schema.paths,function(m,v,k){ - m[k] = v.instance || 'Boolean'; - return m; - },{})); -} - -/* - ------------------------------------------------------------------------ - Tasks - ------------------------------------------------------------------------ -*/ - - -/* - Local Methods - --------------- -*/ - -var findTask = function(req, res) { - return res.locals.user.tasks[req.params.id]; -}; - -function findTaskByIdOrLegacyId (user, taskId, callback) { - asyncM.waterfall([ - function (cb) { - Tasks.Task.findOne({ - _id: taskId, - userId: user._id, - }, cb); - }, - function (task, cb) { - if (task) return cb(null, task); - - Tasks.Task.findOne({ - _legacyId: taskId, - userId: user._id, - }, cb); - }, - ], callback); -} - -/* - API Routes - --------------- -*/ - -api.score = function(req, res, next) { - var id = req.params.id, - direction = req.params.direction, - user = res.locals.user, - body = req.body || {}, - task; - - // Send error responses for improper API call - if (!id) return res.json(400, {err: ':id required'}); - if (direction !== 'up' && direction !== 'down') { - if (direction == 'unlink' || direction == 'sort') return next(); - return res.json(400, {err: ":direction must be 'up' or 'down'"}); - } - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if (err) return next(err); - - // If exists already, score it - if (!task) { - // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it - // Defaults. Other defaults are handled in user.ops.addTask() - var taskOptions = { - type: body.type || 'habit', - text: body.text || id, - userId: user._id, - notes: body.notes || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." // TODO translate - } - - if (validator.isUUID(id)) { - taskOptions._id = id; // TODO this might easily lead to conflicts as ids are now unique db-wide - } else { - taskOptions._legacyId = id; - } - - task = new Tasks.Task(taskOptions); - - user.tasksOrder[task.type + 's'].unshift(task._id); - } - - // Set completed if type is daily or todo - if (task.type === 'daily' || task.type === 'todo') { - task.completed = direction === 'up'; - } - - var [delta] = shared.ops.scoreTask({ - user, - task, - direction, - }, req); - // Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results) - if (direction === 'up') user.fns.randomDrop({task, delta}, req); - - asyncM.parallel({ - task: task.save.bind(task), - user: user.save.bind(user) - }, function(err, results){ - if(err) return next(err); - - // TODO this is suuuper strange, sometimes results.user is an array, sometimes user directly - var saved = Array.isArray(results.user) ? results.user[0] : results.user; - var task = Array.isArray(results.task) ? results.task[0] : results.task; - - var userStats = saved.toJSON().stats; - var resJsonData = _.extend({ delta: delta, _tmp: user._tmp }, userStats); - res.json(200, resJsonData); - - var webhookData = _generateWebhookTaskData( - task, direction, delta, userStats, user - ); - webhook.sendTaskWebhook(user.preferences.webhooks, webhookData); - - if ( - (!task.challenge.id || task.challenge.broken) // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response and the user doesn't care what happens back there - || (task.type == 'reward') // we don't want to update the reward GP cost - ) return; - - // select name and shortName because they can be synced on syncToUser - Challenge.findById(task.challenge.id, 'name shortName', function(err, chal) { - if (err) return next(err); - if (!chal) { - task.challenge.broken = 'CHALLENGE_DELETED'; - task.save(); - return; - } - - Tasks.Task.findOne({ - '_id': task.challenge.taskId, - userId: {$exists: false} - }, function(err, chalTask){ - if(err) return; //TODO - // this task was removed from the challenge, notify user - if(!chalTask) { - // TODO finish - chal.getTasks(function(err, chalTasks){ - if(err) return; //TODO - chal.syncToUser(user, chalTasks); - }); - } else { - chalTask.value += delta; - if (chalTask.type == 'habit' || chalTask.type == 'daily') - chalTask.history.push({value: chalTask.value, date: +new Date}); - chalTask.save(); - } - }); - }); - }); - }); -}; - -/** - * Get all tasks - */ -api.getTasks = function(req, res, next) { - var user = res.locals.user; - - user.getTasks(req.query.type, function (err, tasks) { - if (err) return next(err); - res.status(200).json(tasks.map(task => task.toJSONV2())); - }); -}; - -/** - * Get Task - */ -api.getTask = function(req, res, next) { - var user = res.locals.user, - id = req.params.id; - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if (err) return next(err); - if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); - res.status(200).json(task.toJSONV2()); - }); -}; - -/* - ------------------------------------------------------------------------ - Items - ------------------------------------------------------------------------ -*/ -// api.buy // handled in Shard.ops - -api.getBuyList = function (req, res, next) { - var list = shared.updateStore(res.locals.user); - return res.status(200).json(list); -}; - -/* - ------------------------------------------------------------------------ - User - ------------------------------------------------------------------------ -*/ - -/** - * Get User - */ -api.getUser = function(req, res, next) { - res.locals.user.getTransformedData(function(err, user){ - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - delete user.apiToken; - if (user.auth && user.auth.local) { - delete user.auth.local.hashed_password; - delete user.auth.local.salt; - } - return res.status(200).json(user); - }); -}; - -/** - * Get anonymized User - */ -api.getUserAnonymized = function(req, res, next) { - res.locals.user.getTransformedData(function(err, user){ - user.stats.toNextLevel = shared.tnl(user.stats.lvl); - user.stats.maxHealth = shared.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - - delete user.apiToken; - - if (user.auth) { - delete user.auth.local; - delete user.auth.facebook; - } - - delete user.newMessages; - - delete user.profile; - delete user.purchased.plan; - delete user.contributor; - delete user.invitations; - - delete user.items.special.nyeReceived; - delete user.items.special.valentineReceived; - - delete user.webhooks; - delete user.achievements.challenges; - - _.forEach(user.inbox.messages, function(msg){ - msg.text = "inbox message text"; - }); - - _.forEach(user.tags, function(tag){ - tag.name = "tag"; - tag.challenge = "challenge"; - }); - - function cleanChecklist(task){ - var checklistIndex = 0; - - _.forEach(task.checklist, function(c){ - c.text = "item" + checklistIndex++; - }); - } - - _.forEach(user.habits, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.rewards, function(task){ - task.text = "task text"; - task.notes = "task notes"; - }); - - _.forEach(user.dailys, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - _.forEach(user.todos, function(task){ - task.text = "task text"; - task.notes = "task notes"; - - cleanChecklist(task); - }); - - return res.status(200).json(user); - }); -}; - -/** - * This tells us for which paths users can call `PUT /user` (or batch-update equiv, which use `User.set()` on our client). - * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) - * TODO - one-by-one we want to widdle down this list, instead replacing each needed set path with API operations - */ -acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (m, v, leaf) => { - let updatablePaths = 'achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '); - let found = _.find(updatablePaths, (rootPath) => { - return leaf.indexOf(rootPath) === 0; - }); - - if (found) m[leaf] = true; - - return m; -}, {}); - -restrictedPUTSubPaths = 'stats.class'.split(' '); - -_.each(restrictedPUTSubPaths, (removePath) => { - delete acceptablePUTPaths[removePath]; -}); - -let requiresPurchase = { - 'preferences.background': 'background', - 'preferences.shirt': 'shirt', - 'preferences.size': 'size', - 'preferences.skin': 'skin', - 'preferences.chair': 'chair', - 'preferences.hair.bangs': 'hair.bangs', - 'preferences.hair.base': 'hair.base', - 'preferences.hair.beard': 'hair.beard', - 'preferences.hair.color': 'hair.color', - 'preferences.hair.flower': 'hair.flower', - 'preferences.hair.mustache': 'hair.mustache', -}; - -let checkPreferencePurchase = (user, path, item) => { - let itemPath = `${path}.${item}`; - let appearance = _.get(shared.content.appearances, itemPath) - if (!appearance) return false; - if (appearance.price === 0) return true; - - return _.get(user.purchased, itemPath); -}; - -/** - * Update user - * Send up PUT /user as `req.body={path1:val, path2:val, etc}`. Example: - * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false} - * See acceptablePUTPaths for which user paths are supported -*/ -api.update = (req, res, next) => { - let user = res.locals.user; - let errors = []; - - if (_.isEmpty(req.body)) return res.status(200).json(user); - - _.each(req.body, (v, k) => { - let purchasable = requiresPurchase[k]; - - if (purchasable && !checkPreferencePurchase(user, purchasable, v)) { - return errors.push(`Must purchase ${v} to set it on ${k}`); - } - - if (acceptablePUTPaths[k]) { - user.fns.dotSet(k, v); - } else { - errors.push(shared.i18n.t('messageUserOperationProtected', { operation: k })); - } - return true; - }); - - user.save((err) => { - if (!_.isEmpty(errors)) return res.status(401).json({err: errors}); - if (err) { - if (err.name == 'ValidationError') { - let errorMessages = _.map(_.values(err.errors), (error) => { - return error.message; - }); - return res.status(400).json({err: errorMessages}); - } - return next(err); - } - - res.status(200).json(user); - user = errors = null; - }); -}; - -api.cron = require('../../middlewares/api-v3/cron'); - -// api.reroll // Shared.ops -// api.reset // Shared.ops - -api.delete = function(req, res, next) { - var user = res.locals.user; - var plan = user.purchased.plan; - - if (plan && plan.customerId && !plan.dateTerminated){ - return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."}); - } - - let types = ['party', 'guilds']; - let groupFields = basicGroupFields.concat(' leader memberCount'); - - Group.getGroups({user, types, groupFields}) - .then(groups => { - return Bluebird.all(groups.map((group) => { - return group.leave(user, 'remove-all'); - })); - }) - .then(() => { - return Tasks.Task.remove({ - userId: user._id, - }).exec(); - }) - .then(() => { - return user.remove(); - }) - .then(() => { - firebase.deleteUser(user._id); - res.sendStatus(200); - }) - .catch(next); -} - -/* - ------------------------------------------------------------------------ - Development Only Operations - ------------------------------------------------------------------------ - */ -if (nconf.get('NODE_ENV') === 'development') { - - api.addTenGems = function(req, res, next) { - var user = res.locals.user; - - user.balance += 2.5; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; - - api.addHourglass = function(req, res, next) { - var user = res.locals.user; - - user.purchased.plan.consecutive.trinkets += 1; - - user.save(function(err){ - if (err) return next(err); - res.sendStatus(204); - }); - }; -} - -/* - ------------------------------------------------------------------------ - Tags - ------------------------------------------------------------------------ - */ - -api.getTags = function (req, res, next) { - res.json(res.locals.user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); -}; - -api.getTag = function (req, res, next) { - let tag = _.find(res.locals.user.tags, {id: req.params.id}); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - res.json({ - name: tag.name, - id: tag.id, - challenge: tag.challenge, - }); -}; - -api.addTag = function (req, res, next) { - let user = res.locals.user; - - user.tags.push(Tag.sanitize(req.body)); - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -}; - -api.updateTag = function (req, res, next) { - let user = res.locals.user; - - let tag = _.find(res.locals.user.tags, {id: req.params.id}); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - tag.name = req.body.name; - user.save(function (err, user) { - if (err) return next(err); - - res.json({ - name: tag.name, - id: tag.id, - challenge: tag.challenge, - }); - }); -} - -api.sortTag = function (req, res, next) { - var ref = req.query; - var to = ref.to; - var from = ref.from; - let user = res.locals.user; - - if (!((to != null) && (from != null))) { - return res.statu(500).json('?to=__&from=__ are required'); - } - - user.tags.splice(to, 0, user.tags.splice(from, 1)[0]); - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -} - -api.deleteTag = function (req, res, next) { - let user = res.locals.user; - - let tag = removeFromArray(user.tags, { id: req.params.id }); - if (!tag) { - return res.status(404).json({err: i18n.t('messageTagNotFound', req.language)}); - } - - - Tasks.Task.update({ - userId: user._id, - }, { - $pull: { - tags: tag.id, - }, - }, {multi: true}).exec(); - - user.save(function (err, user) { - if (err) return next(err); - - res.json(user.tags.toObject().map(tag => { - return { - name: tag.name, - id: tag.id, - challenge: tag.challenge, - } - })); - }); -} - -/* - ------------------------------------------------------------------------ - Spells - ------------------------------------------------------------------------ - */ -api.cast = async function(req, res, next) { - try { - let user = res.locals.user; - let spellId = req.params.spell; - let targetId = req.query.targetId; - - if (spellId === 'heallAll') { - spellId = 'healAll'; - } else if (spellId === 'spookDust') { - spellId = 'spookySparkles'; - } - - let klass = shared.content.spells.special[spellId] ? 'special' : user.stats.class; - let spell = shared.content.spells[klass][spellId]; - - if (!spell) return res.status(404).json({err: 'Spell "' + req.params.spell + '" not found.'}); - if (spell.mana > user.stats.mp) return res.status(400).json({err: 'Not enough mana to cast spell'}); - - let targetType = spell.target; - - if (targetType === 'task') { - let task = await Tasks.Task.findOne({ - _id: targetId, - userId: user._id, - }).exec(); - if (!task) { - return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); - } - - spell.cast(user, task, req); - await task.save(); - } else if (targetType === 'self') { - spell.cast(user, null, req); - await user.save(); - } else if (targetType === 'tasks') { // new target type when all the user's tasks are necessary - let tasks = await Tasks.Task.find({ - userId: user._id, - 'challenge.id': {$exists: false}, // exclude challenge tasks - $or: [ // Exclude completed todos - {type: 'todo', completed: false}, - {type: {$in: ['habit', 'daily', 'reward']}}, - ], - }).exec(); - - spell.cast(user, tasks, req); - - let toSave = tasks.filter(t => t.isModified()); - let isUserModified = user.isModified(); - toSave.unshift(user.save()); - let saved = await Bluebird.all(toSave); - } else if (targetType === 'party' || targetType === 'user') { - let party = await Group.getGroup({groupId: 'party', user}); - // arrays of users when targetType is 'party' otherwise single users - let partyMembers; - - if (targetType === 'party') { - if (!party) { - partyMembers = [user]; // Act as solo party - } else { - partyMembers = await User.find({'party._id': party._id}).select(partyMembersFields).exec(); - } - - spell.cast(user, partyMembers, req); - await Bluebird.all(partyMembers.map(m => m.save())); - } else { - if (!party && (!targetId || user._id === targetId)) { - partyMembers = user; - } else { - partyMembers = await User.findOne({_id: targetId, 'party._id': party._id}).select(partyMembersFields).exec(); - } - - if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); - spell.cast(user, partyMembers, req); - if (partyMembers === user) { - await partyMembers.save(); - } else { - await Bluebird.all([ - await partyMembers.save(), - await user.save(), - ]); - } - } - - if (party && !spell.silent) { - let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; - party.sendChat(message); - await party.save(); - } - } - - user.getTransformedData(function (err, transformedUser) { - if (err) next(err); - res.json(transformedUser); - }); - } catch (e) { - return res.status(500).json({err: 'An error happened'}); - } -} - -// It supports guild too now but we'll stick to partyInvite for backward compatibility -api.sessionPartyInvite = function(req,res,next){ - if (!req.session.partyInvite) return next(); - var inv = res.locals.user.invitations; - if (inv.party && inv.party.id) return next(); // already invited to a party - asyncM.waterfall([ - function(cb){ - Group.findOne({_id:req.session.partyInvite.id, members:{$in:[req.session.partyInvite.inviter]}}) - .select('invites members type').exec(cb); - }, - function(group, cb){ - if (!group){ - // Don't send error as it will prevent users from using the site - delete req.session.partyInvite; - return cb(); - } - - if (group.type == 'guild'){ - inv.guilds.push(req.session.partyInvite); - } else{ - //req.body.type in 'guild', 'party' - inv.party = req.session.partyInvite; - } - inv.party = req.session.partyInvite; - delete req.session.partyInvite; - if (!~group.invites.indexOf(res.locals.user._id)) - group.invites.push(res.locals.user._id); //$addToSt - group.save(cb); - }, - function(saved, cb){ - res.locals.user.save(cb); - } - ], next); -} - -api.clearCompleted = function(req, res, next) { - var user = res.locals.user; - - Tasks.Task.remove({ - userId: user._id, - type: 'todo', - completed: true, - 'challenge.id': {$exists: false}, - }, function (err) { - if (err) return next(err); - - Tasks.Task.find({ - userId: user._id, - type: 'todo', - completed: false, - }, function (err, uncompleted) { - if (err) return next(err); - res.json(uncompleted); - }); - }); -}; - -api.sortTask = async function (req, res, next) { - try { - let user = res.locals.user; - let to = Number(req.query.to); - - let task = await Tasks.Task.findOne({ - _id: req.params.id, - userId: user._id, - }).exec(); - - if (!task) return res.status(404).json(i18n.t('messageTaskNotFound', req.language)); - if (task.type !== 'todo' || !task.completed) { - let order = user.tasksOrder[`${task.type}s`]; - let currentIndex = order.indexOf(task._id); - - // If for some reason the task isn't ordered (should never happen), push it in the new position - // if the task is moved to a non existing position - // or if the task is moved to position -1 (push to bottom) - // -> push task at end of list - if (!order[to] && to !== -1) { - order.push(task._id); - } else { - if (currentIndex !== -1) order.splice(currentIndex, 1); - if (to === -1) { - order.push(task._id); - } else { - order.splice(to, 0, task._id); - } - } - await user.save(); - } - - user.getTasks(function (err, userTasks) { - if(err) return next(err); - res.json(userTasks); - }); - } catch (e) { - res.status(500).json({err: 'An error happened.'}); - } -} - -api.deleteTask = function(req, res, next) { - var user = res.locals.user; - if(!req.params || !req.params.id) return res.json(404, shared.i18n.t('messageTaskNotFound', req.language)); - - var id = req.params.id; - // Try removing from all orders since we don't know the task's type - var removeTaskFromOrder = function(array) { - removeFromArray(array, id); - }; - - ['habits', 'dailys', 'todos', 'rewards'].forEach(function (type){ - removeTaskFromOrder(user.tasksOrder[type]) - }); - - asyncM.parallel({ - user: user.save.bind(user), - task: function(cb) { - Tasks.Task.remove({_id: id, userId: user._id}, cb); - } - }, function(err, results) { - if(err) return next(err); - - if(results.task.result.n < 1){ - return res.status(404).json({err: shared.i18n.t('messageTaskNotFound', req.language)}) - } - - res.status(200).json({}); - }); -}; - -api.updateTask = function(req, res, next) { - var user = res.locals.user, - id = req.params.id; - - req.body = Tasks.Task.fromJSONV2(req.body); - - findTaskByIdOrLegacyId(user, id, function (err, task) { - if(err) return next(err); - if(!task) return res.status(404).json({err: 'Task not found.'}) - - try { - _.assign(task, shared.ops.updateTask(task.toObject(), req)[0]); - task.save(function(err, task){ - if(err) return next(err); - - return res.json(task.toJSONV2()); - }); - } catch (err) { - return res.status(err.code).json({err: err.message}); - } - }); -}; - -api.addTask = function(req, res, next) { - var user = res.locals.user; - req.body.type = req.body.type || 'habit'; - req.body.text = req.body.text || 'text'; - req.body = Tasks.Task.fromJSONV2(req.body); - - var task = new Tasks[req.body.type](Tasks.Task.sanitize(req.body)); - - task.userId = user._id; - user.tasksOrder[task.type + 's'].unshift(task._id); - - // Validate that the task is valid and throw if it isn't - // otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality - let validationErrors = task.validateSync(); - if (validationErrors) return next(validationErrors); - - Bluebird.all([ - user.save(), - task.save({validateBeforeSave: false}) // already done ^ - ]).then(results => { - res.status(200).json(results[1].toJSONV2()); - }).catch(next); -}; - -/** - * All other user.ops which can easily be mapped to common/script/index.js, not requiring custom API-wrapping - */ -_.each(shared.ops, function(op,k){ - var kv3; - - if (['rebirth', 'reroll', 'reset'].indexOf(k) !== -1) { // proxy ops that change tasks directly to v3 - if (k === 'rebirth') kv3 = 'userRebirth'; // the name is different in v3 - if (k === 'reroll') kv3 = 'userReroll'; - if (k === 'reset') kv3 = 'userReset'; - - api[k] = function (req, res, next) { - req.v2 = true; - v3UserController[kv3].handler(req, res, next).catch(next); - } - } else if (!api[k]) { - api[k] = function(req, res, next) { - var opResponse; - try { - req.v2 = true; // Used to indicate to the shared code that the old response data should be returned - opResponse = shared.ops[k](res.locals.user, req, analytics); - if (Array.isArray(opResponse) && opResponse.length < 3) { - opResponse = opResponse[0]; - } - } catch (err) { - if (!err.code) return next(err); - if (err.code >= 400) return res.status(err.code).json({err:err.message}); - } - - // If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"} - res.locals.user.save(function(err){ - if (err) return next(err); - if (opResponse === res.locals.user) { // add tasks - res.locals.user.getTransformedData(function (err, transformedUser) { - if (err) return next(err); - res.status(200).json(transformedUser); - }); - } else { - res.status(200).json(opResponse); - } - }); - } - } -}) - -/* - ------------------------------------------------------------------------ - Batch Update - Run a bunch of updates all at once - ------------------------------------------------------------------------ -*/ -api.batchUpdate = function(req, res, next) { - if (_.isEmpty(req.body)) req.body = []; // cases of {} or null - if (req.body[0] && req.body[0].data) - return res.status(501).json({err: "API has been updated, please refresh your browser or upgrade your mobile app."}) - - var user = res.locals.user; - var oldSend = res.send; - var oldJson = res.json; - - // Stash user.save, we'll queue the save op till the end (so we don't overload the server) - //var oldSave = user.save; - //user.save = function(cb){cb(null,user)} - - // Setup the array of functions we're going to call in parallel with async - res.locals.ops = []; - var ops = _.transform(req.body, function(m,_req){ - if (_.isEmpty(_req)) return; - _req.language = req.language; - - m.push(function() { - var cb = arguments[arguments.length-1]; - res.locals.ops.push(_req); - res.send = res.json = function(code, data) { - if (_.isNumber(code) && code >= 500) - return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data))); - return cb(); - }; - if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); } - - api[_req.op](_req, res, cb); - }); - }) - // Finally, save user at the end - .concat(/*function(){ - user.save = oldSave; - user.save(arguments[arguments.length-1]); - }*/); - - // call all the operations, then return the user object to the requester - asyncM.waterfall(ops, function(err) { - res.json = oldJson; - res.send = oldSend; - if (err) return next(err); - - var response; - - // return only drops & streaks - if (user._tmp && user._tmp.drop){ - response = user.toJSON(); - res.status(200).json({_tmp: {drop: response._tmp.drop}, _v: response._v}); - - // Fetch full user object - } else if (res.locals.wasModified){ - // Preen 3-day past-completed To-Dos from Angular & mobile app - user.getTransformedData(function(err, transformedData){ - if (err) next(err); - response = transformedData; - - response.todos = shared.preenTodos(response.todos); - response.wasModified = true; - res.status(200).json(response); - }); - // return only the version number - } else{ - response = user.toJSON(); - res.status(200).json({_v: response._v}); - } - - //user.fns.nullify(); - user = res.locals.user = oldSend = oldJson = null; - }); -}; - -function _generateWebhookTaskData(task, direction, delta, stats, user) { - var extendedStats = _.extend(stats, { - toNextLevel: shared.tnl(user.stats.lvl), - maxHealth: shared.maxHealth, - maxMP: user._statsComputed.maxMP - }); - - var userData = { - _id: user._id, - _tmp: user._tmp, - stats: extendedStats - }; - - var taskData = { - details: task, - direction: direction, - delta: delta - } - - return { - task: taskData, - user: userData - } -} diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js deleted file mode 100644 index f8eabf4c82..0000000000 --- a/website/server/controllers/api-v3/auth.js +++ /dev/null @@ -1,512 +0,0 @@ -import validator from 'validator'; -import moment from 'moment'; -import passport from 'passport'; -import nconf from 'nconf'; -import { - authWithHeaders, -} from '../../middlewares/api-v3/auth'; -import { - NotAuthorized, - BadRequest, - NotFound, -} from '../../libs/api-v3/errors'; -import Bluebird from 'bluebird'; -import * as passwordUtils from '../../libs/api-v3/password'; -import logger from '../../libs/api-v3/logger'; -import { model as User } from '../../models/user'; -import { model as Group } from '../../models/group'; -import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; -import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; -import { decrypt } from '../../libs/api-v3/encryption'; -import FirebaseTokenGenerator from 'firebase-token-generator'; -import { send as sendEmail } from '../../libs/api-v3/email'; - -let api = {}; - -// When the user signed up after having been invited to a group, invite them automatically to the group -async function _handleGroupInvitation (user, invite) { - // wrapping the code in a try because we don't want it to prevent the user from signing up - // that's why errors are not translated - try { - let {sentAt, id: groupId, inviter} = JSON.parse(decrypt(invite)); - - // check that the invite has not expired (after 7 days) - if (sentAt && moment().subtract(7, 'days').isAfter(sentAt)) { - let err = new Error('Invite expired.'); - err.privateData = invite; - throw err; - } - - let group = await Group.getGroup({user, optionalMembership: true, groupId, fields: 'name type'}); - if (!group) throw new NotFound('Group not found.'); - - if (group.type === 'party') { - user.invitations.party = {id: group._id, name: group.name, inviter}; - } else { - user.invitations.guilds.push({id: group._id, name: group.name, inviter}); - } - } catch (err) { - logger.error(err); - } -} - -/** - * @api {post} /api/v3/user/auth/local/register Register - * @apiDescription Register a new user with email, username and password or attach local auth to a social user - * @apiVersion 3.0.0 - * @apiName UserRegisterLocal - * @apiGroup User - * - * @apiParam {String} username Body parameter - Username of the new user - * @apiParam {String} email Body parameter - Email address of the new user - * @apiParam {String} password Body parameter - Password for the new user - * @apiParam {String} confirmPassword Body parameter - Password confirmation - * - * @apiSuccess {Object} data The user object, if local auth was just attached to a social user then only user.auth.local - */ -api.registerLocal = { - method: 'POST', - middlewares: [authWithHeaders(true)], - url: '/user/auth/local/register', - async handler (req, res) { - let fbUser = res.locals.user; // If adding local auth to social user - - req.checkBody({ - email: { - notEmpty: {errorMessage: res.t('missingEmail')}, - isEmail: {errorMessage: res.t('notAnEmail')}, - }, - username: {notEmpty: {errorMessage: res.t('missingUsername')}}, - password: { - notEmpty: {errorMessage: res.t('missingPassword')}, - equals: {options: [req.body.confirmPassword], errorMessage: res.t('passwordConfirmationMatch')}, - }, - }); - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let { email, username, password } = req.body; - - // Get the lowercase version of username to check that we do not have duplicates - // So we can search for it in the database and then reject the choosen username if 1 or more results are found - email = email.toLowerCase(); - let lowerCaseUsername = username.toLowerCase(); - - // Search for duplicates using lowercase version of username - let user = await User.findOne({$or: [ - {'auth.local.email': email}, - {'auth.local.lowerCaseUsername': lowerCaseUsername}, - ]}, {'auth.local': 1}).exec(); - - if (user) { - if (email === user.auth.local.email) throw new NotAuthorized(res.t('emailTaken')); - // Check that the lowercase username isn't already used - if (lowerCaseUsername === user.auth.local.lowerCaseUsername) throw new NotAuthorized(res.t('usernameTaken')); - } - - let salt = passwordUtils.makeSalt(); - let hashed_password = passwordUtils.encrypt(password, salt); // eslint-disable-line camelcase - let newUser = { - auth: { - local: { - username, - lowerCaseUsername, - email, - salt, - hashed_password, // eslint-disable-line camelcase - }, - }, - preferences: { - language: req.language, - }, - }; - - if (fbUser) { - if (!fbUser.auth.facebook.id) throw new NotAuthorized(res.t('onlySocialAttachLocal')); - fbUser.auth.local = newUser.auth.local; - newUser = fbUser; - } else { - newUser = new User(newUser); - newUser.registeredThrough = req.headers['x-client']; // Not saved, used to create the correct tasks based on the device used - } - - // we check for partyInvite for backward compatibility - if (req.query.groupInvite || req.query.partyInvite) { - await _handleGroupInvitation(newUser, req.query.groupInvite || req.query.partyInvite); - } - - let savedUser = await newUser.save(); - - if (savedUser.auth.facebook.id) { - res.respond(200, savedUser.toJSON().auth.local); // We convert to toJSON to hide private fields - } else { - res.respond(201, savedUser); - } - - // Clean previous email preferences and send welcome email - EmailUnsubscription - .remove({email: savedUser.auth.local.email}) - .then(() => sendTxnEmail(savedUser, 'welcome')); - - if (!savedUser.auth.facebook.id) { - res.analytics.track('register', { - category: 'acquisition', - type: 'local', - gaLabel: 'local', - uuid: savedUser._id, - }); - } - - return null; - }, -}; - -function _loginRes (user, req, res) { - if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id})); - return res.respond(200, {id: user._id, apiToken: user.apiToken}); -} - -/** - * @api {post} /api/v3/user/auth/local/login Login - * @apiDescription Login a user with email / username and password - * @apiVersion 3.0.0 - * @apiName UserLoginLocal - * @apiGroup User - * - * @apiParam {String} username Body parameter - Username or email of the user - * @apiParam {String} password Body parameter - The user's password - * - * @apiSuccess {String} data._id The user's unique identifier - * @apiSuccess {String} data.apiToken The user's api token that must be used to authenticate requests. - */ -api.loginLocal = { - method: 'POST', - url: '/user/auth/local/login', - middlewares: [], - async handler (req, res) { - req.checkBody({ - username: { - notEmpty: true, - errorMessage: res.t('missingUsernameEmail'), - }, - password: { - notEmpty: true, - errorMessage: res.t('missingPassword'), - }, - }); - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - req.sanitizeBody('username').trim(); - req.sanitizeBody('password').trim(); - - let login; - let username = req.body.username; - - if (validator.isEmail(username)) { - login = {'auth.local.email': username.toLowerCase()}; // Emails are stored lowercase - } else { - login = {'auth.local.username': username}; - } - - let user = await User.findOne(login, {auth: 1, apiToken: 1}).exec(); - let isValidPassword = user && user.auth.local.hashed_password === passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (!isValidPassword) throw new NotAuthorized(res.t('invalidLoginCredentialsLong')); - return _loginRes(user, ...arguments); - }, -}; - -function _passportFbProfile (accessToken) { - return new Bluebird((resolve, reject) => { - passport._strategies.facebook.userProfile(accessToken, (err, profile) => { - if (err) { - reject(err); - } else { - resolve(profile); - } - }); - }); -} - -// Called as a callback by Facebook (or other social providers). Internal route -api.loginSocial = { - method: 'POST', - url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2 - async handler (req, res) { - let accessToken = req.body.authResponse.access_token; - let network = req.body.network; - - if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported')); - - let profile = await _passportFbProfile(accessToken); - - let user = await User.findOne({ - [`auth.${network}.id`]: profile.id, - }, {_id: 1, apiToken: 1, auth: 1}).exec(); - - // User already signed up - if (user) { - _loginRes(user, ...arguments); - } else { // Create new user - user = new User({ - auth: { - [network]: profile, - }, - preferences: { - language: req.language, - }, - }); - user.registeredThrough = req.headers['x-client']; - - let savedUser = await user.save(); - - _loginRes(user, ...arguments); - - // Clean previous email preferences - if (savedUser.auth[network].emails && savedUser.auth.facebook.emails[0] && savedUser.auth[network].emails[0].value) { - EmailUnsubscription - .remove({email: savedUser.auth[network].emails[0].value.toLowerCase()}) - .exec() - .then(() => sendTxnEmail(savedUser, 'welcome')); // eslint-disable-line max-nested-callbacks - } - - res.analytics.track('register', { - category: 'acquisition', - type: network, - gaLabel: network, - uuid: savedUser._id, - }); - - return null; - } - }, -}; - -/** - * @api {put} /api/v3/user/auth/update-username Update username - * @apiDescription Update the username of a local user - * @apiVersion 3.0.0 - * @apiName UpdateUsername - * @apiGroup User - * - * @apiParam {string} password Body parameter - The current user password - * @apiParam {string} username Body parameter - The new username - - * @apiSuccess {String} data.username The new username - **/ -api.updateUsername = { - method: 'PUT', - middlewares: [authWithHeaders()], - url: '/user/auth/update-username', - async handler (req, res) { - let user = res.locals.user; - - req.checkBody({ - password: { - notEmpty: {errorMessage: res.t('missingPassword')}, - }, - username: { - notEmpty: { errorMessage: res.t('missingUsername') }, - }, - }); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - if (!user.auth.local.username) throw new BadRequest(res.t('userHasNoLocalRegistration')); - - let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); - - let count = await User.count({ 'auth.local.lowerCaseUsername': req.body.username.toLowerCase() }); - if (count > 0) throw new BadRequest(res.t('usernameTaken')); - - // save username - user.auth.local.lowerCaseUsername = req.body.username.toLowerCase(); - user.auth.local.username = req.body.username; - await user.save(); - - res.respond(200, { username: req.body.username }); - }, -}; - -/** - * @api {put} /api/v3/user/auth/update-password - * @apiDescription Update the password of a local user - * @apiVersion 3.0.0 - * @apiName UpdatePassword - * @apiGroup User - * - * @apiParam {string} password Body parameter - The old password - * @apiParam {string} newPassword Body parameter - The new password - * @apiParam {string} confirmPassword Body parameter - New password confirmation - * - * @apiSuccess {Object} data An empty object - **/ -api.updatePassword = { - method: 'PUT', - middlewares: [authWithHeaders()], - url: '/user/auth/update-password', - async handler (req, res) { - let user = res.locals.user; - - if (!user.auth.local.hashed_password) throw new BadRequest(res.t('userHasNoLocalRegistration')); - - let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); - - req.checkBody({ - password: { - notEmpty: {errorMessage: res.t('missingNewPassword')}, - }, - newPassword: { - notEmpty: {errorMessage: res.t('missingPassword')}, - }, - }); - - if (req.body.newPassword !== req.body.confirmPassword) throw new NotAuthorized(res.t('passwordConfirmationMatch')); - - user.auth.local.hashed_password = passwordUtils.encrypt(req.body.newPassword, user.auth.local.salt); // eslint-disable-line camelcase - await user.save(); - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/user/reset-password Reset password - * @apiDescription Reset the user password - * @apiVersion 3.0.0 - * @apiName ResetPassword - * @apiGroup User - * - * @apiParam {string} email Body parameter - The email address of the user - * - * @apiSuccess {string} message The localized success message - **/ -api.resetPassword = { - method: 'POST', - middlewares: [], - url: '/user/reset-password', - async handler (req, res) { - req.checkBody({ - email: { - notEmpty: {errorMessage: res.t('missingEmail')}, - }, - }); - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let email = req.body.email.toLowerCase(); - let salt = passwordUtils.makeSalt(); - let newPassword = passwordUtils.makeSalt(); // use a salt as the new password too (they'll change it later) - let hashedPassword = passwordUtils.encrypt(newPassword, salt); - - let user = await User.findOne({ 'auth.local.email': email }, { 'auth.local': 1 }); - - if (user) { - user.auth.local.salt = salt; - user.auth.local.hashed_password = hashedPassword; // eslint-disable-line camelcase - sendEmail({ - from: 'Habitica ', - to: email, - subject: res.t('passwordResetEmailSubject'), - text: res.t('passwordResetEmailText', { username: user.auth.local.username, - newPassword, - baseUrl: nconf.get('BASE_URL'), - }), - html: res.t('passwordResetEmailHtml', { username: user.auth.local.username, - newPassword, - baseUrl: nconf.get('BASE_URL'), - }), - }); - await user.save(); - } - res.respond(200, {}, res.t('passwordReset')); - }, -}; - -/** - * @api {put} /api/v3/user/auth/update-email Update email - * @apiDescription Change the user email address - * @apiVersion 3.0.0 - * @apiName UpdateEmail - * @apiGroup User - * - * @apiParam {string} Body parameter - newEmail The new email address. - * @apiParam {string} Body parameter - password The user password. - * - * @apiSuccess {string} data.email The updated email address - */ -api.updateEmail = { - method: 'PUT', - middlewares: [authWithHeaders()], - url: '/user/auth/update-email', - async handler (req, res) { - let user = res.locals.user; - - if (!user.auth.local.email) throw new BadRequest(res.t('userHasNoLocalRegistration')); - - req.checkBody('newEmail', res.t('newEmailRequired')).notEmpty().isEmail(); - req.checkBody('password', res.t('missingPassword')).notEmpty(); - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let candidatePassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (candidatePassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); - - user.auth.local.email = req.body.newEmail; - await user.save(); - - return res.respond(200, { email: user.auth.local.email }); - }, -}; - -const firebaseTokenGenerator = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); - -// Internal route -api.getFirebaseToken = { - method: 'POST', - url: '/user/auth/firebase', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - // Expires 24 hours from now (60*60*24*1000) (in milliseconds) - let expires = new Date(); - expires.setTime(expires.getTime() + 86400000); - - let token = firebaseTokenGenerator.createToken({ - uid: user._id, - isHabiticaUser: true, - }, { expires }); - - res.respond(200, {token, expires}); - }, -}; - -/** - * @api {delete} /api/v3/user/auth/social/:network Delete social authentication method - * @apiDescription Remove a social authentication method (only facebook supported) from a user profile. The user must have local authentication enabled - * @apiVersion 3.0.0 - * @apiName UserDeleteSocial - * @apiGroup User - * - * @apiSuccess {Object} data Empty object - */ -api.deleteSocial = { - method: 'DELETE', - url: '/user/auth/social/:network', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let network = req.params.network; - - if (network !== 'facebook') throw new NotAuthorized(res.t('onlyFbSupported')); - if (!user.auth.local.username) throw new NotAuthorized(res.t('cantDetachFb')); - - await User.update({_id: user._id}, {$unset: {'auth.facebook': 1}}).exec(); - - res.respond(200, {}); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js deleted file mode 100644 index 6d7bc56667..0000000000 --- a/website/server/controllers/api-v3/challenges.js +++ /dev/null @@ -1,518 +0,0 @@ -import { authWithHeaders, authWithSession } from '../../middlewares/api-v3/auth'; -import _ from 'lodash'; -import { model as Challenge } from '../../models/challenge'; -import { - model as Group, - basicFields as basicGroupFields, - TAVERN_ID, -} from '../../models/group'; -import { - model as User, - nameFields, -} from '../../models/user'; -import { - NotFound, - NotAuthorized, -} from '../../libs/api-v3/errors'; -import * as Tasks from '../../models/task'; -import Bluebird from 'bluebird'; -import csvStringify from '../../libs/api-v3/csvStringify'; - -let api = {}; - -/** - * @api {post} /api/v3/challenges Create a new challenge - * @apiVersion 3.0.0 - * @apiName CreateChallenge - * @apiGroup Challenge - * - * @apiSuccess {object} data The newly created challenge - */ -api.createChallenge = { - method: 'POST', - url: '/challenges', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkBody('group', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let groupId = req.body.group; - let prize = req.body.prize; - - let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); - if (!group) throw new NotFound(res.t('groupNotFound')); - if (!group.isMember(user)) throw new NotAuthorized(res.t('mustBeGroupMember')); - - if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { - throw new NotAuthorized(res.t('onlyGroupLeaderChal')); - } - - if (group._id === TAVERN_ID && prize < 1) { - throw new NotAuthorized(res.t('tavChalsMinPrize')); - } - - if (prize > 0) { - let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; - let prizeCost = prize / 4; - - if (prizeCost > user.balance + groupBalance) { - throw new NotAuthorized(res.t('cantAfford')); - } - - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - let remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - } - - group.challengeCount += 1; - - req.body.leader = user._id; - req.body.official = user.contributor.admin && req.body.official ? true : false; - let challenge = new Challenge(Challenge.sanitize(req.body)); - - // First validate challenge so we don't save group if it's invalid (only runs sync validators) - let challengeValidationErrors = challenge.validateSync(); - if (challengeValidationErrors) throw challengeValidationErrors; - - let results = await Bluebird.all([challenge.save({ - validateBeforeSave: false, // already validate - }), group.save()]); - let savedChal = results[0]; - - await savedChal.syncToUser(user); // (it also saves the user) - - let response = savedChal.toJSON(); - response.leader = { // the leader is the authenticated user - _id: user._id, - profile: {name: user.profile.name}, - }; - response.group = { // we already have the group data - _id: group._id, - name: group.name, - type: group.type, - privacy: group.privacy, - }; - - res.respond(201, response); - }, -}; - -/** - * @api {post} /api/v3/challenges/:challengeId/join Joins a challenge - * @apiVersion 3.0.0 - * @apiName JoinChallenge - * @apiGroup Challenge - * @apiParam {UUID} challengeId The challenge _id - * - * @apiSuccess {object} data The challenge the user joined - */ -api.joinChallenge = { - method: 'POST', - url: '/challenges/:challengeId/join', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let challenge = await Challenge.findOne({ _id: req.params.challengeId }); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge')); - - let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); - if (!group || !challenge.hasAccess(user, group)) throw new NotFound(res.t('challengeNotFound')); - - challenge.memberCount += 1; - - // Add all challenge's tasks to user's tasks and save the challenge - let results = await Bluebird.all([challenge.syncToUser(user), challenge.save()]); - - let response = results[1].toJSON(); - response.group = { // we already have the group data - _id: group._id, - name: group.name, - type: group.type, - privacy: group.privacy, - }; - let chalLeader = await User.findById(response.leader).select(nameFields).exec(); - response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; - - res.respond(200, response); - }, -}; - -/** - * @api {post} /api/v3/challenges/:challengeId/leave Leaves a challenge - * @apiVersion 3.0.0 - * @apiName LeaveChallenge - * @apiGroup Challenge - * @apiParam {UUID} challengeId The challenge _id - * - * @apiSuccess {object} data An empty object - */ -api.leaveChallenge = { - method: 'POST', - url: '/challenges/:challengeId/leave', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let keep = req.body.keep === 'remove-all' ? 'remove-all' : 'keep-all'; - - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let challenge = await Challenge.findOne({ _id: req.params.challengeId }); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - - if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound')); - - challenge.memberCount -= 1; - - // Unlink challenge's tasks from user's tasks and save the challenge - await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]); - res.respond(200, {}); - }, -}; - -/** - * @api {get} /api/v3/challenges/user Get challenges for a user - * @apiVersion 3.0.0 - * @apiName GetUserChallenges - * @apiGroup Challenge - * - * @apiSuccess {Array} data An array of challenges - */ -api.getUserChallenges = { - method: 'GET', - url: '/challenges/user', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - let challenges = await Challenge.find({ - $or: [ - {_id: {$in: user.challenges}}, // Challenges where the user is participating - {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member - {leader: user._id}, // Challenges where I'm the leader - ], - _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit - }) - .sort('-official -timestamp') - // see below why we're not using populate - // .populate('group', basicGroupFields) - // .populate('leader', nameFields) - .exec(); - - let resChals = challenges.map(challenge => challenge.toJSON()); - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - await Bluebird.all(resChals.map((chal, index) => { - return Bluebird.all([ - User.findById(chal.leader).select(nameFields).exec(), - Group.findById(chal.group).select(basicGroupFields).exec(), - ]).then(populatedData => { - resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({minimize: true}) : null; - resChals[index].group = populatedData[1] ? populatedData[1].toJSON({minimize: true}) : null; - }); - })); - - res.respond(200, resChals); - }, -}; - -/** - * @api {get} /api/v3/challenges/group/group:Id Get challenges for a group - * @apiDescription Get challenges that the user is a member, public challenges and the ones from the user's groups. - * @apiVersion 3.0.0 - * @apiName GetGroupChallenges - * @apiGroup Challenge - * - * @apiParam {groupId} groupId The group _id - * - * @apiSuccess {Array} data An array of challenges - */ -api.getGroupChallenges = { - method: 'GET', - url: '/challenges/groups/:groupId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - let challenges = await Challenge.find({group: groupId}) - .sort('-official -timestamp') - // .populate('leader', nameFields) // Only populate the leader as the group is implicit - .exec(); - - let resChals = challenges.map(challenge => challenge.toJSON()); - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - await Bluebird.all(resChals.map((chal, index) => { - return User.findById(chal.leader).select(nameFields).exec().then(populatedLeader => { - resChals[index].leader = populatedLeader ? populatedLeader.toJSON({minimize: true}) : null; - }); - })); - - res.respond(200, resChals); - }, -}; - -/** - * @api {get} /api/v3/challenges/:challengeId Get a challenge given its id - * @apiVersion 3.0.0 - * @apiName GetChallenge - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The challenge _id - * - * @apiSuccess {object} data The challenge object - */ -api.getChallenge = { - method: 'GET', - url: '/challenges/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - - let challenge = await Challenge.findById(challengeId) - // Don't populate the group as we'll fetch it manually later - // .populate('leader', nameFields) - .exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - - // Fetching basic group data - let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - - let chalRes = challenge.toJSON(); - chalRes.group = group.toJSON({minimize: true}); - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - let chalLeader = await User.findById(chalRes.leader).select(nameFields).exec(); - chalRes.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; - - res.respond(200, chalRes); - }, -}; - -/** - * @api {get} /api/v3/challenges/:challengeId/export/csv Export a challenge in CSV - * @apiVersion 3.0.0 - * @apiName ExportChallengeCsv - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The challenge _id - * - * @apiSuccess {string} challenge A csv file - */ -api.exportChallengeCsv = { - method: 'GET', - url: '/challenges/:challengeId/export/csv', - middlewares: [authWithSession], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - - let challenge = await Challenge.findById(challengeId).select('_id group leader tasksOrder').exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - - // In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all - // results on the server so the perf difference isn't that big (hopefully) - - let [members, tasks] = await Bluebird.all([ - User.find({challenges: challengeId}) - .select(nameFields) - .sort({_id: 1}) - .lean() // so we don't involve mongoose - .exec(), - - Tasks.Task.find({'challenge.id': challengeId, userId: {$exists: true}}) - .sort({userId: 1, text: 1}).select('userId type text value notes').lean().exec(), - ]); - - let resArray = members.map(member => [member._id, member.profile.name]); - - // We assume every user in the challenge as at least some data so we can say that members[0] tasks will be at tasks [0] - let lastUserId; - let index = -1; - tasks.forEach(task => { - if (task.userId !== lastUserId) { - lastUserId = task.userId; - index++; - } - - resArray[index].push(`${task.type}:${task.text}`, task.value, task.notes); - }); - - // The first row is going to be UUID name Task Value Notes repeated n times for the n challenge tasks - let challengeTasks = _.reduce(challenge.tasksOrder.toObject(), (result, array) => { - return result.concat(array); - }, []).sort(); - resArray.unshift(['UUID', 'name']); - _.times(challengeTasks.length, () => resArray[0].push('Task', 'Value', 'Notes')); - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': `attachment; filename=${challengeId}.csv`, - }); - - let csvRes = await csvStringify(resArray); - res.status(200).send(csvRes); - }, -}; - -/** - * @api {put} /api/v3/challenges/:challengeId Update a challenge - * @apiVersion 3.0.0 - * @apiName UpdateChallenge - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The challenge _id - * - * @apiSuccess {object} data The updated challenge - */ -api.updateChallenge = { - method: 'PUT', - url: '/challenges/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - - let challenge = await Challenge.findById(challengeId).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - - let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderUpdateChal')); - - _.merge(challenge, Challenge.sanitizeUpdate(req.body)); - - let savedChal = await challenge.save(); - let response = savedChal.toJSON(); - response.group = { // we already have the group data - _id: group._id, - name: group.name, - type: group.type, - privacy: group.privacy, - }; - let chalLeader = await User.findById(response.leader).select(nameFields).exec(); - response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null; - res.respond(200, response); - }, -}; - -/** - * @api {delete} /api/v3/challenges/:challengeId Delete a challenge - * @apiVersion 3.0.0 - * @apiName DeleteChallenge - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The _id for the challenge to delete - * - * @apiSuccess {object} data An empty object - */ -api.deleteChallenge = { - method: 'DELETE', - url: '/challenges/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_DELETED'}); - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/challenges/:challengeId/selectWinner/:winnerId Select winner for challenge - * @apiVersion 3.0.0 - * @apiName SelectChallengeWinner - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The _id for the challenge to close with a winner - * @apiParam {UUID} winnerId The _id of the winning user - * - * @apiSuccess {object} data An empty object - */ -api.selectChallengeWinner = { - method: 'POST', - url: '/challenges/:challengeId/selectWinner/:winnerId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - req.checkParams('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); - - let winner = await User.findOne({_id: req.params.winnerId}).exec(); - if (!winner || winner.challenges.indexOf(challenge._id) === -1) throw new NotFound(res.t('winnerNotFound', {userId: req.params.winnerId})); - - // Close channel in background, some ops are run in the background without `await`ing - await challenge.closeChal({broken: 'CHALLENGE_CLOSED', winner}); - res.respond(200, {}); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js deleted file mode 100644 index 7738189b6f..0000000000 --- a/website/server/controllers/api-v3/chat.js +++ /dev/null @@ -1,398 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { - model as Group, - TAVERN_ID, -} from '../../models/group'; -import { model as User } from '../../models/user'; -import { - NotFound, - NotAuthorized, -} from '../../libs/api-v3/errors'; -import _ from 'lodash'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; -import { sendTxn } from '../../libs/api-v3/email'; -import nconf from 'nconf'; -import Bluebird from 'bluebird'; - -const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => { - return { email, canSend: true }; -}); - -let api = {}; - -/** - * @api {get} /api/v3/groups/:groupId/chat Get chat messages from a group - * @apiVersion 3.0.0 - * @apiName GetChat - * @apiGroup Chat - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Array} data An array of chat messages - */ -api.getChat = { - method: 'GET', - url: '/groups/:groupId/chat', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'chat'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - res.respond(200, Group.toJSONCleanChat(group, user).chat); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/chat Post chat message to a group - * @apiVersion 3.0.0 - * @apiName PostCat - * @apiGroup Chat - * - * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {message} Body parameter - message The message to post - * @apiParam {previousMsg} previousMsg Query parameter - The previous chat message which will force a return of the full group chat - * - * @apiSuccess data An array of chat messages if a new message was posted after previousMsg, otherwise the posted message - */ -api.postChat = { - method: 'POST', - url: '/groups/:groupId/chat', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - let chatUpdated; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkBody('message', res.t('messageGroupChatBlankMessage')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId}); - - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party' && user.flags.chatRevoked) { - throw new NotFound('Your chat privileges have been revoked.'); - } - - let lastClientMsg = req.query.previousMsg; - chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; - - group.sendChat(req.body.message, user); - - let toSave = [group.save()]; - - if (group.type === 'party') { - user.party.lastMessageSeen = group.chat[0].id; - toSave.push(user.save()); - } - - let [savedGroup] = await Bluebird.all(toSave); - if (chatUpdated) { - res.respond(200, {chat: Group.toJSONCleanChat(savedGroup, user).chat}); - } else { - res.respond(200, {message: savedGroup.chat[0]}); - } - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/chat/:chatId/like Like a group chat message - * @apiVersion 3.0.0 - * @apiName LikeChat - * @apiGroup Chat - * - * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {chatId} chatId The chat message _id - * - * @apiSuccess {Object} data The liked chat message - */ -api.likeChat = { - method: 'POST', - url: '/groups/:groupId/chat/:chatId/like', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - let message = _.find(group.chat, {id: req.params.chatId}); - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatLikeOwnMessage')); - - let update = {$set: {}}; - - if (!message.likes) message.likes = {}; - - message.likes[user._id] = !message.likes[user._id]; - update.$set[`chat.$.likes.${user._id}`] = message.likes[user._id]; - - await Group.update( - {_id: group._id, 'chat.id': message.id}, - update - ); - res.respond(200, message); // TODO what if the message is flagged and shouldn't be returned? - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/chat/:chatId/like Like a group chat message - * @apiVersion 3.0.0 - * @apiName LikeChat - * @apiGroup Chat - * - * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {chatId} chatId The chat message id - * - * @apiSuccess {object} data The flagged chat message - */ -api.flagChat = { - method: 'POST', - url: '/groups/:groupId/chat/:chatId/flag', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId}); - if (!group) throw new NotFound(res.t('groupNotFound')); - let message = _.find(group.chat, {id: req.params.chatId}); - - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - - if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatFlagOwnMessage')); - - let author = await User.findOne({_id: message.uuid}, {auth: 1}); - - let update = {$set: {}}; - - // Log user ids that have flagged the message - if (!message.flags) message.flags = {}; - if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); - message.flags[user._id] = true; - update.$set[`chat.$.flags.${user._id}`] = true; - - // Log total number of flags (publicly viewable) - if (!message.flagCount) message.flagCount = 0; - if (user.contributor.admin) { - // Arbitraty amount, higher than 2 - message.flagCount = 5; - } else { - message.flagCount++; - } - update.$set['chat.$.flagCount'] = message.flagCount; - - await Group.update( - {_id: group._id, 'chat.id': message.id}, - update - ); - - let reporterEmailContent; - if (user.auth.local) { - reporterEmailContent = user.auth.local.email; - } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) { - reporterEmailContent = user.auth.facebook.emails[0].value; - } - - let authorEmailContent; - if (author.auth.local) { - authorEmailContent = author.auth.local.email; - } else if (author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) { - authorEmailContent = author.auth.facebook.emails[0].value; - } - - let groupUrl; - if (group._id === TAVERN_ID) { - groupUrl = '/#/options/groups/tavern'; - } else if (group.type === 'guild') { - groupUrl = `/#/options/groups/guilds/${group._id}`; - } else { - groupUrl = 'party'; - } - - sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', [ - {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, - {name: 'MESSAGE_TEXT', content: message.text}, - - {name: 'REPORTER_USERNAME', content: user.profile.name}, - {name: 'REPORTER_UUID', content: user._id}, - {name: 'REPORTER_EMAIL', content: reporterEmailContent}, - {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId=${user._id}`}, - - {name: 'AUTHOR_USERNAME', content: message.user}, - {name: 'AUTHOR_UUID', content: message.uuid}, - {name: 'AUTHOR_EMAIL', content: authorEmailContent}, - {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId=${message.uuid}`}, - - {name: 'GROUP_NAME', content: group.name}, - {name: 'GROUP_TYPE', content: group.type}, - {name: 'GROUP_ID', content: group._id}, - {name: 'GROUP_URL', content: groupUrl}, - ]); - - res.respond(200, message); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/chat/:chatId/clear-flags Clear a group chat message's flags - * @apiDescription Admin-only - * @apiVersion 3.0.0 - * @apiName ClearFlags - * @apiGroup Chat - * - * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {chatId} chatId The chat message id - * - * @apiSuccess {Object} data An empty object - */ -api.clearChatFlags = { - method: 'Post', - url: '/groups/:groupId/chat/:chatId/clearflags', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - let chatId = req.params.chatId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - if (!user.contributor.admin) { - throw new NotAuthorized(res.t('messageGroupChatAdminClearFlagCount')); - } - - let group = await Group.getGroup({user, groupId}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - let message = _.find(group.chat, {id: chatId}); - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - - message.flagCount = 0; - - await Group.update( - {_id: group._id, 'chat.id': message.id}, - {$set: {'chat.$.flagCount': message.flagCount}} - ); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/chat/:chatId/seen Seen a group chat message - * @apiVersion 3.0.0 - * @apiName SeenChat - * @apiGroup Chat - * - * @apiParam {groupId} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Object} data An empty object - */ -api.seenChat = { - method: 'POST', - url: '/groups/:groupId/chat/seen', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - // Do not validate group existence, it doesn't really matter and make it works if the group gets deleted - // let group = await Group.getGroup({user, groupId}); - // if (!group) throw new NotFound(res.t('groupNotFound')); - - let update = {$unset: {}}; - update.$unset[`newMessages.${groupId}`] = true; - - await User.update({_id: user._id}, update).exec(); - res.respond(200, {}); - }, -}; - -/** - * @api {delete} /api/v3/groups/:groupId/chat/:chatId Delete chat message from a group - * @apiVersion 3.0.0 - * @apiName DeleteChat - * @apiGroup Chat - * - * @apiParam {string} previousMsg Query parameter - The last message fetched by the client so that the whole chat will be returned only if new messages have been posted in the meantime - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {string} chatId The chat message id - * - * @apiSuccess data The updated chat array or an empty object if no message was posted after previousMsg - * @apiSuccess {Object} data An empty object when the previous message was deleted - */ -api.deleteChat = { - method: 'DELETE', - url: '/groups/:groupId/chat/:chatId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - let chatId = req.params.chatId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId, fields: 'chat'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - let message = _.find(group.chat, {id: chatId}); - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - - if (user._id !== message.uuid && !user.contributor.admin) { - throw new NotAuthorized(res.t('onlyCreatorOrAdminCanDeleteChat')); - } - - let lastClientMsg = req.query.previousMsg; - let chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; - - await Group.update( - {_id: group._id}, - {$pull: {chat: {id: chatId}}} - ); - - if (chatUpdated) { - let chatRes = Group.toJSONCleanChat(group, user).chat; - removeFromArray(chatRes, {id: chatId}); - res.respond(200, chatRes); - } else { - res.respond(200, {}); - } - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js deleted file mode 100644 index 0235f61645..0000000000 --- a/website/server/controllers/api-v3/content.js +++ /dev/null @@ -1,110 +0,0 @@ -import common from '../../../../common'; -import _ from 'lodash'; -import { langCodes } from '../../libs/api-v3/i18n'; -import Bluebird from 'bluebird'; -import fsCallback from 'fs'; -import path from 'path'; -import logger from '../../libs/api-v3/logger'; - -// Transform fs methods that accept callbacks in ones that return promises -const fs = { - readFile: Bluebird.promisify(fsCallback.readFile, {context: fsCallback}), - writeFile: Bluebird.promisify(fsCallback.writeFile, {context: fsCallback}), - stat: Bluebird.promisify(fsCallback.stat, {context: fsCallback}), - mkdir: Bluebird.promisify(fsCallback.mkdir, {context: fsCallback}), -}; - -let api = {}; - -function walkContent (obj, lang) { - _.each(obj, (item, key, source) => { - if (_.isPlainObject(item) || _.isArray(item)) return walkContent(item, lang); - if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); - }); -} - -// After the getContent route is called the first time for a certain language -// the response is saved on disk and subsequentially served directly from there to reduce computation. -// Example: if `cachedContentResponses.en` is true it means that the response is cached -let cachedContentResponses = {}; - -// Language key set to true while the cache file is being written -let cacheBeingWritten = {}; - -_.each(langCodes, code => { - cachedContentResponses[code] = false; - cacheBeingWritten[code] = false; -}); - - -const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../build/content_cache/'); - -async function saveContentToDisk (language, content) { - try { - cacheBeingWritten[language] = true; - - await fs.stat(CONTENT_CACHE_PATH); // check if the directory exists, if it doesn't an error is thrown - await fs.writeFile(`${CONTENT_CACHE_PATH}${language}.json`, content, 'utf8'); - - cacheBeingWritten[language] = false; - cachedContentResponses[language] = true; - } catch (err) { - if (err.code === 'ENOENT' && err.syscall === 'stat') { // the directory doesn't exists, create it and retry - await fs.mkdir(CONTENT_CACHE_PATH); - return saveContentToDisk(language, content); - } else { - cacheBeingWritten[language] = false; - logger.error(err); - return; - } - } -} - -/** - * @api {get} /api/v3/content Get all available content objects - * @apiDescription Does not require authentication. - * @apiVersion 3.0.0 - * @apiName ContentGet - * @apiGroup Content - * - * @apiParam {string} language Query parameter, the language code used for the items' strings. Defaulting to english - * - * @apiSuccess {Object} data All the content available on Habitica - */ -api.getContent = { - method: 'GET', - url: '/content', - async handler (req, res) { - let language = 'en'; - let proposedLang = req.query.language && req.query.language.toString(); - - if (proposedLang in cachedContentResponses) { - language = proposedLang; - } - - let content; - - // is the content response for this language cached? - if (cachedContentResponses[language] === true) { - content = await fs.readFile(`${CONTENT_CACHE_PATH}${language}.json`, 'utf8'); - } else { // generate the response - content = _.cloneDeep(common.content); - walkContent(content, language); - content = JSON.stringify(content); - } - - res.set({ - 'Content-Type': 'application/json', - }); - - let jsonResString = `{"success": true, "data": ${content}}`; - res.status(200).send(jsonResString); - - // save the file in background unless it's already cached or being written right now - if (cachedContentResponses[language] !== true && cacheBeingWritten[language] !== true) { - saveContentToDisk(language, content); - } - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/coupon.js b/website/server/controllers/api-v3/coupon.js deleted file mode 100644 index 4dc434aa8c..0000000000 --- a/website/server/controllers/api-v3/coupon.js +++ /dev/null @@ -1,126 +0,0 @@ -import csvStringify from '../../libs/api-v3/csvStringify'; -import { - authWithHeaders, - authWithSession, -} from '../../middlewares/api-v3/auth'; -import { ensureSudo } from '../../middlewares/api-v3/ensureAccessRight'; -import { model as Coupon } from '../../models/coupon'; -import _ from 'lodash'; -import couponCode from 'coupon-code'; - -let api = {}; - -/** - * @api {get} /api/v3/coupons Get coupons - * @apiDescription Sudo users only - * @apiVersion 3.0.0 - * @apiName GetCoupons - * @apiGroup Coupon - * - * @apiSuccess {string} Coupons in CSV format - */ -api.getCoupons = { - method: 'GET', - url: '/coupons', - middlewares: [authWithSession, ensureSudo], - async handler (req, res) { - let coupons = await Coupon.find().sort('createdAt').lean().exec(); - - let output = [['code', 'event', 'date', 'user']].concat(_.map(coupons, coupon => { - return [coupon._id, coupon.event, coupon.createdAt, coupon.user]; - })); - let csv = await csvStringify(output); - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-coupons.csv', - }); - res.status(200).send(csv); - }, -}; - -/** - * @api {post} /api/v3/coupons/generate/:event Generate coupons for an event - * @apiDescription Sudo users only - * @apiVersion 3.0.0 - * @apiName GenerateCoupons - * @apiGroup Coupon - * - * @apiParam {string} event The event for which the coupon should be generated - * @apiParam {number} count Query parameter to specify the number of coupon codes to generate - * - * @apiSuccess {array} data Generated coupons - */ -api.generateCoupons = { - method: 'POST', - url: '/coupons/generate/:event', - middlewares: [authWithHeaders(), ensureSudo], - async handler (req, res) { - req.checkParams('event', res.t('eventRequired')).notEmpty(); - req.checkQuery('count', res.t('countRequired')).notEmpty().isNumeric(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let coupons = await Coupon.generate(req.params.event, req.query.count); - res.respond(200, coupons); - }, -}; - -/** - * @api {post} /api/v3/user/coupon/:code Enter coupon code - * @apiVersion 3.0.0 - * @apiName EnterCouponCode - * @apiGroup Coupon - * - * @apiParam {string} code The coupon code to apply - * - * @apiSuccess {object} data User object - */ -api.enterCouponCode = { - method: 'POST', - url: '/coupons/enter/:code', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('code', res.t('couponCodeRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - await Coupon.apply(user, req, req.params.code); - res.respond(200, user); - }, -}; - -/** - * @api {post} /api/v3/coupons/validate/:code Validate a coupon code - * @apiVersion 3.0.0 - * @apiName ValidateCoupon - * @apiGroup Coupon - * - * @apiSuccess {boolean} data.valid True or false - */ -api.validateCoupon = { - method: 'POST', - url: '/coupons/validate/:code', - middlewares: [authWithHeaders(true)], - async handler (req, res) { - req.checkParams('code', res.t('couponCodeRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let valid = false; - let code = couponCode.validate(req.params.code); - if (code) { - let coupon = await Coupon.findOne({_id: code}).exec(); - valid = coupon ? true : false; - } - - res.respond(200, {valid}); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js deleted file mode 100644 index 9949421513..0000000000 --- a/website/server/controllers/api-v3/debug.js +++ /dev/null @@ -1,190 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import ensureDevelpmentMode from '../../middlewares/api-v3/ensureDevelpmentMode'; -import { BadRequest } from '../../libs/api-v3/errors'; -import { content } from '../../../../common'; -import _ from 'lodash'; - -let api = {}; - -/** - * @api {post} /api/v3/debug/add-ten-gems Add ten gems to the current user - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName AddTenGems - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -api.addTenGems = { - method: 'POST', - url: '/debug/add-ten-gems', - middlewares: [ensureDevelpmentMode, authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - user.balance += 2.5; - - await user.save(); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/debug/add-hourglass Add Hourglass to the current user - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName AddHourglass - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -api.addHourglass = { - method: 'POST', - url: '/debug/add-hourglass', - middlewares: [ensureDevelpmentMode, authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - user.purchased.plan.consecutive.trinkets += 1; - - await user.save(); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/debug/set-cron Sets lastCron for user - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName setCron - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -api.setCron = { - method: 'POST', - url: '/debug/set-cron', - middlewares: [ensureDevelpmentMode, authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let cron = req.body.lastCron; - - user.lastCron = cron; - - await user.save(); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/debug/make-admin Sets contributor.admin to true - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName setCron - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -// TODO: Re-enable after v3 prod testing is done -// api.makeAdmin = { -// method: 'POST', -// url: '/debug/make-admin', -// middlewares: [ensureDevelpmentMode, authWithHeaders()], -// async handler (req, res) { -// let user = res.locals.user; -// -// user.contributor.admin = true; -// -// await user.save(); -// -// res.respond(200, {}); -// }, -// }; - -/** - * @api {post} /api/v3/debug/modify-inventory Manipulate user's inventory - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName modifyInventory - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -api.modifyInventory = { - method: 'POST', - url: '/debug/modify-inventory', - middlewares: [ensureDevelpmentMode, authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let { gear } = req.body; - - if (gear) { - user.items.gear.owned = gear; - } - - [ - 'special', - 'pets', - 'mounts', - 'eggs', - 'hatchingPotions', - 'food', - 'quests', - ].forEach((type) => { - if (req.body[type]) { - user.items[type] = req.body[type]; - } - }); - - await user.save(); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/debug/quest-progress Artificially accelerate quest progress - * @apiDescription Only available in development mode. - * @apiVersion 3.0.0 - * @apiName questProgress - * @apiGroup Development - * - * @apiSuccess {Object} data An empty Object - */ -api.questProgress = { - method: 'POST', - url: '/debug/quest-progress', - middlewares: [ensureDevelpmentMode, authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let key = _.get(user, 'party.quest.key'); - let quest = content.quests[key]; - - if (!quest) { - throw new BadRequest('User is not on a valid quest.'); - } - - if (quest.boss) { - user.party.quest.progress.up += 1000; - } - - if (quest.collect) { - let collect = user.party.quest.progress.collect; - _.each(quest.collect, (details, item) => { - collect[item] = collect[item] || 0; - collect[item] += 300; - }); - } - - user.markModified('party.quest.progress'); - - await user.save(); - - res.respond(200, {}); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js deleted file mode 100644 index 07e9b5be54..0000000000 --- a/website/server/controllers/api-v3/groups.js +++ /dev/null @@ -1,670 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import { - INVITES_LIMIT, - model as Group, - basicFields as basicGroupFields, -} from '../../models/group'; -import { - model as User, - nameFields, -} from '../../models/user'; -import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; -import { - NotFound, - BadRequest, - NotAuthorized, -} from '../../libs/api-v3/errors'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; -import * as firebase from '../../libs/api-v3/firebase'; -import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email'; -import { encrypt } from '../../libs/api-v3/encryption'; -import common from '../../../../common'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; -let api = {}; - -/** - * @api {post} /api/v3/groups Create group - * @apiVersion 3.0.0 - * @apiName CreateGroup - * @apiGroup Group - * - * @apiSuccess {Object} data The create group - */ -api.createGroup = { - method: 'POST', - url: '/groups', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let group = new Group(Group.sanitize(req.body)); - group.leader = user._id; - - if (group.type === 'guild') { - if (user.balance < 1) throw new NotAuthorized(res.t('messageInsufficientGems')); - - group.balance = 1; - - user.balance--; - user.guilds.push(group._id); - } else { - if (group.privacy !== 'private') throw new NotAuthorized(res.t('partyMustbePrivate')); - if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty')); - - user.party._id = group._id; - } - - let results = await Bluebird.all([user.save(), group.save()]); - let savedGroup = results[1]; - - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - // await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise - let response = savedGroup.toJSON(); - // the leader is the authenticated user - response.leader = { - _id: user._id, - profile: {name: user.profile.name}, - }; - res.respond(201, response); // do not remove chat flags data as we've just created the group - - firebase.updateGroupData(savedGroup); - firebase.addUserToGroup(savedGroup._id, user._id); - }, -}; - -/** - * @api {get} /api/v3/groups Get groups for a user - * @apiVersion 3.0.0 - * @apiName GetGroups - * @apiGroup Group - * - * @apiParam {string} type The type of groups to retrieve. Must be a query string representing a list of values like 'tavern,party'. Possible values are party, guilds, privateGuilds, publicGuilds, tavern - * - * @apiSuccess {Array} data An array of the requested groups - */ -api.getGroups = { - method: 'GET', - url: '/groups', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkQuery('type', res.t('groupTypesRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let types = req.query.type.split(','); - let groupFields = basicGroupFields.concat(' description memberCount balance'); - let sort = '-memberCount'; - - let results = await Group.getGroups({user, types, groupFields, sort}); - res.respond(200, results); - }, -}; - -/** - * @api {get} /api/v3/groups/:groupId Get group - * @apiVersion 3.0.0 - * @apiName GetGroup - * @apiGroup Group - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Object} data The group object - */ -api.getGroup = { - method: 'GET', - url: '/groups/:groupId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, populateLeader: false}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - group = Group.toJSONCleanChat(group, user); - // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 - let leader = await User.findById(group.leader).select(nameFields).exec(); - if (leader) group.leader = leader.toJSON({minimize: true}); - - res.respond(200, group); - }, -}; - -/** - * @api {put} /api/v3/groups/:groupId Update group - * @apiVersion 3.0.0 - * @apiName UpdateGroup - * @apiGroup Group - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Object} data The updated group - */ -api.updateGroup = { - method: 'PUT', - url: '/groups/:groupId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - if (group.leader !== user._id) throw new NotAuthorized(res.t('messageGroupOnlyLeaderCanUpdate')); - - _.assign(group, _.merge(group.toObject(), Group.sanitizeUpdate(req.body))); - - let savedGroup = await group.save(); - let response = Group.toJSONCleanChat(savedGroup, user); - // If the leader changed fetch new data, otherwise use authenticated user - if (response.leader !== user._id) { - response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true}); - } else { - response.leader = { - _id: user._id, - profile: {name: user.profile.name}, - }; - } - res.respond(200, response); - - firebase.updateGroupData(savedGroup); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/join Join a group - * @apiVersion 3.0.0 - * @apiName JoinGroup - * @apiGroup Group - * - * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Object} data The joined group - */ -api.joinGroup = { - method: 'POST', - url: '/groups/:groupId/join', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let inviter; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party' - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - // Works even if the user is not yet a member of the group - let group = await Group.getGroup({user, groupId: req.params.groupId, optionalMembership: true}); // Do not fetch chat and work even if the user is not yet a member of the group - if (!group) throw new NotFound(res.t('groupNotFound')); - - let isUserInvited = false; - - if (group.type === 'party' && group._id === user.invitations.party.id) { - inviter = user.invitations.party.inviter; - user.invitations.party = {}; // Clear invite - user.markModified('invitations.party'); - - // invite new user to pending quest - if (group.quest.key && !group.quest.active) { - user.party.quest.RSVPNeeded = true; - user.party.quest.key = group.quest.key; - group.quest.members[user._id] = null; - group.markModified('quest.members'); - } - - // If user was in a different party (when partying solo you can be invited to a new party) - // make him leave that party before doing anything - if (user.party._id) { - let userPreviousParty = await Group.getGroup({user, groupId: user.party._id}); - if (userPreviousParty) await userPreviousParty.leave(user); - } - - user.party._id = group._id; // Set group as user's party - - isUserInvited = true; - } else if (group.type === 'guild') { - let hasInvitation = removeFromArray(user.invitations.guilds, { id: group._id }); - - if (hasInvitation) { - isUserInvited = true; - } else { - isUserInvited = group.privacy === 'private' ? false : true; - } - } - - if (isUserInvited && group.type === 'guild') { - if (user.guilds.indexOf(group._id) !== -1) { // if user is already a member (party is checked previously) - throw new NotAuthorized(res.t('userAlreadyInGroup')); - } - user.guilds.push(group._id); // Add group to user's guilds - } - if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); - - if (group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader - - group.memberCount += 1; - - let promises = [group.save(), user.save()]; - - if (group.type === 'party' && inviter) { - promises.push(User.update({_id: inviter}, {$inc: {'items.quests.basilist': 1}}).exec()); // Reward inviter - if (group.memberCount > 1) { - promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyUp': {$ne: true}}, {$set: {'achievements.partyUp': true}}, {multi: true}).exec()); - } - if (group.memberCount > 3) { - promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyOn': {$ne: true}}, {$set: {'achievements.partyOn': true}}, {multi: true}).exec()); - } - } - - promises = await Bluebird.all(promises); - - let response = Group.toJSONCleanChat(promises[0], user); - let leader = await User.findById(response.leader).select(nameFields).exec(); - if (leader) { - response.leader = leader.toJSON({minimize: true}); - } - res.respond(200, response); - - firebase.addUserToGroup(group._id, user._id); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/reject Reject a group invitation - * @apiVersion 3.0.0 - * @apiName RejectGroupInvite - * @apiGroup Group - * - * @apiParam {UUID} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiSuccess {Object} data An empty object - */ -api.rejectGroupInvite = { - method: 'POST', - url: '/groups/:groupId/reject-invite', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); // .isUUID(); can't be used because it would block 'habitrpg' or 'party' - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let groupId = req.params.groupId; - let isUserInvited = false; - - if (groupId === user.invitations.party.id) { - user.invitations.party = {}; - user.markModified('invitations.party'); - isUserInvited = true; - } else { - let hasInvitation = removeFromArray(user.invitations.guilds, { id: groupId }); - - if (hasInvitation) { - isUserInvited = true; - } - } - - if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); - - await user.save(); - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/leave Leave a group - * @apiVersion 3.0.0 - * @apiName LeaveGroup - * @apiGroup Group - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {string="remove-all","keep-all"} keep Query parameter - Whether to keep or not challenges' tasks. Defaults to keep-all - * - * @apiSuccess {Object} data An empty object - */ -api.leaveGroup = { - method: 'POST', - url: '/groups/:groupId/leave', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - // When removing the user from challenges, should we keep the tasks? - req.checkQuery('keep', res.t('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat', requireMembership: true}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - // During quests, checke wheter user can leave - if (group.type === 'party') { - if (group.quest && group.quest.leader === user._id) { - throw new NotAuthorized(res.t('questLeaderCannotLeaveGroup')); - } - - if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { - throw new NotAuthorized(res.t('cannotLeaveWhileActiveQuest')); - } - } - - await group.leave(user, req.query.keep); - res.respond(200, {}); - }, -}; - -// Send an email to the removed user with an optional message from the leader -function _sendMessageToRemoved (group, removedUser, message) { - if (removedUser.preferences.emailNotifications.kickedGroup !== false) { - sendTxnEmail(removedUser, `kicked-from-${group.type}`, [ - {name: 'GROUP_NAME', content: group.name}, - {name: 'MESSAGE', content: message}, - {name: 'GUILDS_LINK', content: '/#/options/groups/guilds/public'}, - {name: 'PARTY_WANTED_GUILD', content: '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'}, - ]); - } -} - -/** - * @api {post} /api/v3/groups/:groupId/removeMember/:memberId Remove a member from a group - * @apiVersion 3.0.0 - * @apiName RemoveGroupMember - * @apiGroup Group - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * @apiParam {UUID} memberId The _id of the member to remove - * @apiParam {string} message Query parameter - The message to send to the removed members - * - * @apiSuccess {Object} data An empty object - */ -api.removeGroupMember = { - method: 'POST', - url: '/groups/:groupId/removeMember/:memberId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('memberId', res.t('userIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat'}); // Do not fetch chat - if (!group) throw new NotFound(res.t('groupNotFound')); - - let uuid = req.params.memberId; - - if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCanRemoveMember')); - if (user._id === uuid) throw new NotAuthorized(res.t('memberCannotRemoveYourself')); - - let member = await User.findOne({_id: uuid}).exec(); - - // We're removing the user from a guild or a party? is the user invited only? - let isInGroup; - if (member.party._id === group._id) { - isInGroup = 'party'; - } else if (member.guilds.indexOf(group._id) !== -1) { - isInGroup = 'guild'; - } - - let isInvited; - if (member.invitations.party && member.invitations.party.id === group._id) { - isInvited = 'party'; - } else if (_.findIndex(member.invitations.guilds, {id: group._id}) !== -1) { - isInvited = 'guild'; - } - - if (isInGroup) { - group.memberCount -= 1; - - if (group.quest && group.quest.leader === member._id) { - group.quest.key = undefined; - group.quest.leader = undefined; - } else if (group.quest && group.quest.members) { - // remove member from quest - group.quest.members[member._id] = undefined; - group.markModified('quest.members'); - } - - if (isInGroup === 'guild') { - removeFromArray(member.guilds, group._id); - } - if (isInGroup === 'party') member.party._id = undefined; // TODO remove quest information too? Use group.leave()? - - if (member.newMessages[group._id]) { - member.newMessages[group._id] = undefined; - member.markModified('newMessages'); - } - - if (group.quest && group.quest.active && group.quest.leader === member._id) { - member.items.quests[group.quest.key] += 1; - } - } else if (isInvited) { - if (isInvited === 'guild') { - removeFromArray(member.invitations.guilds, { id: group._id }); - } - if (isInvited === 'party') { - user.invitations.party = {}; - user.markModified('invitations.party'); - } - } else { - throw new NotFound(res.t('groupMemberNotFound')); - } - - let message = req.query.message; - if (message) _sendMessageToRemoved(group, member, message); - - await Bluebird.all([ - member.save(), - group.save(), - ]); - res.respond(200, {}); - }, -}; - -async function _inviteByUUID (uuid, group, inviter, req, res) { - let userToInvite = await User.findById(uuid).exec(); - - if (!userToInvite) { - throw new NotFound(res.t('userWithIDNotFound', {userId: uuid})); - } - - if (group.type === 'guild') { - if (_.contains(userToInvite.guilds, group._id)) { - throw new NotAuthorized(res.t('userAlreadyInGroup')); - } - if (_.find(userToInvite.invitations.guilds, {id: group._id})) { - throw new NotAuthorized(res.t('userAlreadyInvitedToGroup')); - } - userToInvite.invitations.guilds.push({id: group._id, name: group.name, inviter: inviter._id}); - } else if (group.type === 'party') { - if (userToInvite.invitations.party.id) { - throw new NotAuthorized(res.t('userAlreadyPendingInvitation')); - } - - if (userToInvite.party._id) { - let userParty = await Group.getGroup({user: userToInvite, groupId: 'party', fields: 'memberCount'}); - - // Allow user to be invited to a new party when they're partying solo - if (userParty.memberCount !== 1) throw new NotAuthorized(res.t('userAlreadyInAParty')); - } - - userToInvite.invitations.party = {id: group._id, name: group.name, inviter: inviter._id}; - } - - let groupLabel = group.type === 'guild' ? 'Guild' : 'Party'; - let groupTemplate = group.type === 'guild' ? 'guild' : 'party'; - if (userToInvite.preferences.emailNotifications[`invited${groupLabel}`] !== false) { - let emailVars = [ - {name: 'INVITER', content: inviter.profile.name}, - ]; - - if (group.type === 'guild') { - emailVars.push( - {name: 'GUILD_NAME', content: group.name}, - {name: 'GUILD_URL', content: '/#/options/groups/guilds/public'} - ); - } else { - emailVars.push( - {name: 'PARTY_NAME', content: group.name}, - {name: 'PARTY_URL', content: '/#/options/groups/party'} - ); - } - - sendTxnEmail(userToInvite, `invited-${groupTemplate}`, emailVars); - } - - sendPushNotification( - userToInvite, - common.i18n.t(group.type === 'guild' ? 'invitedGuild' : 'invitedParty'), - group.name - ); - - let userInvited = await userToInvite.save(); - if (group.type === 'guild') { - return userInvited.invitations.guilds[userToInvite.invitations.guilds.length - 1]; - } else if (group.type === 'party') { - return userInvited.invitations.party; - } -} - -async function _inviteByEmail (invite, group, inviter, req, res) { - let userReturnInfo; - - if (!invite.email) throw new BadRequest(res.t('inviteMissingEmail')); - - let userToContact = await User.findOne({$or: [ - {'auth.local.email': invite.email}, - {'auth.facebook.emails.value': invite.email}, - ]}) - .select({_id: true, 'preferences.emailNotifications': true}) - .exec(); - - if (userToContact) { - userReturnInfo = await _inviteByUUID(userToContact._id, group, inviter, req, res); - } else { - userReturnInfo = invite.email; - const groupQueryString = JSON.stringify({ - id: group._id, - inviter: inviter._id, - sentAt: Date.now(), // so we can let it expire - }); - let link = `/static/front?groupInvite=${encrypt(groupQueryString)}`; - - let variables = [ - {name: 'LINK', content: link}, - {name: 'INVITER', content: req.body.inviter || inviter.profile.name}, - ]; - - if (group.type === 'guild') { - variables.push({name: 'GUILD_NAME', content: group.name}); - } - - // Check for the email address not to be unsubscribed - let userIsUnsubscribed = await EmailUnsubscription.findOne({email: invite.email}).exec(); - let groupLabel = group.type === 'guild' ? '-guild' : ''; - if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables); - } - - return userReturnInfo; -} - -/** - * @api {post} /api/v3/groups/:groupId/invite Invite users to a group using their UUIDs or email addresses - * @apiVersion 3.0.0 - * @apiName InviteToGroup - * @apiGroup Group - * - * @apiParam {string} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted) - * - * @apiParam {array} emails Body parameter - An array of emails addresses to invite (optional) - * @apiParam {array} uuids Body parameter - An array of uuids to invite (optional) - * @apiParam {string} inviter Body parameter - The inviters' name (optional) - * - * @apiSuccess {array} data The invites - */ -api.inviteToGroup = { - method: 'POST', - url: '/groups/:groupId/invite', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: '-chat'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - - let uuids = req.body.uuids; - let emails = req.body.emails; - - let uuidsIsArray = Array.isArray(uuids); - let emailsIsArray = Array.isArray(emails); - - if (!uuids && !emails) { - throw new BadRequest(res.t('canOnlyInviteEmailUuid')); - } - - let results = []; - let totalInvites = 0; - - if (uuids) { - if (!uuidsIsArray) { - throw new BadRequest(res.t('uuidsMustBeAnArray')); - } else { - totalInvites += uuids.length; - } - } - - if (emails) { - if (!emailsIsArray) { - throw new BadRequest(res.t('emailsMustBeAnArray')); - } else { - totalInvites += emails.length; - } - } - - if (totalInvites > INVITES_LIMIT) { - throw new BadRequest(res.t('canOnlyInviteMaxInvites', {maxInvites: INVITES_LIMIT})); - } - - if (uuids) { - let uuidInvites = uuids.map((uuid) => _inviteByUUID(uuid, group, user, req, res)); - let uuidResults = await Bluebird.all(uuidInvites); - results.push(...uuidResults); - } - - if (emails) { - let emailInvites = emails.map((invite) => _inviteByEmail(invite, group, user, req, res)); - let emailResults = await Bluebird.all(emailInvites); - results.push(...emailResults); - } - - res.respond(200, results); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/hall.js b/website/server/controllers/api-v3/hall.js deleted file mode 100644 index 077b1ef74b..0000000000 --- a/website/server/controllers/api-v3/hall.js +++ /dev/null @@ -1,183 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { ensureAdmin } from '../../middlewares/api-v3/ensureAccessRight'; -import { model as User } from '../../models/user'; -import { - NotFound, -} from '../../libs/api-v3/errors'; -import _ from 'lodash'; - -let api = {}; - -/** - * @api {get} /api/v3/hall/patrons Get all patrons - * @apiDescription Only the first 50 patrons are returned. More can be accessed passing ?page=n - * @apiVersion 3.0.0 - * @apiName GetPatrons - * @apiGroup Hall - * - * @apiParam {Number} page Query Parameter - The result page. Default is 0 - * - * @apiSuccess {Array} data An array of patrons - */ -api.getPatrons = { - method: 'GET', - url: '/hall/patrons', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkQuery('page', res.t('pageMustBeNumber')).optional().isNumeric(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let page = req.query.page ? Number(req.query.page) : 0; - const perPage = 50; - - let patrons = await User - .find({ - 'backer.tier': {$gt: 0}, - }) - .select('contributor backer profile.name') - .sort('-backer.tier') - .skip(page * perPage) - .limit(perPage) - .lean() - .exec(); - - res.respond(200, patrons); - }, -}; - -/** - * @api {get} /api/v3/hall/heroes Get all Heroes - * @apiVersion 3.0.0 - * @apiName GetHeroes - * @apiGroup Hall - * - * @apiSuccess {Array} data An array of heroes - */ -api.getHeroes = { - method: 'GET', - url: '/hall/heroes', - middlewares: [authWithHeaders()], - async handler (req, res) { - let heroes = await User - .find({ - 'contributor.level': {$gt: 0}, - }) - .select('contributor backer profile.name') - .sort('-contributor.level') - .lean() - .exec(); - - res.respond(200, heroes); - }, -}; - -// Note, while the following routes are called getHero / updateHero -// they can be used by admins to get/update any user - -const heroAdminFields = 'contributor balance profile.name purchased items auth'; - -/** - * @api {get} /api/v3/hall/heroes/:heroId Get any user ("hero") given the UUID - * @apiDescription Must be an admin to make this request. - * @apiVersion 3.0.0 - * @apiName GetHero - * @apiGroup Hall - * - * @apiSuccess {Object} data The user object - */ -api.getHero = { - method: 'GET', - url: '/hall/heroes/:heroId', - middlewares: [authWithHeaders(), ensureAdmin], - async handler (req, res) { - let heroId = req.params.heroId; - - req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let hero = await User - .findById(heroId) - .select(heroAdminFields) - .exec(); - - if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId})); - let heroRes = hero.toJSON({minimize: true}); - // supply to the possible absence of hero.contributor - // if we didn't pass minimize: true it would have returned all fields as empty - if (!heroRes.contributor) heroRes.contributor = {}; - res.respond(200, heroRes); - }, -}; - -// e.g., tier 5 gives 4 gems. Tier 8 = moderator. Tier 9 = staff -const gemsPerTier = {1: 3, 2: 3, 3: 3, 4: 4, 5: 4, 6: 4, 7: 4, 8: 0, 9: 0}; - -/** - * @api {put} /api/v3/hall/heroes/:heroId Update any user ("hero") - * @apiDescription Must be an admin to make this request. - * @apiVersion 3.0.0 - * @apiName UpdateHero - * @apiGroup Hall - * - * @apiSuccess {Object} data The updated user object - */ -api.updateHero = { - method: 'PUT', - url: '/hall/heroes/:heroId', - middlewares: [authWithHeaders(), ensureAdmin], - async handler (req, res) { - let heroId = req.params.heroId; - let updateData = req.body; - - req.checkParams('heroId', res.t('heroIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let hero = await User.findById(heroId).exec(); - if (!hero) throw new NotFound(res.t('userWithIDNotFound', {userId: heroId})); - - if (updateData.balance) hero.balance = updateData.balance; - - // give them gems if they got an higher level - let newTier = updateData.contributor && updateData.contributor.level; // tier = level in this context - let oldTier = hero.contributor && hero.contributor.level || 0; - if (newTier > oldTier) { - hero.flags.contributor = true; - let tierDiff = newTier - oldTier; // can be 2+ tier increases at once - while (tierDiff) { - hero.balance += gemsPerTier[newTier] / 4; // balance is in $ - tierDiff--; - newTier--; // give them gems for the next tier down if they weren't aready that tier - } - } - - if (updateData.contributor) _.assign(hero.contributor, updateData.contributor); - if (updateData.purchased && updateData.purchased.ads) hero.purchased.ads = updateData.purchased.ads; - - // give them the Dragon Hydra pet if they're above level 6 - if (hero.contributor.level >= 6) hero.items.pets['Dragon-Hydra'] = 5; - if (updateData.itemPath && updateData.itemVal && - updateData.itemPath.indexOf('items.') === 0 && - User.schema.paths[updateData.itemPath]) { - _.set(hero, updateData.itemPath, updateData.itemVal); // Sanitization at 5c30944 (deemed unnecessary) - } - - if (updateData.auth && _.isBoolean(updateData.auth.blocked)) hero.auth.blocked = updateData.auth.blocked; - - let savedHero = await hero.save(); - let heroJSON = savedHero.toJSON(); - let responseHero = {_id: heroJSON._id}; // only respond with important fields - heroAdminFields.split(' ').forEach(field => { - _.set(responseHero, field, _.get(heroJSON, field)); - }); - - res.respond(200, responseHero); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/iap.js b/website/server/controllers/api-v3/iap.js deleted file mode 100644 index daaef0cef8..0000000000 --- a/website/server/controllers/api-v3/iap.js +++ /dev/null @@ -1,4 +0,0 @@ -// NOTE: this file is only used because the mobile apps expect IAP routes -// to be found at /api/v3/iap instead of /iap. - -module.exports = require('../top-level/payments/iap'); \ No newline at end of file diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js deleted file mode 100644 index 37bc3c5d27..0000000000 --- a/website/server/controllers/api-v3/members.js +++ /dev/null @@ -1,364 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { - model as User, - publicFields as memberFields, - nameFields, -} from '../../models/user'; -import { model as Group } from '../../models/group'; -import { model as Challenge } from '../../models/challenge'; -import { - NotFound, - NotAuthorized, -} from '../../libs/api-v3/errors'; -import * as Tasks from '../../models/task'; -import { - getUserInfo, - sendTxn as sendTxnEmail, -} from '../../libs/api-v3/email'; -import Bluebird from 'bluebird'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; - -let api = {}; - -/** - * @api {get} /api/v3/members/:memberId Get a member profile - * @apiVersion 3.0.0 - * @apiName GetMember - * @apiGroup Member - * - * @apiParam {UUID} memberId The member's id - * - * @apiSuccess {object} data The member object - */ -api.getMember = { - method: 'GET', - url: '/members/:memberId', - middlewares: [], - async handler (req, res) { - req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let memberId = req.params.memberId; - - let member = await User - .findById(memberId) - .select(memberFields) - .exec(); - - if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); - - // manually call toJSON with minimize: true so empty paths aren't returned - res.respond(200, member.toJSON({minimize: true})); - }, -}; - -// Return a request handler for getMembersForGroup / getInvitesForGroup / getMembersForChallenge -// type is `invites` or `members` -function _getMembersForItem (type) { - if (['group-members', 'group-invites', 'challenge-members'].indexOf(type) === -1) { - throw new Error('Type must be one of "group-members", "group-invites", "challenge-members"'); - } - - return async function handleGetMembersForItem (req, res) { - if (type === 'challenge-members') { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - } else { - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - } - req.checkQuery('lastId').optional().notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let groupId = req.params.groupId; - let challengeId = req.params.challengeId; - let lastId = req.query.lastId; - let user = res.locals.user; - let challenge; - let group; - - if (type === 'challenge-members') { - challenge = await Challenge.findById(challengeId).select('_id type leader group').exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - - // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge - // for example if you've been booted from it, are the leader or a site admin - group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - } else { - group = await Group.getGroup({user, groupId, fields: '_id type'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - } - - let query = {}; - let fields = nameFields; - - if (type === 'challenge-members') { - query.challenges = challenge._id; - } else if (type === 'group-members') { - if (group.type === 'guild') { - query.guilds = group._id; - } else { - query['party._id'] = group._id; // group._id and not groupId because groupId could be === 'party' - - if (req.query.includeAllPublicFields === 'true') { - fields = memberFields; - } - } - } else if (type === 'group-invites') { - if (group.type === 'guild') { // eslint-disable-line no-lonely-if - query['invitations.guilds.id'] = group._id; - } else { - query['invitations.party.id'] = group._id; // group._id and not groupId because groupId could be === 'party' - } - } - - if (lastId) query._id = {$gt: lastId}; - - let members = await User - .find(query) - .sort({_id: 1}) - .limit(30) - .select(fields) - .exec(); - - // manually call toJSON with minimize: true so empty paths aren't returned - res.respond(200, members.map(member => member.toJSON({minimize: true}))); - }; -} - -/** - * @api {get} /api/v3/groups/:groupId/members Get members for a group - * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. - * @apiVersion 3.0.0 - * @apiName GetMembersForGroup - * @apiGroup Member - * - * @apiParam {UUID} groupId The group id - * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results - * @apiParam {boolean} includeAllPublicFields Query parameter available only when fetching a party. If === `true` then all public fields for members will be returned (liek when making a request for a single member) - * - * @apiSuccess {array} data An array of members, sorted by _id - */ -api.getMembersForGroup = { - method: 'GET', - url: '/groups/:groupId/members', - middlewares: [authWithHeaders()], - handler: _getMembersForItem('group-members'), -}; - -/** - * @api {get} /api/v3/groups/:groupId/invites Get invites for a group - * @apiDescription With a limit of 30 member per request. To get all invites run requests against this routes (updating the lastId query parameter) until you get less than 30 results. - * @apiVersion 3.0.0 - * @apiName GetInvitesForGroup - * @apiGroup Member - * - * @apiParam {UUID} groupId The group id - * @apiParam {UUID} lastId Query parameter to specify the last invite returned in a previous request to this route and get the next batch of results - * - * @apiSuccess {array} data An array of invites, sorted by _id - */ -api.getInvitesForGroup = { - method: 'GET', - url: '/groups/:groupId/invites', - middlewares: [authWithHeaders()], - handler: _getMembersForItem('group-invites'), -}; - -/** - * @api {get} /api/v3/challenges/:challengeId/members Get members for a challenge - * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. - * @apiVersion 3.0.0 - * @apiName GetMembersForChallenge - * @apiGroup Member - * - * @apiParam {UUID} challengeId The challenge id - * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results - * - * @apiSuccess {array} data An array of members, sorted by _id - */ -api.getMembersForChallenge = { - method: 'GET', - url: '/challenges/:challengeId/members', - middlewares: [authWithHeaders()], - handler: _getMembersForItem('challenge-members'), -}; - -/** - * @api {get} /api/v3/challenges/:challengeId/members/:memberId Get a challenge member progress - * @apiVersion 3.0.0 - * @apiName GetChallenge - * @apiGroup Challenge - * - * @apiParam {UUID} challengeId The challenge _id - * @apiParam {UUID} member The member _id - * - * @apiSuccess {object} data Return an object with member _id, profile.name and a tasks object with the challenge tasks for the member - */ -api.getChallengeMemberProgress = { - method: 'GET', - url: '/challenges/:challengeId/members/:memberId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - let memberId = req.params.memberId; - - let member = await User.findById(memberId).select(`${nameFields} challenges`).exec(); - if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); - - let challenge = await Challenge.findById(challengeId).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - - // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge - // for example if you've been booted from it, are the leader or a site admin - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - if (!challenge.isMember(member)) throw new NotFound(res.t('challengeMemberNotFound')); - - let chalTasks = await Tasks.Task.find({ - userId: memberId, - 'challenge.id': challengeId, - }) - .select('-tags') // We don't want to return the tags publicly TODO same for other data? - .exec(); - - // manually call toJSON with minimize: true so empty paths aren't returned - let response = member.toJSON({minimize: true}); - delete response.challenges; - response.tasks = chalTasks.map(chalTask => chalTask.toJSON({minimize: true})); - res.respond(200, response); - }, -}; - -/** - * @api {posts} /members/send-private-message Send a private message to a member - * @apiVersion 3.0.0 - * @apiName SendPrivateMessage - * @apiGroup Members - * - * @apiParam {String} message Body parameter - The message - * @apiParam {UUID} toUserId Body parameter - The user to contact - * - * @apiSuccess {Object} data An empty Object - */ -api.sendPrivateMessage = { - method: 'POST', - url: '/members/send-private-message', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkBody('message', res.t('messageRequired')).notEmpty(); - req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let sender = res.locals.user; - let message = req.body.message; - - let receiver = await User.findById(req.body.toUserId).exec(); - if (!receiver) throw new NotFound(res.t('userNotFound')); - - let userBlockedSender = receiver.inbox.blocks.indexOf(sender._id) !== -1; - let userIsBlockBySender = sender.inbox.blocks.indexOf(receiver._id) !== -1; - let userOptedOutOfMessaging = receiver.inbox.optOut; - - if (userBlockedSender || userIsBlockBySender || userOptedOutOfMessaging) { - throw new NotAuthorized(res.t('notAuthorizedToSendMessageToThisUser')); - } - - await sender.sendMessage(receiver, message); - - if (receiver.preferences.emailNotifications.newPM !== false) { - sendTxnEmail(receiver, 'new-pm', [ - {name: 'SENDER', content: getUserInfo(sender, ['name']).name}, - {name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'}, - ]); - } - - res.respond(200, {}); - }, -}; - -/** - * @api {posts} /members/transfer-gems Send a gem gift to a member - * @apiVersion 3.0.0 - * @apiName TransferGems - * @apiGroup Members - * - * @apiParam {String} message Body parameter The message - * @apiParam {UUID} toUserId Body parameter The toUser _id - * @apiParam {Integer} gemAmount Body parameter The number of gems to send - * - * @apiSuccess {Object} data An empty Object - */ -api.transferGems = { - method: 'POST', - url: '/members/transfer-gems', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkBody('toUserId', res.t('toUserIDRequired')).notEmpty().isUUID(); - req.checkBody('gemAmount', res.t('gemAmountRequired')).notEmpty().isInt(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let sender = res.locals.user; - - let receiver = await User.findById(req.body.toUserId).exec(); - if (!receiver) throw new NotFound(res.t('userNotFound')); - - if (receiver._id === sender._id) { - throw new NotAuthorized(res.t('cannotSendGemsToYourself')); - } - - let gemAmount = req.body.gemAmount; - let amount = gemAmount / 4; - - if (amount <= 0 || sender.balance < amount) { - throw new NotAuthorized(res.t('badAmountOfGemsToSend')); - } - - receiver.balance += amount; - sender.balance -= amount; - let promises = [receiver.save(), sender.save()]; - await Bluebird.all(promises); - - let message = res.t('privateMessageGiftIntro', { - receiverName: receiver.profile.name, - senderName: sender.profile.name, - }); - message += res.t('privateMessageGiftGemsMessage', {gemAmount}); - - if (req.body.message) { - message += req.body.message; - } - - await sender.sendMessage(receiver, message); - - let byUsername = getUserInfo(sender, ['name']).name; - - if (receiver.preferences.emailNotifications.giftedGems !== false) { - sendTxnEmail(receiver, 'gifted-gems', [ - {name: 'GIFTER', content: byUsername}, - {name: 'X_GEMS_GIFTED', content: gemAmount}, - ]); - } - - sendPushNotification(sender, res.t('giftedGems'), res.t('giftedGemsInfo', { amount: gemAmount, name: byUsername })); - - res.respond(200, {}); - }, -}; - - -module.exports = api; diff --git a/website/server/controllers/api-v3/modelsPaths.js b/website/server/controllers/api-v3/modelsPaths.js deleted file mode 100644 index 927087df01..0000000000 --- a/website/server/controllers/api-v3/modelsPaths.js +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose from 'mongoose'; - -let api = {}; - -let tasksModels = ['habit', 'daily', 'todo', 'reward']; -let allModels = ['user', 'tag', 'challenge', 'group'].concat(tasksModels); - -/** - * @api {get} /api/v3/models/:model/paths Get all paths for the specified model - * @apiDescription Doesn't require authentication - * @apiVersion 3.0.0 - * @apiName GetUserModelPaths - * @apiGroup Meta - * - * @apiParam {string="user","group","challenge","tag","habit","daily","todo","reward"} model The name of the model - * - * @apiSuccess {object} data A key-value object made of fieldPath: fieldType (like {'field.nested': Boolean}) - */ -api.getModelPaths = { - method: 'GET', - url: '/models/:model/paths', - async handler (req, res) { - req.checkParams('model', res.t('modelNotFound')).notEmpty().isIn(allModels); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let model = req.params.model; - // tasks models are lowercase, the others have the first letter uppercase (User, Group) - if (tasksModels.indexOf(model) === -1) { - model = model.charAt(0).toUpperCase() + model.slice(1); - } - - model = mongoose.model(model); - - res.respond(200, model.getModelPaths()); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/quests.js b/website/server/controllers/api-v3/quests.js deleted file mode 100644 index 9107fda8d6..0000000000 --- a/website/server/controllers/api-v3/quests.js +++ /dev/null @@ -1,451 +0,0 @@ -import _ from 'lodash'; -import Bluebird from 'bluebird'; -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import analytics from '../../libs/api-v3/analyticsService'; -import { - model as Group, -} from '../../models/group'; -import { model as User } from '../../models/user'; -import { - NotFound, - NotAuthorized, - BadRequest, -} from '../../libs/api-v3/errors'; -import { - getUserInfo, - sendTxn as sendTxnEmail, -} from '../../libs/api-v3/email'; -import common from '../../../../common'; -import sendPushNotification from '../../libs/api-v3/pushNotifications'; - -const questScrolls = common.content.quests; - -function canStartQuestAutomatically (group) { - // If all members are either true (accepted) or false (rejected) return true - // If any member is null/undefined (undecided) return false - return _.every(group.quest.members, _.isBoolean); -} - -let api = {}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/invite Invite users to a quest - * @apiVersion 3.0.0 - * @apiName InviteToQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * @apiParam {string} questKey - * - * @apiSuccess {Object} data Quest object - */ -api.inviteToQuest = { - method: 'POST', - url: '/groups/:groupId/quests/invite/:questKey', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let questKey = req.params.questKey; - let quest = questScrolls[questKey]; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); - - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!quest) throw new NotFound(res.t('questNotFound', { key: questKey })); - if (!user.items.quests[questKey]) throw new NotAuthorized(res.t('questNotOwned')); - if (user.stats.lvl < quest.lvl) throw new NotAuthorized(res.t('questLevelTooHigh', { level: quest.lvl })); - if (group.quest.key) throw new NotAuthorized(res.t('questAlreadyUnderway')); - - let members = await User.find({ - 'party._id': group._id, - _id: {$ne: user._id}, - }).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') - .exec(); - - group.markModified('quest'); - group.quest.key = questKey; - group.quest.leader = user._id; - group.quest.members = {}; - group.quest.members[user._id] = true; - - user.party.quest.RSVPNeeded = false; - user.party.quest.key = questKey; - - await User.update({ - 'party._id': group._id, - _id: {$ne: user._id}, - }, { - $set: { - 'party.quest.RSVPNeeded': true, - 'party.quest.key': questKey, - }, - }, {multi: true}).exec(); - - _.each(members, (member) => { - group.quest.members[member._id] = null; - }); - - if (canStartQuestAutomatically(group)) { - await group.startQuest(user); - } - - let [savedGroup] = await Bluebird.all([ - group.save(), - user.save(), - ]); - - res.respond(200, savedGroup.quest); - - // send out invites - let inviterVars = getUserInfo(user, ['name', 'email']); - let membersToEmail = members.filter(member => { - // send push notifications while filtering members before sending emails - sendPushNotification( - member, - common.i18n.t('questInvitationTitle'), - common.i18n.t('questInvitationInfo', { quest: quest.text() }) - ); - - return member.preferences.emailNotifications.invitedQuest !== false; - }); - sendTxnEmail(membersToEmail, `invite-${quest.boss ? 'boss' : 'collection'}-quest`, [ - {name: 'QUEST_NAME', content: quest.text()}, - {name: 'INVITER', content: inviterVars.name}, - {name: 'PARTY_URL', content: '/#/options/groups/party'}, - ]); - - // track that the inviting user has accepted the quest - analytics.track('quest', { - category: 'behavior', - owner: true, - response: 'accept', - gaLabel: 'accept', - questName: questKey, - uuid: user._id, - }); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/accept Accept a pending quest - * @apiVersion 3.0.0 - * @apiName AcceptQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.acceptQuest = { - method: 'POST', - url: '/groups/:groupId/quests/accept', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); - - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.key) throw new NotFound(res.t('questInviteNotFound')); - if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); - if (group.quest.members[user._id]) throw new BadRequest(res.t('questAlreadyAccepted')); - - group.markModified('quest'); - group.quest.members[user._id] = true; - user.party.quest.RSVPNeeded = false; - - if (canStartQuestAutomatically(group)) { - await group.startQuest(user); - } - - let [savedGroup] = await Bluebird.all([ - group.save(), - user.save(), - ]); - - res.respond(200, savedGroup.quest); - - // track that a user has accepted the quest - analytics.track('quest', { - category: 'behavior', - owner: false, - response: 'accept', - gaLabel: 'accept', - questName: group.quest.key, - uuid: user._id, - }); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/reject Reject a quest - * @apiVersion 3.0.0 - * @apiName RejectQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.rejectQuest = { - method: 'POST', - url: '/groups/:groupId/quests/reject', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist')); - if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); - if (group.quest.members[user._id]) throw new BadRequest(res.t('questAlreadyAccepted')); - if (group.quest.members[user._id] === false) throw new BadRequest(res.t('questAlreadyRejected')); - - group.quest.members[user._id] = false; - group.markModified('quest.members'); - - user.party.quest = Group.cleanQuestProgress(); - user.markModified('party.quest'); - - if (canStartQuestAutomatically(group)) { - await group.startQuest(user); - } - - let [savedGroup] = await Bluebird.all([ - group.save(), - user.save(), - ]); - - res.respond(200, savedGroup.quest); - - analytics.track('quest', { - category: 'behavior', - owner: false, - response: 'reject', - gaLabel: 'reject', - questName: group.quest.key, - uuid: user._id, - }); - }, -}; - - -/** - * @api {post} /api/v3/groups/:groupId/quests/force-start Force-start a pending quest - * @apiVersion 3.0.0 - * @apiName ForceQuestStart - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.forceStart = { - method: 'POST', - url: '/groups/:groupId/quests/force-start', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest leader'}); - - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.key) throw new NotFound(res.t('questNotPending')); - if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway')); - if (!(user._id === group.quest.leader || user._id === group.leader)) throw new NotAuthorized(res.t('questOrGroupLeaderOnlyStartQuest')); - - group.markModified('quest'); - - await group.startQuest(user); - - let [savedGroup] = await Bluebird.all([ - group.save(), - user.save(), - ]); - - res.respond(200, savedGroup.quest); - - analytics.track('quest', { - category: 'behavior', - owner: user._id === group.quest.leader, - response: 'force-start', - gaLabel: 'force-start', - questName: group.quest.key, - uuid: user._id, - }); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/cancel Cancels a quest - * @apiVersion 3.0.0 - * @apiName CancelQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.cancelQuest = { - method: 'POST', - url: '/groups/:groupId/quests/cancel', - middlewares: [authWithHeaders()], - async handler (req, res) { - // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) - // Quest scroll has not yet left quest owner's inventory so no need to return it. - // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId, fields: 'type leader quest'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist')); - if (user._id !== group.leader && group.quest.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCancelQuest')); - if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest')); - - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); - - let [savedGroup] = await Bluebird.all([ - group.save(), - User.update( - {'party._id': groupId}, - {$set: {'party.quest': Group.cleanQuestProgress()}}, - {multi: true} - ), - ]); - - res.respond(200, savedGroup.quest); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/abort Abort the current quest - * @apiVersion 3.0.0 - * @apiName AbortQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.abortQuest = { - method: 'POST', - url: '/groups/:groupId/quests/abort', - middlewares: [authWithHeaders()], - async handler (req, res) { - // Abort a quest AFTER it has begun (see questCancel for BEFORE) - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId, fields: 'type quest leader'}); - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.active) throw new NotFound(res.t('noActiveQuestToAbort')); - if (user._id !== group.leader && user._id !== group.quest.leader) throw new NotAuthorized(res.t('onlyLeaderAbortQuest')); - - let memberUpdates = User.update({ - 'party._id': groupId, - }, { - $set: {'party.quest': Group.cleanQuestProgress()}, - }, {multi: true}).exec(); - - let questLeaderUpdate = User.update({ - _id: group.quest.leader, - }, { - $inc: { - [`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader - }, - }).exec(); - - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); - - let [groupSaved] = await Bluebird.all([group.save(), memberUpdates, questLeaderUpdate]); - - res.respond(200, groupSaved.quest); - }, -}; - -/** - * @api {post} /api/v3/groups/:groupId/quests/leave Leaves the active quest - * @apiVersion 3.0.0 - * @apiName LeaveQuest - * @apiGroup Group - * - * @apiParam {string} groupId The group _id (or 'party') - * - * @apiSuccess {Object} data Quest Object - */ -api.leaveQuest = { - method: 'POST', - url: '/groups/:groupId/quests/leave', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({user, groupId, fields: 'type quest'}); - - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported')); - if (!group.quest.active) throw new NotFound(res.t('noActiveQuestToLeave')); - if (group.quest.leader === user._id) throw new NotAuthorized(res.t('questLeaderCannotLeaveQuest')); - if (!group.quest.members[user._id]) throw new NotAuthorized(res.t('notPartOfQuest')); - - group.quest.members[user._id] = false; - group.markModified('quest.members'); - - user.party.quest = Group.cleanQuestProgress(); - user.markModified('party.quest'); - - let [savedGroup] = await Bluebird.all([ - group.save(), - user.save(), - ]); - - res.respond(200, savedGroup.quest); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/status.js b/website/server/controllers/api-v3/status.js deleted file mode 100644 index 68c232463f..0000000000 --- a/website/server/controllers/api-v3/status.js +++ /dev/null @@ -1,21 +0,0 @@ -let api = {}; - -/** - * @api {get} /api/v3/status Get Habitica's API status - * @apiVersion 3.0.0 - * @apiName GetStatus - * @apiGroup Status - * - * @apiSuccess {status} data.status 'up' if everything is ok - */ -api.getStatus = { - method: 'GET', - url: '/status', - async handler (req, res) { - res.respond(200, { - status: 'up', - }); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/tags.js b/website/server/controllers/api-v3/tags.js deleted file mode 100644 index 250c343537..0000000000 --- a/website/server/controllers/api-v3/tags.js +++ /dev/null @@ -1,190 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { model as Tag } from '../../models/tag'; -import * as Tasks from '../../models/task'; -import { - NotFound, -} from '../../libs/api-v3/errors'; -import _ from 'lodash'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; - -let api = {}; - -/** - * @api {post} /api/v3/tags Create a new tag - * @apiVersion 3.0.0 - * @apiName CreateTag - * @apiGroup Tag - * - * @apiSuccess {Object} data The newly created tag - */ -api.createTag = { - method: 'POST', - url: '/tags', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - user.tags.push(Tag.sanitize(req.body)); - let savedUser = await user.save(); - - let l = savedUser.tags.length; - let tag = savedUser.tags[l - 1]; - res.respond(201, tag); - }, -}; - -/** - * @api {get} /api/v3/tag Get a user's tags - * @apiVersion 3.0.0 - * @apiName GetTags - * @apiGroup Tag - * - * @apiSuccess {Array} data An array of tags - */ -api.getTags = { - method: 'GET', - url: '/tags', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - res.respond(200, user.tags); - }, -}; - -/** - * @api {get} /api/v3/tags/:tagId Get a tag given its id - * @apiVersion 3.0.0 - * @apiName GetTag - * @apiGroup Tag - * - * @apiParam {UUID} tagId The tag _id - * - * @apiSuccess {object} data The tag object - */ -api.getTag = { - method: 'GET', - url: '/tags/:tagId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let tag = _.find(user.tags, {id: req.params.tagId}); - if (!tag) throw new NotFound(res.t('tagNotFound')); - res.respond(200, tag); - }, -}; - -/** - * @api {put} /api/v3/tag/:tagId Update a tag - * @apiVersion 3.0.0 - * @apiName UpdateTag - * @apiGroup Tag - * - * @apiParam {UUID} tagId The tag _id - * - * @apiSuccess {object} data The updated tag - */ -api.updateTag = { - method: 'PUT', - url: '/tags/:tagId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); - - let tagId = req.params.tagId; - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let tag = _.find(user.tags, {id: tagId}); - if (!tag) throw new NotFound(res.t('tagNotFound')); - - _.merge(tag, Tag.sanitize(req.body)); - - let savedUser = await user.save(); - res.respond(200, _.find(savedUser.tags, {id: tagId})); - }, -}; - -/** - * @api {post} /api/v3/reorder-tags Reorder a tag - * @apiVersion 3.0.0 - * @apiName ReorderTags - * @apiGroup Tag - * - * @apiParam {tagId} UUID Id of the tag to move - * @apiParam {to} number Position the tag is moving to - * - * @apiSuccess {object} data An empty object - */ -api.reorderTags = { - method: 'POST', - url: '/reorder-tags', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkBody('to', res.t('toRequired')).notEmpty(); - req.checkBody('tagId', res.t('tagIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let tagIndex = _.findIndex(user.tags, function findTag (tag) { - return tag.id === req.body.tagId; - }); - if (tagIndex === -1) throw new NotFound(res.t('tagNotFound')); - user.tags.splice(req.body.to, 0, user.tags.splice(tagIndex, 1)[0]); - - await user.save(); - res.respond(200, {}); - }, -}; - -/** - * @api {delete} /api/v3/tag/:tagId Delete a user tag given its id - * @apiVersion 3.0.0 - * @apiName DeleteTag - * @apiGroup Tag - * - * @apiParam {UUID} tagId The tag _id - * - * @apiSuccess {object} data An empty object - */ -api.deleteTag = { - method: 'DELETE', - url: '/tags/:tagId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let tag = removeFromArray(user.tags, { id: req.params.tagId }); - if (!tag) throw new NotFound(res.t('tagNotFound')); - - // Remove from all the tasks TODO test - await Tasks.Task.update({ - userId: user._id, - }, { - $pull: { - tags: tag.id, - }, - }, {multi: true}).exec(); - - await user.save(); - res.respond(200, {}); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js deleted file mode 100644 index 919c2fb541..0000000000 --- a/website/server/controllers/api-v3/tasks.js +++ /dev/null @@ -1,971 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import { sendTaskWebhook } from '../../libs/api-v3/webhook'; -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; -import * as Tasks from '../../models/task'; -import { model as Challenge } from '../../models/challenge'; -import { model as Group } from '../../models/group'; -import { - NotFound, - NotAuthorized, - BadRequest, -} from '../../libs/api-v3/errors'; -import common from '../../../../common'; -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import logger from '../../libs/api-v3/logger'; - -let api = {}; - -// challenge must be passed only when a challenge task is being created -async function _createTasks (req, res, user, challenge) { - let toSave = Array.isArray(req.body) ? req.body : [req.body]; - - toSave = toSave.map(taskData => { - // Validate that task.type is valid - if (!taskData || Tasks.tasksTypes.indexOf(taskData.type) === -1) throw new BadRequest(res.t('invalidTaskType')); - - let taskType = taskData.type; - let newTask = new Tasks[taskType](Tasks.Task.sanitize(taskData)); - - if (challenge) { - newTask.challenge.id = challenge.id; - } else { - newTask.userId = user._id; - } - - // Validate that the task is valid and throw if it isn't - // otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality - let validationErrors = newTask.validateSync(); - if (validationErrors) throw validationErrors; - - // Otherwise update the user/challenge - (challenge || user).tasksOrder[`${taskType}s`].unshift(newTask._id); - - return newTask; - }).map(task => task.save({ // If all tasks are valid (this is why it's not in the previous .map()), save everything, withough running validation again - validateBeforeSave: false, - })); - - toSave.unshift((challenge || user).save()); - - let tasks = await Bluebird.all(toSave); - tasks.splice(0, 1); // Remove user or challenge - return tasks; -} - -/** - * @api {post} /api/v3/tasks/user Create a new task belonging to the user - * @apiDescription Can be passed an object to create a single task or an array of objects to create multiple tasks. - * @apiVersion 3.0.0 - * @apiName CreateUserTasks - * @apiGroup Task - * - * @apiSuccess data An object if a single task was created, otherwise an array of tasks - */ -api.createUserTasks = { - method: 'POST', - url: '/tasks/user', - middlewares: [authWithHeaders()], - async handler (req, res) { - let tasks = await _createTasks(req, res, res.locals.user); - res.respond(201, tasks.length === 1 ? tasks[0] : tasks); - }, -}; - -/** - * @api {post} /api/v3/tasks/challenge/:challengeId Create a new task belonging to a challenge - * @apiDescription Can be passed an object to create a single task or an array of objects to create multiple tasks. - * @apiVersion 3.0.0 - * @apiName CreateChallengeTasks - * @apiGroup Task - * - * @apiParam {UUID} challengeId The id of the challenge the new task(s) will belong to - * - * @apiSuccess data An object if a single task was created, otherwise an array of tasks - */ -api.createChallengeTasks = { - method: 'POST', - url: '/tasks/challenge/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - - let reqValidationErrors = req.validationErrors(); - if (reqValidationErrors) throw reqValidationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - - let challenge = await Challenge.findOne({_id: challengeId}).exec(); - - // If the challenge does not exist, or if it exists but user is not the leader -> throw error - if (!challenge || user.challenges.indexOf(challengeId) === -1) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - - let tasks = await _createTasks(req, res, user, challenge); - - res.respond(201, tasks.length === 1 ? tasks[0] : tasks); - - // If adding tasks to a challenge -> sync users - if (challenge) challenge.addTasks(tasks); - - return null; - }, -}; - -// challenge must be passed only when a challenge task is being created -async function _getTasks (req, res, user, challenge) { - let query = challenge ? {'challenge.id': challenge.id, userId: {$exists: false}} : {userId: user._id}; - let type = req.query.type; - - if (type) { - if (type === 'todos') { - query.completed = false; // Exclude completed todos - query.type = 'todo'; - } else if (type === 'completedTodos') { - query = Tasks.Task.find({ - userId: user._id, - type: 'todo', - completed: true, - }).limit(30).sort({ // TODO add ability to pick more than 30 completed todos - dateCompleted: -1, - }); - } else { - query.type = type.slice(0, -1); // removing the final "s" - } - } else { - query.$or = [ // Exclude completed todos - {type: 'todo', completed: false}, - {type: {$in: ['habit', 'daily', 'reward']}}, - ]; - } - - let tasks = await Tasks.Task.find(query).exec(); - - // Order tasks based on tasksOrder - if (type && type !== 'completedTodos') { - let order = (challenge || user).tasksOrder[type]; - let orderedTasks = new Array(tasks.length); - let unorderedTasks = []; // what we want to add later - - tasks.forEach((task, index) => { - let taskId = task._id; - let i = order[index] === taskId ? index : order.indexOf(taskId); - if (i === -1) { - unorderedTasks.push(task); - } else { - orderedTasks[i] = task; - } - }); - - // Remove empty values from the array and add any unordered task - orderedTasks = _.compact(orderedTasks).concat(unorderedTasks); - res.respond(200, orderedTasks); - } else { - res.respond(200, tasks); - } -} - -/** - * @api {get} /api/v3/tasks/user Get a user's tasks - * @apiVersion 3.0.0 - * @apiName GetUserTasks - * @apiGroup Task - * - * @apiParam {string="habits","dailys","todos","rewards","completedTodos"} type Optional query parameter to return just a type of tasks. By default all types will be returned except completed todos that must be requested separately. - * - * @apiSuccess {Array} data An array of tasks - */ -api.getUserTasks = { - method: 'GET', - url: '/tasks/user', - middlewares: [authWithHeaders()], - async handler (req, res) { - let types = Tasks.tasksTypes.map(type => `${type}s`); - types.push('completedTodos'); - req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - return await _getTasks(req, res, res.locals.user); - }, -}; - -/** - * @api {get} /api/v3/tasks/challenge/:challengeId Get a challenge's tasks - * @apiVersion 3.0.0 - * @apiName GetChallengeTasks - * @apiGroup Task - * - * @apiParam {UUID} challengeId The id of the challenge from which to retrieve the tasks - * @apiParam {string="habits","dailys","todos","rewards"} type Optional query parameter to return just a type of tasks - * - * @apiSuccess {Array} data An array of tasks - */ -api.getChallengeTasks = { - method: 'GET', - url: '/tasks/challenge/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - let types = Tasks.tasksTypes.map(type => `${type}s`); - req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(types); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let challengeId = req.params.challengeId; - - let challenge = await Challenge.findOne({_id: challengeId}).select('group leader tasksOrder').exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); - if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); - - return await _getTasks(req, res, res.locals.user, challenge); - }, -}; - -/** - * @api {get} /api/v3/task/:taskId Get a task - * @apiVersion 3.0.0 - * @apiName GetTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * - * @apiSuccess {object} data The task object - */ -api.getTask = { - method: 'GET', - url: '/tasks/:taskId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - }).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - let challenge = await Challenge.find({_id: task.challenge.id}).select('leader').exec(); - if (!challenge || (user.challenges.indexOf(task.challenge.id) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens - throw new NotFound(res.t('taskNotFound')); - } - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } - - res.respond(200, task); - }, -}; - -/** - * @api {put} /api/v3/task/:taskId Update a task - * @apiVersion 3.0.0 - * @apiName UpdateTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * - * @apiSuccess {object} data The updated task - */ -api.updateTask = { - method: 'PUT', - url: '/tasks/:taskId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let challenge; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - }).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } - - // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? - let [updatedTaskObj] = common.ops.updateTask(task.toObject(), req); - - - // Sanitize differently user tasks linked to a challenge - let sanitizedObj; - - if (!challenge && task.userId && task.challenge && task.challenge.id) { - sanitizedObj = Tasks.Task.sanitizeUserChallengeTask(updatedTaskObj); - } else { - sanitizedObj = Tasks.Task.sanitize(updatedTaskObj); - } - - _.assign(task, sanitizedObj); - // console.log(task.modifiedPaths(), task.toObject().repeat === tep) - // repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject() - // see https://github.com/Automattic/mongoose/issues/2749 - - let savedTask = await task.save(); - res.respond(200, savedTask); - if (challenge) challenge.updateTask(savedTask); - - return null; - }, -}; - -function _generateWebhookTaskData (task, direction, delta, stats, user) { - let extendedStats = _.extend(stats, { - toNextLevel: common.tnl(user.stats.lvl), - maxHealth: common.maxHealth, - maxMP: common.statsComputed(user).maxMP, - }); - - let userData = { - _id: user._id, - _tmp: user._tmp, - stats: extendedStats, - }; - - let taskData = { - details: task, - direction, - delta, - }; - - return { - task: taskData, - user: userData, - }; -} - -/** - * @api {put} /api/v3/tasks/:taskId/score/:direction Score a task - * @apiVersion 3.0.0 - * @apiName ScoreTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {string="up","down"} direction The direction for scoring the task - * - * @apiSuccess {object} data._tmp If an item was dropped it'll be returned in te _tmp object - * @apiSuccess {number} data.delta - * @apiSuccess {object} data The user stats - */ -api.scoreTask = { - method: 'POST', - url: '/tasks/:taskId/score/:direction', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let direction = req.params.direction; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - - let wasCompleted = task.completed; - - let [delta] = common.ops.scoreTask({task, user, direction}, req); - // Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results) - if (direction === 'up') user.fns.randomDrop({task, delta}, req); - - // If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list - // TODO move to common code? - if (task.type === 'todo') { - if (!wasCompleted && task.completed) { - removeFromArray(user.tasksOrder.todos, task._id); - } else if (wasCompleted && !task.completed) { - let hasTask = removeFromArray(user.tasksOrder.todos, task._id); - if (!hasTask) { - user.tasksOrder.todos.push(task._id); - } // If for some reason it hadn't been removed previously don't do anything - } - } - - let results = await Bluebird.all([ - user.save(), - task.save(), - ]); - - let savedUser = results[0]; - - let userStats = savedUser.stats.toJSON(); - let resJsonData = _.extend({delta, _tmp: user._tmp}, userStats); - res.respond(200, resJsonData); - - sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user)); - - if (task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') { - // Wrapping everything in a try/catch block because if an error occurs using `await` it MUST NOT bubble up because the request has already been handled - try { - let chalTask = await Tasks.Task.findOne({ - _id: task.challenge.taskId, - }).exec(); - - await chalTask.scoreChallengeTask(delta); - } catch (e) { - logger.error(e); - } - } - - return null; - }, -}; - -/** - * @api {post} /api/v3/tasks/:taskId/move/to/:position Move a task to a new position - * @apiDescription Note: completed To-Dos are not sortable, do not appear in user.tasksOrder.todos, and are ordered by date of completion. - * @apiVersion 3.0.0 - * @apiName MoveTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {Number} position Query parameter - Where to move the task (-1 means push to bottom). First position is 0 - * - * @apiSuccess {array} data The new tasks order (user.tasksOrder.{task.type}s) - */ -api.moveTask = { - method: 'POST', - url: '/tasks/:taskId/move/to/:position', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let to = Number(req.params.position); - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type === 'todo' && task.completed) throw new BadRequest(res.t('cantMoveCompletedTodo')); - let order = user.tasksOrder[`${task.type}s`]; - let currentIndex = order.indexOf(task._id); - - // If for some reason the task isn't ordered (should never happen), push it in the new position - // if the task is moved to a non existing position - // or if the task is moved to position -1 (push to bottom) - // -> push task at end of list - if (!order[to] && to !== -1) { - order.push(task._id); - } else { - if (currentIndex !== -1) order.splice(currentIndex, 1); - if (to === -1) { - order.push(task._id); - } else { - order.splice(to, 0, task._id); - } - } - - await user.save(); - res.respond(200, order); - }, -}; - -/** - * @api {post} /api/v3/tasks/:taskId/checklist Add an item to the task's checklist - * @apiVersion 3.0.0 - * @apiName AddChecklistItem - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * - * @apiSuccess {object} data The updated task - */ -api.addChecklistItem = { - method: 'POST', - url: '/tasks/:taskId/checklist', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let challenge; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - }).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } - - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - - task.checklist.push(Tasks.Task.sanitizeChecklist(req.body)); - let savedTask = await task.save(); - - res.respond(200, savedTask); - if (challenge) challenge.updateTask(savedTask); - - return null; - }, -}; - -/** - * @api {post} /api/v3/tasks/:taskId/checklist/:itemId/score Score a checklist item - * @apiVersion 3.0.0 - * @apiName ScoreChecklistItem - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {UUID} itemId The checklist item _id - * - * @apiSuccess {object} data The updated task - */ -api.scoreCheckListItem = { - method: 'POST', - url: '/tasks/:taskId/checklist/:itemId/score', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - - let item = _.find(task.checklist, {id: req.params.itemId}); - - if (!item) throw new NotFound(res.t('checklistItemNotFound')); - item.completed = !item.completed; - let savedTask = await task.save(); - - res.respond(200, savedTask); - }, -}; - -/** - * @api {put} /api/v3/tasks/:taskId/checklist/:itemId Update a checklist item - * @apiVersion 3.0.0 - * @apiName UpdateChecklistItem - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {UUID} itemId The checklist item _id - * - * @apiSuccess {object} data The updated task - */ -api.updateChecklistItem = { - method: 'PUT', - url: '/tasks/:taskId/checklist/:itemId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let challenge; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - }).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - - let item = _.find(task.checklist, {id: req.params.itemId}); - if (!item) throw new NotFound(res.t('checklistItemNotFound')); - - _.merge(item, Tasks.Task.sanitizeChecklist(req.body)); - let savedTask = await task.save(); - - res.respond(200, savedTask); - if (challenge) challenge.updateTask(savedTask); - - return null; - }, -}; - -/** - * @api {delete} /api/v3/tasks/:taskId/checklist/:itemId Remove a checklist item - * @apiVersion 3.0.0 - * @apiName RemoveChecklistItem - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {UUID} itemId The checklist item _id - * - * @apiSuccess {object} data The updated task - */ -api.removeChecklistItem = { - method: 'DELETE', - url: '/tasks/:taskId/checklist/:itemId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let challenge; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - }).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - - let hasItem = removeFromArray(task.checklist, { id: req.params.itemId }); - if (!hasItem) throw new NotFound(res.t('checklistItemNotFound')); - - let savedTask = await task.save(); - res.respond(200, savedTask); - if (challenge) challenge.updateTask(savedTask); - - return null; - }, -}; - -/** - * @api {post} /api/v3/tasks/:taskId/tags/:tagId Add a tag to a task - * @apiVersion 3.0.0 - * @apiName AddTagToTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {UUID} tagId The tag id - * - * @apiSuccess {object} data The updated task - */ -api.addTagToTask = { - method: 'POST', - url: '/tasks/:taskId/tags/:tagId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - let userTags = user.tags.map(tag => tag.id); - req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - let tagId = req.params.tagId; - - let alreadyTagged = task.tags.indexOf(tagId) !== -1; - if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged')); - - task.tags.push(tagId); - - let savedTask = await task.save(); - res.respond(200, savedTask); - }, -}; - -/** - * @api {delete} /api/v3/tasks/:taskId/tags/:tagId Remove a tag from atask - * @apiVersion 3.0.0 - * @apiName RemoveTagFromTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {UUID} tagId The tag id - * - * @apiSuccess {object} data The updated task - */ -api.removeTagFromTask = { - method: 'DELETE', - url: '/tasks/:taskId/tags/:tagId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let task = await Tasks.Task.findOne({ - _id: req.params.taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - - let hasTag = removeFromArray(task.tags, req.params.tagId); - if (!hasTag) throw new NotFound(res.t('tagNotFound')); - - let savedTask = await task.save(); - res.respond(200, savedTask); - }, -}; - -/** - * @api {post} /api/v3/tasks/unlink-all/:challengeId Unlink all tasks from a challenge - * @apiVersion 3.0.0 - * @apiName UnlinkAllTasks - * @apiGroup Task - * - * @apiParam {UUID} challengeId The challenge _id - * @apiParam {string} keep Query parameter - keep-all or remove-all - * - * @apiSuccess {object} data An empty object - */ -api.unlinkAllTasks = { - method: 'POST', - url: '/tasks/unlink-all/:challengeId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID(); - req.checkQuery('keep', res.t('keepOrRemoveAll')).notEmpty().isIn(['keep-all', 'remove-all']); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let keep = req.query.keep; - let challengeId = req.params.challengeId; - - let tasks = await Tasks.Task.find({ - 'challenge.id': challengeId, - userId: user._id, - }).exec(); - - let validTasks = tasks.every(task => { - return task.challenge.broken; - }); - - if (!validTasks) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); - - if (keep === 'keep-all') { - await Bluebird.all(tasks.map(task => { - task.challenge = {}; - return task.save(); - })); - } else { // remove - let toSave = []; - - tasks.forEach(task => { - if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if - removeFromArray(user.tasksOrder[`${task.type}s`], task._id); - } - - toSave.push(task.remove()); - }); - - toSave.push(user.save()); - - await Bluebird.all(toSave); - } - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/tasks/unlink-one/:taskId Unlink a challenge task - * @apiVersion 3.0.0 - * @apiName UnlinkOneTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * @apiParam {string} keep Query parameter - keep or remove - * - * @apiSuccess {object} data An empty object - */ -api.unlinkOneTask = { - method: 'POST', - url: '/tasks/unlink-one/:taskId', - middlewares: [authWithHeaders()], - async handler (req, res) { - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - req.checkQuery('keep', res.t('keepOrRemove')).notEmpty().isIn(['keep', 'remove']); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let user = res.locals.user; - let keep = req.query.keep; - let taskId = req.params.taskId; - - let task = await Tasks.Task.findOne({ - _id: taskId, - userId: user._id, - }).exec(); - - if (!task) throw new NotFound(res.t('taskNotFound')); - if (!task.challenge.id) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); - if (!task.challenge.broken) throw new BadRequest(res.t('cantOnlyUnlinkChalTask')); - - if (keep === 'keep') { - task.challenge = {}; - await task.save(); - } else { // remove - if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if - removeFromArray(user.tasksOrder[`${task.type}s`], taskId); - await Bluebird.all([user.save(), task.remove()]); - } else { - await task.remove(); - } - } - - res.respond(200, {}); - }, -}; - -/** - * @api {post} /api/v3/tasks/clearCompletedTodos Delete user's completed todos - * @apiVersion 3.0.0 - * @apiName ClearCompletedTodos - * @apiGroup Task - * - * @apiSuccess {object} data An empty object - */ -api.clearCompletedTodos = { - method: 'POST', - url: '/tasks/clearCompletedTodos', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - - // Clear completed todos - // Do not delete challenges completed todos unless the task is broken - await Tasks.Task.remove({ - userId: user._id, - type: 'todo', - completed: true, - $or: [ - {'challenge.id': {$exists: false}}, - {'challenge.broken': {$exists: true}}, - ], - }).exec(); - - res.respond(200, {}); - }, -}; - -/** - * @api {delete} /api/v3/tasks/:taskId Delete a task given its id - * @apiVersion 3.0.0 - * @apiName DeleteTask - * @apiGroup Task - * - * @apiParam {UUID} taskId The task _id - * - * @apiSuccess {object} data An empty object - */ -api.deleteTask = { - method: 'DELETE', - url: '/tasks/:taskId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let user = res.locals.user; - let challenge; - - req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let taskId = req.params.taskId; - let task = await Tasks.Task.findById(taskId).exec(); - - if (!task) { - throw new NotFound(res.t('taskNotFound')); - } else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights - challenge = await Challenge.findOne({_id: task.challenge.id}).exec(); - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks')); - } else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one - throw new NotFound(res.t('taskNotFound')); - } else if (task.userId && task.challenge.id && !task.challenge.broken) { - throw new NotAuthorized(res.t('cantDeleteChallengeTasks')); - } - - if (task.type !== 'todo' || !task.completed) { - removeFromArray((challenge || user).tasksOrder[`${task.type}s`], taskId); - await Bluebird.all([(challenge || user).save(), task.remove()]); - } else { - await task.remove(); - } - - res.respond(200, {}); - if (challenge) challenge.removeTask(task); - - return null; - }, -}; - -module.exports = api; diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js deleted file mode 100644 index c3b03f620d..0000000000 --- a/website/server/controllers/api-v3/user.js +++ /dev/null @@ -1,1358 +0,0 @@ -import { authWithHeaders } from '../../middlewares/api-v3/auth'; -import common from '../../../../common'; -import { - NotFound, - BadRequest, - NotAuthorized, -} from '../../libs/api-v3/errors'; -import * as Tasks from '../../models/task'; -import { - basicFields as basicGroupFields, - model as Group, -} from '../../models/group'; -import { model as User } from '../../models/user'; -import Bluebird from 'bluebird'; -import _ from 'lodash'; -import * as firebase from '../../libs/api-v3/firebase'; -import * as passwordUtils from '../../libs/api-v3/password'; - -let api = {}; - -/** - * @api {get} /api/v3/user Get the authenticated user's profile - * @apiVersion 3.0.0 - * @apiName UserGet - * @apiGroup User - * - * @apiSuccess {Object} data The user object - */ -api.getUser = { - method: 'GET', - middlewares: [authWithHeaders()], - url: '/user', - async handler (req, res) { - let user = res.locals.user.toJSON(); - - // Remove apiToken from response TODO make it private at the user level? returned in signup/login - delete user.apiToken; - - // TODO move to model? (maybe virtuals, maybe in toJSON) - // NOTE: if an item is manually added to user.stats common/fns/predictableRandom must be tweaked - // so it's not considered. Otherwise the client will have it while the server won't and the results will be different. - user.stats.toNextLevel = common.tnl(user.stats.lvl); - user.stats.maxHealth = common.maxHealth; - user.stats.maxMP = common.statsComputed(user).maxMP; - - return res.respond(200, user); - }, -}; - -/** - * @api {get} /api/v3/user/inventory/buy Get the gear items available for purchase for the current user - * @apiVersion 3.0.0 - * @apiName UserGetBuyList - * @apiGroup User - * - * @apiSuccess {Object} data The buy list - */ -api.getBuyList = { - method: 'GET', - middlewares: [authWithHeaders()], - url: '/user/inventory/buy', - async handler (req, res) { - let list = _.cloneDeep(common.updateStore(res.locals.user)); - - // return text and notes strings - _.each(list, item => { - _.each(item, (itemPropVal, itemPropKey) => { - if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language); - }); - }); - - res.respond(200, list); - }, -}; - -let updatablePaths = [ - 'flags.customizationsNotification', - 'flags.showTour', - 'flags.tour', - 'flags.tutorial', - 'flags.communityGuidelinesAccepted', - 'flags.welcomed', - 'flags.cardReceived', - 'flags.warnedLowHealth', - 'flags.newStuff', - - 'achievements', - - 'party.order', - 'party.orderAscending', - 'party.quest.completed', - 'party.quest.RSVPNeeded', - - 'preferences', - 'profile', - 'stats', - 'inbox.optOut', -]; - -// This tells us for which paths users can call `PUT /user`. -// The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) -let acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (accumulator, val, leaf) => { - let found = _.find(updatablePaths, (rootPath) => { - return leaf.indexOf(rootPath) === 0; - }); - - if (found) accumulator[leaf] = true; - - return accumulator; -}, {}); - -let restrictedPUTSubPaths = [ - 'stats.class', - - 'preferences.disableClasses', - 'preferences.sleep', - 'preferences.webhooks', -]; - -_.each(restrictedPUTSubPaths, (removePath) => { - delete acceptablePUTPaths[removePath]; -}); - -let requiresPurchase = { - 'preferences.background': 'background', - 'preferences.shirt': 'shirt', - 'preferences.size': 'size', - 'preferences.skin': 'skin', - 'preferences.hair.bangs': 'hair.bangs', - 'preferences.hair.base': 'hair.base', - 'preferences.hair.beard': 'hair.beard', - 'preferences.hair.color': 'hair.color', - 'preferences.hair.flower': 'hair.flower', - 'preferences.hair.mustache': 'hair.mustache', -}; - -let checkPreferencePurchase = (user, path, item) => { - let itemPath = `${path}.${item}`; - let appearance = _.get(common.content.appearances, itemPath); - if (!appearance) return false; - if (appearance.price === 0) return true; - - return _.get(user.purchased, itemPath); -}; - -/** - * @api {put} /api/v3/user Update the user - * @apiDescription Example body: {'stats.hp':50, 'preferences.background': 'beach'} - * @apiVersion 3.0.0 - * @apiName UserUpdate - * @apiGroup User - * - * @apiSuccess {object} data The updated user object - */ -api.updateUser = { - method: 'PUT', - middlewares: [authWithHeaders()], - url: '/user', - async handler (req, res) { - let user = res.locals.user; - - _.each(req.body, (val, key) => { - let purchasable = requiresPurchase[key]; - - if (purchasable && !checkPreferencePurchase(user, purchasable, val)) { - throw new NotAuthorized(res.t('mustPurchaseToSet', { val, key })); - } - - if (acceptablePUTPaths[key]) { - _.set(user, key, val); - } else { - throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key })); - } - }); - - await user.save(); - return res.respond(200, user); - }, -}; - -/** - * @api {delete} /api/v3/user Delete an authenticated user's account - * @apiVersion 3.0.0 - * @apiName UserDelete - * @apiGroup User - * - * @apiParam {string} password The user's password (unless it's a Facebook account) - * - * @apiSuccess {Object} data An empty Object - */ -api.deleteUser = { - method: 'DELETE', - middlewares: [authWithHeaders()], - url: '/user', - async handler (req, res) { - let user = res.locals.user; - let plan = user.purchased.plan; - - req.checkBody({ - password: { - notEmpty: {errorMessage: res.t('missingPassword')}, - }, - }); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); - - if (plan && plan.customerId && !plan.dateTerminated) { - throw new NotAuthorized(res.t('cannotDeleteActiveAccount')); - } - - let types = ['party', 'guilds']; - let groupFields = basicGroupFields.concat(' leader memberCount'); - - let groupsUserIsMemberOf = await Group.getGroups({user, types, groupFields}); - - let groupLeavePromises = groupsUserIsMemberOf.map((group) => { - return group.leave(user, 'remove-all'); - }); - - await Bluebird.all(groupLeavePromises); - - await Tasks.Task.remove({ - userId: user._id, - }).exec(); - - await user.remove(); - - res.respond(200, {}); - - firebase.deleteUser(user._id); - }, -}; - -function _cleanChecklist (task) { - _.forEach(task.checklist, (c, i) => { - c.text = `item ${i}`; - }); -} - -/** - * @api {get} /api/v3/user/anonymized Get anonymized user data - * @apiVersion 3.0.0 - * @apiName UserGetAnonymized - * @apiGroup User - * - * @apiSuccess {Object} data.user - * @apiSuccess {Array} data.tasks - **/ -api.getUserAnonymized = { - method: 'GET', - middlewares: [authWithHeaders()], - url: '/user/anonymized', - async handler (req, res) { - let user = res.locals.user.toJSON(); - user.stats.toNextLevel = common.tnl(user.stats.lvl); - user.stats.maxHealth = common.maxHealth; - user.stats.maxMP = res.locals.user._statsComputed.maxMP; - - delete user.apiToken; - if (user.auth) { - delete user.auth.local; - delete user.auth.facebook; - } - delete user.newMessages; - delete user.profile; - delete user.purchased.plan; - delete user.contributor; - delete user.invitations; - delete user.items.special.nyeReceived; - delete user.items.special.valentineReceived; - delete user.webhooks; - delete user.achievements.challenges; - - _.forEach(user.inbox.messages, (msg) => { - msg.text = 'inbox message text'; - }); - _.forEach(user.tags, (tag) => { - tag.name = 'tag'; - tag.challenge = 'challenge'; - }); - - let query = { - userId: user._id, - $or: [ - { type: 'todo', completed: false }, - { type: { $in: ['habit', 'daily', 'reward'] } }, - ], - }; - let tasks = await Tasks.Task.find(query).exec(); - - _.forEach(tasks, (task) => { - task.text = 'task text'; - task.notes = 'task notes'; - if (task.type === 'todo' || task.type === 'daily') { - _cleanChecklist(task); - } - }); - - return res.respond(200, { user, tasks }); - }, -}; - -const partyMembersFields = 'profile.name stats achievements items.special'; - -/** - * @api {post} /api/v3/user/class/cast/:spellId Cast a skill (spell) on a target - * @apiVersion 3.0.0 - * @apiName UserCast - * @apiGroup User - * - * @apiParam {string} spellId The skill to cast - * @apiParam {UUID} targetId Optional query parameter, the id of the target when casting a skill on a party member or a task - * - * @apiSuccess data Will return the modified targets. For party members only the necessary fields will be populated. The user is always returned. - */ -api.castSpell = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/class/cast/:spellId', - async handler (req, res) { - let user = res.locals.user; - let spellId = req.params.spellId; - let targetId = req.query.targetId; - - // optional because not required by all targetTypes, presence is checked later if necessary - req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID(); - - let reqValidationErrors = req.validationErrors(); - if (reqValidationErrors) throw reqValidationErrors; - - let klass = common.content.spells.special[spellId] ? 'special' : user.stats.class; - let spell = common.content.spells[klass][spellId]; - - if (!spell) throw new NotFound(res.t('spellNotFound', {spellId})); - if (spell.mana > user.stats.mp) throw new NotAuthorized(res.t('notEnoughMana')); - if (spell.value > user.stats.gp && !spell.previousPurchase) throw new NotAuthorized(res.t('messageNotEnoughGold')); - if (spell.lvl > user.stats.lvl) throw new NotAuthorized(res.t('spellLevelTooHigh', {level: spell.lvl})); - - let targetType = spell.target; - - if (targetType === 'task') { - if (!targetId) throw new BadRequest(res.t('targetIdUUID')); - - let task = await Tasks.Task.findOne({ - _id: targetId, - userId: user._id, - }).exec(); - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.challenge.id) throw new BadRequest(res.t('challengeTasksNoCast')); - - spell.cast(user, task, req); - - let results = await Bluebird.all([ - user.save(), - task.save(), - ]); - - res.respond(200, { - user: results[0], - task: results[1], - }); - } else if (targetType === 'self') { - spell.cast(user, null, req); - await user.save(); - res.respond(200, { user }); - } else if (targetType === 'tasks') { // new target type in v3: when all the user's tasks are necessary - let tasks = await Tasks.Task.find({ - userId: user._id, - $or: [ // exclude challenge tasks - {'challenge.id': {$exists: false}}, - {'challenge.broken': {$exists: true}}, - ], - }).exec(); - - spell.cast(user, tasks, req); - - let toSave = tasks - .filter(t => t.isModified()) - .map(t => t.save()); - - toSave.unshift(user.save()); - let saved = await Bluebird.all(toSave); - - let response = { - tasks: saved, - user, - }; - - res.respond(200, response); - } else if (targetType === 'party' || targetType === 'user') { - let party = await Group.getGroup({groupId: 'party', user}); - // arrays of users when targetType is 'party' otherwise single users - let partyMembers; - - if (targetType === 'party') { - if (!party) { - partyMembers = [user]; // Act as solo party - } else { - partyMembers = await User - .find({ - 'party._id': party._id, - _id: { $ne: user._id }, // add separately - }) - // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save - // default values for non-selected fields and pre('save') will mess up thinking some values are missing - .exec(); - - partyMembers.unshift(user); - } - - spell.cast(user, partyMembers, req); - await Bluebird.all(partyMembers.map(m => m.save())); - } else { - if (!party && (!targetId || user._id === targetId)) { - partyMembers = user; - } else { - if (!targetId) throw new BadRequest(res.t('targetIdUUID')); - if (!party) throw new NotFound(res.t('partyNotFound')); - partyMembers = await User - .findOne({_id: targetId, 'party._id': party._id}) - // .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save - // default values for non-selected fields and pre('save') will mess up thinking some values are missing - .exec(); - } - - if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); - - spell.cast(user, partyMembers, req); - - if (partyMembers !== user) { - await Bluebird.all([ - user.save(), - partyMembers.save(), - ]); - } else { - await partyMembers.save(); // partyMembers is user - } - } - - let partyMembersRes = Array.isArray(partyMembers) ? partyMembers : [partyMembers]; - // Only return some fields. - // See comment above on why we can't just select the necessary fields when querying - partyMembersRes = partyMembersRes.map(partyMember => { - return common.pickDeep(partyMember.toJSON(), common.$w(partyMembersFields)); - }); - - res.respond(200, { - partyMembers: partyMembersRes, - user, - }); - - if (party && !spell.silent) { - let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``; - party.sendChat(message); - await party.save(); - } - } - }, -}; - -/** - * @api {post} /api/v3/user/sleep Make the user start / stop sleeping (resting in the Inn) - * @apiVersion 3.0.0 - * @apiName UserSleep - * @apiGroup User - * - * @apiSuccess {boolean} data user.preferences.sleep - */ -api.sleep = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/sleep', - async handler (req, res) { - let user = res.locals.user; - let sleepRes = common.ops.sleep(user); - await user.save(); - res.respond(200, ...sleepRes); - }, -}; - -/** - * @api {post} /api/v3/user/allocate Allocate an attribute point - * @apiVersion 3.0.0 - * @apiName UserAllocate - * @apiGroup User - * - * @apiParam {string} stat Query parameter - Defaults to 'str', mast be one of be of str, con, int or per - * - * @apiSuccess {Object} data user.stats - */ -api.allocate = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/allocate', - async handler (req, res) { - let user = res.locals.user; - let allocateRes = common.ops.allocate(user, req); - await user.save(); - res.respond(200, ...allocateRes); - }, -}; - -/** - * @api {post} /api/v3/user/allocate-now Allocate all attribute points - * @apiDescription Uses the user's chosen automatic allocation method, or if none, assigns all to STR. - * @apiVersion 3.0.0 - * @apiName UserAllocateNow - * @apiGroup User - * - * @apiSuccess {Object} data user.stats - */ -api.allocateNow = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/allocate-now', - async handler (req, res) { - let user = res.locals.user; - let allocateNowRes = common.ops.allocateNow(user, req); - await user.save(); - res.respond(200, ...allocateNowRes); - }, -}; - -/** - * @api {post} /user/buy/:key Buy gear, armoire or potion - * @apiDescription Under the hood uses UserBuyGear, UserBuyPotion and UserBuyArmoire - * @apiVersion 3.0.0 - * @apiName UserBuy - * @apiGroup User - * - * @apiParam {string} key The item to buy - */ -api.buy = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy/:key', - async handler (req, res) { - let user = res.locals.user; - let buyRes = common.ops.buy(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyRes); - }, -}; - -/** - * @api {post} /user/buy-gear/:key Buy a piece of gear - * @apiVersion 3.0.0 - * @apiName UserBuyGear - * @apiGroup User - * - * @apiParam {string} key The item to buy - * - * @apiSuccess {object} data.items user.items - * @apiSuccess {object} data.flags user.flags - * @apiSuccess {object} data.achievements user.achievements - * @apiSuccess {object} data.stats user.stats - * @apiSuccess {string} message Success message - */ -api.buyGear = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-gear/:key', - async handler (req, res) { - let user = res.locals.user; - let buyGearRes = common.ops.buyGear(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyGearRes); - }, -}; - -/** - * @api {post} /user/buy-armoire Buy an armoire item - * @apiVersion 3.0.0 - * @apiName UserBuyArmoire - * @apiGroup User - * - * @apiSuccess {object} data.items user.items - * @apiSuccess {object} data.flags user.flags - * @apiSuccess {object} data.armoire Extra item given by the armoire - * @apiSuccess {string} message Success message - */ -api.buyArmoire = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-armoire', - async handler (req, res) { - let user = res.locals.user; - let buyArmoireResponse = common.ops.buyArmoire(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyArmoireResponse); - }, -}; - -/** - * @api {post} /user/buy-health-potion Buy a health potion - * @apiVersion 3.0.0 - * @apiName UserBuyPotion - * @apiGroup User - * - * @apiSuccess {Object} data user.stats - * @apiSuccess {string} message Success message - */ -api.buyHealthPotion = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-health-potion', - async handler (req, res) { - let user = res.locals.user; - let buyHealthPotionResponse = common.ops.buyHealthPotion(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyHealthPotionResponse); - }, -}; - -/** - * @api {post} /user/buy-mystery-set/:key Buy a mystery set - * @apiVersion 3.0.0 - * @apiName UserBuyMysterySet - * @apiGroup User - * - * @apiParam {string} key The mystery set to buy - * - * @apiSuccess {Object} data.items user.items - * @apiSuccess {Object} data.purchasedPlanConsecutive user.purchased.plan.consecutive - * @apiSuccess {string} message Success message - */ -api.buyMysterySet = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-mystery-set/:key', - async handler (req, res) { - let user = res.locals.user; - let buyMysterySetRes = common.ops.buyMysterySet(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyMysterySetRes); - }, -}; - -/** - * @api {post} /api/v3/user/buy-quest/:key Buy a quest with gold - * @apiVersion 3.0.0 - * @apiName UserBuyQuest - * @apiGroup User - * - * @apiParam {string} key The quest scroll to buy - * - * @apiSuccess {Object} data `user.items.quests` - * @apiSuccess {string} message Success message - */ -api.buyQuest = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-quest/:key', - async handler (req, res) { - let user = res.locals.user; - let buyQuestRes = common.ops.buyQuest(user, req, res.analytics); - await user.save(); - res.respond(200, ...buyQuestRes); - }, -}; - -/** - * @api {post} /api/v3/user/buy-special-spell/:key Buy special "spell" item - * @apiDescription Includes gift cards (e.g., birthday card), and avatar Transformation Items and their antidotes (e.g., Snowball item and Salt reward). - * @apiVersion 3.0.0 - * @apiName UserBuySpecialSpell - * @apiGroup User - * - * @apiParam {string} key The special item to buy. Must be one of the keys from "content.special", such as birthday, snowball, salt. - * - * @apiSuccess {Object} data.stats user.stats - * @apiSuccess {Object} data.items user.items - * @apiSuccess {string} message Success message - */ -api.buySpecialSpell = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/buy-special-spell/:key', - async handler (req, res) { - let user = res.locals.user; - let buySpecialSpellRes = common.ops.buySpecialSpell(user, req); - await user.save(); - res.respond(200, ...buySpecialSpellRes); - }, -}; - -/** - * @api {post} /api/v3/user/hatch/:egg/:hatchingPotion Hatch a pet - * @apiVersion 3.0.0 - * @apiName UserHatch - * @apiGroup User - * - * @apiParam {string} egg The egg to use - * @apiParam {string} hatchingPotion The hatching potion to use - * - * @apiSuccess {Object} data user.items - * @apiSuccess {string} message - */ -api.hatch = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/hatch/:egg/:hatchingPotion', - async handler (req, res) { - let user = res.locals.user; - let hatchRes = common.ops.hatch(user, req); - await user.save(); - res.respond(200, ...hatchRes); - }, -}; - -/** - * @api {post} /api/v3/user/equip/:type/:key Equip an item - * @apiVersion 3.0.0 - * @apiName UserEquip - * @apiGroup User - * - * @apiParam {string} type The type of item to equip (mount, pet, costume or equipped) - * @apiParam {string} key The item to equip - * - * @apiSuccess {Object} data user.items - * @apiSuccess {string} message Optional success message - */ -api.equip = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/equip/:type/:key', - async handler (req, res) { - let user = res.locals.user; - let equipRes = common.ops.equip(user, req); - await user.save(); - res.respond(200, ...equipRes); - }, -}; - -/** - * @api {post} /api/v3/user/equip/:pet/:food Feed a pet - * @apiVersion 3.0.0 - * @apiName UserFeed - * @apiGroup User - * - * @apiParam {string} pet - * @apiParam {string} food - * - * @apiSuccess {number} data The pet value - * @apiSuccess {string} message Success message - */ -api.feed = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/feed/:pet/:food', - async handler (req, res) { - let user = res.locals.user; - let feedRes = common.ops.feed(user, req); - await user.save(); - res.respond(200, ...feedRes); - }, -}; - -/** -* @api {post} /api/v3/user/change-class Change class -* @apiDescription User must be at least level 10. If ?class is defined and user.flags.classSelected is false it'll change the class. If user.preferences.disableClasses it'll enable classes, otherwise it sets user.flags.classSelected to false (costs 3 gems) -* @apiVersion 3.0.0 -* @apiName UserChangeClass -* @apiGroup User -* -* @apiParam {string} class Query parameter - ?class={warrior|rogue|wizard|healer} -* -* @apiSuccess {object} data.flags user.flags -* @apiSuccess {object} data.stats user.stats -* @apiSuccess {object} data.preferences user.preferences -* @apiSuccess {object} data.items user.items -*/ -api.changeClass = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/change-class', - async handler (req, res) { - let user = res.locals.user; - let changeClassRes = common.ops.changeClass(user, req, res.analytics); - await user.save(); - res.respond(200, ...changeClassRes); - }, -}; - -/** -* @api {post} /api/v3/user/disable-classes Disable classes -* @apiVersion 3.0.0 -* @apiName UserDisableClasses -* @apiGroup User -* -* @apiSuccess {object} data.flags user.flags -* @apiSuccess {object} data.stats user.stats -* @apiSuccess {object} data.preferences user.preferences -*/ -api.disableClasses = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/disable-classes', - async handler (req, res) { - let user = res.locals.user; - let disableClassesRes = common.ops.disableClasses(user, req); - await user.save(); - res.respond(200, ...disableClassesRes); - }, -}; - -/** -* @api {post} /api/v3/user/purchase/:type/:key Purchase Gem or Gem-purchasable item -* @apiVersion 3.0.0 -* @apiName UserPurchase -* @apiGroup User -* -* @apiParam {string} type Type of item to purchase. Must be one of: gems, eggs, hatchingPotions, food, quests, or gear -* @apiParam {string} key Item's key (use "gem" for purchasing gems) -* -* @apiSuccess {object} data.items user.items -* @apiSuccess {number} data.balance user.balance -* @apiSuccess {string} message Success message -*/ -api.purchase = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/purchase/:type/:key', - async handler (req, res) { - let user = res.locals.user; - let purchaseRes = common.ops.purchase(user, req, res.analytics); - await user.save(); - res.respond(200, ...purchaseRes); - }, -}; - -/** -* @api {post} /api/v3/user/purchase-hourglass/:type/:key Purchase Hourglass-purchasable item -* @apiVersion 3.0.0 -* @apiName UserPurchaseHourglass -* @apiGroup User -* -* @apiParam {string} type The type of item to purchase (pets or mounts) -* @apiParam {string} key Ex: {MantisShrimp-Base}. The key for the mount/pet -* -* @apiSuccess {object} data.items user.items -* @apiSuccess {object} data.purchasedPlanConsecutive user.purchased.plan.consecutive -* @apiSuccess {string} message Success message -*/ -api.userPurchaseHourglass = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/purchase-hourglass/:type/:key', - async handler (req, res) { - let user = res.locals.user; - let purchaseHourglassRes = common.ops.purchaseHourglass(user, req, res.analytics); - await user.save(); - res.respond(200, ...purchaseHourglassRes); - }, -}; - -/** -* @api {post} /api/v3/user/read-card/:cardType Reads a card -* @apiVersion 3.0.0 -* @apiName UserReadCard -* @apiGroup User -* -* @apiParam {string} cardType Type of card to read -* -* @apiSuccess {object} data.specialItems user.items.special -* @apiSuccess {boolean} data.cardReceived user.flags.cardReceived -* @apiSuccess {string} message Success message -*/ -api.readCard = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/read-card/:cardType', - async handler (req, res) { - let user = res.locals.user; - let readCardRes = common.ops.readCard(user, req); - await user.save(); - res.respond(200, ...readCardRes); - }, -}; - -/** -* @api {post} /api/v3/user/open-mystery-item Open the Mystery Item box -* @apiVersion 3.0.0 -* @apiName UserOpenMysteryItem -* @apiGroup User -* -* @apiSuccess {Object} data user.items.gear.owned -* @apiSuccess {string} message Success message -*/ -api.userOpenMysteryItem = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/open-mystery-item', - async handler (req, res) { - let user = res.locals.user; - let openMysteryItemRes = common.ops.openMysteryItem(user, req, res.analytics); - await user.save(); - res.respond(200, ...openMysteryItemRes); - }, -}; - -/** -* @api {post} /api/v3/user/webhook Create a new webhook - BETA -* @apiVersion 3.0.0 -* @apiName UserAddWebhook -* @apiGroup User -* -* @apiParam {string} url Body parameter - The webhook's URL -* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled -* -* @apiSuccess {Object} data The created webhook -*/ -api.addWebhook = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/webhook', - async handler (req, res) { - let user = res.locals.user; - let addWebhookRes = common.ops.addWebhook(user, req); - await user.save(); - res.respond(200, ...addWebhookRes); - }, -}; - -/** -* @api {put} /api/v3/user/webhook/:id Edit a webhook - BETA -* @apiVersion 3.0.0 -* @apiName UserUpdateWebhook -* @apiGroup User -* -* @apiParam {UUID} id The id of the webhook to update -* @apiParam {string} url Body parameter - The webhook's URL -* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled -* -* @apiSuccess {Object} data The updated webhook -*/ -api.updateWebhook = { - method: 'PUT', - middlewares: [authWithHeaders()], - url: '/user/webhook/:id', - async handler (req, res) { - let user = res.locals.user; - let updateWebhookRes = common.ops.updateWebhook(user, req); - await user.save(); - res.respond(200, ...updateWebhookRes); - }, -}; - -/** -* @api {delete} /api/v3/user/webhook/:id Delete a webhook - BETA -* @apiVersion 3.0.0 -* @apiName UserDeleteWebhook -* @apiGroup User -* -* @apiParam {UUID} id The id of the webhook to delete -* -* @apiSuccess {Object} data The user webhooks -*/ -api.deleteWebhook = { - method: 'DELETE', - middlewares: [authWithHeaders()], - url: '/user/webhook/:id', - async handler (req, res) { - let user = res.locals.user; - let deleteWebhookRes = common.ops.deleteWebhook(user, req); - await user.save(); - res.respond(200, ...deleteWebhookRes); - }, -}; - - -/* @api {post} /api/v3/user/release-pets Release pets -* @apiVersion 3.0.0 -* @apiName UserReleasePets -* @apiGroup User -* -* @apiSuccess {Object} data.items `user.items.pets` -* @apiSuccess {string} message Success message -*/ -api.userReleasePets = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/release-pets', - async handler (req, res) { - let user = res.locals.user; - let releasePetsRes = common.ops.releasePets(user, req, res.analytics); - await user.save(); - res.respond(200, ...releasePetsRes); - }, -}; - -/** -* @api {post} /api/v3/user/release-both Release pets and mounts and grants Triad Bingo -* @apiVersion 3.0.0 -* @apiName UserReleaseBoth -* @apiGroup User - -* @apiSuccess {Object} data.achievements -* @apiSuccess {Object} data.items -* @apiSuccess {number} data.balance -* @apiSuccess {string} message Success message -*/ -api.userReleaseBoth = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/release-both', - async handler (req, res) { - let user = res.locals.user; - let releaseBothRes = common.ops.releaseBoth(user, req, res.analytics); - await user.save(); - res.respond(200, ...releaseBothRes); - }, -}; - -/** -* @api {post} /api/v3/user/release-mounts Release mounts -* @apiVersion 3.0.0 -* @apiName UserReleaseMounts -* @apiGroup User -* -* @apiSuccess {Object} data user.items.mounts -* @apiSuccess {string} message Success message -*/ -api.userReleaseMounts = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/release-mounts', - async handler (req, res) { - let user = res.locals.user; - let releaseMountsRes = common.ops.releaseMounts(user, req, res.analytics); - await user.save(); - res.respond(200, ...releaseMountsRes); - }, -}; - -/** -* @api {post} /api/v3/user/sell/:type/:key Sell a gold-sellable item owned by the user -* @apiVersion 3.0.0 -* @apiName UserSell -* @apiGroup User -* -* @apiParam {string} type The type of item to sell. Must be one of: eggs, hatchingPotions, or food -* @apiParam {string} key The key of the item -* -* @apiSuccess {Object} data.stats -* @apiSuccess {Object} data.items -* @apiSuccess {string} message Success message -*/ -api.userSell = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/sell/:type/:key', - async handler (req, res) { - let user = res.locals.user; - let sellRes = common.ops.sell(user, req); - await user.save(); - res.respond(200, ...sellRes); - }, -}; - -/** -* @api {post} /api/v3/user/unlock Unlock item or set of items by purchase -* @apiVersion 3.0.0 -* @apiName UserUnlock -* @apiGroup User -* -* @apiParam {string} path Query parameter. The path to unlock -* -* @apiSuccess {Object} data.purchased -* @apiSuccess {Object} data.items -* @apiSuccess {Object} data.preferences -* @apiSuccess {string} message -*/ -api.userUnlock = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/unlock', - async handler (req, res) { - let user = res.locals.user; - let unlockRes = common.ops.unlock(user, req); - await user.save(); - res.respond(200, ...unlockRes); - }, -}; - -/** -* @api {post} /api/v3/user/revive Revive user from death -* @apiVersion 3.0.0 -* @apiName UserRevive -* @apiGroup User -* -* @apiSuccess {Object} data user.items -* @apiSuccess {string} message Success message -*/ -api.userRevive = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/revive', - async handler (req, res) { - let user = res.locals.user; - let reviveRes = common.ops.revive(user, req, res.analytics); - await user.save(); - res.respond(200, ...reviveRes); - }, -}; - -/** -* @api {post} /api/v3/user/rebirth Use Orb of Rebirth on user -* @apiVersion 3.0.0 -* @apiName UserRebirth -* @apiGroup User -* -* @apiSuccess {Object} data.user -* @apiSuccess {array} data.tasks User's modified tasks (no rewards) -* @apiSuccess {string} message Success message -*/ -api.userRebirth = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/rebirth', - async handler (req, res) { - let user = res.locals.user; - let tasks = await Tasks.Task.find({ - userId: user._id, - type: {$in: ['daily', 'habit', 'todo']}, - $or: [ // exclude challenge tasks - {'challenge.id': {$exists: false}}, - {'challenge.broken': {$exists: true}}, - ], - }).exec(); - - let rebirthRes = common.ops.rebirth(user, tasks, req, res.analytics); - - let toSave = tasks.map(task => task.save()); - - toSave.push(user.save()); - - await Bluebird.all(toSave); - - res.respond(200, ...rebirthRes); - }, -}; - -/** - * @api {post} /api/v3/user/block/:uuid Block and unblock a user - * @apiDescription Must be an admin to make this request. - * @apiVersion 3.0.0 - * @apiName BlockUser - * @apiGroup User - * - * @apiParam {UUID} uuid The uuid of the user to block / unblock - * - * @apiSuccess {array} data user.inbox.blocks -**/ -api.blockUser = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/block/:uuid', - async handler (req, res) { - let user = res.locals.user; - let blockUserRes = common.ops.blockUser(user, req); - await user.save(); - res.respond(200, ...blockUserRes); - }, -}; - -/** - * @api {delete} /api/v3/user/messages/:id Delete a message - * @apiVersion 3.0.0 - * @apiName deleteMessage - * @apiGroup User - * - * @apiParam {UUID} id The id of the message to delete - * - * @apiSuccess {object} data user.inbox.messages -**/ -api.deleteMessage = { - method: 'DELETE', - middlewares: [authWithHeaders()], - url: '/user/messages/:id', - async handler (req, res) { - let user = res.locals.user; - let deletePMRes = common.ops.deletePM(user, req); - await user.save(); - res.respond(200, ...deletePMRes); - }, -}; - -/** - * @api {delete} /api/v3/user/messages Delete all messages - * @apiVersion 3.0.0 - * @apiName clearMessages - * @apiGroup User - * - * @apiSuccess {object} data user.inbox.messages -**/ -api.clearMessages = { - method: 'DELETE', - middlewares: [authWithHeaders()], - url: '/user/messages', - async handler (req, res) { - let user = res.locals.user; - let clearPMsRes = common.ops.clearPMs(user, req); - await user.save(); - res.respond(200, ...clearPMsRes); - }, -}; - -/** - * @api {post} /api/v3/user/mark-pms-read Marks Private Messages as read - * @apiVersion 3.0.0 - * @apiName markPmsRead - * @apiGroup User - * - * @apiSuccess {object} data user.inbox.messages -**/ -api.markPmsRead = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/mark-pms-read', - async handler (req, res) { - let user = res.locals.user; - let markPmsResponse = common.ops.markPmsRead(user, req); - await user.save(); - res.respond(200, markPmsResponse); - }, -}; - -/** -* @api {post} /api/v3/user/reroll Reroll a user using the Fortify Potion -* @apiVersion 3.0.0 -* @apiName UserReroll -* @apiGroup User -* -* @apiSuccess {Object} data.user -* @apiSuccess {Object} data.tasks User's modified tasks (no rewards) -* @apiSuccess {Object} message Success message -*/ -api.userReroll = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/reroll', - async handler (req, res) { - let user = res.locals.user; - let query = { - userId: user._id, - type: {$in: ['daily', 'habit', 'todo']}, - $or: [ // exclude challenge tasks - {'challenge.id': {$exists: false}}, - {'challenge.broken': {$exists: true}}, - ], - }; - let tasks = await Tasks.Task.find(query).exec(); - let rerollRes = common.ops.reroll(user, tasks, req, res.analytics); - - let promises = tasks.map(task => task.save()); - promises.push(user.save()); - - await Bluebird.all(promises); - - res.respond(200, ...rerollRes); - }, -}; - -/** -* @api {post} /api/v3/user/addPushDevice Add a push device to a user -* @apiVersion 3.0.0 -* @apiName UserAddPushDevice -* @apiGroup User -* -* @apiParam {string} regId The id of the push device -* @apiParam {string} uuid The type of push device -* -* @apiSuccess {Object} data List of push devices -* @apiSuccess {string} message Success message -*/ -api.userAddPushDevice = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/addPushDevice', - async handler (req, res) { - let user = res.locals.user; - - let addPushDeviceRes = common.ops.addPushDevice(user, req); - await user.save(); - - res.respond(200, ...addPushDeviceRes); - }, -}; - -/** -* @api {post} /api/v3/user/reset Reset user -* @apiVersion 3.0.0 -* @apiName UserReset -* @apiGroup User -* -* @apiSuccess {Object} data.user -* @apiSuccess {Object} data.tasksToRemove IDs of removed tasks -* @apiSuccess {string} message Success message -*/ -api.userReset = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/reset', - async handler (req, res) { - let user = res.locals.user; - - let tasks = await Tasks.Task.find({ - userId: user._id, - $or: [ // exclude challenge tasks - {'challenge.id': {$exists: false}}, - {'challenge.broken': {$exists: true}}, - ], - }).select('_id type challenge').exec(); - - let resetRes = common.ops.reset(user, tasks, req); - - await Bluebird.all([ - Tasks.Task.remove({_id: {$in: resetRes[0].tasksToRemove}, userId: user._id}), - user.save(), - ]); - - res.respond(200, ...resetRes); - }, -}; - -/** -* @api {post} /api/v3/user/custom-day-start Sets preferences.dayStart for user -* @apiVersion 3.0.0 -* @apiName setCustomDayStart -* @apiGroup User -* -* @apiSuccess {Object} data An empty Object -*/ -api.setCustomDayStart = { - method: 'POST', - middlewares: [authWithHeaders()], - url: '/user/custom-day-start', - async handler (req, res) { - let user = res.locals.user; - let dayStart = req.body.dayStart; - - user.preferences.dayStart = dayStart; - user.lastCron = new Date(); - - await user.save(); - - res.respond(200, { - message: res.t('customDayStartHasChanged'), - }); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/auth.js b/website/server/controllers/top-level/auth.js deleted file mode 100644 index 90724ab1f4..0000000000 --- a/website/server/controllers/top-level/auth.js +++ /dev/null @@ -1,16 +0,0 @@ -let api = {}; - -// Internal authentication routes - -// Logout the user from the website. -api.logout = { - method: 'GET', - url: '/logout', - async handler (req, res) { - if (req.logout) req.logout(); // passportjs method - req.session = null; - res.redirect('/'); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js deleted file mode 100644 index 08bc4a1f8e..0000000000 --- a/website/server/controllers/top-level/dataexport.js +++ /dev/null @@ -1,247 +0,0 @@ -import { authWithSession } from '../../middlewares/api-v3/auth'; -import { model as User } from '../../models/user'; -import * as Tasks from '../../models/task'; -import { - NotFound, -} from '../../libs/api-v3/errors'; -import _ from 'lodash'; -import csvStringify from '../../libs/api-v3/csvStringify'; -import moment from 'moment'; -import js2xml from 'js2xmlparser'; -import Pageres from 'pageres'; -import AWS from 'aws-sdk'; -import nconf from 'nconf'; -import got from 'got'; -import Bluebird from 'bluebird'; -import locals from '../../middlewares/api-v3/locals'; - -let S3 = new AWS.S3({ - accessKeyId: nconf.get('S3:accessKeyId'), - secretAccessKey: nconf.get('S3:secretAccessKey'), -}); -const S3_BUCKET = nconf.get('S3:bucket'); - -const BASE_URL = nconf.get('BASE_URL'); - -let api = {}; - -/** - * @api {get} /export/history.csv Export user tasks history in CSV format - * @apiDescription History is only available for habits and dailys so todos and rewards won't be included. NOTE: Part of the private API that may change at any time. - * @apiVersion 3.0.0 - * @apiName ExportUserHistory - * @apiGroup DataExport - * - * @apiSuccess {string} A cvs file - */ -api.exportUserHistory = { - method: 'GET', - url: '/export/history.csv', - middlewares: [authWithSession], - async handler (req, res) { - let user = res.locals.user; - - let tasks = await Tasks.Task.find({ - userId: user._id, - type: {$in: ['habit', 'daily']}, - }).exec(); - - let output = [ - ['Task Name', 'Task ID', 'Task Type', 'Date', 'Value'], - ]; - - tasks.forEach(task => { - task.history.forEach(history => { - output.push([ - task.text, - task._id, - task.type, - moment(history.date).format('YYYY-MM-DD HH:mm:ss'), - history.value, - ]); - }); - }); - - res.set({ - 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-tasks-history.csv', - }); - - let csvRes = await csvStringify(output); - res.status(200).send(csvRes); - }, -}; - -// Convert user to json and attach tasks divided by type -// at user.tasks[`${taskType}s`] (user.tasks.{dailys/habits/...}) -async function _getUserDataForExport (user) { - let userData = user.toJSON(); - userData.tasks = {}; - - let tasks = await Tasks.Task.find({ - userId: user._id, - }).exec(); - - tasks = _.chain(tasks) - .map(task => task.toJSON()) - .groupBy(task => task.type) - .each((tasksPerType, taskType) => { - userData.tasks[`${taskType}s`] = tasksPerType; - }) - .value(); - - return userData; -} - -/** - * @api {get} /export/userdata.json Export user data in JSON format - * @apiVersion 3.0.0 - * @apiName ExportUserDataJson - * @apiGroup DataExport - * @apiDescription NOTE: Part of the private API that may change at any time. - * - * @apiSuccess {string} A json file - */ -api.exportUserDataJson = { - method: 'GET', - url: '/export/userdata.json', - middlewares: [authWithSession], - async handler (req, res) { - let userData = await _getUserDataForExport(res.locals.user); - - res.set({ - 'Content-Type': 'application/json', - 'Content-disposition': 'attachment; filename=habitica-user-data.json', - }); - let jsonRes = JSON.stringify(userData); - - res.status(200).send(jsonRes); - }, -}; - -/** - * @api {get} /export/userdata.xml Export user data in XML format - * @apiVersion 3.0.0 - * @apiName ExportUserDataXml - * @apiGroup DataExport - * @apiDescription NOTE: Part of the private API that may change at any time. - * - * @apiSuccess {string} A xml file - */ -api.exportUserDataXml = { - method: 'GET', - url: '/export/userdata.xml', - middlewares: [authWithSession], - async handler (req, res) { - let userData = await _getUserDataForExport(res.locals.user); - - res.set({ - 'Content-Type': 'text/xml', - 'Content-disposition': 'attachment; filename=habitica-user-data.xml', - }); - res.status(200).send(js2xml('user', userData)); - }, -}; - -/** - * @api {get} /export/avatar-:uuid.html Render a user avatar as an HTML page - * @apiVersion 3.0.0 - * @apiName ExportUserAvatarHtml - * @apiGroup DataExport - * @apiDescription NOTE: Part of the private API that may change at any time. - * - * @apiSuccess {string} An html page - */ -api.exportUserAvatarHtml = { - method: 'GET', - url: '/export/avatar-:memberId.html', - middlewares: [locals], - async handler (req, res) { - req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let memberId = req.params.memberId; - let member = await User - .findById(memberId) - .select('stats profile items achievements preferences backer contributor') - .exec(); - - if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId})); - res.render('avatar-static', { - title: member.profile.name, - env: _.defaults({user: member}, res.locals.habitrpg), - }); - }, -}; - -/** - * @api {get} /export/avatar-:uuid.html Export a user avatar as a PNG file - * @apiVersion 3.0.0 - * @apiName ExportUserAvatarPng - * @apiGroup DataExport - * @apiDescription NOTE: Part of the private API that may change at any time. - * - * @apiSuccess {string} A png file - */ -api.exportUserAvatarPng = { - method: 'GET', - url: '/export/avatar-:memberId.png', - async handler (req, res) { - req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let memberId = req.params.memberId; - - let filename = `avatars/${memberId}.png`; - let s3url = `https://${S3_BUCKET}.s3.amazonaws.com/${filename}`; - - let response; - try { - response = await got.head(s3url); - } catch (gotError) { - if (gotError.code !== 'ENOTFOUND' && gotError.statusCode !== 404) { - throw gotError; - } - } - - // cache images for 30 minutes on aws, else upload a new one - if (response && response.statusCode === 200 && moment().diff(response.headers['last-modified'], 'minutes') < 30) { - return res.redirect(s3url); - } - - let [stream] = await new Pageres() - .src(`${BASE_URL}/export/avatar-${memberId}.html`, ['140x147'], { - crop: true, - filename: filename.replace('.png', ''), - }) - .run(); - - let s3upload = S3.upload({ - Bucket: S3_BUCKET, - Key: filename, - ACL: 'public-read', - StorageClass: 'REDUCED_REDUNDANCY', - ContentType: 'image/png', - Expires: moment().add({minutes: 5}).toDate(), - Body: stream, - }); - - let s3res = await new Bluebird((resolve, reject) => { - s3upload.send((err, s3uploadRes) => { - if (err) { - reject(err); - } else { - resolve(s3uploadRes); - } - }); - }); - - res.redirect(s3res.Location); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/email.js b/website/server/controllers/top-level/email.js deleted file mode 100644 index b84f54c6e1..0000000000 --- a/website/server/controllers/top-level/email.js +++ /dev/null @@ -1,54 +0,0 @@ -import { model as User } from '../../models/user'; -import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; -import { decrypt } from '../../libs/api-v3/encryption'; -import { - NotFound, -} from '../../libs/api-v3/errors'; - -let api = {}; - -/** - * @api {get} /api/v3/email/unsubscribe Unsubscribe an email or user from email notifications - * @apiDescription Does not require authentication - * @apiVersion 3.0.0 - * @apiName UnsubscribeEmail - * @apiGroup Unsubscribe - * @apiDescription This is a GET method so that you can put the unsubscribe link in emails. - * - * @apiParam {String} code Query parameter - An unsubscription code - * - * @apiSuccess {String} An html success message - */ -api.unsubscribe = { - method: 'GET', - url: '/email/unsubscribe', - async handler (req, res) { - req.checkQuery({ - code: { - notEmpty: {errorMessage: res.t('missingUnsubscriptionCode')}, - }, - }); - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let data = JSON.parse(decrypt(req.query.code)); - - if (data._id) { - let userUpdated = await User.update( - {_id: data._id}, - { $set: {'preferences.emailNotifications.unsubscribeFromAll': true}} - ); - - if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound')); - - res.send(`

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextUsers')}`); - } else { - let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email.toLowerCase()}); - let okResponse = `

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextOthers')}`; - if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email.toLowerCase()}); - res.send(okResponse); - } - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/pages.js b/website/server/controllers/top-level/pages.js deleted file mode 100644 index 614b1edc0a..0000000000 --- a/website/server/controllers/top-level/pages.js +++ /dev/null @@ -1,79 +0,0 @@ -import locals from '../../middlewares/api-v3/locals'; -import _ from 'lodash'; -import markdownIt from 'markdown-it'; - -const md = markdownIt({ - html: true, -}); - -let api = {}; - -const TOTAL_USER_COUNT = '1,100,000'; - -api.getFrontPage = { - method: 'GET', - url: '/', - middlewares: [locals], - runCron: false, - async handler (req, res) { - if (!req.header('x-api-user') && !req.header('x-api-key') && !(req.session && req.session.userId)) { - return res.redirect('/static/front'); - } - - return res.render('index.jade', { - title: 'Habitica | Your Life The Role Playing Game', - env: res.locals.habitrpg, - }); - }, -}; - -let staticPages = ['front', 'privacy', 'terms', 'api-v2', 'features', - 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', - 'old-news', 'press-kit', 'faq', 'overview', 'apps', - 'clear-browser-data', 'merch', 'maintenance-info']; - -_.each(staticPages, (name) => { - api[`get${name}Page`] = { - method: 'GET', - url: `/static/${name}`, - middlewares: [locals], - runCron: false, - async handler (req, res) { - return res.render(`static/${name}.jade`, { - env: res.locals.habitrpg, - md, - userCount: TOTAL_USER_COUNT, - }); - }, - }; -}); - -let shareables = ['level-up', 'hatch-pet', 'raise-pet', 'unlock-quest', 'won-challenge', 'achievement']; - -_.each(shareables, (name) => { - api[`get${name}ShareablePage`] = { - method: 'GET', - url: `/social/${name}`, - middlewares: [locals], - runCron: false, - async handler (req, res) { - return res.render(`social/${name}`, { - env: res.locals.habitrpg, - md, - userCount: TOTAL_USER_COUNT, - }); - }, - }; -}); - -api.redirectExtensionsPage = { - method: 'GET', - url: '/static/extensions', - runCron: false, - async handler (req, res) { - return res.redirect('http://habitica.wikia.com/wiki/App_and_Extension_Integrations'); - }, -}; - - -module.exports = api; diff --git a/website/server/controllers/top-level/payments/amazon.js b/website/server/controllers/top-level/payments/amazon.js deleted file mode 100644 index a22618fa23..0000000000 --- a/website/server/controllers/top-level/payments/amazon.js +++ /dev/null @@ -1,256 +0,0 @@ -import { - BadRequest, - NotAuthorized, -} from '../../../libs/api-v3/errors'; -import amzLib from '../../../libs/api-v3/amazonPayments'; -import { - authWithHeaders, - authWithUrl, -} from '../../../middlewares/api-v3/auth'; -import shared from '../../../../../common'; -import payments from '../../../libs/api-v3/payments'; -import moment from 'moment'; -import { model as Coupon } from '../../../models/coupon'; -import { model as User } from '../../../models/user'; -import cc from 'coupon-code'; - -let api = {}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /amazon/verifyAccessToken Amazon Payments: verify access token - * @apiVersion 3.0.0 - * @apiName AmazonVerifyAccessToken - * @apiGroup Payments - * - * @apiSuccess {Object} data Empty object - **/ -api.verifyAccessToken = { - method: 'POST', - url: '/amazon/verifyAccessToken', - middlewares: [authWithHeaders()], - async handler (req, res) { - let accessToken = req.body.access_token; - - if (!accessToken) throw new BadRequest('Missing req.body.access_token'); - - await amzLib.getTokenInfo(accessToken); - res.respond(200, {}); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /amazon/createOrderReferenceId Amazon Payments: create order reference id - * @apiVersion 3.0.0 - * @apiName AmazonCreateOrderReferenceId - * @apiGroup Payments - * - * @apiSuccess {string} data.orderReferenceId The order reference id. - **/ -api.createOrderReferenceId = { - method: 'POST', - url: '/amazon/createOrderReferenceId', - middlewares: [authWithHeaders()], - async handler (req, res) { - let billingAgreementId = req.body.billingAgreementId; - - if (!billingAgreementId) throw new BadRequest('Missing req.body.billingAgreementId'); - - let response = await amzLib.createOrderReferenceId({ - Id: billingAgreementId, - IdType: 'BillingAgreement', - ConfirmNow: false, - }); - - res.respond(200, { - orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId, - }); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /amazon/checkout Amazon Payments: checkout - * @apiVersion 3.0.0 - * @apiName AmazonCheckout - * @apiGroup Payments - * - * @apiSuccess {object} data Empty object - **/ -api.checkout = { - method: 'POST', - url: '/amazon/checkout', - middlewares: [authWithHeaders()], - async handler (req, res) { - let gift = req.body.gift; - let user = res.locals.user; - let orderReferenceId = req.body.orderReferenceId; - let amount = 5; - - if (!orderReferenceId) throw new BadRequest('Missing req.body.orderReferenceId'); - - if (gift) { - if (gift.type === 'gems') { - amount = gift.gems.amount / 4; - } else if (gift.type === 'subscription') { - amount = shared.content.subscriptionBlocks[gift.subscription.key].price; - } - } - - await amzLib.setOrderReferenceDetails({ - AmazonOrderReferenceId: orderReferenceId, - OrderReferenceAttributes: { - OrderTotal: { - CurrencyCode: 'USD', - Amount: amount, - }, - SellerNote: 'HabitRPG Payment', - SellerOrderAttributes: { - SellerOrderId: shared.uuid(), - StoreName: 'HabitRPG', - }, - }, - }); - - await amzLib.confirmOrderReference({ AmazonOrderReferenceId: orderReferenceId }); - - await amzLib.authorize({ - AmazonOrderReferenceId: orderReferenceId, - AuthorizationReferenceId: shared.uuid().substring(0, 32), - AuthorizationAmount: { - CurrencyCode: 'USD', - Amount: amount, - }, - SellerAuthorizationNote: 'HabitRPG Payment', - TransactionTimeout: 0, - CaptureNow: true, - }); - - await amzLib.closeOrderReference({ AmazonOrderReferenceId: orderReferenceId }); - - // execute payment - let method = 'buyGems'; - let data = { user, paymentMethod: 'Amazon Payments' }; - - if (gift) { - if (gift.type === 'subscription') method = 'createSubscription'; - gift.member = await User.findById(gift ? gift.uuid : undefined); - data.gift = gift; - data.paymentMethod = 'Gift'; - } - - await payments[method](data); - - res.respond(200); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /amazon/subscribe Amazon Payments: subscribe - * @apiVersion 3.0.0 - * @apiName AmazonSubscribe - * @apiGroup Payments - * - * @apiSuccess {object} data Empty object - **/ -api.subscribe = { - method: 'POST', - url: '/amazon/subscribe', - middlewares: [authWithHeaders()], - async handler (req, res) { - let billingAgreementId = req.body.billingAgreementId; - let sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false; - let coupon = req.body.coupon; - let user = res.locals.user; - - if (!sub) throw new BadRequest(res.t('missingSubscriptionCode')); - if (!billingAgreementId) throw new BadRequest('Missing req.body.billingAgreementId'); - - if (sub.discount) { // apply discount - if (!coupon) throw new BadRequest(res.t('couponCodeRequired')); - let result = await Coupon.findOne({_id: cc.validate(coupon), event: sub.key}); - if (!result) throw new NotAuthorized(res.t('invalidCoupon')); - } - - await amzLib.setBillingAgreementDetails({ - AmazonBillingAgreementId: billingAgreementId, - BillingAgreementAttributes: { - SellerNote: 'HabitRPG Subscription', - SellerBillingAgreementAttributes: { - SellerBillingAgreementId: shared.uuid(), - StoreName: 'HabitRPG', - CustomInformation: 'HabitRPG Subscription', - }, - }, - }); - - await amzLib.confirmBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId, - }); - - await amzLib.authorizeOnBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId, - AuthorizationReferenceId: shared.uuid().substring(0, 32), - AuthorizationAmount: { - CurrencyCode: 'USD', - Amount: sub.price, - }, - SellerAuthorizationNote: 'HabitRPG Subscription Payment', - TransactionTimeout: 0, - CaptureNow: true, - SellerNote: 'HabitRPG Subscription Payment', - SellerOrderAttributes: { - SellerOrderId: shared.uuid(), - StoreName: 'HabitRPG', - }, - }); - - await payments.createSubscription({ - user, - customerId: billingAgreementId, - paymentMethod: 'Amazon Payments', - sub, - }); - - res.respond(200); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /amazon/subscribe/cancel Amazon Payments: subscribe cancel - * @apiVersion 3.0.0 - * @apiName AmazonSubscribe - * @apiGroup Payments - **/ -api.subscribeCancel = { - method: 'GET', - url: '/amazon/subscribe/cancel', - middlewares: [authWithUrl], - async handler (req, res) { - let user = res.locals.user; - let billingAgreementId = user.purchased.plan.customerId; - - if (!billingAgreementId) throw new NotAuthorized(res.t('missingSubscription')); - - await amzLib.closeBillingAgreement({ - AmazonBillingAgreementId: billingAgreementId, - }); - - await payments.cancelSubscription({ - user, - nextBill: moment(user.purchased.plan.lastBillingDate).add({ days: 30 }), - paymentMethod: 'Amazon Payments', - }); - - if (req.query.noRedirect) { - res.respond(200); - } else { - res.redirect('/'); - } - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/payments/iap.js b/website/server/controllers/top-level/payments/iap.js deleted file mode 100644 index e99590fa71..0000000000 --- a/website/server/controllers/top-level/payments/iap.js +++ /dev/null @@ -1,191 +0,0 @@ -import iap from 'in-app-purchase'; -import nconf from 'nconf'; -import { - authWithHeaders, - authWithUrl, -} from '../../../middlewares/api-v3/auth'; -import payments from '../../../libs/api-v3/payments'; - -// NOT PORTED TO v3 - -iap.config({ - // this is the path to the directory containing iap-sanbox/iap-live files - googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR'), -}); - -// Validation ERROR Codes -const INVALID_PAYLOAD = 6778001; -// const CONNECTION_FAILED = 6778002; -// const PURCHASE_EXPIRED = 6778003; - -let api = {}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /iap/android/verify Android Verify IAP - * @apiVersion 3.0.0 - * @apiName IapAndroidVerify - * @apiGroup Payments - **/ -api.iapAndroidVerify = { - method: 'POST', - url: '/iap/android/verify', - middlewares: [authWithUrl], - async handler (req, res) { - let user = res.locals.user; - let iapBody = req.body; - - iap.setup((error) => { - if (error) { - let resObj = { - ok: false, - data: 'IAP Error', - }; - - return res.json(resObj); - } - - // google receipt must be provided as an object - // { - // "data": "{stringified data object}", - // "signature": "signature from google" - // } - let testObj = { - data: iapBody.transaction.receipt, - signature: iapBody.transaction.signature, - }; - - // iap is ready - iap.validate(iap.GOOGLE, testObj, (err, googleRes) => { - if (err) { - let resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString(), - }, - }; - - return res.json(resObj); - } - - if (iap.isValidated(googleRes)) { - let resObj = { - ok: true, - data: googleRes, - }; - - payments.buyGems({ - user, - paymentMethod: 'IAP GooglePlay', - amount: 5.25, - }).then(() => res.json(resObj)); - } - }); - }); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /iap/ios/verify iOS Verify IAP - * @apiVersion 3.0.0 - * @apiName IapiOSVerify - * @apiGroup Payments - **/ -api.iapiOSVerify = { - method: 'POST', - url: '/iap/ios/verify', - middlewares: [authWithHeaders()], - async handler (req, res) { - let iapBody = req.body; - let user = res.locals.user; - - iap.setup(function iosSetupResult (error) { - if (error) { - let resObj = { - ok: false, - data: 'IAP Error', - }; - - return res.json(resObj); - } - - // iap is ready - iap.validate(iap.APPLE, iapBody.transaction.receipt, (err, appleRes) => { - if (err) { - let resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: err.toString(), - }, - }; - - return res.json(resObj); - } - - if (iap.isValidated(appleRes)) { - let purchaseDataList = iap.getPurchaseData(appleRes); - if (purchaseDataList.length > 0) { - let correctReceipt = true; - - for (let index in purchaseDataList) { - switch (purchaseDataList[index].productId) { - case 'com.habitrpg.ios.Habitica.4gems': - payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 1}); - break; - case 'com.habitrpg.ios.Habitica.8gems': - payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 2}); - break; - case 'com.habitrpg.ios.Habitica.20gems': - case 'com.habitrpg.ios.Habitica.21gems': - payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 5.25}); - break; - case 'com.habitrpg.ios.Habitica.42gems': - payments.buyGems({user, paymentMethod: 'IAP AppleStore', amount: 10.5}); - break; - default: - correctReceipt = false; - } - } - - if (correctReceipt) { - let resObj = { - ok: true, - data: appleRes, - }; - - // yay good! - return res.json(resObj); - } - } - - // wrong receipt content - let resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: 'Incorrect receipt content', - }, - }; - - return res.json(resObj); - } - - // invalid receipt - let resObj = { - ok: false, - data: { - code: INVALID_PAYLOAD, - message: 'Invalid receipt', - }, - }; - - return res.json(resObj); - }); - }); - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js deleted file mode 100644 index 4bcd2d0735..0000000000 --- a/website/server/controllers/top-level/payments/paypal.js +++ /dev/null @@ -1,278 +0,0 @@ -/* eslint-disable camelcase */ - -import nconf from 'nconf'; -import moment from 'moment'; -import _ from 'lodash'; -import payments from '../../../libs/api-v3/payments'; -import ipn from 'paypal-ipn'; -import paypal from 'paypal-rest-sdk'; -import shared from '../../../../../common'; -import cc from 'coupon-code'; -import Bluebird from 'bluebird'; -import { model as Coupon } from '../../../models/coupon'; -import { model as User } from '../../../models/user'; -import { - authWithUrl, - authWithSession, -} from '../../../middlewares/api-v3/auth'; -import { - BadRequest, - NotAuthorized, -} from '../../../libs/api-v3/errors'; - -const BASE_URL = nconf.get('BASE_URL'); - -// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have -// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created -// there, get it's plan.id and store it in config.json -_.each(shared.content.subscriptionBlocks, (block) => { - block.paypalKey = nconf.get(`PAYPAL:billing_plans:${block.key}`); -}); - -paypal.configure({ - mode: nconf.get('PAYPAL:mode'), // sandbox or live - client_id: nconf.get('PAYPAL:client_id'), - client_secret: nconf.get('PAYPAL:client_secret'), -}); - -// TODO better handling of errors -const paypalPaymentCreate = Bluebird.promisify(paypal.payment.create, {context: paypal.payment}); -const paypalPaymentExecute = Bluebird.promisify(paypal.payment.execute, {context: paypal.payment}); -const paypalBillingAgreementCreate = Bluebird.promisify(paypal.billingAgreement.create, {context: paypal.billingAgreement}); -const paypalBillingAgreementExecute = Bluebird.promisify(paypal.billingAgreement.execute, {context: paypal.billingAgreement}); -const paypalBillingAgreementGet = Bluebird.promisify(paypal.billingAgreement.get, {context: paypal.billingAgreement}); -const paypalBillingAgreementCancel = Bluebird.promisify(paypal.billingAgreement.cancel, {context: paypal.billingAgreement}); - -const ipnVerifyAsync = Bluebird.promisify(ipn.verify, {context: ipn}); - -let api = {}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /paypal/checkout Paypal: checkout - * @apiVersion 3.0.0 - * @apiName PaypalCheckout - * @apiGroup Payments - **/ -api.checkout = { - method: 'GET', - url: '/paypal/checkout', - middlewares: [authWithUrl], - async handler (req, res) { - let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; - req.session.gift = req.query.gift; - - let amount = 5.00; - let description = 'HabitRPG gems'; - if (gift) { - if (gift.type === 'gems') { - amount = Number(gift.gems.amount / 4).toFixed(2); - description = `${description} (Gift)`; - } else { - amount = Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2); - description = 'mo. HabitRPG Subscription (Gift)'; - } - } - - let createPayment = { - intent: 'sale', - payer: { payment_method: 'Paypal' }, - redirect_urls: { - return_url: `${BASE_URL}/paypal/checkout/success`, - cancel_url: `${BASE_URL}`, - }, - transactions: [{ - item_list: { - items: [{ - name: description, - // sku: 1, - price: amount, - currency: 'USD', - quantity: 1, - }], - }, - amount: { - currency: 'USD', - total: amount, - }, - description, - }], - }; - - let result = await paypalPaymentCreate(createPayment); - let link = _.find(result.links, { rel: 'approval_url' }).href; - res.redirect(link); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /paypal/checkout/success Paypal: checkout success - * @apiVersion 3.0.0 - * @apiName PaypalCheckoutSuccess - * @apiGroup Payments - **/ -api.checkoutSuccess = { - method: 'GET', - url: '/paypal/checkout/success', - middlewares: [authWithSession], - async handler (req, res) { - let paymentId = req.query.paymentId; - let customerId = req.query.payerID; - - let method = 'buyGems'; - let data = { - user: res.locals.user, - customerId, - paymentMethod: 'Paypal', - }; - - let gift = req.session.gift ? JSON.parse(req.session.gift) : undefined; - delete req.session.gift; - - if (gift) { - gift.member = await User.findById(gift.uuid); - if (gift.type === 'subscription') { - method = 'createSubscription'; - } - - data.paymentMethod = 'Gift'; - data.gift = gift; - } - - await paypalPaymentExecute(paymentId, { payer_id: customerId }); - await payments[method](data); - res.redirect('/'); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /paypal/subscribe Paypal: subscribe - * @apiVersion 3.0.0 - * @apiName PaypalSubscribe - * @apiGroup Payments - **/ -api.subscribe = { - method: 'GET', - url: '/paypal/subscribe', - middlewares: [authWithUrl], - async handler (req, res) { - let sub = shared.content.subscriptionBlocks[req.query.sub]; - - if (sub.discount) { - if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); - let coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); - if (!coupon) throw new NotAuthorized(res.t('invalidCoupon')); - } - - let billingPlanTitle = `HabitRPG Subscription ($${sub.price} every ${sub.months} months, recurring)`; - let billingAgreementAttributes = { - name: billingPlanTitle, - description: billingPlanTitle, - start_date: moment().add({ minutes: 5 }).format(), - plan: { - id: sub.paypalKey, - }, - payer: { - payment_method: 'Paypal', - }, - }; - let billingAgreement = await paypalBillingAgreementCreate(billingAgreementAttributes); - - req.session.paypalBlock = req.query.sub; - let link = _.find(billingAgreement.links, { rel: 'approval_url' }).href; - res.redirect(link); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /paypal/subscribe/success Paypal: subscribe success - * @apiVersion 3.0.0 - * @apiName PaypalSubscribeSuccess - * @apiGroup Payments - **/ -api.subscribeSuccess = { - method: 'GET', - url: '/paypal/subscribe/success', - middlewares: [authWithSession], - async handler (req, res) { - let user = res.locals.user; - let block = shared.content.subscriptionBlocks[req.session.paypalBlock]; - delete req.session.paypalBlock; - - let result = await paypalBillingAgreementExecute(req.query.token, {}); - await payments.createSubscription({ - user, - customerId: result.id, - paymentMethod: 'Paypal', - sub: block, - }); - - res.redirect('/'); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /paypal/subscribe/cancel Paypal: subscribe cancel - * @apiVersion 3.0.0 - * @apiName PaypalSubscribeCancel - * @apiGroup Payments - **/ -api.subscribeCancel = { - method: 'GET', - url: '/paypal/subscribe/cancel', - middlewares: [authWithUrl], - async handler (req, res) { - let user = res.locals.user; - let customerId = user.purchased.plan.customerId; - if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription')); - - let customer = await paypalBillingAgreementGet(customerId); - - let nextBillingDate = customer.agreement_details.next_billing_date; - if (customer.agreement_details.cycles_completed === '0') { // hasn't billed yet - throw new BadRequest(res.t('planNotActive', { nextBillingDate })); - } - - await paypalBillingAgreementCancel(customerId, { note: res.t('cancelingSubscription') }); - await payments.cancelSubscription({ - user, - paymentMethod: 'Paypal', - nextBill: nextBillingDate, - }); - - res.redirect('/'); - }, -}; - -// General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their -// recurring paypal payments in their paypal dashboard. TODO ? Remove this when we can move to webhooks or some other solution - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /paypal/ipn Paypal IPN - * @apiVersion 3.0.0 - * @apiName PaypalIpn - * @apiGroup Payments - **/ -api.ipn = { - method: 'POST', - url: '/paypal/ipn', - async handler (req, res) { - res.sendStatus(200); - - await ipnVerifyAsync(req.body); - - if (req.body.txn_type === 'recurring_payment_profile_cancel' || req.body.txn_type === 'subscr_cancel') { - let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }); - if (user) { - await payments.cancelSubscription({ user, paymentMethod: 'Paypal' }); - } - } - }, -}; - -module.exports = api; diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js deleted file mode 100644 index 2ac8c863f7..0000000000 --- a/website/server/controllers/top-level/payments/stripe.js +++ /dev/null @@ -1,169 +0,0 @@ -import stripeModule from 'stripe'; -import shared from '../../../../../common'; -import { - BadRequest, - NotAuthorized, -} from '../../../libs/api-v3/errors'; -import { model as Coupon } from '../../../models/coupon'; -import payments from '../../../libs/api-v3/payments'; -import nconf from 'nconf'; -import { model as User } from '../../../models/user'; -import cc from 'coupon-code'; -import { - authWithHeaders, - authWithUrl, -} from '../../../middlewares/api-v3/auth'; - -const stripe = stripeModule(nconf.get('STRIPE_API_KEY')); - -let api = {}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /stripe/checkout Stripe checkout - * @apiVersion 3.0.0 - * @apiName StripeCheckout - * @apiGroup Payments - * - * @apiParam {string} id Body parameter - The token - * @apiParam {string} email Body parameter - the customer email - * @apiParam {string} gift Query parameter - stringified json object, gift - * @apiParam {string} sub Query parameter - subscription, possible values are: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo - * @apiParam {string} coupon Query parameter - coupon for the matching subscription, required only for certain subscriptions - * - * @apiSuccess {Object} data Empty object - **/ -api.checkout = { - method: 'POST', - url: '/stripe/checkout', - middlewares: [authWithHeaders()], - async handler (req, res) { - let token = req.body.id; - let user = res.locals.user; - let gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; - let sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false; - let coupon; - let response; - - if (!token) throw new BadRequest('Missing req.body.id'); - - if (sub) { - if (sub.discount) { - if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); - coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); - if (!coupon) throw new BadRequest(res.t('invalidCoupon')); - } - - response = await stripe.customers.create({ - email: req.body.email, - metadata: { uuid: user._id }, - card: token, - plan: sub.key, - }); - } else { - let amount = 500; // $5 - - if (gift) { - if (gift.type === 'subscription') { - amount = `${shared.content.subscriptionBlocks[gift.subscription.key].price * 100}`; - } else { - amount = `${gift.gems.amount / 4 * 100}`; - } - } - - response = await stripe.charges.create({ - amount, - currency: 'usd', - card: token, - }); - } - - if (sub) { - await payments.createSubscription({ - user, - customerId: response.id, - paymentMethod: 'Stripe', - sub, - }); - } else { - let method = 'buyGems'; - let data = { - user, - customerId: response.id, - paymentMethod: 'Stripe', - gift, - }; - - if (gift) { - let member = await User.findById(gift.uuid); - gift.member = member; - if (gift.type === 'subscription') method = 'createSubscription'; - data.paymentMethod = 'Gift'; - } - - await payments[method](data); - } - - res.respond(200, {}); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {post} /stripe/subscribe/edit Edit Stripe subscription - * @apiVersion 3.0.0 - * @apiName StripeSubscribeEdit - * @apiGroup Payments - * - * @apiParam {string} id Body parameter - The token - * - * @apiSuccess {Object} data Empty object - **/ -api.subscribeEdit = { - method: 'POST', - url: '/stripe/subscribe/edit', - middlewares: [authWithHeaders()], - async handler (req, res) { - let token = req.body.id; - let user = res.locals.user; - let customerId = user.purchased.plan.customerId; - - if (!customerId) throw new NotAuthorized(res.t('missingSubscription')); - if (!token) throw new BadRequest('Missing req.body.id'); - - let subscriptions = await stripe.customers.listSubscriptions(customerId); - let subscriptionId = subscriptions.data[0].id; - await stripe.customers.updateSubscription(customerId, subscriptionId, { card: token }); - - res.respond(200, {}); - }, -}; - -/** - * @apiIgnore Payments are considered part of the private API - * @api {get} /stripe/subscribe/cancel Cancel Stripe subscription - * @apiVersion 3.0.0 - * @apiName StripeSubscribeCancel - * @apiGroup Payments - **/ -api.subscribeCancel = { - method: 'GET', - url: '/stripe/subscribe/cancel', - middlewares: [authWithUrl], - async handler (req, res) { - let user = res.locals.user; - if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription')); - - let customer = await stripe.customers.retrieve(user.purchased.plan.customeerId); - await stripe.customers.del(user.purchased.plan.customerId); - await payments.cancelSubscriptoin({ - user, - nextBill: customer.subscription.current_period_end * 1000, // timestamp in seconds - paymentMethod: 'Stripe', - }); - - res.redirect('/'); - }, -}; - -module.exports = api; diff --git a/website/server/index.js b/website/server/index.js deleted file mode 100644 index ec8e882c99..0000000000 --- a/website/server/index.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; -/* eslint-disable global-require, no-process-env */ - -// Register babel hook so we can write the real entry file (server.js) in ES6 -// In production, the es6 code is pre-transpiled so it doesn't need it -if (process.env.NODE_ENV !== 'production') { - require('babel-register'); -} - -// The BabelJS polyfill is needed in production too -require('babel-polyfill'); - -// Setup Bluebird as the global promise library -global.Promise = require('bluebird'); - -// Initialize configuration BEFORE anything -const setupNconf = require('./libs/api-v3/setupNconf'); -setupNconf(); - -const nconf = require('nconf'); - -const cluster = require('cluster'); -const logger = require('./libs/api-v3/logger'); - -const IS_PROD = nconf.get('IS_PROD'); -const IS_DEV = nconf.get('IS_DEV'); -const CORES = Number(nconf.get('WEB_CONCURRENCY')) || 0; - -// Initialize New Relic -if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') require('newrelic'); - -// Setup the cluster module -if (CORES !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) { - // Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production) - for (let i = 0; i < CORES; i += 1) { - cluster.fork(); - } - - cluster.on('disconnect', function onWorkerDisconnect (worker) { - let w = cluster.fork(); // replace the dead worker - - logger.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); - }); -} else { - module.exports = require('./server.js'); -} diff --git a/website/server/libs/api-v3/amazonPayments.js b/website/server/libs/api-v3/amazonPayments.js deleted file mode 100644 index 4d3a3756b8..0000000000 --- a/website/server/libs/api-v3/amazonPayments.js +++ /dev/null @@ -1,62 +0,0 @@ -import amazonPayments from 'amazon-payments'; -import nconf from 'nconf'; -import common from '../../../../common'; -import Bluebird from 'bluebird'; -import { - BadRequest, -} from './errors'; - -// TODO better handling of errors - -const i18n = common.i18n; -const IS_PROD = nconf.get('NODE_ENV') === 'production'; - -let amzPayment = amazonPayments.connect({ - environment: amazonPayments.Environment[IS_PROD ? 'Production' : 'Sandbox'], - sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), - mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'), - mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'), - clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'), -}); - -let getTokenInfo = Bluebird.promisify(amzPayment.api.getTokenInfo, {context: amzPayment.api}); -let createOrderReferenceId = Bluebird.promisify(amzPayment.offAmazonPayments.createOrderReferenceForId, {context: amzPayment.offAmazonPayments}); -let setOrderReferenceDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setOrderReferenceDetails, {context: amzPayment.offAmazonPayments}); -let confirmOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.confirmOrderReference, {context: amzPayment.offAmazonPayments}); -let closeOrderReference = Bluebird.promisify(amzPayment.offAmazonPayments.closeOrderReference, {context: amzPayment.offAmazonPayments}); -let setBillingAgreementDetails = Bluebird.promisify(amzPayment.offAmazonPayments.setBillingAgreementDetails, {context: amzPayment.offAmazonPayments}); -let confirmBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.confirmBillingAgreement, {context: amzPayment.offAmazonPayments}); -let closeBillingAgreement = Bluebird.promisify(amzPayment.offAmazonPayments.closeBillingAgreement, {context: amzPayment.offAmazonPayments}); - -let authorizeOnBillingAgreement = (inputSet) => { - return new Promise((resolve, reject) => { - amzPayment.offAmazonPayments.authorizeOnBillingAgreement(inputSet, (err, response) => { - if (err) return reject(err); - if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful'))); - return resolve(response); - }); - }); -}; - -let authorize = (inputSet) => { - return new Promise((resolve, reject) => { - amzPayment.offAmazonPayments.authorize(inputSet, (err, response) => { - if (err) return reject(err); - if (response.AuthorizationDetails.AuthorizationStatus.State === 'Declined') return reject(new BadRequest(i18n.t('paymentNotSuccessful'))); - return resolve(response); - }); - }); -}; - -module.exports = { - getTokenInfo, - createOrderReferenceId, - setOrderReferenceDetails, - confirmOrderReference, - closeOrderReference, - confirmBillingAgreement, - setBillingAgreementDetails, - closeBillingAgreement, - authorizeOnBillingAgreement, - authorize, -}; diff --git a/website/server/libs/api-v3/analyticsService.js b/website/server/libs/api-v3/analyticsService.js deleted file mode 100644 index b810ac1858..0000000000 --- a/website/server/libs/api-v3/analyticsService.js +++ /dev/null @@ -1,237 +0,0 @@ -/* eslint-disable camelcase */ -import nconf from 'nconf'; -import Amplitude from 'amplitude'; -import Bluebird from 'bluebird'; -import googleAnalytics from 'universal-analytics'; -import { - each, - omit, -} from 'lodash'; -import { content as Content } from '../../../../common'; - -const AMPLIUDE_TOKEN = nconf.get('AMPLITUDE_KEY'); -const GA_TOKEN = nconf.get('GA_ID'); -const GA_POSSIBLE_LABELS = ['gaLabel', 'itemKey']; -const GA_POSSIBLE_VALUES = ['gaValue', 'gemCost', 'goldCost']; -const AMPLITUDE_PROPERTIES_TO_SCRUB = ['uuid', 'user', 'purchaseValue', 'gaLabel', 'gaValue']; - -let amplitude = new Amplitude(AMPLIUDE_TOKEN); -let ga = googleAnalytics(GA_TOKEN); - -let _lookUpItemName = (itemKey) => { - if (!itemKey) return; - - let gear = Content.gear.flat[itemKey]; - let egg = Content.eggs[itemKey]; - let food = Content.food[itemKey]; - let hatchingPotion = Content.hatchingPotions[itemKey]; - let quest = Content.quests[itemKey]; - let spell = Content.special[itemKey]; - - let itemName; - - if (gear) { - itemName = gear.text(); - } else if (egg) { - itemName = `${egg.text()} Egg`; - } else if (food) { - itemName = food.text(); - } else if (hatchingPotion) { - itemName = `${hatchingPotion.text()} Hatching Potion`; - } else if (quest) { - itemName = quest.text(); - } else if (spell) { - itemName = spell.text(); - } - - return itemName; -}; - -let _formatUserData = (user) => { - let properties = {}; - - if (user.stats) { - properties.Class = user.stats.class; - properties.Experience = Math.floor(user.stats.exp); - properties.Gold = Math.floor(user.stats.gp); - properties.Health = Math.ceil(user.stats.hp); - properties.Level = user.stats.lvl; - properties.Mana = Math.floor(user.stats.mp); - } - - properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2; - - if (user.habits && user.dailys && user.todos && user.rewards) { - properties['Number Of Tasks'] = { - habits: user.habits.length, - dailys: user.dailys.length, - todos: user.todos.length, - rewards: user.rewards.length, - }; - } - - if (user.contributor && user.contributor.level) { - properties.contributorLevel = user.contributor.level; - } - - if (user.purchased && user.purchased.plan.planId) { - properties.subscription = user.purchased.plan.planId; - } - - return properties; -}; - - -let _formatDataForAmplitude = (data) => { - let event_properties = omit(data, AMPLITUDE_PROPERTIES_TO_SCRUB); - - let ampData = { - user_id: data.uuid || 'no-user-id-was-provided', - platform: 'server', - event_properties, - }; - - if (data.user) { - ampData.user_properties = _formatUserData(data.user); - } - - let itemName = _lookUpItemName(data.itemKey); - - if (itemName) { - event_properties.itemName = itemName; - } - - return ampData; -}; - -let _sendDataToAmplitude = (eventType, data) => { - let amplitudeData = _formatDataForAmplitude(data); - - amplitudeData.event_type = eventType; - - return new Bluebird((resolve, reject) => { - amplitude.track(amplitudeData) - .then(resolve) - .catch(reject); - }); -}; - -let _generateLabelForGoogleAnalytics = (data) => { - let label; - - each(GA_POSSIBLE_LABELS, (key) => { - if (data[key]) { - label = data[key]; - return false; // exit each early - } - }); - - return label; -}; - -let _generateValueForGoogleAnalytics = (data) => { - let value; - - each(GA_POSSIBLE_VALUES, (key) => { - if (data[key]) { - value = data[key]; - return false; // exit each early - } - }); - - return value; -}; - -let _sendDataToGoogle = (eventType, data) => { - let eventData = { - ec: data.category, - ea: eventType, - }; - - let label = _generateLabelForGoogleAnalytics(data); - - if (label) { - eventData.el = label; - } - - let value = _generateValueForGoogleAnalytics(data); - - if (value) { - eventData.ev = value; - } - - return new Bluebird((resolve, reject) => { - ga.event(eventData, (err) => { - if (err) return reject(err); - resolve(); - }); - }); -}; - -let _sendPurchaseDataToAmplitude = (data) => { - let amplitudeData = _formatDataForAmplitude(data); - - amplitudeData.event_type = 'purchase'; - amplitudeData.revenue = data.purchaseValue; - - return new Bluebird((resolve, reject) => { - amplitude.track(amplitudeData) - .then(resolve) - .catch(reject); - }); -}; - -let _sendPurchaseDataToGoogle = (data) => { - let label = data.paymentMethod; - let type = data.purchaseType; - let price = data.purchaseValue; - let qty = data.quantity; - let sku = data.sku; - let itemKey = data.itemPurchased; - let variation = type; - - if (data.gift) variation += ' - Gift'; - - let eventData = { - ec: 'commerce', - ea: type, - el: label, - ev: price, - }; - - return new Bluebird((resolve) => { - ga.event(eventData).send(); - - ga.transaction(data.uuid, price) - .item(price, qty, sku, itemKey, variation) - .send(); - - resolve(); - }); -}; - -function track (eventType, data) { - return Bluebird.all([ - _sendDataToAmplitude(eventType, data), - _sendDataToGoogle(eventType, data), - ]); -} - -function trackPurchase (data) { - return Bluebird.all([ - _sendPurchaseDataToAmplitude(data), - _sendPurchaseDataToGoogle(data), - ]); -} - -// Stub for non-prod environments -let mockAnalyticsService = { - track: () => { }, - trackPurchase: () => { }, -}; - -module.exports = { - track, - trackPurchase, - mockAnalyticsService, -}; diff --git a/website/server/libs/api-v3/baseModel.js b/website/server/libs/api-v3/baseModel.js deleted file mode 100644 index 009b735fa6..0000000000 --- a/website/server/libs/api-v3/baseModel.js +++ /dev/null @@ -1,79 +0,0 @@ -import { v4 as uuid } from 'uuid'; -import validator from 'validator'; -import objectPath from 'object-path'; // TODO use lodash's unset once v4 is out -import _ from 'lodash'; - -module.exports = function baseModel (schema, options = {}) { - if (options._id !== false) { - schema.add({ - _id: { - type: String, - default: uuid, - validate: [validator.isUUID, 'Invalid uuid.'], - }, - }); - } - - if (options.timestamps) { - schema.add({ - createdAt: { - type: Date, - default: Date.now, - }, - updatedAt: { - type: Date, - default: Date.now, - }, - }); - } - - if (options.timestamps) { - schema.pre('save', function updateUpdatedAt (next) { - if (!this.isNew) this.updatedAt = Date.now(); - next(); - }); - - schema.pre('update', function preUpdateModel () { - this.update({}, { $set: { updatedAt: new Date() } }); - }); - } - - let noSetFields = ['createdAt', 'updatedAt']; - let privateFields = ['__v']; - - if (Array.isArray(options.noSet)) noSetFields.push(...options.noSet); - // This method accepts an additional array of fields to be sanitized that can be passed at runtime - schema.statics.sanitize = function sanitize (objToSanitize = {}, additionalFields = []) { - noSetFields.concat(additionalFields).forEach((fieldPath) => { - objectPath.del(objToSanitize, fieldPath); - }); - - // Allow a sanitize transform function to be used - return options.sanitizeTransform ? options.sanitizeTransform(objToSanitize) : objToSanitize; - }; - - if (Array.isArray(options.private)) privateFields.push(...options.private); - - if (!schema.options.toJSON) schema.options.toJSON = {}; - schema.options.toJSON.transform = function transformToObject (doc, plainObj) { - privateFields.forEach((fieldPath) => { - objectPath.del(plainObj, fieldPath); - }); - - // Always return `id` - if (!plainObj.id && plainObj._id) plainObj.id = plainObj._id; - - // Allow an additional toJSON transform function to be used - return options.toJSONTransform ? options.toJSONTransform(plainObj, doc) : plainObj; - }; - - schema.statics.getModelPaths = function getModelPaths () { - return _.reduce(this.schema.paths, (result, field, path) => { - if (privateFields.indexOf(path) === -1) { - result[path] = field.instance || 'Boolean'; - } - - return result; - }, {}); - }; -}; diff --git a/website/server/libs/api-v3/buildManifest.js b/website/server/libs/api-v3/buildManifest.js deleted file mode 100644 index 55db474354..0000000000 --- a/website/server/libs/api-v3/buildManifest.js +++ /dev/null @@ -1,62 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import nconf from 'nconf'; - -const MANIFEST_FILE_PATH = path.join(__dirname, '/../../../client/manifest.json'); -const BUILD_FOLDER_PATH = path.join(__dirname, '/../../../build'); -let manifestFiles = require(MANIFEST_FILE_PATH); - -const IS_PROD = nconf.get('IS_PROD'); -let buildFiles = []; - -function _walk (folder) { - let files = fs.readdirSync(folder); - - files.forEach((fileName) => { - let file = `${folder}/${fileName}`; - - if (fs.statSync(file).isDirectory()) { - _walk(file); - } else { - let relFolder = path.relative(BUILD_FOLDER_PATH, folder); - let original = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); // Match the hash part of the filename - - if (relFolder) { - original = `${relFolder}/${original}`; - fileName = `${relFolder}/${fileName}`; - } - - buildFiles[original] = fileName; - } - }); -} - -// Walks through all the files in the build directory -// and creates a map of original files names and hashed files names -_walk(BUILD_FOLDER_PATH); - -export function getBuildUrl (url) { - return `/${buildFiles[url] || url}`; -} - -export function getManifestFiles (page) { - let files = manifestFiles[page]; - - if (!files) throw new Error(`Page "${page}" not found!`); - - let htmlCode = ''; - - if (IS_PROD) { - htmlCode += ``; // eslint-disable-line prefer-template - htmlCode += ``; // eslint-disable-line prefer-template - } else { - files.css.forEach((file) => { - htmlCode += ``; - }); - files.js.forEach((file) => { - htmlCode += ``; - }); - } - - return htmlCode; -} diff --git a/website/server/libs/api-v3/collectionManipulators.js b/website/server/libs/api-v3/collectionManipulators.js deleted file mode 100644 index 95d3981601..0000000000 --- a/website/server/libs/api-v3/collectionManipulators.js +++ /dev/null @@ -1,22 +0,0 @@ -import { - findIndex, - isPlainObject, -} from 'lodash'; - -export function removeFromArray (array, element) { - let elementIndex; - - if (isPlainObject(element)) { - elementIndex = findIndex(array, element); - } else { - elementIndex = array.indexOf(element); - } - - if (elementIndex !== -1) { - let removedElement = array[elementIndex]; - array.splice(elementIndex, 1); - return removedElement; - } - - return false; -} diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js deleted file mode 100644 index da076b54ce..0000000000 --- a/website/server/libs/api-v3/cron.js +++ /dev/null @@ -1,280 +0,0 @@ -import moment from 'moment'; -import common from '../../../../common/'; -import { preenUserHistory } from '../../libs/api-v3/preening'; -import _ from 'lodash'; -import nconf from 'nconf'; - -const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; -const shouldDo = common.shouldDo; -const scoreTask = common.ops.scoreTask; -// const maxPMs = 200; - -let CLEAR_BUFFS = { - str: 0, - int: 0, - per: 0, - con: 0, - stealth: 0, - streaks: false, -}; - -function grantEndOfTheMonthPerks (user, now) { - let plan = user.purchased.plan; - - if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) { - plan.gemsBought = 0; // reset gem-cap - plan.dateUpdated = now; - // For every month, inc their "consecutive months" counter. Give perks based on consecutive blocks - // If they already got perks for those blocks (eg, 6mo subscription, subscription gifts, etc) - then dec the offset until it hits 0 - // TODO use month diff instead of ++ / --? see https://github.com/HabitRPG/habitrpg/issues/4317 - _.defaults(plan.consecutive, {count: 0, offset: 0, trinkets: 0, gemCapExtra: 0}); - - plan.consecutive.count++; - - if (plan.consecutive.offset > 0) { - plan.consecutive.offset--; - } else if (plan.consecutive.count % 3 === 0) { // every 3 months - plan.consecutive.trinkets++; - plan.consecutive.gemCapExtra += 5; - if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25; // cap it at 50 (hard 25 limit + extra 25) - } - } -} - -function removeTerminatedSubscription (user) { - // If subscription's termination date has arrived - let plan = user.purchased.plan; - - if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(new Date())) { - _.merge(plan, { - planId: null, - customerId: null, - paymentMethod: null, - }); - - _.merge(plan.consecutive, { - count: 0, - offset: 0, - gemCapExtra: 0, - }); - - user.markModified('purchased.plan'); - } -} - -function performSleepTasks (user, tasksByType, now) { - user.stats.buffs = _.cloneDeep(CLEAR_BUFFS); - - tasksByType.dailys.forEach((daily) => { - let completed = daily.completed; - let thatDay = moment(now).subtract({days: 1}); - - if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { - // TODO also untick checklists if the Daily was due on previous missed days, if two or more days were missed at once -- https://github.com/HabitRPG/habitrpg/pull/7218#issuecomment-219256016 - daily.checklist.forEach(box => box.completed = false); - } - - daily.completed = false; - }); -} - -// Perform various beginning-of-day reset actions. -export function cron (options = {}) { - let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options; - - user.auth.timestamps.loggedin = now; - user.lastCron = now; - user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; - // User is only allowed a certain number of drops a day. This resets the count. - if (user.items.lastDrop.count > 0) user.items.lastDrop.count = 0; - - // "Perfect Day" achievement for perfect-days - let perfect = true; - - if (user.isSubscribed()) { - grantEndOfTheMonthPerks(user, now); - if (!CRON_SAFE_MODE) removeTerminatedSubscription(user); - } - - // User is resting at the inn. - // On cron, buffs are cleared and all dailies are reset without performing damage - if (user.preferences.sleep === true) { - performSleepTasks(user, tasksByType, now); - return; - } - - let multiDaysCountAsOneDay = true; - // If the user does not log in for two or more days, cron (mostly) acts as if it were only one day. - // When site-wide difficulty settings are introduced, this can be a user preference option. - - // Tally each task - let todoTally = 0; - - tasksByType.todos.forEach(task => { // make uncompleted To-Dos redder (further incentive to complete them) - scoreTask({ - task, - user, - direction: 'down', - cron: true, - times: multiDaysCountAsOneDay ? 1 : daysMissed, - }); - - todoTally += task.value; - }); - - // For incomplete Dailys, add value (further incentive), deduct health, keep records for later decreasing the nightly mana gain - let dailyChecked = 0; // how many dailies were checked? - let dailyDueUnchecked = 0; // how many dailies were un-checked? - if (!user.party.quest.progress.down) user.party.quest.progress.down = 0; - - tasksByType.dailys.forEach((task) => { - let completed = task.completed; - // Deduct points for missed Daily tasks - let EvadeTask = 0; - let scheduleMisses = daysMissed; - - if (completed) { - dailyChecked += 1; - } else { - // dailys repeat, so need to calculate how many they've missed according to their own schedule - scheduleMisses = 0; - - for (let i = 0; i < daysMissed; i++) { - let thatDay = moment(now).subtract({days: i + 1}); - - if (shouldDo(thatDay.toDate(), task, user.preferences)) { - scheduleMisses++; - if (user.stats.buffs.stealth) { - user.stats.buffs.stealth--; - EvadeTask++; - } - if (multiDaysCountAsOneDay) break; - } - } - - if (scheduleMisses > EvadeTask) { - // The user did not complete this due Daily (but no penalty if cron is running in safe mode). - if (CRON_SAFE_MODE) { - dailyChecked += 1; // allows full allotment of mp to be gained - } else { - perfect = false; - - if (task.checklist && task.checklist.length > 0) { // Partially completed checklists dock fewer mana points - let fractionChecked = _.reduce(task.checklist, (m, i) => m + (i.completed ? 1 : 0), 0) / task.checklist.length; - dailyDueUnchecked += 1 - fractionChecked; - dailyChecked += fractionChecked; - } else { - dailyDueUnchecked += 1; - } - - let delta = scoreTask({ - user, - task, - direction: 'down', - times: multiDaysCountAsOneDay ? 1 : scheduleMisses - EvadeTask, - cron: true, - }); - - // Apply damage from a boss, less damage for Trivial priority (difficulty) - user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); - // NB: Medium and Hard priorities do not increase damage from boss. This was by accident - // initially, and when we realised, we could not fix it because users are used to - // their Medium and Hard Dailies doing an Easy amount of damage from boss. - // Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future - // setting between Trivial and Easy. - } - } - } - - task.history.push({ - date: Number(new Date()), - value: task.value, - }); - task.completed = false; - - if (completed || scheduleMisses > 0) { - task.checklist.forEach(i => i.completed = false); - } - }); - - // move singleton Habits towards yellow. - tasksByType.habits.forEach((task) => { // slowly reset 'onlies' value to 0 - if (task.up === false || task.down === false) { - task.value = Math.abs(task.value) < 0.1 ? 0 : task.value = task.value / 2; - } - }); - - // Finished tallying - user.history.todos.push({date: now, value: todoTally}); - - // tally experience - let expTally = user.stats.exp; - let lvl = 0; // iterator - while (lvl < user.stats.lvl - 1) { - lvl++; - expTally += common.tnl(lvl); - } - - user.history.exp.push({date: now, value: expTally}); - - // preen user history so that it doesn't become a performance problem - // also for subscribed users but differently - // TODO also do while resting in the inn. Note that later we'll be allowing the value/color of tasks to change while sleeping (https://github.com/HabitRPG/habitrpg/issues/5232), so the code in performSleepTasks() might be best merged back into here for that. Perhaps wait until then to do preen history for sleeping users. - preenUserHistory(user, tasksByType, user.preferences.timezoneOffset); - - if (perfect) { - user.achievements.perfect++; - let lvlDiv2 = Math.ceil(common.capByLevel(user.stats.lvl) / 2); - user.stats.buffs = { - str: lvlDiv2, - int: lvlDiv2, - per: lvlDiv2, - con: lvlDiv2, - stealth: 0, - streaks: false, - }; - } else { - user.stats.buffs = _.cloneDeep(CLEAR_BUFFS); - } - - // Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit - // Adjust for fraction of dailies completed - if (dailyDueUnchecked === 0 && dailyChecked === 0) dailyChecked = 1; - user.stats.mp += _.max([10, 0.1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked); - if (user.stats.mp > user._statsComputed.maxMP) user.stats.mp = user._statsComputed.maxMP; - - // After all is said and done, progress up user's effect on quest, return those values & reset the user's - let progress = user.party.quest.progress; - let _progress = _.cloneDeep(progress); - _.merge(progress, {down: 0, up: 0}); - progress.collect = _.transform(progress.collect, (m, v, k) => m[k] = 0); - - // TODO: Clean PMs - keep 200 for subscribers and 50 for free users. Should also be done while resting in the inn - // let numberOfPMs = Object.keys(user.inbox.messages).length; - // if (numberOfPMs > maxPMs) { - // _(user.inbox.messages) - // .sortBy('timestamp') - // .takeRight(numberOfPMs - maxPMs) - // .each(pm => { - // delete user.inbox.messages[pm.id]; - // }).value(); - // - // user.markModified('inbox.messages'); - // } - - // Analytics - user.flags.cronCount++; - analytics.track('Cron', { // TODO also do while resting in the inn. https://github.com/HabitRPG/habitrpg/issues/7161#issuecomment-218214191 - category: 'behavior', - gaLabel: 'Cron Count', - gaValue: user.flags.cronCount, - uuid: user._id, - user, - resting: user.preferences.sleep, - cronCount: user.flags.cronCount, - progressUp: _.min([_progress.up, 900]), - progressDown: _progress.down, - }); - - return _progress; -} diff --git a/website/server/libs/api-v3/csvStringify.js b/website/server/libs/api-v3/csvStringify.js deleted file mode 100644 index 39fb7c16c8..0000000000 --- a/website/server/libs/api-v3/csvStringify.js +++ /dev/null @@ -1,11 +0,0 @@ -import csvStringify from 'csv-stringify'; -import Bluebird from 'bluebird'; - -module.exports = (input) => { - return new Bluebird((resolve, reject) => { - csvStringify(input, (err, output) => { - if (err) return reject(err); - return resolve(output); - }); - }); -}; diff --git a/website/server/libs/api-v3/email.js b/website/server/libs/api-v3/email.js deleted file mode 100644 index fd2e166098..0000000000 --- a/website/server/libs/api-v3/email.js +++ /dev/null @@ -1,156 +0,0 @@ -import { createTransport } from 'nodemailer'; -import nconf from 'nconf'; -import { encrypt } from './encryption'; -import request from 'request'; -import logger from './logger'; - -const IS_PROD = nconf.get('IS_PROD'); -const EMAIL_SERVER = { - url: nconf.get('EMAIL_SERVER:url'), - auth: { - user: nconf.get('EMAIL_SERVER:authUser'), - password: nconf.get('EMAIL_SERVER:authPassword'), - }, -}; -const BASE_URL = nconf.get('BASE_URL'); - -let smtpTransporter = createTransport({ - service: nconf.get('SMTP_SERVICE'), - auth: { - user: nconf.get('SMTP_USER'), - pass: nconf.get('SMTP_PASS'), - }, -}); - -// Send email directly from the server using the smtpTransporter, -// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them -export function send (mailData) { - return smtpTransporter.sendMail(mailData); // promise -} - -export function getUserInfo (user, fields = []) { - let info = {}; - - if (fields.indexOf('name') !== -1) { - info.name = user.profile && user.profile.name; - - if (!info.name) { - if (user.auth.local && user.auth.local.username) { - info.name = user.auth.local.username; - } else if (user.auth.facebook) { - info.name = user.auth.facebook.displayName || user.auth.facebook.username; - } - } - } - - if (fields.indexOf('email') !== -1) { - if (user.auth.local && user.auth.local.email) { - info.email = user.auth.local.email; - } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) { - info.email = user.auth.facebook.emails[0].value; - } - } - - if (fields.indexOf('_id') !== -1) { - info._id = user._id; - } - - if (fields.indexOf('canSend') !== -1) { - if (user.preferences && user.preferences.emailNotifications) { - info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true; - } - } - - return info; -} - -// Send a transactional email using Mandrill through the external email server -export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) { - mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; - - variables = [ - {name: 'BASE_URL', content: BASE_URL}, - ].concat(variables || []); - - // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed - mailingInfoArray = mailingInfoArray.map((mailingInfo) => { - return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo; - }).filter((mailingInfo) => { - // Always send reset-password emails - // Don't check canSend for non registered users as already checked before - return mailingInfo.email && (!mailingInfo._id || mailingInfo.canSend || emailType === 'reset-password'); - }); - - // Personal variables are personal to each email recipient, if they are missing - // we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL - // otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables - if (!personalVariables || personalVariables.length === 0) { - personalVariables = mailingInfoArray.map((mailingInfo) => { - return { - rcpt: mailingInfo.email, - vars: [ - { - name: 'RECIPIENT_NAME', - content: mailingInfo.name, - }, - { - name: 'RECIPIENT_UNSUB_URL', - content: `/email/unsubscribe?code=${encrypt(JSON.stringify({ - _id: mailingInfo._id, - email: mailingInfo.email, - }))}`, - }, - ], - }; - }); - } else { - let temporaryPersonalVariables = {}; - - mailingInfoArray.forEach((mailingInfo) => { - temporaryPersonalVariables[mailingInfo.email] = { - name: mailingInfo.name, - _id: mailingInfo._id, - }; - }); - - personalVariables.forEach((singlePersonalVariables) => { - singlePersonalVariables.vars.push( - { - name: 'RECIPIENT_NAME', - content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name, - }, - { - name: 'RECIPIENT_UNSUB_URL', - content: `/email/unsubscribe?code=${encrypt(JSON.stringify({ - _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id, - email: singlePersonalVariables.rcpt, - }))}`, - } - ); - }); - } - - if (IS_PROD && mailingInfoArray.length > 0) { - request.post({ - url: `${EMAIL_SERVER.url}/job`, - auth: { - user: EMAIL_SERVER.auth.user, - pass: EMAIL_SERVER.auth.password, - }, - json: { - type: 'email', - data: { - emailType, - to: mailingInfoArray, - variables, - personalVariables, - }, - options: { - priority: 'high', - attempts: 5, - backoff: {delay: 10 * 60 * 1000, type: 'fixed'}, - }, - }, - }, (err) => logger.error(err)); - } -} diff --git a/website/server/libs/api-v3/encryption.js b/website/server/libs/api-v3/encryption.js deleted file mode 100644 index 390c59234e..0000000000 --- a/website/server/libs/api-v3/encryption.js +++ /dev/null @@ -1,24 +0,0 @@ -import { - createCipher, - createDecipher, -} from 'crypto'; -import nconf from 'nconf'; - -const algorithm = 'aes-256-ctr'; -const SESSION_SECRET = nconf.get('SESSION_SECRET'); - -export function encrypt (text) { - let cipher = createCipher(algorithm, SESSION_SECRET); - let crypted = cipher.update(text, 'utf8', 'hex'); - - crypted += cipher.final('hex'); - return crypted; -} - -export function decrypt (text) { - let decipher = createDecipher(algorithm, SESSION_SECRET); - let dec = decipher.update(text, 'hex', 'utf8'); - - dec += decipher.final('utf8'); - return dec; -} diff --git a/website/server/libs/api-v3/errors.js b/website/server/libs/api-v3/errors.js deleted file mode 100644 index 2b6d52bbe3..0000000000 --- a/website/server/libs/api-v3/errors.js +++ /dev/null @@ -1,62 +0,0 @@ -import common from '../../../../common'; - -export const CustomError = common.errors.CustomError; - -/** - * @apiDefine NotAuthorized - * @apiError NotAuthorized The client is not authorized to make this request. - * - * @apiErrorExample Error-Response: - * HTTP/1.1 401 Unauthorized - * { - * "error": "NotAuthorized", - * "message": "Not authorized." - * } - */ -export const NotAuthorized = common.errors.NotAuthorized; - -/** - * @apiDefine BadRequest - * @apiError BadRequest The request wasn't formatted correctly. - * - * @apiErrorExample Error-Response: - * HTTP/1.1 400 Bad Request - * { - * "error": "BadRequest", - * "message": "Bad request." - * } - */ -export const BadRequest = common.errors.BadRequest; - -/** - * @apiDefine NotFound - * @apiError NotFound The requested resource was not found. - * - * @apiErrorExample Error-Response: - * HTTP/1.1 404 Not Found - * { - * "error": "NotFound", - * "message": "Not found." - * } - */ -export const NotFound = common.errors.NotFound; - -/** - * @apiDefine InternalServerError - * @apiError InternalServerError An unexpected error occurred. - * - * @apiErrorExample Error-Response: - * HTTP/1.1 500 Internal Server Error - * { - * "error": "InternalServerError", - * "message": "An unexpected error occurred." - * } - */ -export class InternalServerError extends CustomError { - constructor (customMessage) { - super(); - this.name = this.constructor.name; - this.httpCode = 500; - this.message = customMessage || 'An unexpected error occurred.'; - } -} diff --git a/website/server/libs/api-v3/firebase.js b/website/server/libs/api-v3/firebase.js deleted file mode 100644 index 324183e85f..0000000000 --- a/website/server/libs/api-v3/firebase.js +++ /dev/null @@ -1,69 +0,0 @@ -import Firebase from 'firebase'; -import nconf from 'nconf'; -import { TAVERN_ID } from '../../models/group'; - -const FIREBASE_CONFIG = nconf.get('FIREBASE'); -const FIREBASE_ENABLED = FIREBASE_CONFIG.ENABLED === 'true'; - -let firebaseRef; - -if (FIREBASE_ENABLED) { - firebaseRef = new Firebase(`https://${FIREBASE_CONFIG.APP}.firebaseio.com`); - - // TODO what happens if an op is sent before client is authenticated? - firebaseRef.authWithCustomToken(FIREBASE_CONFIG.SECRET, (err) => { - // TODO it's ok to kill the server here? what if FB is offline? - if (err) throw new Error('Impossible to authenticate Firebase'); - }); -} - -export function updateGroupData (group) { - if (!FIREBASE_ENABLED) return; - // TODO is throw ok? we don't have callbacks - if (!group) throw new Error('group obj is required.'); - // Return in case of tavern (comparison working because we use string for _id) - if (group._id === TAVERN_ID) return; - - firebaseRef.child(`rooms/${group._id}`) - .set({ - name: group.name, - }); -} - -export function addUserToGroup (groupId, userId) { - if (!FIREBASE_ENABLED) return; - if (!userId || !groupId) throw new Error('groupId, userId are required.'); - if (groupId === TAVERN_ID) return; - - firebaseRef.child(`members/${groupId}/${userId}`).set(true); - firebaseRef.child(`users/${userId}/rooms/${groupId}`).set(true); -} - -export function removeUserFromGroup (groupId, userId) { - if (!FIREBASE_ENABLED) return; - if (!userId || !groupId) throw new Error('groupId, userId are required.'); - if (groupId === TAVERN_ID) return; - - firebaseRef.child(`members/${groupId}/${userId}`).remove(); - firebaseRef.child(`users/${userId}/rooms/${groupId}`).remove(); -} - -export function deleteGroup (groupId) { - if (!FIREBASE_ENABLED) return; - if (!groupId) throw new Error('groupId is required.'); - if (groupId === TAVERN_ID) return; - - firebaseRef.child(`members/${groupId}`).remove(); - // TODO not really necessary as long as we only store room data, - // as empty objects are automatically deleted (/members/... in future...) - firebaseRef.child(`rooms/${groupId}`).remove(); -} - -// TODO not really necessary as long as we only store room data, -// as empty objects are automatically deleted -export function deleteUser (userId) { - if (!FIREBASE_ENABLED) return; - if (!userId) throw new Error('userId is required.'); - - firebaseRef.child(`users/${userId}`).remove(); -} diff --git a/website/server/libs/api-v3/i18n.js b/website/server/libs/api-v3/i18n.js deleted file mode 100644 index 4c424ace39..0000000000 --- a/website/server/libs/api-v3/i18n.js +++ /dev/null @@ -1,105 +0,0 @@ -import fs from 'fs'; -import path from 'path'; -import _ from 'lodash'; -import shared from '../../../../common'; - -export const localePath = path.join(__dirname, '/../../../../common/locales/'); - -// Store translations -export let translations = {}; -// Store MomentJS localization files -export let momentLangs = {}; - -// Handle differencies in language codes between MomentJS and /locales -let momentLangsMapping = { - en: 'en-gb', - en_GB: 'en-gb', // eslint-disable-line camelcase - no: 'nn', - zh: 'zh-cn', - es_419: 'es', // eslint-disable-line camelcase -}; - -function _loadTranslations (locale) { - let files = fs.readdirSync(path.join(localePath, locale)); - - translations[locale] = {}; - - files.forEach((file) => { - if (path.extname(file) !== '.json') return; - - // We use require to load and parse a JSON file - _.merge(translations[locale], require(path.join(localePath, locale, file))); // eslint-disable-line global-require - }); -} - -// First fetch English strings so we can merge them with missing strings in other languages -_loadTranslations('en'); - -// Then load all other languages -fs.readdirSync(localePath).forEach((file) => { - if (file === 'en' || fs.statSync(path.join(localePath, file)).isDirectory() === false) return; - _loadTranslations(file); - - // Merge missing strings from english - _.defaults(translations[file], translations.en); -}); - -// Add translations to shared -shared.i18n.translations = translations; - -export let langCodes = Object.keys(translations); - -export let availableLanguages = langCodes.map((langCode) => { - return { - code: langCode, - name: translations[langCode].languageName, - }; -}); - -langCodes.forEach((code) => { - let lang = _.find(availableLanguages, {code}); - - lang.momentLangCode = momentLangsMapping[code] || code; - - try { - // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files - // We wrap everything in a try catch because the file might not exist - let f = fs.readFileSync(path.join(__dirname, `/../../../node_modules/moment/locale/${lang.momentLangCode}.js`), 'utf8'); - - momentLangs[code] = f; - } catch (e) { // eslint-disable-lint no-empty - // The catch block is mandatory so it won't crash the server - } -}); - -// Remove en_GB from langCodes checked by browser to avoid it being -// used in place of plain original 'en' (it's an optional language that can be enabled only in setting) -export let defaultLangCodes = _.without(langCodes, 'en_GB'); - -// A map of languages that have different versions and the relative versions -export let multipleVersionsLanguages = { - es: { - 'es-419': 'es_419', - 'es-mx': 'es_419', - 'es-gt': 'es_419', - 'es-cr': 'es_419', - 'es-pa': 'es_419', - 'es-do': 'es_419', - 'es-ve': 'es_419', - 'es-co': 'es_419', - 'es-pe': 'es_419', - 'es-ar': 'es_419', - 'es-ec': 'es_419', - 'es-cl': 'es_419', - 'es-uy': 'es_419', - 'es-py': 'es_419', - 'es-bo': 'es_419', - 'es-sv': 'es_419', - 'es-hn': 'es_419', - 'es-ni': 'es_419', - 'es-pr': 'es_419', - }, - zh: { - 'zh-tw': 'zh_TW', - }, -}; diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js deleted file mode 100644 index ed1353e8ad..0000000000 --- a/website/server/libs/api-v3/logger.js +++ /dev/null @@ -1,60 +0,0 @@ -// Logger utility -import winston from 'winston'; -import nconf from 'nconf'; -import _ from 'lodash'; - -const IS_PROD = nconf.get('IS_PROD'); -const IS_TEST = nconf.get('IS_TEST'); -const ENABLE_CONSOLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') === 'true'; - -const logger = new winston.Logger(); - -if (IS_PROD) { - if (ENABLE_CONSOLE_LOGS_IN_PROD) { - logger.add(winston.transports.Console, { - colorize: true, - prettyPrint: true, - }); - } -} else if (IS_TEST) { - // Do not log anything when testing -} else { - logger - .add(winston.transports.Console, { - colorize: true, - prettyPrint: true, - }); -} - -// exports a public interface insteaf of accessing directly the logger module -let loggerInterface = { - info (...args) { - logger.info(...args); - }, - - // Accepts two argument, - // an Error object (required) - // and an object of additional data to log alongside the error - // If the first argument isn't an Error, it'll call logger.error with all the arguments supplied - error (...args) { - let [err, errorData = {}, ...otherArgs] = args; - - if (err instanceof Error) { - // pass the error stack as the first parameter to logger.error - let stack = err.stack || err.message || err; - - if (_.isPlainObject(errorData) && !errorData.fullError) errorData.fullError = err; - logger.error(stack, errorData, ...otherArgs); - } else { - logger.error(...args); - } - }, -}; - -// Logs unhandled promises errors -// when no catch is attached to a promise a unhandledRejection event will be triggered -process.on('unhandledRejection', function handlePromiseRejection (reason) { - loggerInterface.error(reason); -}); - -module.exports = loggerInterface; diff --git a/website/server/libs/api-v3/password.js b/website/server/libs/api-v3/password.js deleted file mode 100644 index c825083936..0000000000 --- a/website/server/libs/api-v3/password.js +++ /dev/null @@ -1,18 +0,0 @@ -// Utilities for working with passwords -import crypto from 'crypto'; - -// Return the encrypted version of a password (using sha1) given a salt -export function encrypt (password, salt) { - return crypto - .createHmac('sha1', salt) - .update(password) - .digest('hex'); -} - -// Create a salt, default length is 10 -export function makeSalt (len = 10) { - return crypto - .randomBytes(Math.ceil(len / 2)) - .toString('hex') - .substring(0, len); -} \ No newline at end of file diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js deleted file mode 100644 index 5a9c888972..0000000000 --- a/website/server/libs/api-v3/payments.js +++ /dev/null @@ -1,185 +0,0 @@ -import _ from 'lodash' ; -import analytics from './analyticsService'; -import { - getUserInfo, - sendTxn as txnEmail, -} from './email'; -import members from '../../controllers/api-v3/members'; -import moment from 'moment'; -import nconf from 'nconf'; -import pushNotify from './pushNotifications'; -import shared from '../../../../common' ; - -const IS_PROD = nconf.get('IS_PROD'); - -let api = {}; - -function revealMysteryItems (user) { - _.each(shared.content.gear.flat, function findMysteryItems (item) { - if ( - item.klass === 'mystery' && - moment().isAfter(shared.content.mystery[item.mystery].start) && - moment().isBefore(shared.content.mystery[item.mystery].end) && - !user.items.gear.owned[item.key] && - user.purchased.plan.mysteryItems.indexOf(item.key) !== -1 - ) { - user.purchased.plan.mysteryItems.push(item.key); - } - }); -} - -api.createSubscription = async function createSubscription (data) { - let recipient = data.gift ? data.gift.member : data.user; - let plan = recipient.purchased.plan; - let block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key]; - let months = Number(block.months); - - if (data.gift) { - if (plan.customerId && !plan.dateTerminated) { // User has active plan - plan.extraMonths += months; - } else { - plan.dateTerminated = moment(plan.dateTerminated).add({months}).toDate(); - if (!plan.dateUpdated) plan.dateUpdated = new Date(); - } - - if (!plan.customerId) plan.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId - } else { - _(plan).merge({ // override with these values - planId: block.key, - customerId: data.customerId, - dateUpdated: new Date(), - gemsBought: 0, - paymentMethod: data.paymentMethod, - extraMonths: Number(plan.extraMonths) + - Number(plan.dateTerminated ? moment(plan.dateTerminated).diff(new Date(), 'months', true) : 0), - dateTerminated: null, - // Specify a lastBillingDate just for Amazon Payments - // Resetted every time the subscription restarts - lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined, - }).defaults({ // allow non-override if a plan was previously used - dateCreated: new Date(), - mysteryItems: [], - }).value(); - } - - // Block sub perks - let perks = Math.floor(months / 3); - if (perks) { - plan.consecutive.offset += months; - plan.consecutive.gemCapExtra += perks * 5; - if (plan.consecutive.gemCapExtra > 25) plan.consecutive.gemCapExtra = 25; - plan.consecutive.trinkets += perks; - } - - revealMysteryItems(recipient); - - if (IS_PROD) { - if (!data.gift) txnEmail(data.user, 'subscription-begins'); - - analytics.trackPurchase({ - uuid: data.user._id, - itemPurchased: 'Subscription', - sku: `${data.paymentMethod.toLowerCase()}-subscription`, - purchaseType: 'subscribe', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: Boolean(data.gift), - purchaseValue: block.price, - }); - } - - data.user.purchased.txnCount++; - - if (data.gift) { - members.sendMessage(data.user, data.gift.member, data.gift); - - let byUserName = getUserInfo(data.user, ['name']).name; - - if (data.gift.member.preferences.emailNotifications.giftedSubscription !== false) { - txnEmail(data.gift.member, 'gifted-subscription', [ - {name: 'GIFTER', content: byUserName}, - {name: 'X_MONTHS_SUBSCRIPTION', content: months}, - ]); - } - - if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`); - } - } - - await data.user.save(); - if (data.gift) await data.gift.member.save(); -}; - -// Sets their subscription to be cancelled later -api.cancelSubscription = async function cancelSubscription (data) { - let plan = data.user.purchased.plan; - let now = moment(); - let remaining = data.nextBill ? moment(data.nextBill).diff(new Date(), 'days') : 30; - let nowStr = `${now.format('MM')}/${moment(plan.dateUpdated).format('DD')}/${now.format('YYYY')}`; - let nowStrFormat = 'MM/DD/YYYY'; - - plan.dateTerminated = - moment(nowStr, nowStrFormat) - .add({days: remaining}) // end their subscription 1mo from their last payment - .add({days: Math.ceil(30 * plan.extraMonths)}) // plus any extra time (carry-over, gifted subscription, etc) they have. - .toDate(); - plan.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated - - await data.user.save(); - - txnEmail(data.user, 'cancel-subscription'); - - analytics.track('unsubscribe', { - uuid: data.user._id, - gaCategory: 'commerce', - gaLabel: data.paymentMethod, - paymentMethod: data.paymentMethod, - }); -}; - -api.buyGems = async function buyGems (data) { - let amt = data.amount || 5; - amt = data.gift ? data.gift.gems.amount / 4 : amt; - - (data.gift ? data.gift.member : data.user).balance += amt; - data.user.purchased.txnCount++; - - if (IS_PROD) { - if (!data.gift) txnEmail(data.user, 'donation'); - - analytics.trackPurchase({ - uuid: data.user._id, - itemPurchased: 'Gems', - sku: `${data.paymentMethod.toLowerCase()}-checkout`, - purchaseType: 'checkout', - paymentMethod: data.paymentMethod, - quantity: 1, - gift: Boolean(data.gift), - purchaseValue: amt, - }); - } - - if (data.gift) { - let byUsername = getUserInfo(data.user, ['name']).name; - let gemAmount = data.gift.gems.amount || 20; - - members.sendMessage(data.user, data.gift.member, data.gift); - if (data.gift.member.preferences.emailNotifications.giftedGems !== false) { - txnEmail(data.gift.member, 'gifted-gems', [ - {name: 'GIFTER', content: byUsername}, - {name: 'X_GEMS_GIFTED', content: gemAmount}, - ]); - } - - if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`); - } - - await data.gift.member.save(); - } - - await data.user.save(); -}; - -module.exports = api; diff --git a/website/server/libs/api-v3/preening.js b/website/server/libs/api-v3/preening.js deleted file mode 100644 index 00be142299..0000000000 --- a/website/server/libs/api-v3/preening.js +++ /dev/null @@ -1,82 +0,0 @@ -import _ from 'lodash'; -import moment from 'moment'; - -// Aggregate entries -function _aggregate (history, aggregateBy) { - return _.chain(history) - .groupBy(entry => { // group entries by aggregateBy - return moment(entry.date).format(aggregateBy); - }) - .sortBy((entry, key) => key) // sort by date - .map(entries => { - return { - date: Number(entries[0].date), - value: _.reduce(entries, (previousValue, entry) => { - return previousValue + entry.value; - }, 0) / entries.length, - }; - }) - .value(); -} - -/* Preen an array of history entries -Free users: -- 1 value for each day of the past 60 days (no compression) -- 1 value each month for the previous 10 months -- 1 value each year for the previous years -Subscribers and challenges: -- 1 value for each day of the past 365 days (no compression) -- 1 value each month for the previous 12 months -- 1 value each year for the previous years - */ -export function preenHistory (history, isSubscribed, timezoneOffset) { - // history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries - let now = timezoneOffset ? moment().zone(timezoneOffset) : moment(); - // Date after which to begin compressing data - let cutOff = now.subtract(isSubscribed ? 365 : 60, 'days').startOf('day'); - - // Keep uncompressed entries (modifies history and returns removed items) - let newHistory = _.remove(history, entry => { - let date = moment(entry.date); - return date.isSame(cutOff) || date.isAfter(cutOff); - }); - - // Date after which to begin compressing data by year - let monthsCutOff = cutOff.subtract(isSubscribed ? 12 : 10, 'months').startOf('day'); - let aggregateByMonth = _.remove(history, entry => { - let date = moment(entry.date); - return date.isSame(monthsCutOff) || date.isAfter(monthsCutOff); - }); - // Aggregate remaining entries by month and year - if (aggregateByMonth.length > 0) newHistory.unshift(..._aggregate(aggregateByMonth, 'YYYYMM')); - if (history.length > 0) newHistory.unshift(..._aggregate(history, 'YYYY')); - - return newHistory; -} - -// Preen history for users and tasks. -export function preenUserHistory (user, tasksByType) { - let isSubscribed = user.isSubscribed(); - let timezoneOffset = user.preferences.timezoneOffset; - let minHistoryLength = isSubscribed ? 365 : 60; - - function _processTask (task) { - if (task.history && task.history.length > minHistoryLength) { - task.history = preenHistory(task.history, isSubscribed, timezoneOffset); - task.markModified('history'); - } - } - - tasksByType.habits.forEach(_processTask); - tasksByType.dailys.forEach(_processTask); - - if (user.history.exp.length > minHistoryLength) { - user.history.exp = preenHistory(user.history.exp, isSubscribed, timezoneOffset); - user.markModified('history.exp'); - } - - if (user.history.todos.length > minHistoryLength) { - user.history.todos = preenHistory(user.history.todos, isSubscribed, timezoneOffset); - user.markModified('history.todos'); - } -} diff --git a/website/server/libs/api-v3/pushNotifications.js b/website/server/libs/api-v3/pushNotifications.js deleted file mode 100644 index 8354de04e4..0000000000 --- a/website/server/libs/api-v3/pushNotifications.js +++ /dev/null @@ -1,53 +0,0 @@ -import _ from 'lodash'; -import nconf from 'nconf'; -import pushNotify from 'push-notify'; - -const GCM_API_KEY = nconf.get('PUSH_CONFIGS:GCM_SERVER_API_KEY'); - -let gcm = GCM_API_KEY ? pushNotify.gcm({ - apiKey: GCM_API_KEY, - retries: 3, -}) : undefined; - -// TODO review and test this file when push notifications are added back - -if (gcm) { - gcm.on('transmitted', (/* result, message, registrationId */) => { - // console.info("transmitted", result, message, registrationId); - }); - - gcm.on('transmissionError', (/* error, message, registrationId */) => { - // console.info("transmissionError", error, message, registrationId); - }); - - gcm.on('updated', (/* result, registrationId */) => { - // console.info("updated", result, registrationId); - }); -} - -module.exports = function sendNotification (user, title, message, timeToLive = 15) { - if (!user) return; - - _.each(user.pushDevices, pushDevice => { - switch (pushDevice.type) { - case 'android': - if (gcm) { - gcm.send({ - registrationId: pushDevice.regId, - // collapseKey: 'COLLAPSE_KEY', - delayWhileIdle: true, - timeToLive, - data: { - title, - message, - }, - }); - } - - break; - - case 'ios': - break; - } - }); -}; diff --git a/website/server/libs/api-v3/routes.js b/website/server/libs/api-v3/routes.js deleted file mode 100644 index c0399fb73c..0000000000 --- a/website/server/libs/api-v3/routes.js +++ /dev/null @@ -1,61 +0,0 @@ -import fs from 'fs'; -import _ from 'lodash'; -import { - getUserLanguage, -} from '../../middlewares/api-v3/language'; -import cron from '../../middlewares/api-v3/cron'; - -// Wrapper function to handler `async` route handlers that return promises -// It takes the async function, execute it and pass any error to next (args[2]) -let _wrapAsyncFn = fn => (...args) => fn(...args).catch(args[2]); -let noop = (req, res, next) => next(); - -module.exports.readController = function readController (router, controller) { - _.each(controller, (action) => { - let {method, url, middlewares = [], handler, runCron} = action; - - // If an authentication middleware is used run getUserLanguage after it, otherwise before - // for cron instead use it only if an authentication middleware is present - let authMiddlewareIndex = _.findIndex(middlewares, middleware => { - if (middleware.name.indexOf('authWith') === 0) { // authWith{Headers|Session|Url|...} - return true; - } else { - return false; - } - }); - - let middlewaresToAdd = [getUserLanguage]; - - if (authMiddlewareIndex !== -1) { // the user will be authenticated, getUserLanguage and cron after authentication - if (!(runCron === false)) { // eslint-disable-line no-extra-parens - middlewaresToAdd.push(cron); - } - - if (authMiddlewareIndex === middlewares.length - 1) { - middlewares.push(...middlewaresToAdd); - } else { - middlewares.splice(authMiddlewareIndex + 1, 0, ...middlewaresToAdd); - } - } else { // no auth, getUserLanguage as the first middleware - middlewares.unshift(...middlewaresToAdd); - } - - method = method.toLowerCase(); - let fn = handler ? _wrapAsyncFn(handler) : noop; - - router[method](url, ...middlewares, fn); - }); -}; - -module.exports.walkControllers = function walkControllers (router, filePath) { - fs - .readdirSync(filePath) - .forEach(fileName => { - if (!fs.statSync(filePath + fileName).isFile()) { - walkControllers(router, `${filePath}${fileName}/`); - } else if (fileName.match(/\.js$/)) { - let controller = require(filePath + fileName); // eslint-disable-line global-require - module.exports.readController(router, controller); - } - }); -}; diff --git a/website/server/libs/api-v3/setupMongoose.js b/website/server/libs/api-v3/setupMongoose.js deleted file mode 100644 index 41ba9e5358..0000000000 --- a/website/server/libs/api-v3/setupMongoose.js +++ /dev/null @@ -1,28 +0,0 @@ -import nconf from 'nconf'; -import logger from './logger'; -import autoinc from 'mongoose-id-autoinc'; -import mongoose from 'mongoose'; -import Bluebird from 'bluebird'; - -const IS_PROD = nconf.get('IS_PROD'); -const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE'); - -// Use Q promises instead of mpromise in mongoose -mongoose.Promise = Bluebird; - -// Do not connect to MongoDB when in maintenance mode -if (MAINTENANCE_MODE !== 'true') { - let mongooseOptions = !IS_PROD ? {} : { - replset: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } }, - server: { socketOptions: { keepAlive: 120, connectTimeoutMS: 30000 } }, - }; - - const NODE_DB_URI = nconf.get('IS_TEST') ? nconf.get('TEST_DB_URI') : nconf.get('NODE_DB_URI'); - - let db = mongoose.connect(NODE_DB_URI, mongooseOptions, (err) => { - if (err) throw err; - logger.info('Connected with Mongoose.'); - }); - - autoinc.init(db); -} \ No newline at end of file diff --git a/website/server/libs/api-v3/setupNconf.js b/website/server/libs/api-v3/setupNconf.js deleted file mode 100644 index d88b04014e..0000000000 --- a/website/server/libs/api-v3/setupNconf.js +++ /dev/null @@ -1,17 +0,0 @@ -import nconf from 'nconf'; -import { join, resolve } from 'path'; - -const PATH_TO_CONFIG = join(resolve(__dirname, '../../../../config.json')); - -module.exports = function setupNconf (file) { - let configFile = file || PATH_TO_CONFIG; - - nconf - .argv() - .env() - .file('user', configFile); - - nconf.set('IS_PROD', nconf.get('NODE_ENV') === 'production'); - nconf.set('IS_DEV', nconf.get('NODE_ENV') === 'development'); - nconf.set('IS_TEST', nconf.get('NODE_ENV') === 'test'); -}; diff --git a/website/server/libs/api-v3/setupPassport.js b/website/server/libs/api-v3/setupPassport.js deleted file mode 100644 index dd9fdeaaa2..0000000000 --- a/website/server/libs/api-v3/setupPassport.js +++ /dev/null @@ -1,24 +0,0 @@ -import passport from 'passport'; -import nconf from 'nconf'; -import passportFacebook from 'passport-facebook'; - -const FacebookStrategy = passportFacebook.Strategy; - -// Passport session setup. -// To support persistent login sessions, Passport needs to be able to -// serialize users into and deserialize users out of the session. Typically, -// this will be as simple as storing the user ID when serializing, and finding -// the user by ID when deserializing. However, since this example does not -// have a database of user records, the complete Facebook profile is serialized -// and deserialized. -passport.serializeUser((user, done) => done(null, user)); -passport.deserializeUser((obj, done) => done(null, obj)); - -// TODO remove? -// This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile) -// The proper fix would be to move to a general OAuth module simply to verify accessTokens -passport.use(new FacebookStrategy({ - clientID: nconf.get('FACEBOOK_KEY'), - clientSecret: nconf.get('FACEBOOK_SECRET'), - // callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" -}, (accessToken, refreshToken, profile, done) => done(null, profile))); diff --git a/website/server/libs/api-v3/webhook.js b/website/server/libs/api-v3/webhook.js deleted file mode 100644 index e53eebf541..0000000000 --- a/website/server/libs/api-v3/webhook.js +++ /dev/null @@ -1,31 +0,0 @@ -import { each } from 'lodash'; -import { post } from 'request'; -import { isURL } from 'validator'; -import logger from './logger'; - -let _sendWebhook = (url, body) => { - post({ - url, - body, - json: true, - }, (err) => logger.error(err)); -}; - -let _isInvalidWebhook = (hook) => { - return !hook.enabled || !isURL(hook.url); -}; - -export function sendTaskWebhook (webhooks, data) { - each(webhooks, (hook) => { - if (_isInvalidWebhook(hook)) return; - - let body = { - direction: data.task.direction, - task: data.task.details, - delta: data.task.delta, - user: data.user, - }; - - _sendWebhook(hook.url, body); - }); -} diff --git a/website/server/middlewares/api-v3/analytics.js b/website/server/middlewares/api-v3/analytics.js deleted file mode 100644 index 8512d16011..0000000000 --- a/website/server/middlewares/api-v3/analytics.js +++ /dev/null @@ -1,23 +0,0 @@ -import nconf from 'nconf'; -import { - track, - trackPurchase, - mockAnalyticsService, -} from '../../libs/api-v3/analyticsService'; - -let service; - -if (nconf.get('IS_PROD')) { - service = { - track, - trackPurchase, - }; -} else { - service = mockAnalyticsService; -} - -module.exports = function attachAnalytics (req, res, next) { - res.analytics = service; - - next(); -}; diff --git a/website/server/middlewares/api-v3/auth.js b/website/server/middlewares/api-v3/auth.js deleted file mode 100644 index 0feb73a98d..0000000000 --- a/website/server/middlewares/api-v3/auth.js +++ /dev/null @@ -1,87 +0,0 @@ -import { - NotAuthorized, -} from '../../libs/api-v3/errors'; -import { - model as User, -} from '../../models/user'; - -// Strins won't be translated here because getUserLanguage has not run yet - -// Authenticate a request through the x-api-user and x-api key header -// If optional is true, don't error on missing authentication -export function authWithHeaders (optional = false) { - return function authWithHeadersHandler (req, res, next) { - let userId = req.header('x-api-user'); - let apiToken = req.header('x-api-key'); - - if (!userId || !apiToken) { - if (optional) return next(); - return next(new NotAuthorized(res.t('missingAuthHeaders'))); - } - - return User.findOne({ - _id: userId, - apiToken, - }) - .exec() - .then((user) => { - if (!user) throw new NotAuthorized(res.t('invalidCredentials')); - if (user.auth.blocked) throw new NotAuthorized(res.t('accountSuspended', {userId: user._id})); - - res.locals.user = user; - - req.session.userId = user._id; - return next(); - }) - .catch(next); - }; -} - -// Authenticate a request through a valid session -export function authWithSession (req, res, next) { - let userId = req.session.userId; - - // Always allow authentication with headers - if (!userId) { - if (!req.header('x-api-user') || !req.header('x-api-key')) { - return next(new NotAuthorized(res.t('invalidCredentials'))); - } else { - return authWithHeaders()(req, res, next); - } - } - - return User.findOne({ - _id: userId, - }) - .exec() - .then((user) => { - if (!user) throw new NotAuthorized(res.t('invalidCredentials')); - - res.locals.user = user; - return next(); - }) - .catch(next); -} - -export function authWithUrl (req, res, next) { - let userId = req.query._id; - let apiToken = req.query.apiToken; - - // Always allow authentication with headers - if (!userId || !apiToken) { - if (!req.header('x-api-user') || !req.header('x-api-key')) { - return next(new NotAuthorized(res.t('missingAuthParams'))); - } else { - return authWithHeaders()(req, res, next); - } - } - - return User.findOne({ _id: userId, apiToken }).exec() - .then((user) => { - if (!user) throw new NotAuthorized(res.t('invalidCredentials')); - - res.locals.user = user; - return next(); - }) - .catch(next); -} diff --git a/website/server/middlewares/api-v3/cors.js b/website/server/middlewares/api-v3/cors.js deleted file mode 100644 index c249c183c6..0000000000 --- a/website/server/middlewares/api-v3/cors.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = function corsMiddleware (req, res, next) { - res.set({ - 'Access-Control-Allow-Origin': req.header('origin') || '*', - 'Access-Control-Allow-Methods': 'OPTIONS,GET,POST,PUT,HEAD,DELETE', - 'Access-Control-Allow-Headers': 'Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key', - }); - if (req.method === 'OPTIONS') return res.sendStatus(200); - return next(); -}; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js deleted file mode 100644 index 780f400e8f..0000000000 --- a/website/server/middlewares/api-v3/cron.js +++ /dev/null @@ -1,160 +0,0 @@ -import _ from 'lodash'; -import moment from 'moment'; -import common from '../../../../common'; -import * as Tasks from '../../models/task'; -import Bluebird from 'bluebird'; -import { model as Group } from '../../models/group'; -import { model as User } from '../../models/user'; -import { cron } from '../../libs/api-v3/cron'; - -const daysSince = common.daysSince; - -module.exports = function cronMiddleware (req, res, next) { - let user = res.locals.user; - - if (!user) return next(); // User might not be available when authentication is not mandatory - - let analytics = res.analytics; - - let now = new Date(); - - // If the user's timezone has changed (due to travel or daylight savings), - // cron can be triggered twice in one day, so we check for that and use - // both timezones to work out if cron should run. - // CDS = Custom Day Start time. - let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; - let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; - let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset')); - timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs; - // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults - - if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { - // The user's browser has just told Habitica that the user's timezone has - // changed so store and use the new zone. - user.preferences.timezoneOffset = timezoneOffsetFromBrowser; - timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; - } - - // How many days have we missed using the user's current timezone: - let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences)); - - if (timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs) { - // Since cron last ran, the user's timezone has changed. - // How many days have we missed using the old timezone: - let daysMissedNewZone = daysMissed; - let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ - now, - timezoneOffsetOverride: timezoneOffsetAtLastCron, - }, user.preferences)); - - if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { - // The timezone change was in the unsafe direction. - // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). - // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). - // Local time changed from, for example, 03:00 to 02:00. - - if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { - // Both old and new timezones indicate that we SHOULD run cron, so - // it is safe to do so immediately. - daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); - // use minimum value to be nice to user - } else if (daysMissedOldZone > 0) { - // The old timezone says that cron should run; the new timezone does not. - // This should be impossible for this direction of timezone change, but - // just in case I'm wrong... - // TODO - // console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens - } else if (daysMissedNewZone > 0) { - // The old timezone says that cron should NOT run -- i.e., cron has - // already run today, from the old timezone's point of view. - // The new timezone says that cron SHOULD run, but this is almost - // certainly incorrect. - // This happens when cron occurred at a time soon after the CDS. When - // you reinterpret that time in the new timezone, it looks like it - // was before the CDS, because local time has stepped backwards. - // To fix this, rewrite the cron time to a time that the new - // timezone interprets as being in today. - - daysMissed = 0; // prevent cron running now - let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; - // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 - - user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); - // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. - // From now on we can ignore the old timezone: - user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; - } else { - // Both old and new timezones indicate that cron should - // NOT run. - daysMissed = 0; // prevent cron running now - } - } else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { - daysMissed = daysMissedNewZone; - // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. - // There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; - // if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). - } - } - - if (daysMissed <= 0) return next(); - - // Fetch active tasks (no completed todos) - Tasks.Task.find({ - userId: user._id, - $or: [ // Exclude completed todos - {type: 'todo', completed: false}, - {type: {$in: ['habit', 'daily', 'reward']}}, - ], - }).exec() - .then(tasks => { - let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; - tasks.forEach(task => tasksByType[`${task.type}s`].push(task)); - - // Run cron - let progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); - - // Clear old completed todos - 30 days for free users, 90 for subscribers - // Do not delete challenges completed todos TODO unless the task is broken? - Tasks.Task.remove({ - userId: user._id, - type: 'todo', - completed: true, - dateCompleted: { - $lt: moment(now).subtract(user.isSubscribed() ? 90 : 30, 'days').toDate(), - }, - 'challenge.id': {$exists: false}, - }).exec(); - - let ranCron = user.isModified(); - let quest = common.content.quests[user.party.quest.key]; - - if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired - if (!ranCron) return next(); - - // Group.tavernBoss(user, progress); - - // Save user and tasks - let toSave = [user.save()]; - tasks.forEach(task => { - if (task.isModified()) toSave.push(task.save()); - }); - - return Bluebird.all(toSave) - .then(saved => { - user = res.locals.user = saved[0]; - if (!quest) return; - // If user is on a quest, roll for boss & player, or handle collections - let questType = quest.boss ? 'boss' : 'collect'; - // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? - return Group[`${questType}Quest`](user, progress) - .then(() => User.findById(user._id).exec()) // fetch the updated user... - .then(updatedUser => { - res.locals.user = updatedUser; - - return null; - }); - }) - .then(() => next()) - .catch(next); - }); -}; diff --git a/website/server/middlewares/api-v3/domain.js b/website/server/middlewares/api-v3/domain.js deleted file mode 100644 index fb5e28d352..0000000000 --- a/website/server/middlewares/api-v3/domain.js +++ /dev/null @@ -1,13 +0,0 @@ -import domainMiddleware from 'domain-middleware'; - -module.exports = function implementDomainMiddleware (server, mongoose) { - return domainMiddleware({ - server: { - close () { - server.close(); - mongoose.connection.close(); - }, - }, - killTimeout: 10000, - }); -}; diff --git a/website/server/middlewares/api-v3/ensureAccessRight.js b/website/server/middlewares/api-v3/ensureAccessRight.js deleted file mode 100644 index 2fb64ed8af..0000000000 --- a/website/server/middlewares/api-v3/ensureAccessRight.js +++ /dev/null @@ -1,23 +0,0 @@ -import { - NotAuthorized, -} from '../../libs/api-v3/errors'; - -export function ensureAdmin (req, res, next) { - let user = res.locals.user; - - if (!user.contributor.admin) { - return next(new NotAuthorized(res.t('noAdminAccess'))); - } - - next(); -} - -export function ensureSudo (req, res, next) { - let user = res.locals.user; - - if (!user.contributor.sudo) { - return next(new NotAuthorized(res.t('noSudoAccess'))); - } - - next(); -} diff --git a/website/server/middlewares/api-v3/ensureDevelpmentMode.js b/website/server/middlewares/api-v3/ensureDevelpmentMode.js deleted file mode 100644 index 98f70d33f5..0000000000 --- a/website/server/middlewares/api-v3/ensureDevelpmentMode.js +++ /dev/null @@ -1,12 +0,0 @@ -import nconf from 'nconf'; -import { - NotFound, -} from '../../libs/api-v3/errors'; - -module.exports = function ensureDevelpmentMode (req, res, next) { - if (nconf.get('IS_PROD')) { - next(new NotFound()); - } else { - next(); - } -}; diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js deleted file mode 100644 index 11d75042e3..0000000000 --- a/website/server/middlewares/api-v3/errorHandler.js +++ /dev/null @@ -1,86 +0,0 @@ -// The error handler middleware that handles all errors -// and respond to the client -import logger from '../../libs/api-v3/logger'; -import { - CustomError, - BadRequest, - InternalServerError, -} from '../../libs/api-v3/errors'; -import { - map, - omit, -} from 'lodash'; - -module.exports = function errorHandler (err, req, res, next) { // eslint-disable-line no-unused-vars - logger.error(err, { - originalUrl: req.originalUrl, - headers: omit(req.headers, ['x-api-key']), - body: req.body, - }); - - // In case of a CustomError class, use it's data - // Otherwise try to identify the type of error (mongoose validation, mongodb unique, ...) - // If we can't identify it, respond with a generic 500 error - let responseErr = err instanceof CustomError ? err : null; - - // Handle errors created with 'http-errors' or similar that have a status/statusCode property - if (err.statusCode && typeof err.statusCode === 'number') { - responseErr = new CustomError(); - responseErr.httpCode = err.statusCode; - responseErr.name = err.name; - responseErr.message = err.message; - } - - // Handle errors by express-validator - if (Array.isArray(err) && err[0].param && err[0].msg) { - responseErr = new BadRequest(res.t('invalidReqParams')); - responseErr.errors = err.map((paramErr) => { - return { - message: paramErr.msg, - param: paramErr.param, - value: paramErr.value, - }; - }); - } - - // Handle mongoose validation errors - if (err.name === 'ValidationError') { - responseErr = new BadRequest(err.message); // TODO standard message? translate? - responseErr.errors = map(err.errors, (mongooseErr) => { - return { - message: mongooseErr.message, - path: mongooseErr.path, - value: mongooseErr.value, - }; - }); - } - - // Handle Stripe Card errors errors (can be safely shown to the users) - // https://stripe.com/docs/api/node#errors - if (err.type === 'StripeCardError') { - responseErr = new BadRequest(err.message); - } - - if (!responseErr || responseErr.httpCode >= 500) { - // Try to identify the error... - // ... - // Otherwise create an InternalServerError and use it - // we don't want to leak anything, just a generic error message - // Use it also in case of identified errors but with httpCode === 500 - responseErr = new InternalServerError(); - } - - let jsonRes = { - success: false, - error: responseErr.name, - message: responseErr.message, - }; - - if (responseErr.errors) { - jsonRes.errors = responseErr.errors; - } - - // In some occasions like when invalid JSON is supplied `res.respond` might be not yet avalaible, - // in this case we use the standard res.status(...).json(...) - return res.status(responseErr.httpCode).json(jsonRes); -}; diff --git a/website/server/middlewares/api-v3/index.js b/website/server/middlewares/api-v3/index.js deleted file mode 100644 index 694fdb6ea5..0000000000 --- a/website/server/middlewares/api-v3/index.js +++ /dev/null @@ -1,87 +0,0 @@ -// This module is only used to attach middlewares to the express app -import errorHandler from './errorHandler'; -import bodyParser from 'body-parser'; -import notFoundHandler from './notFound'; -import nconf from 'nconf'; -import morgan from 'morgan'; -import cookieSession from 'cookie-session'; -import cors from './cors'; -import staticMiddleware from './static'; -import domainMiddleware from './domain'; -import mongoose from 'mongoose'; -import compression from 'compression'; -import favicon from 'serve-favicon'; -import methodOverride from 'method-override'; -import passport from 'passport'; -import path from 'path'; -import maintenanceMode from './maintenanceMode'; -import { - forceSSL, - forceHabitica, -} from './redirects'; -import v1 from './v1'; -import v2 from './v2'; -import v3 from './v3'; -import responseHandler from './response'; -import { - attachTranslateFunction, -} from './language'; - -const IS_PROD = nconf.get('IS_PROD'); -const DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); -const PUBLIC_DIR = path.join(__dirname, '/../../../client'); - -const SESSION_SECRET = nconf.get('SESSION_SECRET'); -const TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; - -module.exports = function attachMiddlewares (app, server) { - app.set('view engine', 'jade'); - app.set('views', `${__dirname}/../views`); - - app.use(domainMiddleware(server, mongoose)); - - if (!IS_PROD && !DISABLE_LOGGING) app.use(morgan('dev')); - - // add res.respond and res.t - app.use(responseHandler); - app.use(attachTranslateFunction); - - app.use(compression()); - app.use(favicon(`${PUBLIC_DIR}/favicon.ico`)); - - app.use(maintenanceMode); - - app.use(cors); - app.use(forceSSL); - app.use(forceHabitica); - - app.use(bodyParser.urlencoded({ - extended: true, // Uses 'qs' library as old connect middleware - })); - app.use(bodyParser.json()); - app.use(methodOverride()); - - app.use(cookieSession({ - name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies - secret: SESSION_SECRET, - httpOnly: true, // so cookies are not accessible with browser JS - // TODO what about https only (secure) ? - maxAge: TWO_WEEKS, - })); - - // Initialize Passport! Also use passport.session() middleware, to support - // persistent login sessions (recommended). - app.use(passport.initialize()); - app.use(passport.session()); - - app.use('/api/v2', v2); - app.use('/api/v1', v1); - app.use(v3); // the main app, also setup top-level routes - staticMiddleware(app); - - app.use(notFoundHandler); - - // Error handler middleware, define as the last one. - // Used for v3 and v1, v2 will keep using its own error handler - app.use(errorHandler); -}; diff --git a/website/server/middlewares/api-v3/language.js b/website/server/middlewares/api-v3/language.js deleted file mode 100644 index b83c9d5208..0000000000 --- a/website/server/middlewares/api-v3/language.js +++ /dev/null @@ -1,91 +0,0 @@ -import { model as User } from '../../models/user'; -import accepts from 'accepts'; -import common from '../../../../common'; -import _ from 'lodash'; -import { - translations, - defaultLangCodes, - multipleVersionsLanguages, -} from '../../libs/api-v3/i18n'; - -const i18n = common.i18n; - -function _getUniqueListOfLanguages (languages) { - let acceptableLanguages = _(languages).map((lang) => { - return lang.slice(0, 2); - }).uniq().value(); - - let uniqueListOfLanguages = _.intersection(acceptableLanguages, defaultLangCodes); - - return uniqueListOfLanguages; -} - -function _checkForApplicableLanguageVariant (originalLanguageOptions) { - let languageVariant = _.find(originalLanguageOptions, (accepted) => { - let trimmedAccepted = accepted.slice(0, 2); - - return multipleVersionsLanguages[trimmedAccepted]; - }); - - return languageVariant; -} - -function _getFromBrowser (req) { - let originalLanguageOptions = accepts(req).languages(); - let uniqueListOfLanguages = _getUniqueListOfLanguages(originalLanguageOptions); - let baseLanguage = (uniqueListOfLanguages[0] || '').toLowerCase(); - let languageMapping = multipleVersionsLanguages[baseLanguage]; - - if (languageMapping) { - let languageVariant = _checkForApplicableLanguageVariant(originalLanguageOptions); - - if (languageVariant) { - languageVariant = languageVariant.toLowerCase(); - } else { - return 'en'; - } - - return languageMapping[languageVariant] || baseLanguage; - } else { - return baseLanguage || 'en'; - } -} - -function _getFromUser (user, req) { - let preferredLang = user && user.preferences && user.preferences.language; - let lang = translations[preferredLang] ? preferredLang : _getFromBrowser(req); - - return lang; -} - -export function attachTranslateFunction (req, res, next) { - res.t = function reqTranslation () { - return i18n.t(...arguments, req.language); - }; - - next(); -} - -export function getUserLanguage (req, res, next) { - if (req.query.lang) { // In case the language is specified in the request url, use it - req.language = translations[req.query.lang] ? req.query.lang : 'en'; - return next(); - } else if (req.locals && req.locals.user) { // If the request is authenticated, use the user's preferred language - req.language = _getFromUser(req.locals.user, req); - return next(); - } else if (req.session && req.session.userId) { // Same thing if the user has a valid session - return User.findOne({ - _id: req.session.userId, - }, 'preferences.language') - .lean() - .exec() - .then((user) => { - req.language = _getFromUser(user, req); - return next(); - }) - .catch(next); - } else { // Otherwise get from browser - req.language = _getFromUser(null, req); - return next(); - } -} diff --git a/website/server/middlewares/api-v3/locals.js b/website/server/middlewares/api-v3/locals.js deleted file mode 100644 index e6e06e3b16..0000000000 --- a/website/server/middlewares/api-v3/locals.js +++ /dev/null @@ -1,61 +0,0 @@ -import nconf from 'nconf'; -import _ from 'lodash'; -import shared from '../../../../common'; -import * as i18n from '../../libs/api-v3/i18n'; -import { - getBuildUrl, - getManifestFiles, -} from '../../libs/api-v3/buildManifest'; -import forceRefresh from './../forceRefresh'; -import { tavernQuest } from '../../models/group'; -import { mods } from '../../models/user'; - -// To avoid stringifying more data then we need, -// items from `env` used on the client will have to be specified in this array -const CLIENT_VARS = ['language', 'isStaticPage', 'availableLanguages', 'translations', - 'FACEBOOK_KEY', 'NODE_ENV', 'BASE_URL', 'GA_ID', - 'AMAZON_PAYMENTS', 'STRIPE_PUB_KEY', 'AMPLITUDE_KEY', - 'worldDmg', 'mods', 'IS_MOBILE']; - -let env = { - getManifestFiles, - getBuildUrl, - _, - clientVars: CLIENT_VARS, - mods, - Content: shared.content, - siteVersion: forceRefresh.siteVersion, - availableLanguages: i18n.availableLanguages, - AMAZON_PAYMENTS: { - SELLER_ID: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), - CLIENT_ID: nconf.get('AMAZON_PAYMENTS:CLIENT_ID'), - }, -}; - -'NODE_ENV BASE_URL GA_ID STRIPE_PUB_KEY FACEBOOK_KEY AMPLITUDE_KEY'.split(' ').forEach(key => { - env[key] = nconf.get(key); -}); - -module.exports = function locals (req, res, next) { - let language = _.find(i18n.availableLanguages, {code: req.language}); - let isStaticPage = req.url.split('/')[1] === 'static'; // If url contains '/static/' - - // Load moment.js language file only when not on static pages - language.momentLang = !isStaticPage && i18n.momentLangs[language.code] || undefined; - - res.locals.habitrpg = _.assign(env, { - IS_MOBILE: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header('User-Agent')), - language, - isStaticPage, - translations: i18n.translations[language.code], - t (...args) { // stringName and vars are the allowed parameters - args.push(language.code); - return shared.i18n.t(...args); - }, - // Defined here and not outside of the middleware because tavernQuest might be an - // empty object until the query to fetch it finishes - worldDmg: tavernQuest && tavernQuest.extra && tavernQuest.extra.worldDmg || {}, - }); - - next(); -}; diff --git a/website/server/middlewares/api-v3/maintenanceMode.js b/website/server/middlewares/api-v3/maintenanceMode.js deleted file mode 100644 index 331663125a..0000000000 --- a/website/server/middlewares/api-v3/maintenanceMode.js +++ /dev/null @@ -1,31 +0,0 @@ -import { getUserLanguage } from './language'; -import nconf from 'nconf'; - -const MAINTENANCE_MODE = nconf.get('MAINTENANCE_MODE'); - -module.exports = function maintenanceMode (req, res, next) { - if (MAINTENANCE_MODE !== 'true') return next(); - - getUserLanguage(req, res, (err) => { - if (err) return next(err); - - let pageVariables = { - maintenanceStart: nconf.get('MAINTENANCE_START'), - maintenanceEnd: nconf.get('MAINTENANCE_END'), - translation: res.t, - }; - - if (req.headers && req.headers.accept && req.headers.accept.indexOf('text/html') !== -1) { - if (req.path === '/views/static/maintenance-info') { - return res.status(503).render('../../../views/static/maintenance-info', pageVariables); - } else { - return res.status(503).render('../../../views/static/maintenance', pageVariables); - } - } else { - return res.status(503).send({ - error: 'Maintenance', - message: 'Server offline for maintenance.', - }); - } - }); -}; diff --git a/website/server/middlewares/api-v3/notFound.js b/website/server/middlewares/api-v3/notFound.js deleted file mode 100644 index 6a71b5def4..0000000000 --- a/website/server/middlewares/api-v3/notFound.js +++ /dev/null @@ -1,7 +0,0 @@ -import { - NotFound, -} from '../../libs/api-v3/errors'; - -module.exports = function NotFoundMiddleware (req, res, next) { - next(new NotFound()); -}; diff --git a/website/server/middlewares/api-v3/redirects.js b/website/server/middlewares/api-v3/redirects.js deleted file mode 100644 index a907a89b14..0000000000 --- a/website/server/middlewares/api-v3/redirects.js +++ /dev/null @@ -1,43 +0,0 @@ -import nconf from 'nconf'; - -const IS_PROD = nconf.get('IS_PROD'); -const IGNORE_REDIRECT = nconf.get('IGNORE_REDIRECT'); -const BASE_URL = nconf.get('BASE_URL'); - -function isHTTP (req) { - return ( // eslint-disable-line no-extra-parens - req.header('x-forwarded-proto') && - req.header('x-forwarded-proto') !== 'https' && - IS_PROD && - BASE_URL.indexOf('https') === 0 - ); -} - -function isProxied (req) { - return ( // eslint-disable-line no-extra-parens - req.header('x-habitica-lb') && - req.header('x-habitica-lb') === 'Yes' - ); -} - -export function forceSSL (req, res, next) { - if (isHTTP(req) && !isProxied(req)) { - return res.redirect(BASE_URL + req.originalUrl); - } - - next(); -} - -// Redirect to habitica for non-api urls - -function nonApiUrl (req) { - return req.originalUrl.search(/\/api\//) === -1; -} - -export function forceHabitica (req, res, next) { - if (IS_PROD && !IGNORE_REDIRECT && !isProxied(req) && nonApiUrl(req)) { - return res.redirect(301, BASE_URL + req.url); - } - - next(); -} diff --git a/website/server/middlewares/api-v3/response.js b/website/server/middlewares/api-v3/response.js deleted file mode 100644 index e00d691fb2..0000000000 --- a/website/server/middlewares/api-v3/response.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = function responseHandler (req, res, next) { - // Only used for successful responses - res.respond = function respond (status = 200, data = {}, message) { - let user = res.locals && res.locals.user; - - let response = { - success: status < 400, - data, - }; - - if (message) response.message = message; - - // When userV=Number (user version) query parameter is passed and a user is logged in, - // sends back the current user._v in the response so that the client - // can verify if it's the most up to date data. - // Considered part of the private API for now and not officially supported - if (user && req.query.userV) { - response.userV = user._v; - } - - res.status(status).json(response); - }; - - next(); -}; diff --git a/website/server/middlewares/api-v3/setupBody.js b/website/server/middlewares/api-v3/setupBody.js deleted file mode 100644 index b3309fb2da..0000000000 --- a/website/server/middlewares/api-v3/setupBody.js +++ /dev/null @@ -1,5 +0,0 @@ -// TODO test this middleware -module.exports = function setupBodyMiddleware (req, res, next) { - req.body = req.body || {}; - next(); -}; diff --git a/website/server/middlewares/api-v3/static.js b/website/server/middlewares/api-v3/static.js deleted file mode 100644 index 19c1c9025c..0000000000 --- a/website/server/middlewares/api-v3/static.js +++ /dev/null @@ -1,18 +0,0 @@ -import express from 'express'; -import nconf from 'nconf'; -import path from 'path'; - -const IS_PROD = nconf.get('IS_PROD'); -const MAX_AGE = IS_PROD ? 31536000000 : 0; -const PUBLIC_DIR = path.join(__dirname, '/../../../client'); -const BUILD_DIR = path.join(__dirname, '/../../../build'); - -module.exports = function staticMiddleware (expressApp) { - // TODO move all static files to a single location (one for public and one for build) - expressApp.use(express.static(BUILD_DIR, { maxAge: MAX_AGE })); - expressApp.use('/common/dist', express.static(`${PUBLIC_DIR}/../../common/dist`, { maxAge: MAX_AGE })); - expressApp.use('/common/audio', express.static(`${PUBLIC_DIR}/../../common/audio`, { maxAge: MAX_AGE })); - expressApp.use('/common/script/public', express.static(`${PUBLIC_DIR}/../../common/script/public`, { maxAge: MAX_AGE })); - expressApp.use('/common/img', express.static(`${PUBLIC_DIR}/../../common/img`, { maxAge: MAX_AGE })); - expressApp.use(express.static(PUBLIC_DIR)); -}; diff --git a/website/server/middlewares/api-v3/v1.js b/website/server/middlewares/api-v3/v1.js deleted file mode 100644 index abe26f42e6..0000000000 --- a/website/server/middlewares/api-v3/v1.js +++ /dev/null @@ -1,19 +0,0 @@ -// API v1 middlewares and routes -// DEPRECATED AND INACTIVE - -import express from 'express'; -import nconf from 'nconf'; -import { - NotFound, -} from '../../libs/api-v3/errors'; - -const router = express.Router(); // eslint-disable-line babel/new-cap - -const BASE_URL = nconf.get('BASE_URL'); - -router.all('*', function deprecatedV1 (req, res, next) { - let error = new NotFound(`API v1 is no longer supported, please use API v3 instead (${BASE_URL}/static/api).`); - return next(error); -}); - -module.exports = router; diff --git a/website/server/middlewares/api-v3/v2.js b/website/server/middlewares/api-v3/v2.js deleted file mode 100644 index ec49e326a4..0000000000 --- a/website/server/middlewares/api-v3/v2.js +++ /dev/null @@ -1,27 +0,0 @@ -// DEPRECATED BUT STILL ACTIVE - -// import path from 'path'; -import swagger from 'swagger-node-express'; -// import shared from '../../../../common'; -import express from 'express'; -import analytics from './analytics'; -import responseHandler from './response'; - -const v2app = express(); - -// re-set the view options because they are not inherited from the top level app -v2app.set('view engine', 'jade'); -v2app.set('views', `${__dirname}/../../../views`); - -v2app.use(analytics); -v2app.use(responseHandler); - - -// Custom Directives -v2app.use('/', require('../../routes/api-v2/auth')); - -require('../../routes/api-v2/swagger')(swagger, v2app); - -v2app.use(require('../api-v2/errorHandler')); - -module.exports = v2app; diff --git a/website/server/middlewares/api-v3/v3.js b/website/server/middlewares/api-v3/v3.js deleted file mode 100644 index f9aa637fa0..0000000000 --- a/website/server/middlewares/api-v3/v3.js +++ /dev/null @@ -1,30 +0,0 @@ -import express from 'express'; -import expressValidator from 'express-validator'; -import analytics from './analytics'; -import setupBody from './setupBody'; -import routes from '../../libs/api-v3/routes'; -import path from 'path'; - -const API_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/api-v3/'); -const TOP_LEVEL_CONTROLLERS_PATH = path.join(__dirname, '/../../controllers/top-level/'); - -const v3app = express(); - -// re-set the view options because they are not inherited from the top level app -v3app.set('view engine', 'jade'); -v3app.set('views', `${__dirname}/../../../views`); - -v3app.use(expressValidator()); -v3app.use(analytics); -v3app.use(setupBody); - -const topLevelRouter = express.Router(); // eslint-disable-line babel/new-cap - -routes.walkControllers(topLevelRouter, TOP_LEVEL_CONTROLLERS_PATH); -v3app.use('/', topLevelRouter); - -const v3Router = express.Router(); // eslint-disable-line babel/new-cap -routes.walkControllers(v3Router, API_CONTROLLERS_PATH); -v3app.use('/api/v3', v3Router); - -module.exports = v3app; diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js deleted file mode 100644 index 9901af0e79..0000000000 --- a/website/server/models/challenge.js +++ /dev/null @@ -1,426 +0,0 @@ -import mongoose from 'mongoose'; -import Bluebird from 'bluebird'; -import validator from 'validator'; -import baseModel from '../libs/api-v3/baseModel'; -import _ from 'lodash'; -import * as Tasks from './task'; -import { model as User } from './user'; -import { - model as Group, - TAVERN_ID, -} from './group'; -import { removeFromArray } from '../libs/api-v3/collectionManipulators'; -import shared from '../../../common'; -import { sendTxn as txnEmail } from '../libs/api-v3/email'; -import sendPushNotification from '../libs/api-v3/pushNotifications'; -import cwait from 'cwait'; - -let Schema = mongoose.Schema; - -let schema = new Schema({ - name: {type: String, required: true}, - shortName: {type: String, required: true, minlength: 3}, - description: String, - official: {type: Boolean, default: false}, - tasksOrder: { - habits: [{type: String, ref: 'Task'}], - dailys: [{type: String, ref: 'Task'}], - todos: [{type: String, ref: 'Task'}], - rewards: [{type: String, ref: 'Task'}], - }, - leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, - group: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, - memberCount: {type: Number, default: 1}, - prize: {type: Number, default: 0, min: 0}, -}, { - strict: true, - minimize: false, // So empty objects are returned -}); - -schema.plugin(baseModel, { - noSet: ['_id', 'memberCount', 'tasksOrder'], - timestamps: true, -}); - -// A list of additional fields that cannot be updated (but can be set on creation) -let noUpdate = ['group', 'official', 'shortName', 'prize']; -schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) { - return this.sanitize(updateObj, noUpdate); -}; - -// Returns true if user is a member of the challenge -schema.methods.isMember = function isChallengeMember (user) { - return user.challenges.indexOf(this._id) !== -1; -}; - -// Returns true if the user can modify (close, selectWinner, ...) the challenge -schema.methods.canModify = function canModifyChallenge (user) { - return user.contributor.admin || this.leader === user._id; -}; - -// Returns true if user has access to the challenge (can join) -schema.methods.hasAccess = function hasAccessToChallenge (user, group) { - if (group.type === 'guild' && group.privacy === 'public') return true; - return user.getGroups().indexOf(this.group) !== -1; -}; - -// Returns true if user can view the challenge -// Different from hasAccess because you can see challenges of groups you've been removed from if you're partecipating in them -schema.methods.canView = function canViewChallenge (user, group) { - if (this.isMember(user)) return true; - return this.hasAccess(user, group); -}; - -// Takes a Task document and return a plain object of attributes that can be synced to the user -function _syncableAttrs (task) { - let t = task.toObject(); // lodash doesn't seem to like _.omit on Document - // only sync/compare important attrs - let omitAttrs = ['_id', 'userId', 'challenge', 'history', 'tags', 'completed', 'streak', 'notes', 'updatedAt']; - if (t.type !== 'reward') omitAttrs.push('value'); - return _.omit(t, omitAttrs); -} - -// Sync challenge to user, including tasks and tags. -// Used when user joins the challenge or to force sync. -schema.methods.syncToUser = async function syncChallengeToUser (user) { - let challenge = this; - challenge.shortName = challenge.shortName || challenge.name; - - // Add challenge to user.challenges - if (!_.contains(user.challenges, challenge._id)) user.challenges.push(challenge._id); - - // Sync tags - let userTags = user.tags; - let i = _.findIndex(userTags, {id: challenge._id}); - - if (i !== -1) { - if (userTags[i].name !== challenge.shortName) { - // update the name - it's been changed since - userTags[i].name = challenge.shortName; - } - } else { - userTags.push({ - id: challenge._id, - name: challenge.shortName, - challenge: true, - }); - } - - let [challengeTasks, userTasks] = await Bluebird.all([ - // Find original challenge tasks - Tasks.Task.find({ - userId: {$exists: false}, - 'challenge.id': challenge._id, - }).exec(), - // Find user's tasks linked to this challenge - Tasks.Task.find({ - userId: user._id, - 'challenge.id': challenge._id, - }).exec(), - ]); - - let toSave = []; // An array of things to save - - challengeTasks.forEach(chalTask => { - let matchingTask = _.find(userTasks, userTask => userTask.challenge.taskId === chalTask._id); - - if (!matchingTask) { // If the task is new, create it - matchingTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask))); - matchingTask.challenge = {taskId: chalTask._id, id: challenge._id}; - matchingTask.userId = user._id; - user.tasksOrder[`${chalTask.type}s`].push(matchingTask._id); - } else { - _.merge(matchingTask, _syncableAttrs(chalTask)); - // Make sure the task is in user.tasksOrder - let orderList = user.tasksOrder[`${chalTask.type}s`]; - if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id); - } - - if (!matchingTask.notes) matchingTask.notes = chalTask.notes; // don't override the notes, but provide it if not provided - if (matchingTask.tags.indexOf(challenge._id) === -1) matchingTask.tags.push(challenge._id); // add tag if missing - toSave.push(matchingTask.save()); - }); - - // Flag deleted tasks as "broken" - userTasks.forEach(userTask => { - if (!_.find(challengeTasks, chalTask => chalTask._id === userTask.challenge.taskId)) { - userTask.challenge.broken = 'TASK_DELETED'; - toSave.push(userTask.save()); - } - }); - - toSave.push(user.save()); - return Bluebird.all(toSave); -}; - -async function _fetchMembersIds (challengeId) { - return (await User.find({challenges: {$in: [challengeId]}}).select('_id').lean().exec()).map(member => member._id); -} - -async function _addTaskFn (challenge, tasks, memberId) { - let updateTasksOrderQ = {$push: {}}; - let toSave = []; - - tasks.forEach(chalTask => { - let userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(_syncableAttrs(chalTask))); - userTask.challenge = {taskId: chalTask._id, id: challenge._id}; - userTask.userId = memberId; - - let tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`]; - if (!tasksOrderList) { - updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`] = { - $position: 0, // unshift - $each: [userTask._id], - }; - } else { - tasksOrderList.$each.unshift(userTask._id); - } - - toSave.push(userTask.save({ - validateBeforeSave: false, // no user data supplied - })); - }); - - // Update the user - toSave.unshift(User.update({_id: memberId}, updateTasksOrderQ).exec()); - return await Bluebird.all(toSave); -} - -// Add a new task to challenge members -schema.methods.addTasks = async function challengeAddTasks (tasks) { - let challenge = this; - let membersIds = await _fetchMembersIds(challenge._id); - - let queue = new cwait.TaskQueue(Bluebird, 5); // process only 5 users concurrently - - await Bluebird.map(membersIds, queue.wrap((memberId) => { - return _addTaskFn(challenge, tasks, memberId); - })); -}; - -// Sync updated task to challenge members -schema.methods.updateTask = async function challengeUpdateTask (task) { - let challenge = this; - - let updateCmd = {$set: {}}; - - let syncableAttrs = _syncableAttrs(task); - for (let key in syncableAttrs) { - updateCmd.$set[key] = syncableAttrs[key]; - } - - // Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks - await Tasks.Task.update({ - userId: {$exists: true}, - 'challenge.id': challenge.id, - 'challenge.taskId': task._id, - }, updateCmd, {multi: true}).exec(); -}; - -// Remove a task from challenge members -schema.methods.removeTask = async function challengeRemoveTask (task) { - let challenge = this; - - // Set the task as broken - await Tasks.Task.update({ - userId: {$exists: true}, - 'challenge.id': challenge.id, - 'challenge.taskId': task._id, - }, { - $set: {'challenge.broken': 'TASK_DELETED'}, - }, {multi: true}).exec(); -}; - -// Unlink challenges tasks (and the challenge itself) from user -schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) { - let challengeId = this._id; - let findQuery = { - userId: user._id, - 'challenge.id': challengeId, - }; - - removeFromArray(user.challenges, challengeId); - - if (keep === 'keep-all') { - await Tasks.Task.update(findQuery, { - $set: {challenge: {}}, - }, {multi: true}).exec(); - - await user.save(); - } else { // keep = 'remove-all' - let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec(); - let taskPromises = tasks.map(task => { - // Remove task from user.tasksOrder and delete them - if (task.type !== 'todo' || !task.completed) { - removeFromArray(user.tasksOrder[`${task.type}s`], task._id); - } - - return task.remove(); - }); - user.markModified('tasksOrder'); - taskPromises.push(user.save()); - return Bluebird.all(taskPromises); - } -}; - -// TODO everything here should be moved to a worker -// actually even for a worker it's probably just too big and will kill mongo, figure out something else -schema.methods.closeChal = async function closeChal (broken = {}) { - let challenge = this; - - let winner = broken.winner; - let brokenReason = broken.broken; - - // Delete the challenge - await this.model('Challenge').remove({_id: challenge._id}).exec(); - - // Refund the leader if the challenge is closed and the group not the tavern - if (challenge.group !== TAVERN_ID && brokenReason === 'CHALLENGE_DELETED') { - await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec(); - } - - // Update the challengeCount on the group - await Group.update({_id: challenge.group}, {$inc: {challengeCount: -1}}).exec(); - - // Award prize to winner and notify - if (winner) { - winner.achievements.challenges.push(challenge.name); - winner.balance += challenge.prize / 4; - let savedWinner = await winner.save(); - if (savedWinner.preferences.emailNotifications.wonChallenge !== false) { - txnEmail(savedWinner, 'won-challenge', [ - {name: 'CHALLENGE_NAME', content: challenge.name}, - ]); - } - - sendPushNotification(savedWinner, shared.i18n.t('wonChallenge'), challenge.name); - } - - // Run some operations in the background withouth blocking the thread - let backgroundTasks = [ - // And it's tasks - Tasks.Task.remove({'challenge.id': challenge._id, userId: {$exists: false}}).exec(), - // Set the challenge tag to non-challenge status and remove the challenge from the user's challenges - User.update({ - challenges: challenge._id, - 'tags._id': challenge._id, - }, { - $set: {'tags.$.challenge': false}, - $pull: {challenges: challenge._id}, - }, {multi: true}).exec(), - // Break users' tasks - Tasks.Task.update({ - 'challenge.id': challenge._id, - }, { - $set: { - 'challenge.broken': brokenReason, - 'challenge.winner': winner && winner.profile.name, - }, - }, {multi: true}).exec(), - ]; - - Bluebird.all(backgroundTasks); -}; - -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the challenge model) -// These will be removed once API v2 is discontinued - -// Get all the tasks belonging to a challenge, -schema.methods.getTasks = function getChallengeTasks () { - let args = Array.from(arguments); - let cb; - let type; - - if (args.length === 1) { - cb = args[0]; - } else if (args.length > 1) { - type = args[0]; - cb = args[1]; - } else { - cb = function noop () {}; - } - - let query = { - userId: { - $exists: false, - }, - - 'challenge.id': this._id, - }; - - if (type) query.type = type; - - return Tasks.Task.find(query, cb); // so we can use it as a promise -}; - -// Given challenge and an array of tasks and one of members return an API compatible challenge + tasks obj + members -schema.methods.addToChallenge = function addToChallenge (tasks, members) { - let obj = this.toJSON(); - obj.members = members; - - let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it - - obj.habits = []; - obj.dailys = []; - obj.todos = []; - obj.rewards = []; - - obj.tasksOrder = undefined; - let unordered = []; - - tasks.forEach((task) => { - // We want to push the task at the same position where it's stored in tasksOrder - let pos = tasksOrder[`${task.type}s`].indexOf(task._id); - if (pos === -1) { // Should never happen, it means the lists got out of sync - unordered.push(task.toJSONV2()); - } else { - obj[`${task.type}s`][pos] = task.toJSONV2(); - } - }); - - // Reconcile unordered items - unordered.forEach((task) => { - obj[`${task.type}s`].push(task); - }); - - // Remove null values that can be created when inserting tasks at an index > length - ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { - obj[type] = _.compact(obj[type]); - }); - - return obj; -}; - -// Return the data maintaining backward compatibility -schema.methods.getTransformedData = function getTransformedData (options) { - let self = this; - - let cb = options.cb; - let populateMembers = options.populateMembers; - - let queryMembers = { - challenges: self._id, - }; - - let selectDataMembers = '_id'; - - if (populateMembers) { - selectDataMembers += ` ${populateMembers}`; - } - - let membersQuery = User.find(queryMembers).select(selectDataMembers); - if (options.limitPopulation) membersQuery.limit(15); - - Bluebird.all([ - membersQuery.exec(), - self.getTasks(), - ]) - .then((results) => { - cb(null, self.addToChallenge(results[1], results[0])); - }) - .catch(cb); -}; - -// END of API v2 methods - -export let model = mongoose.model('Challenge', schema); diff --git a/website/server/models/coupon.js b/website/server/models/coupon.js deleted file mode 100644 index af9953aba0..0000000000 --- a/website/server/models/coupon.js +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable camelcase */ - -import mongoose from 'mongoose'; -import _ from 'lodash'; -import shared from '../../../common'; -import couponCode from 'coupon-code'; -import baseModel from '../libs/api-v3/baseModel'; -import { - BadRequest, - NotAuthorized, -} from '../libs/api-v3/errors'; - -export let schema = new mongoose.Schema({ - _id: {type: String, default: couponCode.generate}, - event: {type: String, enum: ['wondercon', 'google_6mo']}, - user: {type: String, ref: 'User'}, -}, { - strict: true, - minimize: false, // So empty objects are returned -}); - -schema.plugin(baseModel, { - timestamps: true, - _id: false, -}); - -schema.statics.generate = async function generateCoupons (event, count = 1) { - let coupons = _.times(count, () => { - return {event}; - }); - - return await this.create(coupons); -}; - -schema.statics.apply = async function applyCoupon (user, req, code) { - let coupon = await this.findById(couponCode.validate(code)).exec(); - if (!coupon) throw new BadRequest(shared.i18n.t('invalidCoupon', req.language)); - if (coupon.user) throw new NotAuthorized(shared.i18n.t('couponUsed', req.language)); - - if (coupon.event === 'wondercon') { - user.items.gear.owned.eyewear_special_wondercon_red = true; - user.items.gear.owned.eyewear_special_wondercon_black = true; - user.items.gear.owned.back_special_wondercon_black = true; - user.items.gear.owned.back_special_wondercon_red = true; - user.items.gear.owned.body_special_wondercon_red = true; - user.items.gear.owned.body_special_wondercon_black = true; - user.items.gear.owned.body_special_wondercon_gold = true; - user.extra = {signupEvent: 'wondercon'}; - } - - await user.save(); - coupon.user = user._id; - await coupon.save(); -}; - -module.exports.schema = schema; -export let model = mongoose.model('Coupon', schema); diff --git a/website/server/models/emailUnsubscription.js b/website/server/models/emailUnsubscription.js deleted file mode 100644 index fe30e5d608..0000000000 --- a/website/server/models/emailUnsubscription.js +++ /dev/null @@ -1,24 +0,0 @@ -import mongoose from 'mongoose'; -import validator from 'validator'; -import baseModel from '../libs/api-v3/baseModel'; - -// A collection used to store mailing list unsubscription for non registered email addresses -export let schema = new mongoose.Schema({ - email: { - type: String, - required: true, - trim: true, - lowercase: true, - validator: [validator.isEmail, 'Invalid email.'], - }, -}, { - strict: true, - minimize: false, // So empty objects are returned -}); - -schema.plugin(baseModel, { - noSet: ['_id'], - timestamps: true, -}); - -export let model = mongoose.model('EmailUnsubscription', schema); diff --git a/website/server/models/group.js b/website/server/models/group.js deleted file mode 100644 index 53305d94b9..0000000000 --- a/website/server/models/group.js +++ /dev/null @@ -1,760 +0,0 @@ -import mongoose from 'mongoose'; -import { - model as User, - nameFields, -} from './user'; -import shared from '../../../common'; -import _ from 'lodash'; -import { model as Challenge} from './challenge'; -import validator from 'validator'; -import { removeFromArray } from '../libs/api-v3/collectionManipulators'; -import { - InternalServerError, - BadRequest, -} from '../libs/api-v3/errors'; -import * as firebase from '../libs/api-v2/firebase'; -import baseModel from '../libs/api-v3/baseModel'; -import { sendTxn as sendTxnEmail } from '../libs/api-v3/email'; -import Bluebird from 'bluebird'; -import nconf from 'nconf'; -import sendPushNotification from '../libs/api-v3/pushNotifications'; - -const questScrolls = shared.content.quests; -const Schema = mongoose.Schema; - -export const INVITES_LIMIT = 100; -export const TAVERN_ID = shared.TAVERN_ID; - -// NOTE once Firebase is enabled any change to groups' members in MongoDB will have to be run through the API -// changes made directly to the db will cause Firebase to get out of sync -export let schema = new Schema({ - name: {type: String, required: true}, - description: String, - leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, - type: {type: String, enum: ['guild', 'party'], required: true}, - privacy: {type: String, enum: ['private', 'public'], default: 'private', required: true}, - chat: Array, - /* - # [{ - # timestamp: Date - # user: String - # text: String - # contributor: String - # uuid: String - # id: String - # }] - */ - leaderOnly: { // restrict group actions to leader (members can't do them) - challenges: {type: Boolean, default: false, required: true}, - // invites: {type: Boolean, default: false, required: true}, - }, - memberCount: {type: Number, default: 1}, - challengeCount: {type: Number, default: 0}, - balance: {type: Number, default: 0}, - logo: String, - leaderMessage: String, - quest: { - key: String, - active: {type: Boolean, default: false}, - leader: {type: String, ref: 'User'}, - progress: { - hp: Number, - collect: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, // {feather: 5, ingot: 3} - rage: Number, // limit break / "energy stored in shell", for explosion-attacks - }, - - // Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click - // 'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them. - // TODO when booting user, remove from .joined and check again if we can now start the quest - members: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - extra: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - }, -}, { - strict: true, - minimize: false, // So empty objects are returned -}); - -schema.plugin(baseModel, { - noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'challengeCount'], -}); - -// A list of additional fields that cannot be updated (but can be set on creation) -let noUpdate = ['privacy', 'type']; -schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) { - return this.sanitize(updateObj, noUpdate); -}; - -// Basic fields to fetch for populating a group info -export let basicFields = 'name type privacy'; - -schema.pre('remove', true, async function preRemoveGroup (next, done) { - next(); - try { - await this.removeGroupInvitations(); - done(); - } catch (err) { - done(err); - } -}); - -schema.post('remove', function postRemoveGroup (group) { - firebase.deleteGroup(group._id); -}); - -schema.statics.getGroup = async function getGroup (options = {}) { - let {user, groupId, fields, optionalMembership = false, populateLeader = false, requireMembership = false} = options; - let query; - - let isUserParty = groupId === 'party' || user.party._id === groupId; - let isUserGuild = user.guilds.indexOf(groupId) !== -1; - let isTavern = ['habitrpg', TAVERN_ID].indexOf(groupId) !== -1; - - // When requireMembership is true check that user is member even in public guild - if (requireMembership && !isUserParty && !isUserGuild && !isTavern) { - return null; - } - - // When optionalMembership is true it's not required for the user to be a member of the group - if (isUserParty) { - query = {type: 'party', _id: user.party._id}; - } else if (isTavern) { - query = {_id: TAVERN_ID}; - } else if (optionalMembership === true) { - query = {_id: groupId}; - } else if (isUserGuild) { - query = {type: 'guild', _id: groupId}; - } else { - query = {type: 'guild', privacy: 'public', _id: groupId}; - } - - let mQuery = this.findOne(query); - if (fields) mQuery.select(fields); - if (populateLeader === true) mQuery.populate('leader', nameFields); - let group = await mQuery.exec(); - return group; -}; - -export const VALID_QUERY_TYPES = ['party', 'guilds', 'privateGuilds', 'publicGuilds', 'tavern']; - -schema.statics.getGroups = async function getGroups (options = {}) { - let {user, types, groupFields = basicFields, sort = '-memberCount', populateLeader = false} = options; - let queries = []; - - // Throw error if an invalid type is supplied - let areValidTypes = types.every(type => VALID_QUERY_TYPES.indexOf(type) !== -1); - if (!areValidTypes) throw new BadRequest(shared.i18n.t('groupTypesRequired')); - - types.forEach(type => { - switch (type) { - case 'party': { - queries.push(this.getGroup({user, groupId: 'party', fields: groupFields, populateLeader})); - break; - } - case 'guilds': { - let userGuildsQuery = this.find({ - type: 'guild', - _id: {$in: user.guilds}, - }).select(groupFields); - if (populateLeader === true) userGuildsQuery.populate('leader', nameFields); - userGuildsQuery.sort(sort).exec(); - queries.push(userGuildsQuery); - break; - } - case 'privateGuilds': { - let privateGuildsQuery = this.find({ - type: 'guild', - privacy: 'private', - _id: {$in: user.guilds}, - }).select(groupFields); - if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields); - privateGuildsQuery.sort(sort).exec(); - queries.push(privateGuildsQuery); - break; - } - // NOTE: when returning publicGuilds we use `.lean()` so all mongoose methods won't be available. - // Docs are going to be plain javascript objects - case 'publicGuilds': { - let publicGuildsQuery = this.find({ - type: 'guild', - privacy: 'public', - }).select(groupFields); - if (populateLeader === true) publicGuildsQuery.populate('leader', nameFields); - publicGuildsQuery.sort(sort).lean().exec(); - queries.push(publicGuildsQuery); - break; - } - case 'tavern': { - if (types.indexOf('publicGuilds') === -1) { - queries.push(this.getGroup({user, groupId: TAVERN_ID, fields: groupFields})); - } - break; - } - } - }); - - let groupsArray = _.reduce(await Bluebird.all(queries), (previousValue, currentValue) => { - if (_.isEmpty(currentValue)) return previousValue; // don't add anything to the results if the query returned null or an empty array - return previousValue.concat(Array.isArray(currentValue) ? currentValue : [currentValue]); // otherwise concat the new results to the previousValue - }, []); - - return groupsArray; -}; - -// When converting to json remove chat messages with more than 1 flag and remove all flags info -// unless the user is an admin -// Not putting into toJSON because there we can't access user -schema.statics.toJSONCleanChat = function groupToJSONCleanChat (group, user) { - let toJSON = group.toJSON(); - if (!user.contributor.admin) { - _.remove(toJSON.chat, chatMsg => { - chatMsg.flags = {}; - return chatMsg.flagCount >= 2; - }); - } - return toJSON; -}; - -schema.methods.removeGroupInvitations = async function removeGroupInvitations () { - let group = this; - - let usersToRemoveInvitationsFrom = await User.find({ - [`invitations.${group.type}${group.type === 'guild' ? 's' : ''}.id`]: group._id, - }).exec(); - - let userUpdates = usersToRemoveInvitationsFrom.map(user => { - if (group.type === 'party') { - user.invitations.party = {}; - this.markModified('invitations.party'); - } else { - removeFromArray(user.invitations.guilds, { id: group._id }); - } - return user.save(); - }); - - return Bluebird.all(userUpdates); -}; - -// Return true if user is a member of the group -schema.methods.isMember = function isGroupMember (user) { - if (this._id === TAVERN_ID) { - return true; // everyone is considered part of the tavern - } else if (this.type === 'party') { - return user.party._id === this._id ? true : false; - } else { // guilds - return user.guilds.indexOf(this._id) !== -1; - } -}; - -export function chatDefaults (msg, user) { - let message = { - id: shared.uuid(), - text: msg, - timestamp: Number(new Date()), - likes: {}, - flags: {}, - flagCount: 0, - }; - - if (user) { - _.defaults(message, { - uuid: user._id, - contributor: user.contributor && user.contributor.toObject(), - backer: user.backer && user.backer.toObject(), - user: user.profile.name, - }); - } else { - message.uuid = 'system'; - } - - return message; -} - -const NO_CHAT_NOTIFICATIONS = [TAVERN_ID]; -schema.methods.sendChat = function sendChat (message, user) { - this.chat.unshift(chatDefaults(message, user)); - this.chat.splice(200); - - // Kick off chat notifications in the background. - let lastSeenUpdate = {$set: {}, $inc: {_v: 1}}; - lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true}; - - // do not send notifications for guilds with more than 5000 users and for the tavern - if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > 5000) { - // TODO For Tavern, only notify them if their name was mentioned - // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names? - // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec(); - } else { - let query = {}; - - if (this.type === 'party') { - query['party._id'] = this._id; - } else { - query.guilds = this._id; - } - - query._id = { $ne: user ? user._id : ''}; - - User.update(query, lastSeenUpdate, {multi: true}).exec(); - } -}; - -schema.methods.startQuest = async function startQuest (user) { - // not using i18n strings because these errors are meant for devs who forgot to pass some parameters - if (this.type !== 'party') throw new InternalServerError('Must be a party to use this method'); - if (!this.quest.key) throw new InternalServerError('Party does not have a pending quest'); - if (this.quest.active) throw new InternalServerError('Quest is already active'); - - let userIsParticipating = this.quest.members[user._id]; - let quest = questScrolls[this.quest.key]; - let collected = {}; - if (quest.collect) { - collected = _.transform(quest.collect, (result, n, itemToCollect) => { - result[itemToCollect] = 0; - }); - } - - this.markModified('quest'); - this.quest.active = true; - if (quest.boss) { - this.quest.progress.hp = quest.boss.hp; - if (quest.boss.rage) this.quest.progress.rage = 0; - } else if (quest.collect) { - this.quest.progress.collect = collected; - } - - // Changes quest.members to only include participating members - // TODO: is that important? What does it matter if the non-participating members - // are still on the object? - // TODO: is it important to run clean quest progress on non-members like we did in v2? - this.quest.members = _.pick(this.quest.members, _.identity); - let nonUserQuestMembers = _.keys(this.quest.members); - removeFromArray(nonUserQuestMembers, user._id); - - if (userIsParticipating) { - user.party.quest.key = this.quest.key; - user.party.quest.progress.down = 0; - user.party.quest.progress.collect = collected; - user.party.quest.completed = null; - user.markModified('party.quest'); - } - - // Remove the quest from the quest leader items (if they are the current user) - if (this.quest.leader === user._id) { - user.items.quests[this.quest.key] -= 1; - user.markModified('items.quests'); - } else { // another user is starting the quest, update the leader separately - await User.update({_id: this.quest.leader}, { - $inc: { - [`items.quests.${this.quest.key}`]: -1, - }, - }).exec(); - } - - // update the remaining users - await User.update({ - _id: { $in: nonUserQuestMembers }, - }, { - $set: { - 'party.quest.key': this.quest.key, - 'party.quest.progress.down': 0, - 'party.quest.progress.collect': collected, - 'party.quest.completed': null, - }, - }, { multi: true }).exec(); - - // send notifications in the background without blocking - User.find( - { _id: { $in: nonUserQuestMembers } }, - 'party.quest items.quests auth.facebook auth.local preferences.emailNotifications pushDevices profile.name' - ).exec().then((membersToNotify) => { - let membersToEmail = _.filter(membersToNotify, (member) => { - // send push notifications and filter users that disabled emails - sendPushNotification(member, 'HabitRPG', `${shared.i18n.t('questStarted')}: ${quest.text()}`); - - return member.preferences.emailNotifications.questStarted !== false && - member._id !== user._id; - }); - sendTxnEmail(membersToEmail, 'quest-started', [ - { name: 'PARTY_URL', content: '/#/options/groups/party' }, - ]); - }); -}; - -// return a clean object for user.quest -function _cleanQuestProgress (merge) { - let clean = { - key: null, - progress: { - up: 0, - down: 0, - collect: {}, - }, - completed: null, - RSVPNeeded: false, - }; - - if (merge) { - _.merge(clean, _.omit(merge, 'progress')); - if (merge.progress) _.merge(clean.progress, merge.progress); - } - - return clean; -} - -schema.statics.cleanQuestProgress = _cleanQuestProgress; - -// returns a clean object for group.quest -schema.statics.cleanGroupQuest = function cleanGroupQuest () { - return { - key: null, - active: false, - leader: null, - progress: { - collect: {}, - }, - members: {}, - }; -}; - -// Participants: Grant rewards & achievements, finish quest -// Returns the promise from update().exec() -schema.methods.finishQuest = function finishQuest (quest) { - let questK = quest.key; - let updates = {$inc: {}, $set: {}}; - - updates.$inc[`achievements.quests.${questK}`] = 1; - updates.$inc['stats.gp'] = Number(quest.drop.gp); - updates.$inc['stats.exp'] = Number(quest.drop.exp); - updates.$inc._v = 1; - - if (this._id === TAVERN_ID) { - updates.$set['party.quest.completed'] = questK; // Just show the notif - } else { - updates.$set['party.quest'] = _cleanQuestProgress({completed: questK}); // clear quest progress - } - - _.each(quest.drop.items, (item) => { - let dropK = item.key; - - switch (item.type) { - case 'gear': { - // TODO This means they can lose their new gear on death, is that what we want? - updates.$set[`items.gear.owned.${dropK}`] = true; - break; - } - case 'eggs': - case 'food': - case 'hatchingPotions': - case 'quests': { - updates.$inc[`items.${item.type}.${dropK}`] = _.where(quest.drop.items, {type: item.type, key: item.key}).length; - break; - } - case 'pets': { - updates.$set[`items.pets.${dropK}`] = 5; - break; - } - case 'mounts': { - updates.$set[`items.mounts.${dropK}`] = true; - break; - } - } - }); - - let q = this._id === TAVERN_ID ? {} : {_id: {$in: _.keys(this.quest.members)}}; - this.quest = {}; - this.markModified('quest'); - return User.update(q, updates, {multi: true}).exec(); -}; - -function _isOnQuest (user, progress, group) { - return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true; -} - -// Returns a promise -schema.statics.collectQuest = async function collectQuest (user, progress) { - let group = await this.getGroup({user, groupId: 'party'}); - if (!_isOnQuest(user, progress, group)) return; - let quest = shared.content.quests[group.quest.key]; - - _.each(progress.collect, (v, k) => { - group.quest.progress.collect[k] += v; - }); - - let foundText = _.reduce(progress.collect, (m, v, k) => { - m.push(`${v} ${quest.collect[k].text('en')}`); - return m; - }, []); - - foundText = foundText ? foundText.join(', ') : 'nothing'; - group.sendChat(`\`${user.profile.name} found ${foundText}.\``); - group.markModified('quest.progress.collect'); - - // Still needs completing - if (_.find(shared.content.quests[group.quest.key].collect, (v, k) => { - return group.quest.progress.collect[k] < v.count; - })) return group.save(); - - await group.finishQuest(quest); - group.sendChat('`All items found! Party has received their rewards.`'); - return group.save(); -}; - -schema.statics.bossQuest = async function bossQuest (user, progress) { - let group = await this.getGroup({user, groupId: 'party'}); - if (!_isOnQuest(user, progress, group)) return; - - let quest = shared.content.quests[group.quest.key]; - if (!progress || !quest) return; // TODO why is this ever happening, progress should be defined at this point, log? - - let down = progress.down * quest.boss.str; // multiply by boss strength - - group.quest.progress.hp -= progress.up; - // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests! - let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`; - let bossAttack = nconf.get('CRON_SAFE_MODE') === 'true' ? `${quest.boss.name('en')} did not attack the party because it was asleep while maintenance was happening.` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; - group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``); - - // If boss has Rage, increment Rage as well - if (quest.boss.rage) { - group.quest.progress.rage += Math.abs(down); - if (group.quest.progress.rage >= quest.boss.rage.value) { - group.sendChat(quest.boss.rage.effect('en')); - group.quest.progress.rage = 0; - - // TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage - if (quest.boss.rage.healing) group.quest.progress.hp += group.quest.progress.hp * quest.boss.rage.healing; - if (group.quest.progress.hp > quest.boss.hp) group.quest.progress.hp = quest.boss.hp; - } - } - - // Everyone takes damage - await User.update({ - _id: {$in: _.keys(group.quest.members)}, - }, { - $inc: {'stats.hp': down, _v: 1}, - }, {multi: true}).exec(); - // Apply changes the currently cronning user locally so we don't have to reload it to get the updated state - // TODO how to mark not modified? https://github.com/Automattic/mongoose/pull/1167 - // must be notModified or otherwise could overwrite future changes: if the user is saved it'll save - // the modified user.stats.hp but that must not happen as the hp value has already been updated by the User.update above - // if (down) user.stats.hp += down; - - // Boss slain, finish quest - if (group.quest.progress.hp <= 0) { - group.sendChat(`\`You defeated ${quest.boss.name('en')}! Questing party members receive the rewards of victory.\``); - - // Participants: Grant rewards & achievements, finish quest - await group.finishQuest(shared.content.quests[group.quest.key]); - return group.save(); - } - - return group.save(); -}; - -// to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` -// we export an empty object that is then populated with the query-returned data -export let tavernQuest = {}; -let tavernQ = {_id: TAVERN_ID, 'quest.key': {$ne: null}}; - -// we use process.nextTick because at this point the model is not yet available -process.nextTick(() => { - model // eslint-disable-line no-use-before-define - .findOne(tavernQ).exec() - .then(tavern => { - if (!tavern) return; // No tavern quest - - // Using _assign so we don't lose the reference to the exported tavernQuest - _.assign(tavernQuest, tavern.quest.toObject()); - }) - .catch(err => { - throw err; - }); -}); - -// returns a promise -schema.statics.tavernBoss = async function tavernBoss (user, progress) { - if (!progress) return; - - // hack: prevent crazy damage to world boss - let dmg = Math.min(900, Math.abs(progress.up || 0)); - let rage = -Math.min(900, Math.abs(progress.down || 0)); - - let tavern = await this.findOne(tavernQ).exec(); - if (!(tavern && tavern.quest && tavern.quest.key)) return; - - let quest = shared.content.quests[tavern.quest.key]; - - if (tavern.quest.progress.hp <= 0) { - tavern.sendChat(quest.completionChat('en')); - await tavern.finishQuest(quest); - _.assign(tavernQuest, {extra: null}); - return tavern.save(); - } else { - // Deal damage. Note a couple things here, str & def are calculated. If str/def are defined in the database, - // use those first - which allows us to update the boss on the go if things are too easy/hard. - if (!tavern.quest.extra) tavern.quest.extra = {}; - tavern.quest.progress.hp -= dmg / (tavern.quest.extra.def || quest.boss.def); - tavern.quest.progress.rage -= rage * (tavern.quest.extra.str || quest.boss.str); - - if (tavern.quest.progress.rage >= quest.boss.rage.value) { - if (!tavern.quest.extra.worldDmg) tavern.quest.extra.worldDmg = {}; - - let wd = tavern.quest.extra.worldDmg; - // Burnout attacks Ian, Seasonal Sorceress, tavern - // Be-Wilder attacks Alex, Matt, Bailey - let scene = wd.market ? wd.stables ? wd.bailey ? false : 'bailey' : 'stables' : 'market'; // eslint-disable-line no-nested-ternary - - if (!scene) { - tavern.sendChat(`\`${quest.boss.name('en')} tries to unleash ${quest.boss.rage.title('en')} but is too tired.\``); - tavern.quest.progress.rage = 0; // quest.boss.rage.value; - } else { - tavern.sendChat(quest.boss.rage[scene]('en')); - tavern.quest.extra.worldDmg[scene] = true; - tavern.quest.extra.worldDmg.recent = scene; - tavern.markModified('quest.extra.worldDmg'); - tavern.quest.progress.rage = 0; - if (quest.boss.rage.healing) { - tavern.quest.progress.hp += quest.boss.rage.healing * tavern.quest.progress.hp; - } - } - } - - if (quest.boss.desperation && tavern.quest.progress.hp < quest.boss.desperation.threshold && !tavern.quest.extra.desperate) { - tavern.sendChat(quest.boss.desperation.text('en')); - tavern.quest.extra.desperate = true; - tavern.quest.extra.def = quest.boss.desperation.def; - tavern.quest.extra.str = quest.boss.desperation.str; - tavern.markModified('quest.extra'); - } - - _.assign(tavernQuest, tavern.quest.toObject()); - return tavern.save(); - } -}; - -schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') { - let group = this; - - let challenges = await Challenge.find({ - _id: {$in: user.challenges}, - group: group._id, - }); - - let challengesToRemoveUserFrom = challenges.map(chal => { - return chal.unlinkTasks(user, keep); - }); - await Bluebird.all(challengesToRemoveUserFrom); - - let promises = []; - - // remove the group from the user's groups - if (group.type === 'guild') { - promises.push(User.update({_id: user._id}, {$pull: {guilds: group._id}}).exec()); - } else { - promises.push(User.update({_id: user._id}, {$set: {party: {}}}).exec()); - } - - // If user is the last one in group and group is private, delete it - if (group.memberCount <= 1 && group.privacy === 'private') { - promises.push(group.remove()); - } else { // otherwise If the leader is leaving (or if the leader previously left, and this wasn't accounted for) - let update = { - $inc: {memberCount: -1}, - }; - - if (group.leader === user._id) { - let query = group.type === 'party' ? {'party._id': group._id} : {guilds: group._id}; - query._id = {$ne: user._id}; - let seniorMember = await User.findOne(query).select('_id').exec(); - - // could be missing in case of public guild (that can have 0 members) with 1 member who is leaving - if (seniorMember) update.$set = {leader: seniorMember._id}; - } - promises.push(group.update(update).exec()); - } - - firebase.removeUserFromGroup(group._id, user._id); - - return await Bluebird.all(promises); -}; - -// API v2 compatibility methods -schema.methods.getTransformedData = function getTransformedData (options) { - let cb = options.cb; - let populateMembers = options.populateMembers; - let populateInvites = options.populateInvites; - let populateChallenges = options.populateChallenges; - - let obj = this.toJSON(); - - let queryMembers = {}; - let queryInvites = {}; - - if (this.type === 'guild') { - queryInvites['invitations.guilds.id'] = this._id; - } else { - queryInvites['invitations.party.id'] = this._id; - } - - if (this.type === 'guild') { - queryMembers.guilds = this._id; - } else { - queryMembers['party._id'] = this._id; - } - - let selectDataMembers = '_id'; - let selectDataInvites = '_id'; - let selectDataChallenges = '_id'; - - if (populateMembers) { - selectDataMembers += ` ${populateMembers}`; - } - if (populateInvites) { - selectDataInvites += ` ${populateInvites}`; - } - if (populateChallenges) { - selectDataChallenges += ` ${populateChallenges}`; - } - - let membersQuery = User.find(queryMembers).select(selectDataMembers); - if (options.limitPopulation) membersQuery.limit(15); - - Bluebird.all([ - membersQuery.exec(), - User.find(queryInvites).select(populateInvites).exec(), - Challenge.find({group: obj._id}).select(populateMembers).exec(), - ]) - .then((results) => { - obj.members = results[0]; - obj.invites = results[1]; - obj.challenges = results[2]; - - cb(null, obj); - }) - .catch(cb); -}; -// END API v2 compatibility methods - -export let model = mongoose.model('Group', schema); - -// initialize tavern if !exists (fresh installs) -// do not run when testing as it's handled by the tests and can easily cause a race condition -if (!nconf.get('IS_TEST')) { - model.count({_id: TAVERN_ID}, (err, ct) => { - if (err) throw err; - if (ct > 0) return; - new model({ // eslint-disable-line babel/new-cap - _id: TAVERN_ID, - leader: '7bde7864-ebc5-4ee2-a4b7-1070d464cdb0', // Siena Leslie - name: 'Tavern', - type: 'guild', - privacy: 'public', - }).save(); - }); -} diff --git a/website/server/models/tag.js b/website/server/models/tag.js deleted file mode 100644 index f201541540..0000000000 --- a/website/server/models/tag.js +++ /dev/null @@ -1,27 +0,0 @@ -import mongoose from 'mongoose'; -import baseModel from '../libs/api-v3/baseModel'; -import { v4 as uuid } from 'uuid'; -import validator from 'validator'; - -let Schema = mongoose.Schema; - -export let schema = new Schema({ - id: { - type: String, - default: uuid, - validate: [validator.isUUID, 'Invalid uuid.'], - }, - name: {type: String, required: true}, - challenge: {type: String}, -}, { - strict: true, - minimize: false, // So empty objects are returned - _id: false, // use id instead of _id -}); - -schema.plugin(baseModel, { - noSet: ['_id', 'id', 'challenge'], - _id: false, // use id instead of _id -}); - -export let model = mongoose.model('Tag', schema); diff --git a/website/server/models/task.js b/website/server/models/task.js deleted file mode 100644 index 536d95ec0c..0000000000 --- a/website/server/models/task.js +++ /dev/null @@ -1,222 +0,0 @@ -import mongoose from 'mongoose'; -import shared from '../../../common'; -import validator from 'validator'; -import moment from 'moment'; -import baseModel from '../libs/api-v3/baseModel'; -import _ from 'lodash'; -import { preenHistory } from '../libs/api-v3/preening'; - -let Schema = mongoose.Schema; -let discriminatorOptions = { - discriminatorKey: 'type', // the key that distinguishes task types -}; -let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), {_id: false}); - -export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; - -// Important -// When something changes here remember to update the client side model at common/script/libs/taskDefaults -export let TaskSchema = new Schema({ - _legacyId: String, // TODO Remove when v2 is deprecated - type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]}, - text: {type: String, required: true}, - notes: {type: String, default: ''}, - tags: [{ - type: String, - validate: [validator.isUUID, 'Invalid uuid.'], - }], - value: {type: Number, default: 0, required: true}, // redness or cost for rewards Required because it must be settable (for rewards) - priority: { - type: Number, - default: 1, - required: true, - validate: [ - (val) => [0.1, 1, 1.5, 2].indexOf(val) !== -1, - 'Valid priority values are 0.1, 1, 1.5, 2.', - ], - }, - attribute: {type: String, default: 'str', enum: ['str', 'con', 'int', 'per']}, - userId: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set it belongs to a challenge - - challenge: { - id: {type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}, // When set (and userId not set) it's the original task - taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']}, // When not set but challenge.id defined it's the original task - broken: {type: String, enum: ['CHALLENGE_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED', 'CHALLENGE_CLOSED', 'CHALLENGE_TASK_NOT_FOUND']}, // CHALLENGE_TASK_NOT_FOUND comes from v3 migration - winner: String, // user.profile.name of the winner - }, - - reminders: [{ - _id: false, - id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], default: shared.uuid, required: true}, - startDate: {type: Date}, - time: {type: Date, required: true}, - }], -}, _.defaults({ - minimize: true, // So empty objects are returned - strict: true, -}, discriminatorOptions)); - -TaskSchema.plugin(baseModel, { - noSet: ['challenge', 'userId', 'completed', 'history', 'dateCompleted', '_legacyId'], - sanitizeTransform (taskObj) { - if (taskObj.type && taskObj.type !== 'reward') { // value should be settable directly only for rewards - delete taskObj.value; - } - - return taskObj; - }, - private: [], - timestamps: true, -}); - -// Sanitize user tasks linked to a challenge -// See http://habitica.wikia.com/wiki/Challenges#Challenge_Participant.27s_Permissions for more info -TaskSchema.statics.sanitizeUserChallengeTask = function sanitizeUserChallengeTask (taskObj) { - let initialSanitization = this.sanitize(taskObj); - - return _.pick(initialSanitization, ['streak', 'checklist', 'attribute', 'reminders', 'tags', 'notes']); -}; - -// Sanitize checklist objects (disallowing id) -TaskSchema.statics.sanitizeChecklist = function sanitizeChecklist (checklistObj) { - delete checklistObj.id; - return checklistObj; -}; - -// Sanitize reminder objects (disallowing id) -TaskSchema.statics.sanitizeReminder = function sanitizeReminder (reminderObj) { - delete reminderObj.id; - return reminderObj; -}; - -TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta) { - let chalTask = this; - - chalTask.value += delta; - - if (chalTask.type === 'habit' || chalTask.type === 'daily') { - // Add only one history entry per day - let lastChallengHistoryIndex = chalTask.history.length - 1; - - if (chalTask.history[lastChallengHistoryIndex] && - moment(chalTask.history[lastChallengHistoryIndex].date).isSame(new Date(), 'day')) { - chalTask.history[lastChallengHistoryIndex] = { - date: Number(new Date()), - value: chalTask.value, - }; - chalTask.markModified(`history.${lastChallengHistoryIndex}`); - } else { - chalTask.history.push({ - date: Number(new Date()), - value: chalTask.value, - }); - - // Only preen task history once a day when the task is scored first - if (chalTask.history.length > 365) { - chalTask.history = preenHistory(chalTask.history, true); // true means the challenge will retain as much entries as a subscribed user - } - } - } - - await chalTask.save(); -}; - - -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) -// These will be removed once API v2 is discontinued - -// toJSON for API v2 -TaskSchema.methods.toJSONV2 = function toJSONV2 () { - let toJSON = this.toJSON(); - if (toJSON._legacyId) { - toJSON.id = toJSON._legacyId; - } else { - toJSON.id = toJSON._id; - } - - if (!toJSON.challenge) toJSON.challenge = {}; - - let v3Tags = this.tags; - - toJSON.tags = {}; - v3Tags.forEach(tag => { - toJSON.tags[tag] = true; - }); - - toJSON.dateCreated = this.createdAt; - - return toJSON; -}; - -TaskSchema.statics.fromJSONV2 = function fromJSONV2 (taskObj) { - if (taskObj.id) taskObj._id = taskObj.id; - - let v2Tags = taskObj.tags || {}; - - taskObj.tags = []; - taskObj.tags = _.map(v2Tags, (tag, key) => key); - - return taskObj; -}; - -// END of API v2 methods - -export let Task = mongoose.model('Task', TaskSchema); - -// habits and dailies shared fields -let habitDailySchema = () => { - return {history: Array}; // [{date:Date, value:Number}], // this causes major performance problems -}; - -// dailys and todos shared fields -let dailyTodoSchema = () => { - return { - completed: {type: Boolean, default: false}, - // Checklist fields (dailies and todos) - collapseChecklist: {type: Boolean, default: false}, - checklist: [{ - completed: {type: Boolean, default: false}, - text: {type: String, required: false, default: ''}, // required:false because it can be empty on creation - _id: false, - id: {type: String, default: shared.uuid, validate: [validator.isUUID, 'Invalid uuid.']}, - }], - }; -}; - -export let HabitSchema = new Schema(_.defaults({ - up: {type: Boolean, default: true}, - down: {type: Boolean, default: true}, -}, habitDailySchema()), subDiscriminatorOptions); -export let habit = Task.discriminator('habit', HabitSchema); - -export let DailySchema = new Schema(_.defaults({ - frequency: {type: String, default: 'weekly', enum: ['daily', 'weekly']}, - everyX: {type: Number, default: 1}, // e.g. once every X weeks - startDate: { - type: Date, - default () { - return moment().startOf('day').toDate(); - }, - }, - repeat: { // used only for 'weekly' frequency, - m: {type: Boolean, default: true}, - t: {type: Boolean, default: true}, - w: {type: Boolean, default: true}, - th: {type: Boolean, default: true}, - f: {type: Boolean, default: true}, - s: {type: Boolean, default: true}, - su: {type: Boolean, default: true}, - }, - streak: {type: Number, default: 0}, -}, habitDailySchema(), dailyTodoSchema()), subDiscriminatorOptions); -export let daily = Task.discriminator('daily', DailySchema); - -export let TodoSchema = new Schema(_.defaults({ - dateCompleted: Date, - // TODO we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript - date: String, // due date for todos -}, dailyTodoSchema()), subDiscriminatorOptions); -export let todo = Task.discriminator('todo', TodoSchema); - -export let RewardSchema = new Schema({}, subDiscriminatorOptions); -export let reward = Task.discriminator('reward', RewardSchema); diff --git a/website/server/models/user.js b/website/server/models/user.js deleted file mode 100644 index 2d0e561a66..0000000000 --- a/website/server/models/user.js +++ /dev/null @@ -1,824 +0,0 @@ -import mongoose from 'mongoose'; -import shared from '../../../common'; -import _ from 'lodash'; -import validator from 'validator'; -import moment from 'moment'; -import * as Tasks from './task'; -import Bluebird from 'bluebird'; -import { schema as TagSchema } from './tag'; -import baseModel from '../libs/api-v3/baseModel'; -import { - chatDefaults, - TAVERN_ID, -} from './group'; -import { defaults } from 'lodash'; - -let Schema = mongoose.Schema; - -// User schema definition -export let schema = new Schema({ - apiToken: { - type: String, - default: shared.uuid, - }, - - auth: { - blocked: Boolean, - facebook: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - local: { - email: { - type: String, - validate: [validator.isEmail, shared.i18n.t('invalidEmail')], - }, - username: { - type: String, - }, - // Store a lowercase version of username to check for duplicates - lowerCaseUsername: String, - hashed_password: String, // eslint-disable-line camelcase - salt: String, - }, - timestamps: { - created: {type: Date, default: Date.now}, - loggedin: {type: Date, default: Date.now}, - }, - }, - // We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which - // have been updated (http://goo.gl/gQLz41), but we want *every* update - _v: { type: Number, default: 0 }, - migration: String, - achievements: { - originalUser: Boolean, - habitSurveys: Number, - ultimateGearSets: { - healer: {type: Boolean, default: false}, - wizard: {type: Boolean, default: false}, - rogue: {type: Boolean, default: false}, - warrior: {type: Boolean, default: false}, - }, - beastMaster: Boolean, - beastMasterCount: Number, - mountMaster: Boolean, - mountMasterCount: Number, - triadBingo: Boolean, - triadBingoCount: Number, - veteran: Boolean, - snowball: Number, - spookySparkles: Number, - shinySeed: Number, - seafoam: Number, - streak: Number, - challenges: Array, - quests: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - rebirths: Number, - rebirthLevel: Number, - perfect: {type: Number, default: 0}, - habitBirthdays: Number, - valentine: Number, - costumeContest: Boolean, // Superseded by costumeContests - nye: Number, - habiticaDays: Number, - greeting: Number, - thankyou: Number, - costumeContests: Number, - birthday: Number, - partyUp: Boolean, - partyOn: Boolean, - }, - - backer: { - tier: Number, - npc: String, - tokensApplied: Boolean, - }, - - contributor: { - // 1-9, see https://trello.com/c/wkFzONhE/277-contributor-gear https://github.com/HabitRPG/habitrpg/issues/3801 - level: { - type: Number, - min: 0, - max: 9, - }, - admin: Boolean, - sudo: Boolean, - // Artisan, Friend, Blacksmith, etc - text: String, - // a markdown textarea to list their contributions + links - contributions: String, - critical: String, - }, - - balance: {type: Number, default: 0}, - // Not saved on the user right now - filters: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - - purchased: { - ads: {type: Boolean, default: false}, - // eg, {skeleton: true, pumpkin: true, eb052b: true} - skin: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - hair: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - shirt: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - background: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - txnCount: {type: Number, default: 0}, - mobileChat: Boolean, - plan: { - planId: String, - paymentMethod: String, // enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']} - customerId: String, // Billing Agreement Id in case of Amazon Payments - dateCreated: Date, - dateTerminated: Date, - dateUpdated: Date, - extraMonths: {type: Number, default: 0}, - gemsBought: {type: Number, default: 0}, - mysteryItems: {type: Array, default: () => []}, - lastBillingDate: Date, // Used only for Amazon Payments to keep track of billing date - consecutive: { - count: {type: Number, default: 0}, - offset: {type: Number, default: 0}, // when gifted subs, offset++ for each month. offset-- each new-month (cron). count doesn't ++ until offset==0 - gemCapExtra: {type: Number, default: 0}, - trinkets: {type: Number, default: 0}, - }, - }, - }, - - flags: { - customizationsNotification: {type: Boolean, default: false}, - showTour: {type: Boolean, default: true}, - tour: { - // -1 indicates "uninitiated", -2 means "complete", any other number is the current tour step (0-index) - intro: {type: Number, default: -1}, - classes: {type: Number, default: -1}, - stats: {type: Number, default: -1}, - tavern: {type: Number, default: -1}, - party: {type: Number, default: -1}, - guilds: {type: Number, default: -1}, - challenges: {type: Number, default: -1}, - market: {type: Number, default: -1}, - pets: {type: Number, default: -1}, - mounts: {type: Number, default: -1}, - hall: {type: Number, default: -1}, - equipment: {type: Number, default: -1}, - }, - tutorial: { - common: { - habits: {type: Boolean, default: false}, - dailies: {type: Boolean, default: false}, - todos: {type: Boolean, default: false}, - rewards: {type: Boolean, default: false}, - party: {type: Boolean, default: false}, - pets: {type: Boolean, default: false}, - gems: {type: Boolean, default: false}, - skills: {type: Boolean, default: false}, - classes: {type: Boolean, default: false}, - tavern: {type: Boolean, default: false}, - equipment: {type: Boolean, default: false}, - items: {type: Boolean, default: false}, - }, - ios: { - addTask: {type: Boolean, default: false}, - editTask: {type: Boolean, default: false}, - deleteTask: {type: Boolean, default: false}, - filterTask: {type: Boolean, default: false}, - groupPets: {type: Boolean, default: false}, - inviteParty: {type: Boolean, default: false}, - }, - }, - dropsEnabled: {type: Boolean, default: false}, - itemsEnabled: {type: Boolean, default: false}, - newStuff: {type: Boolean, default: false}, - rewrite: {type: Boolean, default: true}, - contributor: Boolean, - classSelected: {type: Boolean, default: false}, - mathUpdates: Boolean, - rebirthEnabled: {type: Boolean, default: false}, - levelDrops: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - chatRevoked: Boolean, - // Used to track the status of recapture emails sent to each user, - // can be 0 - no email sent - 1, 2, 3 or 4 - 4 means no more email will be sent to the user - recaptureEmailsPhase: {type: Number, default: 0}, - // Needed to track the tip to send inside the email - weeklyRecapEmailsPhase: {type: Number, default: 0}, - // Used to track when the next weekly recap should be sent - lastWeeklyRecap: {type: Date, default: Date.now}, - // Used to enable weekly recap emails as users login - lastWeeklyRecapDiscriminator: Boolean, - communityGuidelinesAccepted: {type: Boolean, default: false}, - cronCount: {type: Number, default: 0}, - welcomed: {type: Boolean, default: false}, - armoireEnabled: {type: Boolean, default: false}, - armoireOpened: {type: Boolean, default: false}, - armoireEmpty: {type: Boolean, default: false}, - cardReceived: {type: Boolean, default: false}, - warnedLowHealth: {type: Boolean, default: false}, - }, - - history: { - exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined - todos: Array, // [{data: Date, value: Number}] // big peformance issues if these are defined - }, - - items: { - gear: { - owned: _.transform(shared.content.gear.flat, (m, v) => { - m[v.key] = {type: Boolean}; - if (v.key.match(/[armor|head|shield]_warrior_0/) || v.gearSet === 'glasses') { - m[v.key].default = true; - } - }), - - equipped: { - weapon: String, - armor: {type: String, default: 'armor_base_0'}, - head: {type: String, default: 'head_base_0'}, - shield: {type: String, default: 'shield_base_0'}, - back: String, - headAccessory: String, - eyewear: String, - body: String, - }, - costume: { - weapon: String, - armor: {type: String, default: 'armor_base_0'}, - head: {type: String, default: 'head_base_0'}, - shield: {type: String, default: 'shield_base_0'}, - back: String, - headAccessory: String, - eyewear: String, - body: String, - }, - }, - - special: { - snowball: {type: Number, default: 0}, - spookySparkles: {type: Number, default: 0}, - shinySeed: {type: Number, default: 0}, - seafoam: {type: Number, default: 0}, - valentine: {type: Number, default: 0}, - valentineReceived: Array, // array of strings, by sender name - nye: {type: Number, default: 0}, - nyeReceived: Array, - greeting: {type: Number, default: 0}, - greetingReceived: Array, - thankyou: {type: Number, default: 0}, - thankyouReceived: Array, - birthday: {type: Number, default: 0}, - birthdayReceived: Array, - }, - - // -------------- Animals ------------------- - // Complex bit here. The result looks like: - // pets: { - // 'Wolf-Desert': 0, // 0 means does not own - // 'PandaCub-Red': 10, // Number represents "Growth Points" - // etc... - // } - pets: _.defaults( - // First transform to a 1D eggs/potions mapping - _.transform(shared.content.pets, (m, v, k) => m[k] = Number), - // Then add additional pets (quest, backer, contributor, premium) - _.transform(shared.content.questPets, (m, v, k) => m[k] = Number), - _.transform(shared.content.specialPets, (m, v, k) => m[k] = Number), - _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Number) - ), - currentPet: String, // Cactus-Desert - - // eggs: { - // 'PandaCub': 0, // 0 indicates "doesn't own" - // 'Wolf': 5 // Number indicates "stacking" - // } - eggs: _.transform(shared.content.eggs, (m, v, k) => m[k] = Number), - - // hatchingPotions: { - // 'Desert': 0, // 0 indicates "doesn't own" - // 'CottonCandyBlue': 5 // Number indicates "stacking" - // } - hatchingPotions: _.transform(shared.content.hatchingPotions, (m, v, k) => m[k] = Number), - - // Food: { - // 'Watermelon': 0, // 0 indicates "doesn't own" - // 'RottenMeat': 5 // Number indicates "stacking" - // } - food: _.transform(shared.content.food, (m, v, k) => m[k] = Number), - - // mounts: { - // 'Wolf-Desert': true, - // 'PandaCub-Red': false, - // etc... - // } - mounts: _.defaults( - // First transform to a 1D eggs/potions mapping - _.transform(shared.content.pets, (m, v, k) => m[k] = Boolean), - // Then add quest and premium pets - _.transform(shared.content.questPets, (m, v, k) => m[k] = Boolean), - _.transform(shared.content.premiumPets, (m, v, k) => m[k] = Boolean), - // Then add additional mounts (backer, contributor) - _.transform(shared.content.specialMounts, (m, v, k) => m[k] = Boolean) - ), - currentMount: String, - - // Quests: { - // 'boss_0': 0, // 0 indicates "doesn't own" - // 'collection_honey': 5 // Number indicates "stacking" - // } - quests: _.transform(shared.content.quests, (m, v, k) => m[k] = Number), - - lastDrop: { - date: {type: Date, default: Date.now}, - count: {type: Number, default: 0}, - }, - }, - - lastCron: {type: Date, default: Date.now}, - - // {GROUP_ID: Boolean}, represents whether they have unseen chat messages - newMessages: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - - challenges: [{type: String, ref: 'Challenge', validate: [validator.isUUID, 'Invalid uuid.']}], - - invitations: { - // Using an array without validation because otherwise mongoose treat this as a subdocument and applies _id by default - // Schema is (id, name, inviter) - // TODO one way to fix is http://mongoosejs.com/docs/guide.html#_id - guilds: {type: Array, default: () => []}, - // Using a Mixed type because otherwise user.invitations.party = {} // to reset invitation, causes validation to fail TODO - // schema is the same as for guild invitations (id, name, inviter) - party: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - }, - - guilds: [{type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.']}], - - party: { - _id: {type: String, validate: [validator.isUUID, 'Invalid uuid.'], ref: 'Group'}, - order: {type: String, default: 'level'}, - orderAscending: {type: String, default: 'ascending'}, - quest: { - key: String, - progress: { - up: {type: Number, default: 0}, - down: {type: Number, default: 0}, - collect: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, // {feather:1, ingot:2} - }, - completed: String, // When quest is done, we move it from key => completed, and it's a one-time flag (for modal) that they unset by clicking "ok" in browser - RSVPNeeded: {type: Boolean, default: false}, // Set to true when invite is pending, set to false when quest invite is accepted or rejected, quest starts, or quest is cancelled - }, - }, - preferences: { - dayStart: {type: Number, default: 0, min: 0, max: 23}, - size: {type: String, enum: ['broad', 'slim'], default: 'slim'}, - hair: { - color: {type: String, default: 'red'}, - base: {type: Number, default: 3}, - bangs: {type: Number, default: 1}, - beard: {type: Number, default: 0}, - mustache: {type: Number, default: 0}, - flower: {type: Number, default: 1}, - }, - hideHeader: {type: Boolean, default: false}, - skin: {type: String, default: '915533'}, - shirt: {type: String, default: 'blue'}, - timezoneOffset: {type: Number, default: 0}, - sound: {type: String, default: 'off', enum: ['off', 'danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme']}, - chair: {type: String, default: 'none'}, - timezoneOffsetAtLastCron: Number, - language: String, - automaticAllocation: Boolean, - allocationMode: {type: String, enum: ['flat', 'classbased', 'taskbased'], default: 'flat'}, - autoEquip: {type: Boolean, default: true}, - costume: Boolean, - dateFormat: {type: String, enum: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], default: 'MM/dd/yyyy'}, - sleep: {type: Boolean, default: false}, - stickyHeader: {type: Boolean, default: true}, - disableClasses: {type: Boolean, default: false}, - newTaskEdit: {type: Boolean, default: false}, - dailyDueDefaultView: {type: Boolean, default: false}, - tagsCollapsed: {type: Boolean, default: false}, - advancedCollapsed: {type: Boolean, default: false}, - toolbarCollapsed: {type: Boolean, default: false}, - reverseChatOrder: {type: Boolean, default: false}, - background: String, - displayInviteToPartyWhenPartyIs1: {type: Boolean, default: true}, - webhooks: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - // For the following fields make sure to use strict comparison when searching for falsey values (=== false) - // As users who didn't login after these were introduced may have them undefined/null - emailNotifications: { - unsubscribeFromAll: {type: Boolean, default: false}, - newPM: {type: Boolean, default: true}, - kickedGroup: {type: Boolean, default: true}, - wonChallenge: {type: Boolean, default: true}, - giftedGems: {type: Boolean, default: true}, - giftedSubscription: {type: Boolean, default: true}, - invitedParty: {type: Boolean, default: true}, - invitedGuild: {type: Boolean, default: true}, - questStarted: {type: Boolean, default: true}, - invitedQuest: {type: Boolean, default: true}, - // remindersToLogin: {type: Boolean, default: true}, - // importantAnnouncements are in fact the recapture emails - importantAnnouncements: {type: Boolean, default: true}, - weeklyRecaps: {type: Boolean, default: true}, - }, - suppressModals: { - levelUp: {type: Boolean, default: false}, - hatchPet: {type: Boolean, default: false}, - raisePet: {type: Boolean, default: false}, - streak: {type: Boolean, default: false}, - }, - improvementCategories: { - type: Array, - validate: (categories) => { - const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity']; - let isValidCategory = categories.every(category => validCategories.indexOf(category) !== -1); - return isValidCategory; - }, - }, - }, - profile: { - blurb: String, - imageUrl: String, - name: String, - }, - stats: { - hp: {type: Number, default: shared.maxHealth}, - mp: {type: Number, default: 10}, - exp: {type: Number, default: 0}, - gp: {type: Number, default: 0}, - lvl: {type: Number, default: 1}, - - // Class System - class: {type: String, enum: ['warrior', 'rogue', 'wizard', 'healer'], default: 'warrior', required: true}, - points: {type: Number, default: 0}, - str: {type: Number, default: 0}, - con: {type: Number, default: 0}, - int: {type: Number, default: 0}, - per: {type: Number, default: 0}, - buffs: { - str: {type: Number, default: 0}, - int: {type: Number, default: 0}, - per: {type: Number, default: 0}, - con: {type: Number, default: 0}, - stealth: {type: Number, default: 0}, - streaks: {type: Boolean, default: false}, - snowball: {type: Boolean, default: false}, - spookySparkles: {type: Boolean, default: false}, - shinySeed: {type: Boolean, default: false}, - seafoam: {type: Boolean, default: false}, - }, - training: { - int: {type: Number, default: 0}, - per: {type: Number, default: 0}, - str: {type: Number, default: 0}, - con: {type: Number, default: 0}, - }, - }, - - tags: [TagSchema], - - inbox: { - newMessages: {type: Number, default: 0}, - blocks: {type: Array, default: () => []}, - messages: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - optOut: {type: Boolean, default: false}, - }, - tasksOrder: { - habits: [{type: String, ref: 'Task'}], - dailys: [{type: String, ref: 'Task'}], - todos: [{type: String, ref: 'Task'}], - rewards: [{type: String, ref: 'Task'}], - }, - extra: {type: Schema.Types.Mixed, default: () => { - return {}; - }}, - pushDevices: { - type: [{ - regId: {type: String}, - type: {type: String}, - }], - default: () => [], - }, -}, { - strict: true, - minimize: false, // So empty objects are returned -}); - -schema.plugin(baseModel, { - // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...) - noSet: [], - private: ['auth.local.hashed_password', 'auth.local.salt'], - toJSONTransform: function userToJSON (plainObj, originalDoc) { - // plainObj.filters = {}; // TODO Not saved, remove? - plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs - - return plainObj; - }, -}); - -// A list of publicly accessible fields (not everything from preferences because there are also a lot of settings tha should remain private) -export let publicFields = `preferences.size preferences.hair preferences.skin preferences.shirt - preferences.costume preferences.sleep preferences.background profile stats achievements party - backer contributor auth.timestamps items`; - -// The minimum amount of data needed when populating multiple users -export let nameFields = 'profile.name'; - -schema.post('init', function postInitUser (doc) { - shared.wrap(doc); -}); - -function _populateDefaultTasks (user, taskTypes) { - let tagsI = taskTypes.indexOf('tag'); - - if (tagsI !== -1) { - user.tags = _.map(shared.content.userDefaults.tags, (tag) => { - let newTag = _.cloneDeep(tag); - - // tasks automatically get _id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here - newTag.id = shared.uuid(); - // Render tag's name in user's language - newTag.name = newTag.name(user.preferences.language); - return newTag; - }); - } - - let tasksToCreate = []; - - if (tagsI !== -1) { - taskTypes = _.clone(taskTypes); - taskTypes.splice(tagsI, 1); - } - - _.each(taskTypes, (taskType) => { - let tasksOfType = _.map(shared.content.userDefaults[`${taskType}s`], (taskDefaults) => { - let newTask = new Tasks[taskType](taskDefaults); - - newTask.userId = user._id; - newTask.text = taskDefaults.text(user.preferences.language); - if (newTask.notes) newTask.notes = taskDefaults.notes(user.preferences.language); - if (taskDefaults.checklist) { - newTask.checklist = _.map(taskDefaults.checklist, (checklistItem) => { - checklistItem.text = checklistItem.text(user.preferences.language); - return checklistItem; - }); - } - - return newTask.save(); - }); - - tasksToCreate.push(...tasksOfType); - }); - - return Bluebird.all(tasksToCreate) - .then((tasksCreated) => { - _.each(tasksCreated, (task) => { - user.tasksOrder[`${task.type}s`].push(task._id); - }); - }); -} - -function _populateDefaultsForNewUser (user) { - let taskTypes; - let iterableFlags = user.flags.toObject(); - - if (user.registeredThrough === 'habitica-web' || user.registeredThrough === 'habitica-android') { - taskTypes = ['habit', 'daily', 'todo', 'reward', 'tag']; - - _.each(iterableFlags.tutorial.common, (val, section) => { - user.flags.tutorial.common[section] = true; - }); - } else { - taskTypes = ['todo', 'tag']; - user.flags.showTour = false; - - _.each(iterableFlags.tour, (val, section) => { - user.flags.tour[section] = -2; - }); - } - - return _populateDefaultTasks(user, taskTypes); -} - -function _setProfileName (user) { - let fb = user.auth.facebook; - - let localUsername = user.auth.local && user.auth.local.username; - let facebookUsername = fb && (fb.displayName || fb.name || fb.username || `${fb.first_name && fb.first_name} ${fb.last_name}`); - let anonymous = 'Anonymous'; - - return localUsername || facebookUsername || anonymous; -} - -schema.pre('save', true, function preSaveUser (next, done) { - next(); - - if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) { - this.preferences.dayStart = 0; - } - - if (!this.profile.name) { - this.profile.name = _setProfileName(this); - } - - // Determines if Beast Master should be awarded - let beastMasterProgress = shared.count.beastMasterProgress(this.items.pets); - - if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) { - this.achievements.beastMaster = true; - } - - // Determines if Mount Master should be awarded - let mountMasterProgress = shared.count.mountMasterProgress(this.items.mounts); - - if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) { - this.achievements.mountMaster = true; - } - - // Determines if Triad Bingo should be awarded - - let dropPetCount = shared.count.dropPetsCurrentlyOwned(this.items.pets); - let qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90; - - if (qualifiesForTriad || this.achievements.triadBingoCount > 0) { - this.achievements.triadBingo = true; - } - - // Enable weekly recap emails for old users who sign in - if (this.flags.lastWeeklyRecapDiscriminator) { - // Enable weekly recap emails in 24 hours - this.flags.lastWeeklyRecap = moment().subtract(6, 'days').toDate(); - // Unset the field so this is run only once - this.flags.lastWeeklyRecapDiscriminator = undefined; - } - - // EXAMPLE CODE for allowing all existing and new players to be - // automatically granted an item during a certain time period: - // if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01')) - // this.items.pets['JackOLantern-Base'] = 5; - - // our own version incrementer - if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0; - this._v++; - - // Populate new users with default content - if (this.isNew) { - _populateDefaultsForNewUser(this) - .then(() => done()) - .catch(done); - } else { - done(); - } -}); - -schema.pre('update', function preUpdateUser () { - this.update({}, {$inc: {_v: 1}}); -}); - -schema.methods.isSubscribed = function isSubscribed () { - return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion -}; - -// Get an array of groups ids the user is member of -schema.methods.getGroups = function getUserGroups () { - let userGroups = this.guilds.slice(0); // clone user.guilds so we don't modify the original - if (this.party._id) userGroups.push(this.party._id); - userGroups.push(TAVERN_ID); - return userGroups; -}; - -schema.methods.sendMessage = async function sendMessage (userToReceiveMessage, message) { - let sender = this; - - shared.refPush(userToReceiveMessage.inbox.messages, chatDefaults(message, sender)); - userToReceiveMessage.inbox.newMessages++; - userToReceiveMessage._v++; - userToReceiveMessage.markModified('inbox.messages'); - - shared.refPush(sender.inbox.messages, defaults({sent: true}, chatDefaults(message, userToReceiveMessage))); - sender.markModified('inbox.messages'); - - let promises = [userToReceiveMessage.save(), sender.save()]; - await Bluebird.all(promises); -}; - -// Methods to adapt the new schema to API v2 responses (mostly tasks inside the user model) -// These will be removed once API v2 is discontinued - -// Get all the tasks belonging to a user, -schema.methods.getTasks = function getUserTasks () { - let args = Array.from(arguments); - let cb; - let type; - - if (args.length === 1) { - cb = args[0]; - } else { - type = args[0]; - cb = args[1]; - } - - let query = { - userId: this._id, - }; - - if (type) query.type = type; - - Tasks.Task.find(query, cb); -}; - -// Given user and an array of tasks, return an API compatible user + tasks obj -schema.methods.addTasksToUser = function addTasksToUser (tasks) { - let obj = this.toJSON(); - - obj.id = obj._id; - obj.filters = {}; - - obj.tags = obj.tags.map(tag => { - return { - id: tag.id, - name: tag.name, - challenge: tag.challenge, - }; - }); - - let tasksOrder = obj.tasksOrder; // Saving a reference because we won't return it - - obj.habits = []; - obj.dailys = []; - obj.todos = []; - obj.rewards = []; - - obj.tasksOrder = undefined; - let unordered = []; - - tasks.forEach((task) => { - // We want to push the task at the same position where it's stored in tasksOrder - let pos = tasksOrder[`${task.type}s`].indexOf(task._id); - if (pos === -1) { // Should never happen, it means the lists got out of sync - unordered.push(task.toJSONV2()); - } else { - obj[`${task.type}s`][pos] = task.toJSONV2(); - } - }); - - // Reconcile unordered items - unordered.forEach((task) => { - obj[`${task.type}s`].push(task); - }); - - // Remove null values that can be created when inserting tasks at an index > length - ['habits', 'dailys', 'rewards', 'todos'].forEach((type) => { - obj[type] = _.compact(obj[type]); - }); - - return obj; -}; - -// Return the data maintaining backward compatibility -schema.methods.getTransformedData = function getTransformedData (cb) { - let self = this; - this.getTasks((err, tasks) => { - if (err) return cb(err); - cb(null, self.addTasksToUser(tasks)); - }); -}; - -// END of API v2 methods -export let model = mongoose.model('User', schema); - -// Initially export an empty object so external requires will get -// the right object by reference when it's defined later -// Otherwise it would remain undefined if requested before the query executes -export let mods = []; - -mongoose.model('User') - .find({'contributor.admin': true}) - .sort('-contributor.level -backer.npc profile.name') - .select('profile contributor backer') - .exec() - .then((foundMods) => { - // Using push to maintain the reference to mods - mods.push(...foundMods); - }); // In case of failure we don't want this to crash the whole server diff --git a/website/server/routes/api-v2/auth.js b/website/server/routes/api-v2/auth.js deleted file mode 100644 index f8af1b4338..0000000000 --- a/website/server/routes/api-v2/auth.js +++ /dev/null @@ -1,21 +0,0 @@ -var auth = require('../../controllers/api-v2/auth'); -var express = require('express'); -var i18n = require('../../libs/api-v2/i18n'); -var router = express.Router(); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -/* auth.auth*/ -// auth.setupPassport(router); //TODO make this consistent with the others -router.post('/register', getUserLanguage, auth.registerUser); -router.post('/user/auth/local', getUserLanguage, auth.loginLocal); -router.post('/user/auth/social', getUserLanguage, auth.loginSocial); -router.delete('/user/auth/social', getUserLanguage, auth.auth, auth.deleteSocial); -router.post('/user/reset-password', getUserLanguage, auth.resetPassword); -router.post('/user/change-password', getUserLanguage, auth.auth, auth.changePassword); -router.post('/user/change-username', getUserLanguage, auth.auth, auth.changeUsername); -router.post('/user/change-email', getUserLanguage, auth.auth, auth.changeEmail); -// router.post('/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken); - -module.exports = router; diff --git a/website/server/routes/api-v2/coupon.js b/website/server/routes/api-v2/coupon.js deleted file mode 100644 index 7caee3f815..0000000000 --- a/website/server/routes/api-v2/coupon.js +++ /dev/null @@ -1,15 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../../controllers/api-v2/auth'); -var coupon = require('../../controllers/api-v2/coupon'); -var i18n = require('../../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/coupons', auth.authWithUrl, getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); -router.post('/coupons/generate/:event', auth.auth, getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); -router.post('/user/coupon/:code', auth.auth, getUserLanguage, coupon.enterCode); - -module.exports = router; diff --git a/website/server/routes/api-v2/unsubscription.js b/website/server/routes/api-v2/unsubscription.js deleted file mode 100644 index cbd2e16554..0000000000 --- a/website/server/routes/api-v2/unsubscription.js +++ /dev/null @@ -1,11 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var i18n = require('../../libs/api-v2/i18n'); -var unsubscription = require('../../controllers/api-v2/unsubscription'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/unsubscribe', getUserLanguage, unsubscription.unsubscribe); - -module.exports = router; diff --git a/website/server/routes/payments.js b/website/server/routes/payments.js deleted file mode 100644 index 13e9ec9163..0000000000 --- a/website/server/routes/payments.js +++ /dev/null @@ -1,34 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var auth = require('../controllers/api-v2/auth'); -var payments = require('../controllers/payments'); -var i18n = require('../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; - -router.get('/paypal/checkout', auth.authWithUrl, getUserLanguage, payments.paypalCheckout); -router.get('/paypal/checkout/success', getUserLanguage, payments.paypalCheckoutSuccess); -router.get('/paypal/subscribe', auth.authWithUrl, getUserLanguage, payments.paypalSubscribe); -router.get('/paypal/subscribe/success', getUserLanguage, payments.paypalSubscribeSuccess); -router.get('/paypal/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.paypalSubscribeCancel); -router.post('/paypal/ipn', getUserLanguage, payments.paypalIPN); // misc ipn handling - -router.post('/stripe/checkout', auth.auth, getUserLanguage, payments.stripeCheckout); -router.post('/stripe/subscribe/edit', auth.auth, getUserLanguage, payments.stripeSubscribeEdit); -//router.get('/stripe/subscribe', auth.authWithUrl, getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead -router.get('/stripe/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.stripeSubscribeCancel); - -router.post('/amazon/verifyAccessToken', auth.auth, getUserLanguage, payments.amazonVerifyAccessToken); -router.post('/amazon/createOrderReferenceId', auth.auth, getUserLanguage, payments.amazonCreateOrderReferenceId); -router.post('/amazon/checkout', auth.auth, getUserLanguage, payments.amazonCheckout); -router.post('/amazon/subscribe', auth.auth, getUserLanguage, payments.amazonSubscribe); -router.get('/amazon/subscribe/cancel', auth.authWithUrl, getUserLanguage, payments.amazonSubscribeCancel); - -router.post('/iap/android/verify', auth.authWithUrl, /*getUserLanguage, */payments.iapAndroidVerify); -router.post('/iap/ios/verify', auth.auth, /*getUserLanguage, */ payments.iapIosVerify); - -router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, getUserLanguage, */ payments.validCoupon); - -module.exports = router; diff --git a/website/server/server.js b/website/server/server.js deleted file mode 100644 index f86e6c63b7..0000000000 --- a/website/server/server.js +++ /dev/null @@ -1,35 +0,0 @@ -import nconf from 'nconf'; -import logger from './libs/api-v3/logger'; -import express from 'express'; -import http from 'http'; -import attachMiddlewares from './middlewares/api-v3/index'; -import Bluebird from 'bluebird'; - -global.Promise = Bluebird; - -const server = http.createServer(); -const app = express(); - -app.set('port', nconf.get('PORT')); - -// Setup translations -import './libs/api-v3/i18n'; - -// Load config files -import './libs/api-v3/setupMongoose'; -import './libs/api-v3/firebase'; -import './libs/api-v3/setupPassport'; - -// Load some schemas & models -import './models/challenge'; -import './models/group'; -import './models/user'; - -attachMiddlewares(app, server); - -server.on('request', app); -server.listen(app.get('port'), () => { - logger.info(`Express server listening on port ${app.get('port')}`); -}); - -module.exports = server; diff --git a/website/server/controllers/api-v2/auth.js b/website/src/controllers/api-v2/auth.js similarity index 94% rename from website/server/controllers/api-v2/auth.js rename to website/src/controllers/api-v2/auth.js index cfa5c40a0d..f63c652814 100644 --- a/website/server/controllers/api-v2/auth.js +++ b/website/src/controllers/api-v2/auth.js @@ -3,19 +3,14 @@ var validator = require('validator'); var passport = require('passport'); var shared = require('../../../../common'); var async = require('async'); -var utils = require('../../libs/api-v2/utils'); +var utils = require('../../libs/utils'); var nconf = require('nconf'); var request = require('request'); var FirebaseTokenGenerator = require('firebase-token-generator'); -import { - model as User, -} from '../../models/user'; -import { - model as EmailUnsubscription, -} from '../../models/emailUnsubscription'; - +var User = require('../../models/user').model; +var EmailUnsubscription = require('../../models/emailUnsubscription').model; var analytics = utils.analytics; -var i18n = require('./../../libs/api-v2/i18n'); +var i18n = require('./../../libs/i18n'); var isProd = nconf.get('NODE_ENV') === 'production'; @@ -58,7 +53,6 @@ api.authWithSession = function(req, res, next) { //[todo] there is probably a mo }); }; -// TODO passing auth params as query params is not safe as they are logged by browser history, ... api.authWithUrl = function(req, res, next) { User.findOne({_id:req.query._id, apiToken:req.query.apiToken}, function(err,user){ if (err) return next(err); @@ -132,8 +126,8 @@ api.registerUser = function(req, res, next) { analytics.track('register', analyticsData) user.save(function(err, savedUser){ - if (err) return cb(err); // Clean previous email preferences + // TODO when emails added to EmailUnsubcription they should use lowercase version EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){ utils.txnEmail(savedUser, 'welcome'); }); @@ -143,13 +137,15 @@ api.registerUser = function(req, res, next) { }] }, function(err, data) { if (err) return err.code ? res.status(err.code).json(err) : next(err); - data.register[0].getTransformedData(function(err, userTransformed){ - if(err) return next(err); - res.status(200).json(userTransformed); - }); + res.status(200).json(data.register[0]); }); }; +/* + Register new user with uname / password + */ + + api.loginLocal = function(req, res, next) { var username = req.body.username; var password = req.body.password; @@ -240,7 +236,7 @@ api.loginSocial = function(req, res, next) { api.deleteSocial = function(req,res,next){ if (!res.locals.user.auth.local.username) return res.status(401).json({err:"Account lacks another authentication method, can't detach Facebook"}); - //TODO for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123 + //FIXME for some reason, the following gives https://gist.github.com/lefnire/f93eb306069b9089d123 //res.locals.user.auth.facebook = null; //res.locals.user.auth.save(function(err, saved){ User.update({_id:res.locals.user._id}, {$unset:{'auth.facebook':1}}, function(err){ @@ -351,8 +347,7 @@ api.changePassword = function(req, res, next) { }) }; -// DISABLED FOR API v2 -/*var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); +var firebaseTokenGeneratorInstance = new FirebaseTokenGenerator(nconf.get('FIREBASE:SECRET')); api.getFirebaseToken = function(req, res, next) { var user = res.locals.user; // Expires 24 hours after now (60*60*24*1000) (in milliseconds) @@ -371,10 +366,13 @@ api.getFirebaseToken = function(req, res, next) { token: token, expires: expires }); -};*/ +}; -// DISABLED FOR API v2 -/*api.setupPassport = function(router) { +/* + Registers a new user. Only accepting username/password registrations, no Facebook +*/ + +api.setupPassport = function(router) { router.get('/logout', i18n.getUserLanguage, function(req, res) { req.logout(); @@ -382,4 +380,4 @@ api.getFirebaseToken = function(req, res, next) { res.redirect('/'); }) -};*/ +}; diff --git a/website/src/controllers/api-v2/challenges.js b/website/src/controllers/api-v2/challenges.js new file mode 100644 index 0000000000..967f175213 --- /dev/null +++ b/website/src/controllers/api-v2/challenges.js @@ -0,0 +1,453 @@ +// @see ../routes for routing + +var _ = require('lodash'); +var nconf = require('nconf'); +var async = require('async'); +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; +var logging = require('./../../libs/logging'); +var csvStringify = require('csv-stringify'); +var utils = require('../../libs/utils'); +var api = module.exports; +var pushNotify = require('./../pushNotifications'); + +/* + ------------------------------------------------------------------------ + Challenges + ------------------------------------------------------------------------ +*/ + +api.list = function(req, res, next) { + var user = res.locals.user; + async.waterfall([ + function(cb){ + // Get all available groups I belong to + Group.find({members: {$in: [user._id]}}).select('_id').exec(cb); + }, + function(gids, cb){ + // and their challenges + Challenge.find({ + $or:[ + {leader: user._id}, + {members:{$in:[user._id]}}, // all challenges I belong to (is this necessary? thought is a left a group, but not its challenge) + {group:{$in:gids}}, // all challenges in my groups + {group: 'habitrpg'} // public group + ], + _id:{$ne:'95533e05-1ff9-4e46-970b-d77219f199e9'} // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug + }) + .select('name leader description group memberCount prize official') + .select({members:{$elemMatch:{$in:[user._id]}}}) + .sort('-official -timestamp') + .populate('group', '_id name type') + .populate('leader', 'profile.name') + .exec(cb); + } + ], function(err, challenges){ + if (err) return next(err); + _.each(challenges, function(c){ + c._isMember = c.members.length > 0; + }) + res.json(challenges); + user = null; + }); +} + +// GET +api.get = function(req, res, next) { + var user = res.locals.user; + // TODO use mapReduce() or aggregate() here to + // 1) Find the sum of users.tasks.values within the challnege (eg, {'profile.name':'tyler', 'sum': 100}) + // 2) Sort by the sum + // 3) Limit 30 (only show the 30 users currently in the lead) + Challenge.findById(req.params.cid) + .populate('members', 'profile.name _id') + .populate('group', '_id name type') + .populate('leader', 'profile.name') + .exec(function(err, challenge){ + if(err) return next(err); + if (!challenge) return res.status(404).json({err: 'Challenge ' + req.params.cid + ' not found'}); + challenge._isMember = !!(_.find(challenge.members, function(member) { + return member._id === user._id; + })); + res.json(challenge); + }); +} + +api.csv = function(req, res, next) { + var cid = req.params.cid; + var challenge; + async.waterfall([ + function(cb){ + Challenge.findById(cid,cb) + }, + function(_challenge,cb) { + challenge = _challenge; + if (!challenge) return cb('Challenge ' + cid + ' not found'); + User.aggregate([ + {$match:{'_id':{ '$in': challenge.members}}}, //yes, we want members + {$project:{'profile.name':1,tasks:{$setUnion:["$habits","$dailys","$todos","$rewards"]}}}, + {$unwind:"$tasks"}, + {$match:{"tasks.challenge.id":cid}}, + {$sort:{'tasks.type':1,'tasks.id':1}}, + {$group:{_id:"$_id", "tasks":{$push:"$tasks"},"name":{$first:"$profile.name"}}} + ], cb); + } + ],function(err,users){ + if(err) return next(err); + var output = ['UUID','name']; + _.each(challenge.tasks,function(t){ + //output.push(t.type+':'+t.text); + //not the right order yet + output.push('Task'); + output.push('Value'); + output.push('Notes'); + }) + output = [output]; + _.each(users, function(u){ + var uData = [u._id,u.name]; + _.each(u.tasks,function(t){ + uData = uData.concat([t.type+':'+t.text, t.value, t.notes]); + }) + output.push(uData); + }); + + res.set({ + 'Content-Type': 'text/csv', + 'Content-disposition': `attachment; filename=${cid}.csv`, + }); + + csvStringify(output, (err, csv) => { + if (err) return next(err); + res.status(200).send(csv); + challenge = cid = null; + }); + }) +} + +api.getMember = function(req, res, next) { + var cid = req.params.cid; + var uid = req.params.uid; + + // We need to start using the aggregation framework instead of in-app filtering, see http://docs.mongodb.org/manual/aggregation/ + // See code at 32c0e75 for unwind/group example + + //http://stackoverflow.com/questions/24027213/how-to-match-multiple-array-elements-without-using-unwind + var proj = {'profile.name':'$profile.name'}; + _.each(['habits','dailys','todos','rewards'], function(type){ + proj[type] = { + $setDifference: [{ + $map: { + input: '$'+type, + as: "el", + in: { + $cond: [{$eq: ["$$el.challenge.id", cid]}, '$$el', false] + } + } + }, [false]] + } + }); + User.aggregate() + .match({_id: uid}) + .project(proj) + .exec(function(err, member){ + if (err) return next(err); + if (!member) return res.status(404).json({err: 'Member '+uid+' for challenge '+cid+' not found'}); + res.json(member[0]); + uid = cid = null; + }); +} + +// CREATE +api.create = function(req, res, next){ + var user = res.locals.user; + + async.auto({ + get_group: function(cb){ + var q = {_id:req.body.group}; + if (req.body.group!='habitrpg') q.members = {$in:[user._id]}; // make sure they're a member of the group + Group.findOne(q, cb); + }, + save_chal: ['get_group', function(cb, results){ + var group = results.get_group, + prize = +req.body.prize; + if (!group) + return cb({code:404, err:"Group." + req.body.group + " not found"}); + if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) + return cb({code:401, err: "Only the group leader can create challenges"}); + // If they're adding a prize, do some validation + if (prize < 0) + return cb({code:401, err: 'Challenge prize must be >= 0'}); + if (req.body.group=='habitrpg' && prize < 1) + return cb({code:401, err: 'Prize must be at least 1 Gem for public challenges.'}); + if (prize > 0) { + var groupBalance = ((group.balance && group.leader==user._id) ? group.balance : 0); + var prizeCost = prize/4; // I really should have stored user.balance as gems rather than dollars... stupid... + if (prizeCost > user.balance + groupBalance) + return cb("You can't afford this prize. Purchase more gems or lower the prize amount.") + + if (groupBalance >= prizeCost) { + // Group pays for all of prize + group.balance -= prizeCost; + } else if (groupBalance > 0) { + // User pays remainder of prize cost after group + var remainder = prizeCost - group.balance; + group.balance = 0; + user.balance -= remainder; + } else { + // User pays for all of prize + user.balance -= prizeCost; + } + } + req.body.leader = user._id; + req.body.official = user.contributor.admin && req.body.official; + var chal = new Challenge(req.body); // FIXME sanitize + chal.members.push(user._id); + chal.save(cb); + }], + save_group: ['save_chal', function(cb, results){ + results.get_group.challenges.push(results.save_chal[0]._id); + results.get_group.save(cb); + }], + sync_user: ['save_group', function(cb, results){ + // Auto-join creator to challenge (see members.push above) + results.save_chal[0].syncToUser(user, cb); + }] + }, function(err, results){ + if (err) return err.code? res.status(err.code).json(err) : next(err); + return res.json(results.save_chal[0]); + user = null; + }) +} + +// UPDATE +api.update = function(req, res, next){ + var cid = req.params.cid; + var user = res.locals.user; + var before; + async.waterfall([ + function(cb){ + // We first need the original challenge data, since we're going to compare against new & decide to sync users + Challenge.findById(cid, cb); + }, + function(_before, cb) { + if (!_before) return cb('Challenge ' + cid + ' not found'); + if (_before.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionEditChallenge', req.language)); + // Update the challenge, since syncing will need the updated challenge. But store `before` we're going to do some + // before-save / after-save comparison to determine if we need to sync to users + before = _before; + var attrs = _.pick(req.body, 'name shortName description habits dailys todos rewards date'.split(' ')); + Challenge.findByIdAndUpdate(cid, {$set:attrs}, {new: true}, cb); + }, + function(saved, cb) { + + // Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers + if (before.isOutdated(req.body)) { + User.find({_id: {$in: saved.members}}, function(err, users){ + logging.info('Challenge updated, sync to subscribers'); + if (err) throw err; + _.each(users, function(user){ + saved.syncToUser(user); + }) + }) + } + + // after saving, we're done as far as the client's concerned. We kick off syncing (heavy task) in the background + cb(null, saved); + } + ], function(err, saved){ + if(err) next(err); + res.json(saved); + cid = user = before = null; + }) +} + +/** + * Called by either delete() or selectWinner(). Will delete the challenge and set the "broken" property on all users' subscribed tasks + * @param {cid} the challenge id + * @param {broken} the object representing the broken status of the challenge. Eg: + * {broken: 'CHALLENGE_DELETED', id: CHALLENGE_ID} + * {broken: 'CHALLENGE_CLOSED', id: CHALLENGE_ID, winner: USER_NAME} + */ +function closeChal(cid, broken, cb) { + var removed; + async.waterfall([ + function(cb2){ + Challenge.findOneAndRemove({_id:cid}, cb2) + }, + function(_removed, cb2) { + removed = _removed; + var pull = {'$pull':{}}; pull['$pull'][_removed._id] = 1; + Group.findByIdAndUpdate(_removed.group, {new: true}, pull); + User.find({_id:{$in: removed.members}}, cb2); + }, + function(users, cb2) { + var parallel = []; + _.each(users, function(user){ + var tag = _.find(user.tags, {id:cid}); + if (tag) tag.challenge = undefined; + _.each(user.tasks, function(task){ + if (task.challenge && task.challenge.id == removed._id) { + _.merge(task.challenge, broken); + } + }) + parallel.push(function(cb3){ + user.save(cb3); + }) + }) + async.parallel(parallel, cb2); + removed = null; + } + ], cb); +} + +/** + * Delete & close + */ +api.delete = function(req, res, next){ + var user = res.locals.user; + var cid = req.params.cid; + + async.waterfall([ + function(cb){ + Challenge.findById(cid, cb); + }, + function(chal, cb){ + if (!chal) return cb('Challenge ' + cid + ' not found'); + if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionDeleteChallenge', req.language)); + if (chal.group != 'habitrpg') user.balance += chal.prize/4; // Refund gems to user if a non-tavern challenge + user.save(cb); + }, + function(save, num, cb){ + closeChal(req.params.cid, {broken: 'CHALLENGE_DELETED'}, cb); + } + ], function(err){ + if (err) return next(err); + res.sendStatus(200); + user = cid = null; + }); +} + +/** + * Select Winner & Close + */ +api.selectWinner = function(req, res, next) { + if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'}); + var user = res.locals.user; + var cid = req.params.cid; + var chal; + async.waterfall([ + function(cb){ + Challenge.findById(cid, cb); + }, + function(_chal, cb){ + chal = _chal; + if (!chal) return cb('Challenge ' + cid + ' not found'); + if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionCloseChallenge', req.language)); + User.findById(req.query.uid, cb) + }, + function(winner, cb){ + if (!winner) return cb('Winner ' + req.query.uid + ' not found.'); + _.defaults(winner.achievements, {challenges: []}); + winner.achievements.challenges.push(chal.name); + winner.balance += chal.prize/4; + winner.save(cb); + }, + function(saved, num, cb) { + if(saved.preferences.emailNotifications.wonChallenge !== false){ + utils.txnEmail(saved, 'won-challenge', [ + {name: 'CHALLENGE_NAME', content: chal.name} + ]); + } + + pushNotify.sendNotify(saved, shared.i18n.t('wonChallenge'), chal.name); + + closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb); + } + ], function(err){ + if (err) return next(err); + res.sendStatus(200); + user = cid = chal = null; + }) +} + +api.join = function(req, res, next){ + var user = res.locals.user; + var cid = req.params.cid; + + async.waterfall([ + function(cb) { + Challenge.findByIdAndUpdate(cid, {$addToSet:{members:user._id}}, {new: true}, cb); + }, + function(chal, cb) { + + // Trigger updating challenge member count in the background. We can't do it above because we don't have + // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above. + Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec(); + + if (!~user.challenges.indexOf(cid)) + user.challenges.unshift(cid); + // Add all challenge's tasks to user's tasks + chal.syncToUser(user, function(err){ + if (err) return cb(err); + cb(null, chal); // we want the saved challenge in the return results, due to ng-resource + }); + } + ], function(err, chal){ + if(err) return next(err); + chal._isMember = true; + res.json(chal); + user = cid = null; + }); +} + + +api.leave = function(req, res, next){ + var user = res.locals.user; + var cid = req.params.cid; + // whether or not to keep challenge's tasks. strictly default to true if "keep-all" isn't provided + var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; + + async.waterfall([ + function(cb){ + Challenge.findByIdAndUpdate(cid, {$pull:{members:user._id}}, {new: true}, cb); + }, + function(chal, cb){ + + // Trigger updating challenge member count in the background. We can't do it above because we don't have + // _.size(challenge.members). We can't do it in pre(save) because we're calling findByIdAndUpdate above. + if (chal) + Challenge.update({_id:cid}, {$set:{memberCount:_.size(chal.members)}}).exec(); + + var i = user.challenges.indexOf(cid) + if (~i) user.challenges.splice(i,1); + user.unlink({cid:cid, keep:keep}, function(err){ + if (err) return cb(err); + cb(null, chal); + }) + } + ], function(err, chal){ + if(err) return next(err); + if (chal) chal._isMember = false; + res.json(chal); + user = cid = keep = null; + }); +} + +api.unlink = function(req, res, next) { + // they're scoring the task - commented out, we probably don't need it due to route ordering in api.js + //var urlParts = req.originalUrl.split('/'); + //if (_.contains(['up','down'], urlParts[urlParts.length -1])) return next(); + + var user = res.locals.user; + var tid = req.params.id; + var cid = user.tasks[tid].challenge.id; + if (!req.query.keep) + return res.status(400).json({err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'}); + user.unlink({cid:cid, keep:req.query.keep, tid:tid}, function(err, saved){ + if (err) return next(err); + res.sendStatus(200); + user = tid = cid = null; + }); +} diff --git a/website/server/controllers/api-v2/coupon.js b/website/src/controllers/api-v2/coupon.js similarity index 90% rename from website/server/controllers/api-v2/coupon.js rename to website/src/controllers/api-v2/coupon.js index 17cd289bd3..a69c41e326 100644 --- a/website/server/controllers/api-v2/coupon.js +++ b/website/src/controllers/api-v2/coupon.js @@ -1,7 +1,5 @@ var _ = require('lodash'); -import { - model as Coupon, -} from '../../models/coupon'; +var Coupon = require('./../../models/coupon').model; var api = module.exports; var csvStringify = require('csv-stringify'); var async = require('async'); @@ -30,7 +28,7 @@ api.getCoupons = function(req,res,next) { res.set({ 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-coupons.csv', + 'Content-disposition': `attachment; filename=habitica-coupons.csv`, }); csvStringify(output, (err, csv) => { if (err) return next(err); diff --git a/website/server/controllers/api-v2/groups.js b/website/src/controllers/api-v2/groups.js similarity index 69% rename from website/server/controllers/api-v2/groups.js rename to website/src/controllers/api-v2/groups.js index 31a21d7dc9..e117aa40ad 100644 --- a/website/server/controllers/api-v2/groups.js +++ b/website/src/controllers/api-v2/groups.js @@ -8,31 +8,18 @@ function clone(a) { var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); -var Bluebird = require('bluebird'); -var utils = require('./../../libs/api-v2/utils'); +var Q = require('q'); +var utils = require('./../../libs/utils'); var shared = require('../../../../common'); - -import { removeFromArray } from '../../libs/api-v3/collectionManipulators'; - -import { - model as User, -} from './../../models/user'; -import { - model as Group, - TAVERN_ID, -} from './../../models/group'; -import { - model as Challenge, -} from './../../models/challenge'; -import { - model as EmailUnsubscription, -} from './../../models/emailUnsubscription'; - +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; +var EmailUnsubscription = require('./../../models/emailUnsubscription').model; var isProd = nconf.get('NODE_ENV') === 'production'; var api = module.exports; -var pushNotify = require('./pushNotifications'); +var pushNotify = require('./../pushNotifications'); var analytics = utils.analytics; -var firebase = require('../../libs/api-v2/firebase'); +var firebase = require('../../libs/firebase'); /* ------------------------------------------------------------------------ @@ -85,41 +72,31 @@ api.list = function(req, res, next) { // unecessary given our ui-router setup party: function(cb){ if (!~type.indexOf('party')) return cb(null, {}); - Group.findOne({_id: user.party._id, type: 'party'}) + Group.findOne({type: 'party', members: {'$in': [user._id]}}) .select(groupFields).exec(function(err, party){ if (err) return cb(err); - if (!party) return cb(null, []); - party.getTransformedData({cb: function (err, transformedParty) { - if (err) return cb(err); - cb(null, (transformedParty === null ? [] : [transformedParty])); // return as an array for consistent ngResource use - }}); + cb(null, (party === null ? [] : [party])); // return as an array for consistent ngResource use }); }, guilds: function(cb) { if (!~type.indexOf('guilds')) return cb(null, []); - Group.find({_id: {'$in': user.guilds}, type:'guild'}) - .select(groupFields).sort(sort).exec(function (err, guilds) { - if (err) return cb(err); - async.map(guilds, function (guild, cb1) { - guild.getTransformedData({cb: cb1}) - }, function(err, guildsTransormed) { - cb(err, guildsTransormed); - }); - }); + Group.find({members: {'$in': [user._id]}, type:'guild'}) + .select(groupFields).sort(sort).exec(cb); }, 'public': function(cb) { if (!~type.indexOf('public')) return cb(null, []); Group.find({privacy: 'public'}) - .select(groupFields) + .select(groupFields + ' members') .sort(sort) .lean() .exec(function(err, groups){ if (err) return cb(err); _.each(groups, function(g){ // To save some client-side performance, don't send down the full members arr, just send down temp var _isMember - if (user.guilds.indexOf(g._id) !== -1) g._isMember = true; + if (~g.members.indexOf(user._id)) g._isMember = true; + g.members = undefined; }); cb(null, groups); }); @@ -128,12 +105,9 @@ api.list = function(req, res, next) { // unecessary given our ui-router setup tavern: function(cb) { if (!~type.indexOf('tavern')) return cb(null, {}); - Group.findById(TAVERN_ID).select(groupFields).exec(function(err, tavern){ + Group.findById('habitrpg').select(groupFields).exec(function(err, tavern){ if (err) return cb(err); - tavern.getTransformedData({cb: function (err, transformedTavern) { - if (err) return cb(err); - cb(null, ([transformedTavern])); // return as an array for consistent ngResource use - }}); + cb(null, [tavern]); // return as an array for consistent ngResource use }); } @@ -160,26 +134,14 @@ api.list = function(req, res, next) { api.get = function(req, res, next) { var user = res.locals.user; var gid = req.params.gid; - let isUserGuild = user.guilds.indexOf(gid) !== -1; - var q; - - if (gid === 'party' || gid === user.party._id) { - q = Group.findOne({_id: user.party._id, type: 'party'}) - } else { - - if (isUserGuild) { - q = Group.findOne({type: 'guild', _id: gid}); - } else if (gid === 'habitrpg') { - q = Group.findOne({_id: TAVERN_ID}); - } else { - q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); - } - } - - q.populate('leader', nameFields); - - //populateQuery(gid, q); + var q = (gid == 'party') + ? Group.findOne({type: 'party', members: {'$in': [user._id]}}) + : Group.findOne({$or:[ + {_id:gid, privacy:'public'}, + {_id:gid, privacy:'private', members: {$in:[user._id]}} // if the group is private, only return if they have access + ]}); + populateQuery(gid, q); q.exec(function(err, group){ if (err) return next(err); if(!group){ @@ -190,27 +152,34 @@ api.get = function(req, res, next) { return res.json(group); } - group.getTransformedData({ - cb: function (err, transformedGroup) { + if (!user.contributor.admin) { + _purgeFlagInfoFromChat(group, user); + } + + //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group + var userInGroup = _.find(group.members, function(member){ return member._id == user._id; }); + //If the group is private or the group is a party, then the user must be a member of the group based on access restrictions above + if (group.privacy === 'private' || gid === 'party') { + //If the user is not in the group query, remove a user and add the current user + if (!userInGroup) { + group.members.splice(0,1); + group.members.push(user); + } + res.json(group); + } else if ( group.privacy === "public" ) { //The group is public, we must do an extra check to see if the user is already in the group query + //We must see how to check if a user is a member of a public group, so we requery + var q2 = Group.findOne({ _id: group._id, privacy:'public', members: {$in:[user._id]} }); + q2.exec(function(err, group2){ if (err) return next(err); - - if (!user.contributor.admin) { - _purgeFlagInfoFromChat(transformedGroup, user); + if (group2 && !userInGroup) { + group.members.splice(0,1); + group.members.push(user); } + res.json(group); + }); + } - //Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group - var userInGroup = _.find(transformedGroup.members, function(member){ return member._id == user._id; }); - if ((gid === 'party' || isUserGuild) && !userInGroup) { - transformedGroup.members.splice(0,1); - transformedGroup.members.push(user); - } - - res.json(transformedGroup); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }); + gid = null; }); }; @@ -218,12 +187,10 @@ api.get = function(req, res, next) { api.create = function(req, res, next) { var group = new Group(req.body); var user = res.locals.user; - //group.members = [user._id]; + group.members = [user._id]; group.leader = user._id; - if (!group.name) group.name = 'group name'; if(group.type === 'guild'){ - user.guilds.push(group._id); if(user.balance < 1) return res.status(401).json({err: shared.i18n.t('messageInsufficientGems')}); group.balance = 1; @@ -235,31 +202,33 @@ api.create = function(req, res, next) { function(saved,ct,cb){ firebase.updateGroupData(saved); firebase.addUserToGroup(saved._id, user._id); - saved.getTransformedData({ - populateMembers: nameFields, - cb, - }) + saved.populate('members', nameFields, cb); } - ],function(err,groupTransformed){ + ],function(err,saved){ if (err) return next(err); - res.json(groupTransformed); + res.json(saved); group = user = null; }); } else{ - if (user.party._id) return res.status(400).json({err:shared.i18n.t('messageGroupAlreadyInParty')}); - user.party._id = group._id; - user.save(function (err) { + async.waterfall([ + function(cb){ + Group.findOne({type:'party',members:{$in:[user._id]}},cb); + }, + function(found, cb){ + if (found) return cb(shared.i18n.t('messageGroupAlreadyInParty')); + group.save(cb); + }, + function(saved, count, cb){ + firebase.updateGroupData(saved); + firebase.addUserToGroup(saved._id, user._id); + saved.populate('members', nameFields, cb); + } + ], function(err, populated){ + if (err === shared.i18n.t('messageGroupAlreadyInParty')) return res.status(400).json({err:err}); if (err) return next(err); - group.save(function(err, saved) { - if (err) return next(err); - saved.getTransformedData({ - populateMembers: nameFields, - cb (err, groupTransformed) { - res.json(groupTransformed); - }, - }); - }); + group = user = null; + return res.json(populated); }) } } @@ -272,7 +241,7 @@ api.update = function(req, res, next) { return res.status(401).json({err: shared.i18n.t('messageGroupOnlyLeaderCanUpdate')}); 'name description logo logo leaderMessage leader leaderOnly'.split(' ').forEach(function(attr){ - if (req.body[attr]) group[attr] = req.body[attr]; + group[attr] = req.body[attr]; }); group.save(function(err, saved){ @@ -286,11 +255,8 @@ api.update = function(req, res, next) { // TODO remove from api object? api.attachGroup = function(req, res, next) { var user = res.locals.user; - var gid = req.params.gid === 'party' ? user.party._id : req.params.gid; - if (gid === 'habitrpg') gid = TAVERN_ID; - - let q = Group.findOne({_id: gid}) - + var gid = req.params.gid; + var q = (gid == 'party') ? Group.findOne({type: 'party', members: {'$in': [res.locals.user._id]}}) : Group.findById(gid); q.exec(function(err, group){ if(err) return next(err); if(!group) return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); @@ -308,22 +274,13 @@ api.getChat = function(req, res, next) { // TODO: This code is duplicated from api.get - pull it out into a function to remove duplication. var user = res.locals.user; var gid = req.params.gid; - - var q; - let isUserGuild = user.guilds.indexOf(gid) !== -1; - - if (gid === 'party' || gid === user.party._id) { - q = Group.findOne({_id: user.party._id, type: 'party'}) - } else { - if (isUserGuild) { - q = Group.findOne({type: 'guild', _id: gid}); - } else if (gid === 'habitrpg') { - q = Group.findOne({_id: TAVERN_ID}); - } else { - q = Group.findOne({type: 'guild', privacy: 'public', _id: gid}); - } - } - + var q = (gid == 'party') + ? Group.findOne({type: 'party', members: {$in:[user._id]}}) + : Group.findOne({$or:[ + {_id:gid, privacy:'public'}, + {_id:gid, privacy:'private', members: {$in:[user._id]}} + ]}); + populateQuery(gid, q); q.exec(function(err, group){ if (err) return next(err); if (!group && gid!=='party') return res.status(404).json({err: shared.i18n.t('messageGroupNotFound')}); @@ -346,7 +303,7 @@ api.postChat = function(req, res, next) { var lastClientMsg = req.query.previousMsg; var chatUpdated = (lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg) ? true : false; - group.sendChat(req.query.message, user); // TODO this should be body, but ngResource is funky + group.sendChat(req.query.message, user); // FIXME this should be body, but ngResource is funky if (group.type === 'party') { user.party.lastMessageSeen = group.chat[0].id; @@ -429,7 +386,7 @@ api.flagChatMessage = function(req, res, next){ {name: "GROUP_NAME", content: group.name}, {name: "GROUP_TYPE", content: group.type}, {name: "GROUP_ID", content: group._id}, - {name: "GROUP_URL", content: group._id == TAVERN_ID ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, + {name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')}, ]); return res.sendStatus(204); @@ -498,9 +455,7 @@ api.join = function(req, res, next) { if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) { User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter - user.invitations.party = {}; // Clear invite - user.markModified('invitations.party'); - user.party._id = group._id; + user.invitations.party = undefined; // Clear invite user.save(); // invite new user to pending quest if (group.quest.key && !group.quest.active) { @@ -509,29 +464,20 @@ api.join = function(req, res, next) { group.markModified('quest.members'); } isUserInvited = true; - } else if (group.type == 'guild') { + } else if (group.type == 'guild' && user.invitations && user.invitations.guilds) { var i = _.findIndex(user.invitations.guilds, {id:group._id}); if (~i){ isUserInvited = true; user.invitations.guilds.splice(i,1); - user.guilds.push(group._id); user.save(); }else{ isUserInvited = group.privacy === 'private' ? false : true; - if (isUserInvited) { - user.guilds.push(group._id); - user.save(); - } } } if(!isUserInvited) return res.status(401).json({err: shared.i18n.t('messageGroupRequiresInvite')}); - if (group.memberCount === 0) { - group.leader = user._id; - } - - /*if (!_.contains(group.members, user._id)){ + if (!_.contains(group.members, user._id)){ if (group.members.length === 0) { group.leader = user._id; } @@ -541,7 +487,7 @@ api.join = function(req, res, next) { if (group.invites.length > 0) { group.invites.splice(_.indexOf(group.invites, user._id), 1); } - }*/ + } async.series([ function(cb){ @@ -549,12 +495,8 @@ api.join = function(req, res, next) { }, function(cb){ firebase.addUserToGroup(group._id, user._id); - group.getTransformedData({ - cb, - populateMembers: group.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }) + // TODO why query group once again? + populateQuery(group.type, Group.findById(group._id)).exec(cb); } ], function(err, results){ if (err) return next(err); @@ -581,9 +523,12 @@ api.leave = function(req, res, next) { // When removing the user from challenges, should we keep the tasks? var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - group.leave(user, keep) - .then(() => res.sendStatus(204)) - .catch(next); + group.leave(user, keep, function(err){ + if (err) return next(err); + user = group = keep = null; + + return res.sendStatus(204); + }); }; var inviteByUUIDs = function(uuids, group, req, res, next){ @@ -593,7 +538,7 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ if (!invite) return cb({code:400,err:'User with id "' + uuid + '" not found'}); if (group.type == 'guild') { - if (_.contains(invite.guilds, group._id)) + if (_.contains(group.members,uuid)) return cb({code:400, err: "User already in that group"}); if (invite.invitations && invite.invitations.guilds && _.find(invite.invitations.guilds, {id:group._id})) return cb({code:400, err:"User already invited to that group"}); @@ -601,10 +546,13 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ } else if (group.type == 'party') { if (invite.invitations && !_.isEmpty(invite.invitations.party)) return cb({code: 400,err:"User already pending invitation."}); - if (invite.party && invite.party._id) { - return cb({code: 400, err: "User already in a party."}) - } - sendInvite(); + Group.find({type: 'party', members: {$in: [uuid]}}, function(err, groups){ + if (err) return cb(err); + if (!_.isEmpty(groups) && groups[0].members.length > 1) { + return cb({code: 400, err: "User already in a party."}) + } + sendInvite(); + }); } function sendInvite (){ @@ -619,7 +567,7 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ pushNotify.sendNotify(invite, shared.i18n.t('invitedParty'), group.name); } - //group.invites.push(invite._id); + group.invites.push(invite._id); async.series([ function(cb){ @@ -662,17 +610,10 @@ var inviteByUUIDs = function(uuids, group, req, res, next){ }, function(cb) { // TODO pass group from save above don't find it again, or you have to find it again in order to run populate? - Group.findById(group._id).populate('leader', nameFields).exec(function (err, savedGroup) { - if (err) return next(err); - savedGroup.getTransformedData({ - cb: function (err, transformedGroup) { - if (err) return next(err); - res.json(transformedGroup); - }, - populateMembers: savedGroup.type === 'party' ? partyFields : nameFields, - populateInvites: nameFields, - populateChallenges: challengeFields, - }) + populateQuery(group.type, Group.findById(group._id)).exec(function(err, populatedGroup){ + if(err) return next(err); + + res.json(populatedGroup); }); } ]); @@ -740,17 +681,10 @@ var inviteByEmails = function(invites, group, req, res, next){ api.invite = function(req, res, next){ var group = res.locals.group; - let userParty = res.locals.user.party && res.locals.user.party._id; - let userGuilds = res.locals.user.guilds; - if (group.type === 'party' && userParty !== group._id) { + if (group.privacy === 'private' && !_.contains(group.members,res.locals.user._id)) { return res.status(401).json({err: "Only a member can invite new members!"}); } - - if (group.type === 'guild' && group.privacy === 'private' && !_.contains(userGuilds, group._id)) { - return res.status(401).json({err: "Only a member can invite new members!"}); - } - if (req.body.uuids) { inviteByUUIDs(req.body.uuids, group, req, res, next); } else if (req.body.emails) { @@ -786,35 +720,28 @@ api.removeMember = function(req, res, next){ return res.status(401).json({err: "You cannot remove yourself!"}); } - User.findById(uuid, function(err, removedUser){ - if (err) return next(err); - let isMember = group._id === removedUser.party._id || _.contains(removedUser.guilds, group._id); - let isInvited = group._id === removedUser.invitations.party._id || !!_.find(removedUser.invitations.guilds, {id: group._id}); + if(_.contains(group.members, uuid)){ + var update = {$pull:{members:uuid}}; + if (group.quest && group.quest.leader === uuid) { + update['$set'] = { + quest: { key: null, leader: null } + }; + } else if(group.quest && group.quest.members){ + // remove member from quest + update['$unset'] = {}; + update['$unset']['quest.members.' + uuid] = ""; + } + update['$inc'] = {memberCount: -1}; + Group.update({_id:group._id},update, function(err, saved){ + if (err) return next(err); - if(isMember){ - var update = {}; - if (group.quest && group.quest.leader === uuid) { - update['$set'] = { - quest: { key: null, leader: null } - }; - } else if(group.quest && group.quest.members){ - // remove member from quest - update['$unset'] = {}; - update['$unset']['quest.members.' + uuid] = ""; - } - update['$inc'] = {memberCount: -1}; - Group.update({_id:group._id},update, function(err, saved){ - if (err) return next(err); + User.findById(uuid, function(err, removedUser){ + if(err) return next(err); sendMessage(removedUser); //Mark removed users messages as seen var update = {$unset:{}}; - if (group.type === 'guild') { - update.$pull = {guilds: group._id}; - } else { - update.$unset.party = true; - } update.$unset['newMessages.' + group._id] = ''; if (group.quest && group.quest.active && group.quest.leader === uuid) { update['$inc'] = {}; @@ -827,8 +754,12 @@ api.removeMember = function(req, res, next){ group = uuid = null; return res.sendStatus(204); }); - }else if(isInvited){ - var invitations = removedUser.invitations; + }); + }else if(_.contains(group.invites, uuid)){ + User.findById(uuid, function(err,invited){ + if(err) return next(err); + + var invitations = invited.invitations; if(group.type === 'guild'){ invitations.guilds.splice(_.indexOf(invitations.guilds, group._id), 1); }else{ @@ -837,32 +768,31 @@ api.removeMember = function(req, res, next){ async.series([ function(cb){ - removedUser.save(cb); + invited.save(cb); }, + function(cb){ + Group.update({_id:group._id},{$pull:{invites:uuid}}, cb); + } ], function(err, results){ if (err) return next(err); // Sending an empty 204 because Group.update doesn't return the group // see http://mongoosejs.com/docs/api.html#model_Model.update - sendMessage(removedUser); + sendMessage(invited); group = uuid = null; return res.sendStatus(204); }); - }else{ - group = uuid = null; - return res.status(400).json({err: "User not found among group's members!"}); - } - }); + + }); + }else{ + group = uuid = null; + return res.status(400).json({err: "User not found among group's members!"}); + } } // ------------------------------------ // Quests // ------------------------------------ -function canStartQuestAutomatically (group) { - // If all members are either true (accepted) or false (rejected) return true - // If any member is null/undefined (undecided) return false - return _.every(group.quest.members, _.isBoolean); -} function questStart(req, res, next) { var group = res.locals.group; @@ -978,103 +908,70 @@ api.questAccept = function(req, res, next) { if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."}); if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'}); if (!user.items.quests[key]) return res.status(400).json({err: "You don't own that quest scroll"}); - - let members; + group.quest.key = key; + group.quest.members = {}; + // Invite everyone. true means "accepted", false="rejected", undefined="pending". Once we click "start quest" + // or everyone has either accepted/rejected, then we store quest key in user object. + _.each(group.members, function(m){ + if (m == user._id) { + var analyticsData = { + category: 'behavior', + owner: true, + response: 'accept', + gaLabel: 'accept', + questName: key, + uuid: user._id, + }; + analytics.track('quest',analyticsData); + group.quest.members[m] = true; + group.quest.leader = user._id; + } else { + User.update({_id:m},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec(); + group.quest.members[m] = undefined; + } + }); User.find({ - 'party._id': group._id, - _id: {$ne: user._id}, - }).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') - .exec().then(membersF => { - members = membersF; + _id: { + $in: _.without(group.members, user._id) + } + }, {auth: 1, preferences: 1, profile: 1, pushDevices: 1}, function(err, members){ + if(err) return next(err); - group.markModified('quest'); - group.quest.key = key; - group.quest.leader = user._id; - group.quest.members = {}; - group.quest.members[user._id] = true; + var inviterVars = utils.getUserInfo(user, ['name', 'email']); - user.party.quest.RSVPNeeded = false; - user.party.quest.key = key; - - return User.update({ - 'party._id': group._id, - _id: {$ne: user._id}, - }, { - $set: { - 'party.quest.RSVPNeeded': true, - 'party.quest.key': key, - }, - }, {multi: true}).exec(); - }).then(() => { - _.each(members, (member) => { - group.quest.members[member._id] = null; + var membersToEmail = members.filter(function(member){ + return member.preferences.emailNotifications.invitedQuest !== false; }); - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); + utils.txnEmail(membersToEmail, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [ + {name: 'QUEST_NAME', content: quest.text()}, + {name: 'INVITER', content: inviterVars.name}, + {name: 'PARTY_URL', content: '/#/options/groups/party'} + ]); - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } - }).catch(next); + _.each(members, function(groupMember){ + pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() })); + }); + + questStart(req,res,next); + }); // Party member accepting the invitation } else { - group.markModified('quest'); + if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'}); + var analyticsData = { + category: 'behavior', + owner: false, + response: 'accept', + gaLabel: 'accept', + questName: group.quest.key, + uuid: user._id, + }; + analytics.track('quest',analyticsData); group.quest.members[user._id] = true; - user.party.quest.RSVPNeeded = false; - - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } + User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false}}).exec(); + questStart(req,res,next); } } @@ -1082,102 +979,84 @@ api.questReject = function(req, res, next) { var group = res.locals.group; var user = res.locals.user; + if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'}); + var analyticsData = { + category: 'behavior', + owner: false, + response: 'reject', + gaLabel: 'reject', + questName: group.quest.key, + uuid: user._id, + }; + analytics.track('quest',analyticsData); group.quest.members[user._id] = false; - group.markModified('quest.members'); - - user.party.quest = Group.cleanQuestProgress(); - user.markModified('party.quest'); - - if (canStartQuestAutomatically(group)) { - group.startQuest(user).then(() => { - return Bluebird.all([group.save(), user.save()]) - }) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - - } else { - Bluebird.all([group.save(), user.save()]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }) - .catch(next); - } + User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec(); + questStart(req,res,next); } api.questCancel = function(req, res, next){ - var group = res.locals.group; - - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); - - Bluebird.all([ - group.save(), - User.update( - {'party._id': group._id}, - {$set: {'party.quest': Group.cleanQuestProgress()}}, - {multi: true} - ), - ]).then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); - }).catch(next); - // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) // Quest scroll has not yet left quest owner's inventory so no need to return it. // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. + var group = res.locals.group; + async.parallel([ + function(cb){ + if (! group.quest.active) { + // Do not cancel active quests because this function does + // not do the clean-up required for that. + // TODO: return an informative error when quest is active + group.quest = {key:null,progress:{},leader:null}; + group.markModified('quest'); + group.save(cb); + _.each(group.members, function(m){ + User.update({_id:m}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec(); + }); + } + } + ], function(err){ + if (err) return next(err); + res.json(group); + group = null; + }) } api.questAbort = function(req, res, next){ + // Abort a quest AFTER it has begun (see questCancel for BEFORE) var group = res.locals.group; - - let memberUpdates = User.update({ - 'party._id': group._id, - }, { - $set: {'party.quest': Group.cleanQuestProgress()}, - $inc: {_v: 1}, // TODO update middleware - }, {multi: true}).exec(); - - let questLeaderUpdate = User.update({ - _id: group.quest.leader, - }, { - $inc: { - [`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader + async.parallel([ + function(cb){ + User.update( + {_id:{$in: _.keys(group.quest.members)}}, + { + $set: {'party.quest':Group.cleanQuestProgress()}, + $inc: {_v:1} + }, + {multi:true}, + cb); }, - }).exec(); + // Refund party leader quest scroll + function(cb){ + if (group.quest.active) { + var update = {$inc:{}}; + update['$inc']['items.quests.' + group.quest.key] = 1; + User.update({_id:group.quest.leader}, update).exec(); + } + group.quest = {key:null,progress:{},leader:null}; + group.markModified('quest'); + group.save(cb); + }, function(cb){ + populateQuery(group.type, Group.findById(group._id)).exec(cb); + } + ], function(err, results){ + if (err) return next(err); - group.quest = Group.cleanGroupQuest(); - group.markModified('quest'); + var groupClone = clone(group); - Bluebird.all([group.save(), memberUpdates, questLeaderUpdate]) - .then(results => { - results[0].getTransformedData({ - cb (err, groupTransformed) { - if (err) return next(err); - res.json(groupTransformed); - }, - populateMembers: group.type === 'party' ? partyFields : nameFields, - }); + groupClone.members = results[2].members; + + res.json(groupClone); + group = null; }) - .catch(next); } api.questLeave = function(req, res, next) { @@ -1197,16 +1076,16 @@ api.questLeave = function(req, res, next) { return res.status(403).json({ err: 'Quest leader cannot leave quest' }); } - group.quest.members[user._id] = false; + delete group.quest.members[user._id]; group.markModified('quest.members'); user.party.quest = Group.cleanQuestProgress(); user.markModified('party.quest'); - var groupSavePromise = Bluebird.promisify(group.save, {context: group}); - var userSavePromise = Bluebird.promisify(user.save, {context: user}); + var groupSavePromise = Q.nbind(group.save, group); + var userSavePromise = Q.nbind(user.save, user); - Bluebird.all([groupSavePromise(), userSavePromise()]) + Q.all([groupSavePromise(), userSavePromise()]) .done(function(values) { return res.sendStatus(204); }, function(error) { diff --git a/website/server/controllers/api-v2/hall.js b/website/src/controllers/api-v2/hall.js similarity index 96% rename from website/server/controllers/api-v2/hall.js rename to website/src/controllers/api-v2/hall.js index 05d3bf520c..ec88894c62 100644 --- a/website/server/controllers/api-v2/hall.js +++ b/website/src/controllers/api-v2/hall.js @@ -2,12 +2,8 @@ var _ = require('lodash'); var nconf = require('nconf'); var async = require('async'); var shared = require('../../../../common'); -import { - model as User, -} from '../../models/user'; -import { - model as Group, -} from '../../models/group'; +var User = require('./../../models/user').model; +var Group = require('./../../models/group').model; var api = module.exports; api.ensureAdmin = function(req, res, next) { diff --git a/website/server/controllers/api-v2/members.js b/website/src/controllers/api-v2/members.js similarity index 90% rename from website/server/controllers/api-v2/members.js rename to website/src/controllers/api-v2/members.js index 232bb9ed73..01c136c472 100644 --- a/website/server/controllers/api-v2/members.js +++ b/website/src/controllers/api-v2/members.js @@ -1,18 +1,13 @@ -import { - model as groups, - chatDefaults, -} from '../../models/group'; -import { - model as User, -} from '../../models/user'; -let partyFields = require('./groups').partyFields; +var User = require('mongoose').model('User'); +var groups = require('../../models/group'); +var partyFields = require('./groups').partyFields var api = module.exports; var async = require('async'); var _ = require('lodash'); var shared = require('../../../../common'); -var utils = require('../../libs/api-v2/utils'); +var utils = require('../../libs/utils'); var nconf = require('nconf'); -var pushNotify = require('./pushNotifications'); +var pushNotify = require('./../pushNotifications'); var fetchMember = function(uuid, restrict){ return function(cb){ @@ -54,12 +49,12 @@ api.sendMessage = function(user, member, data){ } msg += data.message ? data.message : ''; } - shared.refPush(member.inbox.messages, chatDefaults(msg, user)); + shared.refPush(member.inbox.messages, groups.chatDefaults(msg, user)); member.inbox.newMessages++; member._v++; member.markModified('inbox.messages'); - shared.refPush(user.inbox.messages, _.defaults({sent:true}, chatDefaults(msg, member))); + shared.refPush(user.inbox.messages, _.defaults({sent:true}, groups.chatDefaults(msg, member))); user.markModified('inbox.messages'); } diff --git a/website/server/controllers/api-v2/unsubscription.js b/website/src/controllers/api-v2/unsubscription.js similarity index 80% rename from website/server/controllers/api-v2/unsubscription.js rename to website/src/controllers/api-v2/unsubscription.js index a91db19d86..2fecbef03f 100644 --- a/website/server/controllers/api-v2/unsubscription.js +++ b/website/src/controllers/api-v2/unsubscription.js @@ -1,10 +1,6 @@ -import { - model as User, -} from '../../models/user'; -import { - model as EmailUnsubscription, -} from '../../models/emailUnsubscription'; -var utils = require('../../libs/api-v2/utils'); +var User = require('../../models/user').model; +var EmailUnsubscription = require('../../models/emailUnsubscription').model; +var utils = require('../../libs/utils'); var i18n = require('../../../../common').i18n; var api = module.exports = {}; @@ -19,7 +15,7 @@ api.unsubscribe = function(req, res, next){ $set: {'preferences.emailNotifications.unsubscribeFromAll': true} }, {multi: false}, function(err, updateRes){ if(err) return next(err); - if(updateRes.n !== 1) return res.json(404, {err: 'User not found'}); + if(updateRes !== 1) return res.status(404).json({err: 'User not found'}); res.send('

' + i18n.t('unsubscribedSuccessfully', null, req.language) + '

' + i18n.t('unsubscribedTextUsers', null, req.language)); }); diff --git a/website/src/controllers/api-v2/user.js b/website/src/controllers/api-v2/user.js new file mode 100644 index 0000000000..da57347d15 --- /dev/null +++ b/website/src/controllers/api-v2/user.js @@ -0,0 +1,707 @@ +var url = require('url'); +var ipn = require('paypal-ipn'); +var _ = require('lodash'); +var nconf = require('nconf'); +var async = require('async'); +var shared = require('../../../../common'); +var User = require('./../../models/user').model; +var utils = require('./../../libs/utils'); +var analytics = utils.analytics; +var Group = require('./../../models/group').model; +var Challenge = require('./../../models/challenge').model; +var moment = require('moment'); +var logging = require('./../../libs/logging'); +let acceptablePUTPaths; +let restrictedPUTSubPaths; + +var api = module.exports; +var firebase = require('../../libs/firebase'); +var webhook = require('../../libs/webhook'); + +// api.purchase // Shared.ops + +api.getContent = function(req, res, next) { + var language = 'en'; + + if (typeof req.query.language != 'undefined') + language = req.query.language.toString(); //|| 'en' in i18n + + var content = _.cloneDeep(shared.content); + var walk = function(obj, lang){ + _.each(obj, function(item, key, source){ + if (_.isPlainObject(item) || _.isArray(item)) return walk(item, lang); + if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang); + }); + } + walk(content, language); + res.json(content); +} + +api.getModelPaths = function(req,res,next){ + res.json(_.reduce(User.schema.paths,function(m,v,k){ + m[k] = v.instance || 'Boolean'; + return m; + },{})); +} + +/* + ------------------------------------------------------------------------ + Tasks + ------------------------------------------------------------------------ +*/ + + +/* + Local Methods + --------------- +*/ + +var findTask = function(req, res) { + return res.locals.user.tasks[req.params.id]; +}; + +/* + API Routes + --------------- +*/ + +api.score = function(req, res, next) { + var id = req.params.id, + direction = req.params.direction, + user = res.locals.user, + task; + + var clearMemory = function(){user = task = id = direction = null;} + + // Send error responses for improper API call + if (!id) return res.status(400).json({err: ':id required'}); + if (direction !== 'up' && direction !== 'down') { + if (direction == 'unlink' || direction == 'sort') return next(); + return res.status(400).json({err: ":direction must be 'up' or 'down'"}); + } + // If exists already, score it + if (task = user.tasks[id]) { + // Set completed if type is daily or todo and task exists + if (task.type === 'daily' || task.type === 'todo') { + task.completed = direction === 'up'; + } + } else { + // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it + // Defaults. Other defaults are handled in user.ops.addTask() + task = { + id: id, + type: req.body && req.body.type, + text: req.body && req.body.text, + notes: (req.body && req.body.notes) || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." + }; + + if (task.type === 'daily' || task.type === 'todo') + task.completed = direction === 'up'; + + task = user.ops.addTask({body:task}); + } + var delta = user.ops.score({params:{id:task.id, direction:direction}, language: req.language}); + + user.save(function(err, saved){ + if (err) return next(err); + + var userStats = saved.toJSON().stats; + var resJsonData = _.extend({ delta: delta, _tmp: user._tmp }, userStats); + res.status(200).json(resJsonData); + + var webhookData = _generateWebhookTaskData( + task, direction, delta, userStats, user + ); + webhook.sendTaskWebhook(user.preferences.webhooks, webhookData); + + if ( + (!task.challenge || !task.challenge.id || task.challenge.broken) // If it's a challenge task, sync the score. Do it in the background, we've already sent down a response and the user doesn't care what happens back there + || (task.type == 'reward') // we don't want to update the reward GP cost + ) return clearMemory(); + + Challenge.findById(task.challenge.id, 'habits dailys todos rewards', function(err, chal) { + if (err) return next(err); + if (!chal) { + task.challenge.broken = 'CHALLENGE_DELETED'; + user.save(); + return clearMemory(); + } + var t = chal.tasks[task.id]; + // this task was removed from the challenge, notify user + if (!t) { + chal.syncToUser(user); + return clearMemory(); + } + + t.value += delta; + if (t.type == 'habit' || t.type == 'daily') { + t.history.push({value: t.value, date: +new Date}); + } + chal.save(); + clearMemory(); + }); + }); +}; + +/** + * Get all tasks + */ +api.getTasks = function(req, res, next) { + var user = res.locals.user; + if (req.query.type) { + return res.json(user[req.query.type+'s']); + } else { + return res.json(_.toArray(user.tasks)); + } +}; + +/** + * Get Task + */ +api.getTask = function(req, res, next) { + var task = findTask(req,res); + if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); + return res.status(200).json(task); +}; + + +/* + Update Task +*/ + +//api.deleteTask // see Shared.ops +// api.updateTask // handled in Shared.ops +// api.addTask // handled in Shared.ops +// api.sortTask // handled in Shared.ops #TODO updated api, mention in docs + +/* + ------------------------------------------------------------------------ + Items + ------------------------------------------------------------------------ +*/ +// api.buy // handled in Shard.ops + +api.getBuyList = function (req, res, next) { + var list = shared.updateStore(res.locals.user); + return res.status(200).json(list); +}; + +/* + ------------------------------------------------------------------------ + User + ------------------------------------------------------------------------ +*/ + +/** + * Get User + */ +api.getUser = function(req, res, next) { + var user = res.locals.user.toJSON(); + user.stats.toNextLevel = shared.tnl(user.stats.lvl); + user.stats.maxHealth = shared.maxHealth; + user.stats.maxMP = res.locals.user._statsComputed.maxMP; + delete user.apiToken; + if (user.auth && user.auth.local) { + delete user.auth.local.hashed_password; + delete user.auth.local.salt; + } + return res.status(200).json(user); +}; + +/** + * Get anonymized User + */ +api.getUserAnonymized = function(req, res, next) { + var user = res.locals.user.toJSON(); + user.stats.toNextLevel = shared.tnl(user.stats.lvl); + user.stats.maxHealth = shared.maxHealth; + user.stats.maxMP = res.locals.user._statsComputed.maxMP; + + delete user.apiToken; + + if (user.auth) { + delete user.auth.local; + delete user.auth.facebook; + } + + delete user.newMessages; + + delete user.profile; + delete user.purchased.plan; + delete user.contributor; + delete user.invitations; + + delete user.items.special.nyeReceived; + delete user.items.special.valentineReceived; + + delete user.webhooks; + delete user.achievements.challenges; + + _.forEach(user.inbox.messages, function(msg){ + msg.text = "inbox message text"; + }); + + _.forEach(user.tags, function(tag){ + tag.name = "tag"; + tag.challenge = "challenge"; + }); + + function cleanChecklist(task){ + var checklistIndex = 0; + + _.forEach(task.checklist, function(c){ + c.text = "item" + checklistIndex++; + }); + } + + _.forEach(user.habits, function(task){ + task.text = "task text"; + task.notes = "task notes"; + }); + + _.forEach(user.rewards, function(task){ + task.text = "task text"; + task.notes = "task notes"; + }); + + _.forEach(user.dailys, function(task){ + task.text = "task text"; + task.notes = "task notes"; + + cleanChecklist(task); + }); + + _.forEach(user.todos, function(task){ + task.text = "task text"; + task.notes = "task notes"; + + cleanChecklist(task); + }); + + return res.status(200).json(user); +}; + +/** + * This tells us for which paths users can call `PUT /user` (or batch-update equiv, which use `User.set()` on our client). + * The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs) + * FIXME - one-by-one we want to widdle down this list, instead replacing each needed set path with API operations + */ +acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (m, v, leaf) => { + let updatablePaths = 'achievements filters flags invitations lastCron party preferences profile stats inbox'.split(' '); + let found = _.find(updatablePaths, (rootPath) => { + return leaf.indexOf(rootPath) === 0; + }); + + if (found) m[leaf] = true; + + return m; +}, {}); + +restrictedPUTSubPaths = 'stats.class'.split(' '); + +_.each(restrictedPUTSubPaths, (removePath) => { + delete acceptablePUTPaths[removePath]; +}); + +let requiresPurchase = { + 'preferences.background': 'background', + 'preferences.shirt': 'shirt', + 'preferences.size': 'size', + 'preferences.skin': 'skin', + 'preferences.chair': 'chair', + 'preferences.hair.bangs': 'hair.bangs', + 'preferences.hair.base': 'hair.base', + 'preferences.hair.beard': 'hair.beard', + 'preferences.hair.color': 'hair.color', + 'preferences.hair.flower': 'hair.flower', + 'preferences.hair.mustache': 'hair.mustache', +}; + +let checkPreferencePurchase = (user, path, item) => { + let itemPath = `${path}.${item}`; + let appearance = _.get(shared.content.appearances, itemPath) + if (!appearance) return false; + if (appearance.price === 0) return true; + + return _.get(user.purchased, itemPath); +}; + +/** + * Update user + * Send up PUT /user as `req.body={path1:val, path2:val, etc}`. Example: + * PUT /user {'stats.hp':50, 'tasks.TASK_ID.repeat.m':false} + * See acceptablePUTPaths for which user paths are supported +*/ +api.update = (req, res, next) => { + let user = res.locals.user; + let errors = []; + + if (_.isEmpty(req.body)) return res.status(200).json(user); + + _.each(req.body, (v, k) => { + let purchasable = requiresPurchase[k]; + + if (purchasable && !checkPreferencePurchase(user, purchasable, v)) { + return errors.push(`Must purchase ${v} to set it on ${k}`); + } + + if (acceptablePUTPaths[k]) { + user.fns.dotSet(k, v); + } else { + errors.push(shared.i18n.t('messageUserOperationProtected', { operation: k })); + } + return true; + }); + + user.save((err) => { + if (!_.isEmpty(errors)) return res.status(401).json({err: errors}); + if (err) { + if (err.name == 'ValidationError') { + let errorMessages = _.map(_.values(err.errors), (error) => { + return error.message; + }); + return res.status(400).json({err: errorMessages}); + } + return next(err); + } + + res.status(200).json(user); + user = errors = null; + }); +}; + +api.cron = function(req, res, next) { + var user = res.locals.user, + progress = user.fns.cron({analytics:utils.analytics, timezoneOffset:req.headers['x-user-timezoneoffset']}), + ranCron = user.isModified(), + quest = shared.content.quests[user.party.quest.key]; + + if (ranCron) res.locals.wasModified = true; + if (!ranCron) return next(null,user); + Group.tavernBoss(user,progress); + if (!quest) return user.save(next); + + // If user is on a quest, roll for boss & player, or handle collections + // FIXME this saves user, runs db updates, loads user. Is there a better way to handle this? + async.waterfall([ + function(cb){ + user.save(cb); // make sure to save the cron effects + }, + function(saved, count, cb){ + var type = quest.boss ? 'boss' : 'collect'; + Group[type+'Quest'](user,progress,cb); + }, + function(){ + var cb = arguments[arguments.length-1]; + // User has been updated in boss-grapple, reload + User.findById(user._id, cb); + } + ], function(err, saved) { + res.locals.user = saved; + next(err,saved); + user = progress = quest = null; + }); +}; + +// api.reroll // Shared.ops +// api.reset // Shared.ops + +api.delete = function(req, res, next) { + var user = res.locals.user; + var plan = user.purchased.plan; + + if (plan && plan.customerId && !plan.dateTerminated){ + return res.status(400).json({err:"You have an active subscription, cancel your plan before deleting your account."}); + } + + Group.find({ + members: { + '$in': [user._id] + } + }, function(err, groups){ + if(err) return next(err); + + async.each(groups, function(group, cb){ + group.leave(user, 'remove-all', cb); + }, function(err){ + if(err) return next(err); + + user.remove(function(err){ + if(err) return next(err); + + firebase.deleteUser(user._id); + res.sendStatus(200); + }); + }); + }); +} + +/* + ------------------------------------------------------------------------ + Development Only Operations + ------------------------------------------------------------------------ + */ +if (nconf.get('NODE_ENV') === 'development') { + + api.addTenGems = function(req, res, next) { + var user = res.locals.user; + + user.balance += 2.5; + + user.save(function(err){ + if (err) return next(err); + res.sendStatus(204); + }); + }; + + api.addHourglass = function(req, res, next) { + var user = res.locals.user; + + user.purchased.plan.consecutive.trinkets += 1; + + user.save(function(err){ + if (err) return next(err); + res.sendStatus(204); + }); + }; +} + +/* + ------------------------------------------------------------------------ + Tags + ------------------------------------------------------------------------ + */ +// api.deleteTag // handled in Shared.ops +// api.addTag // handled in Shared.ops +// api.updateTag // handled in Shared.ops +// api.sortTag // handled in Shared.ops + +/* + ------------------------------------------------------------------------ + Spells + ------------------------------------------------------------------------ + */ +api.cast = function(req, res, next) { + var user = res.locals.user, + targetType = req.query.targetType, + targetId = req.query.targetId, + klass = shared.content.spells.special[req.params.spell] ? 'special' : user.stats.class, + spell = shared.content.spells[klass][req.params.spell]; + + if (!spell) return res.status(404).json({err: 'Spell "' + req.params.spell + '" not found.'}); + if (spell.mana > user.stats.mp) return res.status(400).json({err: 'Not enough mana to cast spell'}); + + var done = function(){ + var err = arguments[0]; + var saved = _.size(arguments == 3) ? arguments[2] : arguments[1]; + if (err) return next(err); + res.json(saved); + user = targetType = targetId = klass = spell = null; + } + + switch (targetType) { + case 'task': + if (!user.tasks[targetId]) return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); + spell.cast(user, user.tasks[targetId]); + user.save(done); + break; + + case 'self': + spell.cast(user); + user.save(done); + break; + + case 'party': + case 'user': + async.waterfall([ + function(cb){ + Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements items.special').exec(cb); + }, + function(group, cb) { + // Solo player? let's just create a faux group for simpler code + var g = group ? group : {members:[user]}; + var series = [], found; + if (targetType == 'party') { + spell.cast(user, g.members); + series = _.transform(g.members, function(m,v,k){ + m.push(function(cb2){v.save(cb2)}); + }); + } else { + found = _.find(g.members, {_id: targetId}) + spell.cast(user, found); + series.push(function(cb2){found.save(cb2)}); + } + + if (group && !spell.silent) { + series.push(function(cb2){ + var message = '`'+user.profile.name+' casts '+spell.text() + (targetType=='user' ? ' on '+found.profile.name : ' for the party')+'.`'; + group.sendChat(message); + group.save(cb2); + }) + } + + series.push(function(cb2){g = group = series = found = null;cb2();}) + + async.series(series, cb); + }, + function(whatever, cb){ + user.save(cb); + } + ], done); + break; + } +} + +// It supports guild too now but we'll stick to partyInvite for backward compatibility +api.sessionPartyInvite = function(req,res,next){ + if (!req.session.partyInvite) return next(); + var inv = res.locals.user.invitations; + if (inv.party && inv.party.id) return next(); // already invited to a party + async.waterfall([ + function(cb){ + Group.findOne({_id:req.session.partyInvite.id, members:{$in:[req.session.partyInvite.inviter]}}) + .select('invites members type').exec(cb); + }, + function(group, cb){ + if (!group){ + // Don't send error as it will prevent users from using the site + delete req.session.partyInvite; + return cb(); + } + + if (group.type == 'guild'){ + inv.guilds.push(req.session.partyInvite); + } else{ + //req.body.type in 'guild', 'party' + inv.party = req.session.partyInvite; + } + inv.party = req.session.partyInvite; + delete req.session.partyInvite; + if (!~group.invites.indexOf(res.locals.user._id)) + group.invites.push(res.locals.user._id); //$addToSt + group.save(cb); + }, + function(saved, cb){ + res.locals.user.save(cb); + } + ], next); +} + +/** + * All other user.ops which can easily be mapped to common/script/index.js, not requiring custom API-wrapping + */ +_.each(shared.wrap({}).ops, function(op,k){ + if (!api[k]) { + api[k] = function(req, res, next) { + res.locals.user.ops[k](req,function(err, response){ + // If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"} + if (err) { + if (!err.code) return next(err); + if (err.code >= 400) return res.status(err.code).json({err:err.message}); + // In the case of 200s, they're friendly alert messages like "You're pet has hatched!" - still send the op + } + res.locals.user.save(function(err){ + if (err) return next(err); + res.status(200).json(response); + }) + }, analytics); + } + } +}) + +/* + ------------------------------------------------------------------------ + Batch Update + Run a bunch of updates all at once + ------------------------------------------------------------------------ +*/ +api.batchUpdate = function(req, res, next) { + if (_.isEmpty(req.body)) req.body = []; // cases of {} or null + if (req.body[0] && req.body[0].data) + return res.status(501).json({err: "API has been updated, please refresh your browser or upgrade your mobile app."}) + + var user = res.locals.user; + var oldSend = res.send; + var oldJson = res.json; + + // Stash user.save, we'll queue the save op till the end (so we don't overload the server) + var oldSave = user.save; + user.save = function(cb){cb(null,user)} + + // Setup the array of functions we're going to call in parallel with async + res.locals.ops = []; + var ops = _.transform(req.body, function(m,_req){ + if (_.isEmpty(_req)) return; + _req.language = req.language; + + m.push(function() { + var cb = arguments[arguments.length-1]; + res.locals.ops.push(_req); + res.send = res.json = function(code, data) { + if (_.isNumber(code) && code >= 500) + return cb(code+": "+ (data.message ? data.message : data.err ? data.err : JSON.stringify(data))); + return cb(); + }; + if(!api[_req.op]) { return cb(shared.i18n.t('messageUserOperationNotFound', { operation: _req.op })); } + api[_req.op](_req, res, cb); + }); + }) + // Finally, save user at the end + .concat(function(){ + user.save = oldSave; + user.save(arguments[arguments.length-1]); + }); + + // call all the operations, then return the user object to the requester + async.waterfall(ops, function(err,_user) { + res.json = oldJson; + res.send = oldSend; + if (err) return next(err); + + var response = _user.toJSON(); + response.wasModified = res.locals.wasModified; + + user.fns.nullify(); + user = res.locals.user = oldSend = oldJson = oldSave = null; + + // return only drops & streaks + if (response._tmp && response._tmp.drop){ + res.status(200).json({_tmp: {drop: response._tmp.drop}, _v: response._v}); + + // Fetch full user object + } else if (response.wasModified){ + // Preen 3-day past-completed To-Dos from Angular & mobile app + response.todos = shared.preenTodos(response.todos); + res.status(200).json(response); + + // return only the version number + } else{ + res.status(200).json({_v: response._v}); + } + }); +}; + +function _generateWebhookTaskData(task, direction, delta, stats, user) { + var extendedStats = _.extend(stats, { + toNextLevel: shared.tnl(user.stats.lvl), + maxHealth: shared.maxHealth, + maxMP: user._statsComputed.maxMP + }); + + var userData = { + _id: user._id, + _tmp: user._tmp, + stats: extendedStats + }; + + var taskData = { + details: task, + direction: direction, + delta: delta + } + + return { + task: taskData, + user: userData + } +} diff --git a/website/server/controllers/api-v2/dataexport.js b/website/src/controllers/dataexport.js similarity index 89% rename from website/server/controllers/api-v2/dataexport.js rename to website/src/controllers/dataexport.js index f0aad2b35c..8c16b9864f 100644 --- a/website/server/controllers/api-v2/dataexport.js +++ b/website/src/controllers/dataexport.js @@ -5,17 +5,15 @@ var nconf = require('nconf'); var moment = require('moment'); var js2xmlparser = require("js2xmlparser"); var pd = require('pretty-data').pd; -import { - model as User, -} from '../../models/user'; +var User = require('../models/user').model; // Avatar screenshot/static-page includes -//var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres -//var AWS = require('aws-sdk'); -//AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")}); -//var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream -//var bucket = nconf.get("S3:bucket"); -//var request = require('request'); +var Pageres = require('pageres'); //https://github.com/sindresorhus/pageres +var AWS = require('aws-sdk'); +AWS.config.update({accessKeyId: nconf.get("S3:accessKeyId"), secretAccessKey: nconf.get("S3:secretAccessKey")}); +var s3Stream = require('s3-upload-stream')(new AWS.S3()); //https://github.com/nathanpeck/s3-upload-stream +var bucket = nconf.get("S3:bucket"); +var request = require('request'); /* ------------------------------------------------------------------------ @@ -44,7 +42,7 @@ dataexport.history = function(req, res) { res.set({ 'Content-Type': 'text/csv', - 'Content-disposition': 'attachment; filename=habitica-tasks-history.csv', + 'Content-disposition': `attachment; filename=habitica-tasks-history.csv`, }); csvStringify(output, (err, csv) => { diff --git a/website/src/controllers/payments/amazon.js b/website/src/controllers/payments/amazon.js new file mode 100644 index 0000000000..8c01663c10 --- /dev/null +++ b/website/src/controllers/payments/amazon.js @@ -0,0 +1,271 @@ +var amazonPayments = require('amazon-payments'); +var mongoose = require('mongoose'); +var moment = require('moment'); +var nconf = require('nconf'); +var async = require('async'); +var User = require('mongoose').model('User'); +var shared = require('../../../../common'); +var payments = require('./index'); +var cc = require('coupon-code'); +var isProd = nconf.get('NODE_ENV') === 'production'; + +var amzPayment = amazonPayments.connect({ + environment: amazonPayments.Environment[isProd ? 'Production' : 'Sandbox'], + sellerId: nconf.get('AMAZON_PAYMENTS:SELLER_ID'), + mwsAccessKey: nconf.get('AMAZON_PAYMENTS:MWS_KEY'), + mwsSecretKey: nconf.get('AMAZON_PAYMENTS:MWS_SECRET'), + clientId: nconf.get('AMAZON_PAYMENTS:CLIENT_ID') +}); + +exports.verifyAccessToken = function(req, res, next){ + if(!req.body || !req.body['access_token']){ + return res.status(400).json({err: 'Access token not supplied.'}); + } + + amzPayment.api.getTokenInfo(req.body['access_token'], function(err, tokenInfo){ + if(err) return res.status(400).json({err:err}); + + res.sendStatus(200); + }); +}; + +exports.createOrderReferenceId = function(req, res, next){ + if(!req.body || !req.body.billingAgreementId){ + return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); + } + + amzPayment.offAmazonPayments.createOrderReferenceForId({ + Id: req.body.billingAgreementId, + IdType: 'BillingAgreement', + ConfirmNow: false + }, function(err, response){ + if(err) return next(err); + if(!response.OrderReferenceDetails || !response.OrderReferenceDetails.AmazonOrderReferenceId){ + return next(new Error('Missing attributes in Amazon response.')); + } + + res.json({ + orderReferenceId: response.OrderReferenceDetails.AmazonOrderReferenceId + }); + }); +}; + +exports.checkout = function(req, res, next){ + if(!req.body || !req.body.orderReferenceId){ + return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); + } + + var gift = req.body.gift; + var user = res.locals.user; + var orderReferenceId = req.body.orderReferenceId; + var amount = 5; + + if(gift){ + if(gift.type === 'gems'){ + amount = gift.gems.amount/4; + }else if(gift.type === 'subscription'){ + amount = shared.content.subscriptionBlocks[gift.subscription.key].price; + } + } + + async.series({ + setOrderReferenceDetails: function(cb){ + amzPayment.offAmazonPayments.setOrderReferenceDetails({ + AmazonOrderReferenceId: orderReferenceId, + OrderReferenceAttributes: { + OrderTotal: { + CurrencyCode: 'USD', + Amount: amount + }, + SellerNote: 'HabitRPG Payment', + SellerOrderAttributes: { + SellerOrderId: shared.uuid(), + StoreName: 'HabitRPG' + } + } + }, cb); + }, + + confirmOrderReference: function(cb){ + amzPayment.offAmazonPayments.confirmOrderReference({ + AmazonOrderReferenceId: orderReferenceId + }, cb); + }, + + authorize: function(cb){ + amzPayment.offAmazonPayments.authorize({ + AmazonOrderReferenceId: orderReferenceId, + AuthorizationReferenceId: shared.uuid().substring(0, 32), + AuthorizationAmount: { + CurrencyCode: 'USD', + Amount: amount + }, + SellerAuthorizationNote: 'HabitRPG Payment', + TransactionTimeout: 0, + CaptureNow: true + }, function(err, res){ + if(err) return cb(err); + + if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){ + return cb(new Error('The payment was not successfull.')); + } + + return cb(); + }); + }, + + closeOrderReference: function(cb){ + amzPayment.offAmazonPayments.closeOrderReference({ + AmazonOrderReferenceId: orderReferenceId + }, cb); + }, + + executePayment: function(cb){ + async.waterfall([ + function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); }, + function(member, cb2){ + var data = {user:user, paymentMethod:'Amazon Payments'}; + var method = 'buyGems'; + + if (gift){ + if (gift.type == 'subscription') method = 'createSubscription'; + gift.member = member; + data.gift = gift; + data.paymentMethod = 'Gift'; + } + + payments[method](data, cb2); + } + ], cb); + } + }, function(err, results){ + if(err) return next(err); + + res.sendStatus(200); + }); + +}; + +exports.subscribe = function(req, res, next){ + if(!req.body || !req.body['billingAgreementId']){ + return res.status(400).json({err: 'Billing Agreement Id not supplied.'}); + } + + var billingAgreementId = req.body.billingAgreementId; + var sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false; + var coupon = req.body.coupon; + var user = res.locals.user; + + if(!sub){ + return res.status(400).json({err: 'Subscription plan not found.'}); + } + + async.series({ + applyDiscount: function(cb){ + if (!sub.discount) return cb(); + if (!coupon) return cb(new Error('Please provide a coupon code for this plan.')); + mongoose.model('Coupon').findOne({_id:cc.validate(coupon), event:sub.key}, function(err, coupon){ + if(err) return cb(err); + if(!coupon) return cb(new Error('Coupon code not found.')); + cb(); + }); + }, + + setBillingAgreementDetails: function(cb){ + amzPayment.offAmazonPayments.setBillingAgreementDetails({ + AmazonBillingAgreementId: billingAgreementId, + BillingAgreementAttributes: { + SellerNote: 'HabitRPG Subscription', + SellerBillingAgreementAttributes: { + SellerBillingAgreementId: shared.uuid(), + StoreName: 'HabitRPG', + CustomInformation: 'HabitRPG Subscription' + } + } + }, cb); + }, + + confirmBillingAgreement: function(cb){ + amzPayment.offAmazonPayments.confirmBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId + }, cb); + }, + + authorizeOnBillingAgreeement: function(cb){ + amzPayment.offAmazonPayments.authorizeOnBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId, + AuthorizationReferenceId: shared.uuid().substring(0, 32), + AuthorizationAmount: { + CurrencyCode: 'USD', + Amount: sub.price + }, + SellerAuthorizationNote: 'HabitRPG Subscription Payment', + TransactionTimeout: 0, + CaptureNow: true, + SellerNote: 'HabitRPG Subscription Payment', + SellerOrderAttributes: { + SellerOrderId: shared.uuid(), + StoreName: 'HabitRPG' + } + }, function(err, res){ + if(err) return cb(err); + + if(res.AuthorizationDetails.AuthorizationStatus.State === 'Declined'){ + return cb(new Error('The payment was not successfull.')); + } + + return cb(); + }); + }, + + createSubscription: function(cb){ + payments.createSubscription({ + user: user, + customerId: billingAgreementId, + paymentMethod: 'Amazon Payments', + sub: sub + }, cb); + } + }, function(err, results){ + if(err) return next(err); + + res.sendStatus(200); + }); +}; + +exports.subscribeCancel = function(req, res, next){ + var user = res.locals.user; + if (!user.purchased.plan.customerId) + return res.status(401).json({err: 'User does not have a plan subscription'}); + + var billingAgreementId = user.purchased.plan.customerId; + + async.series({ + closeBillingAgreement: function(cb){ + amzPayment.offAmazonPayments.closeBillingAgreement({ + AmazonBillingAgreementId: billingAgreementId + }, cb); + }, + + cancelSubscription: function(cb){ + var data = { + user: user, + // Date of next bill + nextBill: moment(user.purchased.plan.lastBillingDate).add({days: 30}), + paymentMethod: 'Amazon Payments' + }; + + payments.cancelSubscription(data, cb); + } + }, function(err, results){ + if (err) return next(err); // don't json this, let toString() handle errors + + if(req.query.noRedirect){ + res.sendStatus(200); + }else{ + res.redirect('/'); + } + + user = null; + }); +}; diff --git a/website/src/controllers/payments/iap.js b/website/src/controllers/payments/iap.js new file mode 100644 index 0000000000..829482ed67 --- /dev/null +++ b/website/src/controllers/payments/iap.js @@ -0,0 +1,155 @@ +var iap = require('in-app-purchase'); +var async = require('async'); +var payments = require('./index'); +var nconf = require('nconf'); + +var inAppPurchase = require('in-app-purchase'); +inAppPurchase.config({ + // this is the path to the directory containing iap-sanbox/iap-live files + googlePublicKeyPath: nconf.get('IAP_GOOGLE_KEYDIR') +}); + +// Validation ERROR Codes +var INVALID_PAYLOAD = 6778001; +var CONNECTION_FAILED = 6778002; +var PURCHASE_EXPIRED = 6778003; + +exports.androidVerify = function(req, res, next) { + var iapBody = req.body; + var user = res.locals.user; + + iap.setup(function (error) { + if (error) { + var resObj = { + ok: false, + data: 'IAP Error' + }; + + return res.json(resObj); + + } + + /* + google receipt must be provided as an object + { + "data": "{stringified data object}", + "signature": "signature from google" + } + */ + var testObj = { + data: iapBody.transaction.receipt, + signature: iapBody.transaction.signature + }; + + // iap is ready + iap.validate(iap.GOOGLE, testObj, function (err, googleRes) { + if (err) { + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + return res.json(resObj); + } + + if (iap.isValidated(googleRes)) { + var resObj = { + ok: true, + data: googleRes + }; + + payments.buyGems({user:user, paymentMethod:'IAP GooglePlay', amount: 5.25}); + + return res.json(resObj); + } + }); + }); +}; + +exports.iosVerify = function(req, res, next) { + var iapBody = req.body; + var user = res.locals.user; + + iap.setup(function (error) { + if (error) { + var resObj = { + ok: false, + data: 'IAP Error' + }; + + return res.json(resObj); + + } + + //iap is ready + iap.validate(iap.APPLE, iapBody.transaction.receipt, function (err, appleRes) { + if (err) { + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: err.toString() + } + }; + + return res.json(resObj); + } + + if (iap.isValidated(appleRes)) { + var purchaseDataList = iap.getPurchaseData(appleRes); + if (purchaseDataList.length > 0) { + var correctReceipt = true; + for (var index in purchaseDataList) { + switch (purchaseDataList[index].productId) { + case 'com.habitrpg.ios.Habitica.4gems': + payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 1}); + break; + case 'com.habitrpg.ios.Habitica.8gems': + payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 2}); + break; + case 'com.habitrpg.ios.Habitica.20gems': + case 'com.habitrpg.ios.Habitica.21gems': + payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 5.25}); + break; + case 'com.habitrpg.ios.Habitica.42gems': + payments.buyGems({user:user, paymentMethod:'IAP AppleStore', amount: 10.5}); + break; + default: + correctReceipt = false; + } + } + if (correctReceipt) { + var resObj = { + ok: true, + data: appleRes + }; + // yay good! + return res.json(resObj); + } + } + //wrong receipt content + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: 'Incorrect receipt content' + } + }; + return res.json(resObj); + } + //invalid receipt + var resObj = { + ok: false, + data: { + code: INVALID_PAYLOAD, + message: 'Invalid receipt' + } + }; + + return res.json(resObj); + }); + }); +}; diff --git a/website/src/controllers/payments/index.js b/website/src/controllers/payments/index.js new file mode 100644 index 0000000000..dad53feb13 --- /dev/null +++ b/website/src/controllers/payments/index.js @@ -0,0 +1,207 @@ +var _ = require('lodash'); +var shared = require('../../../../common'); +var nconf = require('nconf'); +var utils = require('./../../libs/utils'); +var moment = require('moment'); +var isProduction = nconf.get("NODE_ENV") === "production"; +var stripe = require('./stripe'); +var paypal = require('./paypal'); +var amazon = require('./amazon'); +var members = require('../api-v2/members') +var async = require('async'); +var iap = require('./iap'); +var mongoose= require('mongoose'); +var cc = require('coupon-code'); +var pushNotify = require('./../pushNotifications'); + +function revealMysteryItems(user) { + _.each(shared.content.gear.flat, function(item) { + if ( + item.klass === 'mystery' && + moment().isAfter(shared.content.mystery[item.mystery].start) && + moment().isBefore(shared.content.mystery[item.mystery].end) && + !user.items.gear.owned[item.key] && + !~user.purchased.plan.mysteryItems.indexOf(item.key) + ) { + user.purchased.plan.mysteryItems.push(item.key); + } + }); +} + +exports.createSubscription = function(data, cb) { + var recipient = data.gift ? data.gift.member : data.user; + //if (!recipient.purchased.plan) recipient.purchased.plan = {}; // FIXME double-check, this should never be the case + var p = recipient.purchased.plan; + var block = shared.content.subscriptionBlocks[data.gift ? data.gift.subscription.key : data.sub.key]; + var months = +block.months; + + if (data.gift) { + if (p.customerId && !p.dateTerminated) { // User has active plan + p.extraMonths += months; + } else { + p.dateTerminated = moment(p.dateTerminated).add({months: months}).toDate(); + if (!p.dateUpdated) p.dateUpdated = new Date(); + } + if (!p.customerId) p.customerId = 'Gift'; // don't override existing customer, but all sub need a customerId + } else { + _(p).merge({ // override with these values + planId: block.key, + customerId: data.customerId, + dateUpdated: new Date(), + gemsBought: 0, + paymentMethod: data.paymentMethod, + extraMonths: +p.extraMonths + + +(p.dateTerminated ? moment(p.dateTerminated).diff(new Date(),'months',true) : 0), + dateTerminated: null, + // Specify a lastBillingDate just for Amazon Payments + // Resetted every time the subscription restarts + lastBillingDate: data.paymentMethod === 'Amazon Payments' ? new Date() : undefined + }).defaults({ // allow non-override if a plan was previously used + dateCreated: new Date(), + mysteryItems: [] + }).value(); + } + + // Block sub perks + var perks = Math.floor(months/3); + if (perks) { + p.consecutive.offset += months; + p.consecutive.gemCapExtra += perks*5; + if (p.consecutive.gemCapExtra > 25) p.consecutive.gemCapExtra = 25; + p.consecutive.trinkets += perks; + } + revealMysteryItems(recipient); + if(isProduction) { + if (!data.gift) utils.txnEmail(data.user, 'subscription-begins'); + + var analyticsData = { + uuid: data.user._id, + itemPurchased: 'Subscription', + sku: data.paymentMethod.toLowerCase() + '-subscription', + purchaseType: 'subscribe', + paymentMethod: data.paymentMethod, + quantity: 1, + gift: !!data.gift, // coerced into a boolean + purchaseValue: block.price + } + utils.analytics.trackPurchase(analyticsData); + } + data.user.purchased.txnCount++; + if (data.gift){ + members.sendMessage(data.user, data.gift.member, data.gift); + + var byUserName = utils.getUserInfo(data.user, ['name']).name; + + if(data.gift.member.preferences.emailNotifications.giftedSubscription !== false){ + utils.txnEmail(data.gift.member, 'gifted-subscription', [ + {name: 'GIFTER', content: byUserName}, + {name: 'X_MONTHS_SUBSCRIPTION', content: months} + ]); + } + + if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself + pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), months + " months - by "+ byUserName); + } + } + async.parallel([ + function(cb2){data.user.save(cb2)}, + function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);} + ], cb); +} + +/** + * Sets their subscription to be cancelled later + */ +exports.cancelSubscription = function(data, cb) { + var p = data.user.purchased.plan, + now = moment(), + remaining = data.nextBill ? moment(data.nextBill).diff(new Date, 'days') : 30; + + p.dateTerminated = + moment( now.format('MM') + '/' + moment(p.dateUpdated).format('DD') + '/' + now.format('YYYY') ) + .add({days: remaining}) // end their subscription 1mo from their last payment + .add({months: Math.ceil(p.extraMonths)})// plus any extra time (carry-over, gifted subscription, etc) they have. FIXME: moment can't add months in fractions... + .toDate(); + p.extraMonths = 0; // clear extra time. If they subscribe again, it'll be recalculated from p.dateTerminated + + data.user.save(cb); + utils.txnEmail(data.user, 'cancel-subscription'); + var analyticsData = { + uuid: data.user._id, + gaCategory: 'commerce', + gaLabel: data.paymentMethod, + paymentMethod: data.paymentMethod + } + utils.analytics.track('unsubscribe', analyticsData); +} + +exports.buyGems = function(data, cb) { + var amt = data.amount || 5; + amt = data.gift ? data.gift.gems.amount/4 : amt; + (data.gift ? data.gift.member : data.user).balance += amt; + data.user.purchased.txnCount++; + if(isProduction) { + if (!data.gift) utils.txnEmail(data.user, 'donation'); + + var analyticsData = { + uuid: data.user._id, + itemPurchased: 'Gems', + sku: data.paymentMethod.toLowerCase() + '-checkout', + purchaseType: 'checkout', + paymentMethod: data.paymentMethod, + quantity: 1, + gift: !!data.gift, // coerced into a boolean + purchaseValue: amt + } + utils.analytics.trackPurchase(analyticsData); + } + + if (data.gift){ + var byUsername = utils.getUserInfo(data.user, ['name']).name; + var gemAmount = data.gift.gems.amount || 20; + + members.sendMessage(data.user, data.gift.member, data.gift); + if(data.gift.member.preferences.emailNotifications.giftedGems !== false){ + utils.txnEmail(data.gift.member, 'gifted-gems', [ + {name: 'GIFTER', content: byUsername}, + {name: 'X_GEMS_GIFTED', content: gemAmount} + ]); + } + + if (data.gift.member._id != data.user._id) { // Only send push notifications if sending to a user other than yourself + pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), gemAmount + ' Gems - by '+byUsername); + } + } + async.parallel([ + function(cb2){data.user.save(cb2)}, + function(cb2){data.gift ? data.gift.member.save(cb2) : cb2(null);} + ], cb); +} + +exports.validCoupon = function(req, res, next){ + mongoose.model('Coupon').findOne({_id:cc.validate(req.params.code), event:'google_6mo'}, function(err, coupon){ + if (err) return next(err); + if (!coupon) return res.status(401).json({err:"Invalid coupon code"}); + return res.sendStatus(200); + }); +} + +exports.stripeCheckout = stripe.checkout; +exports.stripeSubscribeCancel = stripe.subscribeCancel; +exports.stripeSubscribeEdit = stripe.subscribeEdit; + +exports.paypalSubscribe = paypal.createBillingAgreement; +exports.paypalSubscribeSuccess = paypal.executeBillingAgreement; +exports.paypalSubscribeCancel = paypal.cancelSubscription; +exports.paypalCheckout = paypal.createPayment; +exports.paypalCheckoutSuccess = paypal.executePayment; +exports.paypalIPN = paypal.ipn; + +exports.amazonVerifyAccessToken = amazon.verifyAccessToken; +exports.amazonCreateOrderReferenceId = amazon.createOrderReferenceId; +exports.amazonCheckout = amazon.checkout; +exports.amazonSubscribe = amazon.subscribe; +exports.amazonSubscribeCancel = amazon.subscribeCancel; + +exports.iapAndroidVerify = iap.androidVerify; +exports.iapIosVerify = iap.iosVerify; diff --git a/website/src/controllers/payments/paypal.js b/website/src/controllers/payments/paypal.js new file mode 100644 index 0000000000..3c5258c222 --- /dev/null +++ b/website/src/controllers/payments/paypal.js @@ -0,0 +1,216 @@ +var nconf = require('nconf'); +var moment = require('moment'); +var async = require('async'); +var _ = require('lodash'); +var url = require('url'); +var User = require('mongoose').model('User'); +var payments = require('./index'); +var logger = require('../../libs/logging'); +var ipn = require('paypal-ipn'); +var paypal = require('paypal-rest-sdk'); +var shared = require('../../../../common'); +var mongoose = require('mongoose'); +var cc = require('coupon-code'); + +// This is the plan.id for paypal subscriptions. You have to set up billing plans via their REST sdk (they don't have +// a web interface for billing-plan creation), see ./paypalBillingSetup.js for how. After the billing plan is created +// there, get it's plan.id and store it in config.json +_.each(shared.content.subscriptionBlocks, function(block){ + block.paypalKey = nconf.get("PAYPAL:billing_plans:"+block.key); +}); + +paypal.configure({ + 'mode': nconf.get("PAYPAL:mode"), //sandbox or live + 'client_id': nconf.get("PAYPAL:client_id"), + 'client_secret': nconf.get("PAYPAL:client_secret") +}); + +var parseErr = function(res, err){ + //var error = err.response ? err.response.message || err.response.details[0].issue : err; + var error = JSON.stringify(err); + return res.status(400).json({err:error}); +} + +exports.createBillingAgreement = function(req,res,next){ + var sub = shared.content.subscriptionBlocks[req.query.sub]; + async.waterfall([ + function(cb){ + if (!sub.discount) return cb(null, null); + if (!req.query.coupon) return cb('Please provide a coupon code for this plan.'); + mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb); + }, + function(coupon, cb){ + if (sub.discount && !coupon) return cb('Invalid coupon code.'); + var billingPlanTitle = "HabitRPG Subscription" + ' ($'+sub.price+' every '+sub.months+' months, recurring)'; + var billingAgreementAttributes = { + "name": billingPlanTitle, + "description": billingPlanTitle, + "start_date": moment().add({minutes:5}).format(), + "plan": { + "id": sub.paypalKey + }, + "payer": { + "payment_method": "paypal" + } + }; + paypal.billingAgreement.create(billingAgreementAttributes, cb); + } + ], function(err, billingAgreement){ + if (err) return parseErr(res, err); + // For approving subscription via Paypal, first redirect user to: approval_url + req.session.paypalBlock = req.query.sub; + var approval_url = _.find(billingAgreement.links, {rel:'approval_url'}).href; + res.redirect(approval_url); + }); +} + +exports.executeBillingAgreement = function(req,res,next){ + var block = shared.content.subscriptionBlocks[req.session.paypalBlock]; + delete req.session.paypalBlock; + async.auto({ + exec: function (cb) { + paypal.billingAgreement.execute(req.query.token, {}, cb); + }, + get_user: function (cb) { + User.findById(req.session.userId, cb); + }, + create_sub: ['exec', 'get_user', function (cb, results) { + payments.createSubscription({ + user: results.get_user, + customerId: results.exec.id, + paymentMethod: 'Paypal', + sub: block + }, cb); + }] + },function(err){ + if (err) return parseErr(res, err); + res.redirect('/'); + }) +} + +exports.createPayment = function(req, res) { + // if we're gifting to a user, put it in session for the `execute()` + req.session.gift = req.query.gift || undefined; + var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; + var price = !gift ? 5.00 + : gift.type=='gems' ? Number(gift.gems.amount/4).toFixed(2) + : Number(shared.content.subscriptionBlocks[gift.subscription.key].price).toFixed(2); + var description = !gift ? "HabitRPG Gems" + : gift.type=='gems' ? "HabitRPG Gems (Gift)" + : shared.content.subscriptionBlocks[gift.subscription.key].months + "mo. HabitRPG Subscription (Gift)"; + var create_payment = { + "intent": "sale", + "payer": { + "payment_method": "paypal" + }, + "redirect_urls": { + "return_url": nconf.get('BASE_URL') + '/paypal/checkout/success', + "cancel_url": nconf.get('BASE_URL') + }, + "transactions": [{ + "item_list": { + "items": [{ + "name": description, + //"sku": "1", + "price": price, + "currency": "USD", + "quantity": 1 + }] + }, + "amount": { + "currency": "USD", + "total": price + }, + "description": description + }] + }; + paypal.payment.create(create_payment, function (err, payment) { + if (err) return parseErr(res, err); + var link = _.find(payment.links, {rel: 'approval_url'}).href; + res.redirect(link); + }); +} + +exports.executePayment = function(req, res) { + var paymentId = req.query.paymentId, + PayerID = req.query.PayerID, + gift = req.session.gift ? JSON.parse(req.session.gift) : undefined; + delete req.session.gift; + async.waterfall([ + function(cb){ + paypal.payment.execute(paymentId, {payer_id: PayerID}, cb); + }, + function(payment, cb){ + async.parallel([ + function(cb2){ User.findById(req.session.userId, cb2); }, + function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); } + ], cb); + }, + function(results, cb){ + if (_.isEmpty(results[0])) return cb("User not found when completing paypal transaction"); + var data = {user:results[0], customerId:PayerID, paymentMethod:'Paypal', gift:gift} + var method = 'buyGems'; + if (gift) { + gift.member = results[1]; + if (gift.type=='subscription') method = 'createSubscription'; + data.paymentMethod = 'Gift'; + } + payments[method](data, cb); + } + ],function(err){ + if (err) return parseErr(res, err); + res.redirect('/'); + }) +} + +exports.cancelSubscription = function(req, res, next){ + var user = res.locals.user; + if (!user.purchased.plan.customerId) + return res.status(401).json({err: "User does not have a plan subscription"}); + async.auto({ + get_cus: function(cb){ + paypal.billingAgreement.get(user.purchased.plan.customerId, cb); + }, + verify_cus: ['get_cus', function(cb, results){ + var hasntBilledYet = results.get_cus.agreement_details.cycles_completed == "0"; + if (hasntBilledYet) + return cb("The plan hasn't activated yet (due to a PayPal bug). It will begin "+results.get_cus.agreement_details.next_billing_date+", after which you can cancel to retain your full benefits"); + cb(); + }], + del_cus: ['verify_cus', function(cb, results){ + paypal.billingAgreement.cancel(user.purchased.plan.customerId, {note: "Canceling the subscription"}, cb); + }], + cancel_sub: ['get_cus', 'verify_cus', function(cb, results){ + var data = {user: user, paymentMethod: 'Paypal', nextBill: results.get_cus.agreement_details.next_billing_date}; + payments.cancelSubscription(data, cb) + }] + }, function(err){ + if (err) return parseErr(res, err); + res.redirect('/'); + user = null; + }); +} + +/** + * General IPN handler. We catch cancelled HabitRPG subscriptions for users who manually cancel their + * recurring paypal payments in their paypal dashboard. Remove this when we can move to webhooks or some other solution + */ +exports.ipn = function(req, res, next) { + console.log('IPN Called'); + res.sendStatus(200); // Must respond to PayPal IPN request with an empty 200 first + ipn.verify(req.body, function(err, msg) { + if (err) return logger.error(msg); + switch (req.body.txn_type) { + // TODO what's the diff b/w the two data.txn_types below? The docs recommend subscr_cancel, but I'm getting the other one instead... + case 'recurring_payment_profile_cancel': + case 'subscr_cancel': + User.findOne({'purchased.plan.customerId':req.body.recurring_payment_id},function(err, user){ + if (err) return logger.error(err); + if (_.isEmpty(user)) return; // looks like the cancellation was already handled properly above (see api.paypalSubscribeCancel) + payments.cancelSubscription({user:user, paymentMethod: 'Paypal'}); + }); + break; + } + }); +}; + diff --git a/scripts/paypalBillingSetup.js b/website/src/controllers/payments/paypalBillingSetup.js similarity index 99% rename from scripts/paypalBillingSetup.js rename to website/src/controllers/payments/paypalBillingSetup.js index d21cd80c1c..2effcbd81d 100644 --- a/scripts/paypalBillingSetup.js +++ b/website/src/controllers/payments/paypalBillingSetup.js @@ -2,16 +2,14 @@ // payment plan definitions, instead you have to create it via their REST SDK and keep it updated the same way. So this // file will be used once for initing your billing plan (then you get the resultant plan.id to store in config.json), // and once for any time you need to edit the plan thereafter - var path = require('path'); var nconf = require('nconf'); -var _ = require('lodash'); +_ = require('lodash'); +nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json'))); var paypal = require('paypal-rest-sdk'); var blocks = require('../../../../common').content.subscriptionBlocks; var live = nconf.get('PAYPAL:mode')=='live'; -nconf.argv().env().file('user', path.join(path.resolve(__dirname, '../../../config.json'))); - var OP = 'create'; // list create update remove paypal.configure({ diff --git a/website/src/controllers/payments/stripe.js b/website/src/controllers/payments/stripe.js new file mode 100644 index 0000000000..1a1085227c --- /dev/null +++ b/website/src/controllers/payments/stripe.js @@ -0,0 +1,123 @@ +var nconf = require('nconf'); +var stripe = require('stripe')(nconf.get('STRIPE_API_KEY')); +var async = require('async'); +var payments = require('./index'); +var User = require('mongoose').model('User'); +var shared = require('../../../../common'); +var mongoose = require('mongoose'); +var cc = require('coupon-code'); + +/* + Setup Stripe response when posting payment + */ +exports.checkout = function(req, res, next) { + var token = req.body.id; + var user = res.locals.user; + var gift = req.query.gift ? JSON.parse(req.query.gift) : undefined; + var sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false; + + async.waterfall([ + function(cb){ + if (sub) { + async.waterfall([ + function(cb2){ + if (!sub.discount) return cb2(null, null); + if (!req.query.coupon) return cb2('Please provide a coupon code for this plan.'); + mongoose.model('Coupon').findOne({_id:cc.validate(req.query.coupon), event:sub.key}, cb2); + }, + function(coupon, cb2){ + if (sub.discount && !coupon) return cb2('Invalid coupon code.'); + var customer = { + email: req.body.email, + metadata: {uuid: user._id}, + card: token, + plan: sub.key + }; + stripe.customers.create(customer, cb2); + } + ], cb); + } else { + stripe.charges.create({ + amount: !gift ? '500' //"500" = $5 + : gift.type=='subscription' ? ''+shared.content.subscriptionBlocks[gift.subscription.key].price*100 + : ''+gift.gems.amount/4*100, + currency: 'usd', + card: token + }, cb); + } + }, + function(response, cb) { + if (sub) return payments.createSubscription({user:user, customerId:response.id, paymentMethod:'Stripe', sub:sub}, cb); + async.waterfall([ + function(cb2){ User.findById(gift ? gift.uuid : undefined, cb2); }, + function(member, cb2){ + var data = {user:user, customerId:response.id, paymentMethod:'Stripe', gift:gift}; + var method = 'buyGems'; + if (gift) { + gift.member = member; + if (gift.type=='subscription') method = 'createSubscription'; + data.paymentMethod = 'Gift'; + } + payments[method](data, cb2); + } + ], cb); + } + ], function(err){ + if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors + res.sendStatus(200); + user = token = null; + }); +}; + +exports.subscribeCancel = function(req, res, next) { + var user = res.locals.user; + if (!user.purchased.plan.customerId) + return res.status(401).json({err: 'User does not have a plan subscription'}); + + async.auto({ + get_cus: function(cb){ + stripe.customers.retrieve(user.purchased.plan.customerId, cb); + }, + del_cus: ['get_cus', function(cb, results){ + stripe.customers.del(user.purchased.plan.customerId, cb); + }], + cancel_sub: ['get_cus', function(cb, results) { + var data = { + user: user, + nextBill: results.get_cus.subscription.current_period_end*1000, // timestamp is in seconds + paymentMethod: 'Stripe' + }; + payments.cancelSubscription(data, cb); + }] + }, function(err, results){ + if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors + res.redirect('/'); + user = null; + }); +}; + +exports.subscribeEdit = function(req, res, next) { + var token = req.body.id; + var user = res.locals.user; + var user_id = user.purchased.plan.customerId; + var sub_id; + + async.waterfall([ + function(cb){ + stripe.customers.listSubscriptions(user_id, cb); + }, + function(response, cb) { + sub_id = response.data[0].id; + console.warn(sub_id); + console.warn([user_id, sub_id, { card: token }]); + stripe.customers.updateSubscription(user_id, sub_id, { card: token }, cb); + }, + function(response, cb) { + user.save(cb); + } + ], function(err, saved){ + if (err) return res.send(500, err.toString()); // don't json this, let toString() handle errors + res.sendStatus(200); + token = user = user_id = sub_id; + }); +}; diff --git a/website/server/controllers/api-v2/pushNotifications.js b/website/src/controllers/pushNotifications.js similarity index 98% rename from website/server/controllers/api-v2/pushNotifications.js rename to website/src/controllers/pushNotifications.js index 860cfbf56a..d1c728f365 100644 --- a/website/server/controllers/api-v2/pushNotifications.js +++ b/website/src/controllers/pushNotifications.js @@ -1,4 +1,3 @@ -// TODO move to /api-v2 var api = module.exports; var _ = require('lodash'); var nconf = require('nconf'); diff --git a/website/server/libs/api-v2/analytics.js b/website/src/libs/analytics.js similarity index 98% rename from website/server/libs/api-v2/analytics.js rename to website/src/libs/analytics.js index 6c1ccd3020..dffa7c2a9b 100644 --- a/website/server/libs/api-v2/analytics.js +++ b/website/src/libs/analytics.js @@ -1,7 +1,7 @@ require('./i18n'); var _ = require('lodash'); -var Content = require('../../../../common').content; +var Content = require('../../../common').content; var Amplitude = require('amplitude'); var googleAnalytics = require('universal-analytics'); diff --git a/website/server/libs/api-v2/buildManifest.js b/website/src/libs/buildManifest.js similarity index 91% rename from website/server/libs/api-v2/buildManifest.js rename to website/src/libs/buildManifest.js index bfbab1421a..e2b337860f 100644 --- a/website/server/libs/api-v2/buildManifest.js +++ b/website/src/libs/buildManifest.js @@ -2,7 +2,7 @@ var fs = require('fs'); var path = require('path'); var nconf = require('nconf'); var _ = require('lodash'); -var manifestFiles = require("../../../client/manifest.json"); +var manifestFiles = require("../../public/manifest.json"); var IS_PROD = nconf.get('NODE_ENV') === 'production'; var buildFiles = []; @@ -15,7 +15,7 @@ var walk = function(folder){ if(fs.statSync(file).isDirectory()){ walk(file); }else{ - var relFolder = path.relative(path.join(__dirname, "/../../../build"), folder); + var relFolder = path.relative(path.join(__dirname, "/../../build"), folder); var old = fileName.replace(/-.{8}(\.[\d\w]+)$/, '$1'); if(relFolder){ @@ -28,7 +28,7 @@ var walk = function(folder){ }); }; -walk(path.join(__dirname, "/../../../build")); +walk(path.join(__dirname, "/../../build")); var getBuildUrl = module.exports.getBuildUrl = function(url){ if(buildFiles[url]) return '/' + buildFiles[url]; @@ -56,4 +56,4 @@ module.exports.getManifestFiles = function(page){ } return code; -}; +}; \ No newline at end of file diff --git a/website/server/libs/api-v2/firebase.js b/website/src/libs/firebase.js similarity index 87% rename from website/server/libs/api-v2/firebase.js rename to website/src/libs/firebase.js index 8a8d9f002c..84a90b22d4 100644 --- a/website/server/libs/api-v2/firebase.js +++ b/website/src/libs/firebase.js @@ -6,8 +6,6 @@ var firebaseConfig = nconf.get('FIREBASE'); var firebaseRef; var isFirebaseEnabled = (nconf.get('NODE_ENV') === 'production') && (firebaseConfig.ENABLED === 'true'); -import { TAVERN_ID } from '../../models/group'; - // Setup if(isFirebaseEnabled){ firebaseRef = new Firebase('https://' + firebaseConfig.APP + '.firebaseio.com'); @@ -26,7 +24,7 @@ api.updateGroupData = function(group){ // TODO is throw ok? we don't have callbacks if(!group) throw new Error('group is required.'); // Return in case of tavern (comparison working because we use string for _id) - if(group._id === TAVERN_ID) return; + if(group._id === 'habitrpg') return; firebaseRef.child('rooms/' + group._id) .set({ @@ -37,7 +35,7 @@ api.updateGroupData = function(group){ api.addUserToGroup = function(groupId, userId){ if(!isFirebaseEnabled) return; if(!userId || !groupId) throw new Error('groupId, userId are required.'); - if(groupId === TAVERN_ID) return; + if(groupId === 'habitrpg') return; firebaseRef.child('members/' + groupId + '/' + userId) .set(true); @@ -49,7 +47,7 @@ api.addUserToGroup = function(groupId, userId){ api.removeUserFromGroup = function(groupId, userId){ if(!isFirebaseEnabled) return; if(!userId || !groupId) throw new Error('groupId, userId are required.'); - if(groupId === TAVERN_ID) return; + if(groupId === 'habitrpg') return; firebaseRef.child('members/' + groupId + '/' + userId) .remove(); @@ -61,18 +59,18 @@ api.removeUserFromGroup = function(groupId, userId){ api.deleteGroup = function(groupId){ if(!isFirebaseEnabled) return; if(!groupId) throw new Error('groupId is required.'); - if(groupId === TAVERN_ID) return; + if(groupId === 'habitrpg') return; firebaseRef.child('rooms/' + groupId) .remove(); - // TODO not really necessary as long as we only store room data, + // FIXME not really necessary as long as we only store room data, // as empty objects are automatically deleted (/members/... in future...) firebaseRef.child('members/' + groupId) .remove(); }; -// TODO not really necessary as long as we only store room data, +// FIXME not really necessary as long as we only store room data, // as empty objects are automatically deleted api.deleteUser = function(userId){ if(!isFirebaseEnabled) return; @@ -80,4 +78,4 @@ api.deleteUser = function(userId){ firebaseRef.child('users/' + userId) .remove(); -}; +}; \ No newline at end of file diff --git a/website/server/libs/api-v2/i18n.js b/website/src/libs/i18n.js similarity index 90% rename from website/server/libs/api-v2/i18n.js rename to website/src/libs/i18n.js index e8295deb03..3edf901279 100644 --- a/website/server/libs/api-v2/i18n.js +++ b/website/src/libs/i18n.js @@ -1,12 +1,11 @@ var fs = require('fs'), path = require('path'), _ = require('lodash'), - User = require('../../models/user').model, - accepts = require('accepts'), - shared = require('../../../../common'), + User = require('../models/user').model, + shared = require('../../../common'), translations = {}; -var localePath = path.join(__dirname, "/../../../../common/locales/") +var localePath = path.join(__dirname, "/../../../common/locales/") var loadTranslations = function(locale){ var files = fs.readdirSync(path.join(localePath, locale)); @@ -55,7 +54,7 @@ _.each(langCodes, function(code){ lang.momentLangCode = (momentLangsMapping[code] || code); try{ // MomentJS lang files are JS files that has to be executed in the browser so we load them as plain text files - var f = fs.readFileSync(path.join(__dirname, '/../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); + var f = fs.readFileSync(path.join(__dirname, '/../../../node_modules/moment/locale/' + lang.momentLangCode + '.js'), 'utf8'); momentLangs[code] = f; }catch (e){} }); @@ -95,9 +94,7 @@ var chineseVersions = { var getUserLanguage = function(req, res, next){ var getFromBrowser = function(){ - var acceptedLanguages = accepts(req).languages(); - - var acceptable = _(acceptedLanguages).map(function(lang){ + var acceptable = _(req.acceptedLanguages).map(function(lang){ return lang.slice(0, 2); }).uniq().value(); @@ -106,7 +103,7 @@ var getUserLanguage = function(req, res, next){ var iAcceptedCompleteLang = (matches.length > 0) ? multipleVersionsLanguages.indexOf(matches[0].toLowerCase()) : -1; if(iAcceptedCompleteLang !== -1){ - var acceptedCompleteLang = _.find(acceptedLanguages, function(accepted){ + var acceptedCompleteLang = _.find(req.acceptedLanguages, function(accepted){ return accepted.slice(0, 2) == multipleVersionsLanguages[iAcceptedCompleteLang]; }); diff --git a/website/server/libs/api-v2/logging.js b/website/src/libs/logging.js similarity index 94% rename from website/server/libs/api-v2/logging.js rename to website/src/libs/logging.js index 9737159f43..f832adb6d5 100644 --- a/website/server/libs/api-v2/logging.js +++ b/website/src/libs/logging.js @@ -22,9 +22,9 @@ if (nconf.get('LOGGLY:enabled')){ if (!logger) { logger = new (winston.Logger)({}); - logger.add(winston.transports.Console, {colorize:true}); // TODO remove if (nconf.get('NODE_ENV') !== 'production') { + logger.add(winston.transports.Console, {colorize:true}); logger.add(winston.transports.File, {filename: 'habitrpg.log'}); } } diff --git a/website/server/libs/api-v2/utils.js b/website/src/libs/utils.js similarity index 90% rename from website/server/libs/api-v2/utils.js rename to website/src/libs/utils.js index 8657bf6513..6266843edf 100644 --- a/website/server/libs/api-v2/utils.js +++ b/website/src/libs/utils.js @@ -4,8 +4,8 @@ var crypto = require('crypto'); var path = require("path"); var request = require('request'); -const IS_PROD = nconf.get('IS_PROD'); -const BASE_URL = nconf.get('BASE_URL'); +// Set when utils.setupConfig is run +var isProd, baseUrl; module.exports.sendEmail = function(mailData) { var smtpTransport = nodemailer.createTransport({ @@ -17,7 +17,7 @@ module.exports.sendEmail = function(mailData) { }); smtpTransport.sendMail(mailData, function(error, response){ - var logging = require('./api-v2/logging'); + var logging = require('./logging'); if(error) logging.error(error); else logging.info("Message sent: " + response.message); smtpTransport.close(); // shut down the connection pool, no more messages @@ -60,7 +60,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso var mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray]; var variables = [ - {name: 'BASE_URL', content: BASE_URL} + {name: 'BASE_URL', content: baseUrl} ].concat(variables || []); // It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed @@ -121,7 +121,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso }); } - if(IS_PROD && mailingInfoArray.length > 0){ + if(isProd && mailingInfoArray.length > 0){ request({ url: nconf.get('EMAIL_SERVER:url') + '/job', method: 'POST', @@ -168,12 +168,20 @@ module.exports.analytics = { track: function() { }, trackPurchase: function() { * Load nconf and define default configuration values if config.json or ENV vars are not found */ module.exports.setupConfig = function(){ - if (nconf.get('IS_DEV')) + nconf.argv() + .env() + //.file('defaults', path.join(path.resolve(__dirname, '../config.json.example'))) + .file('user', path.join(path.resolve(__dirname, './../../../config.json'))); + + if (nconf.get('NODE_ENV') === "development") Error.stackTraceLimit = Infinity; - if (IS_PROD && nconf.get('NEW_RELIC_ENABLED') === 'true') + if (nconf.get('NODE_ENV') === 'production' && nconf.get('NEW_RELIC_ENABLED') === 'true') require('newrelic'); - var analytics = IS_PROD && require('./api-v2/analytics'); + isProd = nconf.get('NODE_ENV') === 'production'; + baseUrl = nconf.get('BASE_URL'); + + var analytics = isProd && require('./analytics'); var analyticsTokens = { amplitudeToken: nconf.get('AMPLITUDE_KEY'), googleAnalytics: nconf.get('GA_ID') diff --git a/website/server/libs/api-v2/webhook.js b/website/src/libs/webhook.js similarity index 100% rename from website/server/libs/api-v2/webhook.js rename to website/src/libs/webhook.js diff --git a/website/server/middlewares/apiThrottle.js b/website/src/middlewares/apiThrottle.js similarity index 74% rename from website/server/middlewares/apiThrottle.js rename to website/src/middlewares/apiThrottle.js index b392cc777e..8de298106c 100644 --- a/website/server/middlewares/apiThrottle.js +++ b/website/src/middlewares/apiThrottle.js @@ -3,10 +3,8 @@ var limiter = require('connect-ratelimit'); var IS_PROD = nconf.get('NODE_ENV') === 'production'; -// TODO since Habitica runs on many different servers this module is pretty useless -// as it will only block requests that go to the same server but anyway we should probably have a rate limiter in place - module.exports = function(app) { + // TODO review later // disable the rate limiter middleware if (/*!IS_PROD || */true) return; app.use(limiter({ diff --git a/website/src/middlewares/cors.js b/website/src/middlewares/cors.js new file mode 100644 index 0000000000..e72db26981 --- /dev/null +++ b/website/src/middlewares/cors.js @@ -0,0 +1,7 @@ +module.exports = function(req, res, next) { + res.header("Access-Control-Allow-Origin", req.headers.origin || "*"); + res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE"); + res.header("Access-Control-Allow-Headers", "Content-Type,Accept,Content-Encoding,X-Requested-With,x-api-user,x-api-key"); + if (req.method === 'OPTIONS') return res.sendStatus(200); + return next(); +}; diff --git a/website/server/middlewares/api-v2/domain.js b/website/src/middlewares/domain.js similarity index 100% rename from website/server/middlewares/api-v2/domain.js rename to website/src/middlewares/domain.js diff --git a/website/server/middlewares/api-v2/errorHandler.js b/website/src/middlewares/errorHandler.js similarity index 95% rename from website/server/middlewares/api-v2/errorHandler.js rename to website/src/middlewares/errorHandler.js index e62753f5f9..2b06824dce 100644 --- a/website/server/middlewares/api-v2/errorHandler.js +++ b/website/src/middlewares/errorHandler.js @@ -1,4 +1,4 @@ -var logging = require('../../libs/api-v2/logging'); +var logging = require('../libs/logging'); module.exports = function(err, req, res, next) { //res.locals.domain.emit('error', err); diff --git a/website/server/middlewares/forceRefresh.js b/website/src/middlewares/forceRefresh.js similarity index 83% rename from website/server/middlewares/forceRefresh.js rename to website/src/middlewares/forceRefresh.js index f843694790..6d5b4fa8c9 100644 --- a/website/server/middlewares/forceRefresh.js +++ b/website/src/middlewares/forceRefresh.js @@ -1,5 +1,3 @@ -// TODO do we need this module anymore in v3? No - module.exports.siteVersion = 1; module.exports.middleware = function(req, res, next){ diff --git a/website/server/middlewares/api-v2/locals.js b/website/src/middlewares/locals.js similarity index 94% rename from website/server/middlewares/api-v2/locals.js rename to website/src/middlewares/locals.js index 223186a81a..2f238a6a99 100644 --- a/website/server/middlewares/api-v2/locals.js +++ b/website/src/middlewares/locals.js @@ -1,9 +1,9 @@ var nconf = require('nconf'); var _ = require('lodash'); -var utils = require('../libs/api-v2/utils'); +var utils = require('../libs/utils'); var shared = require('../../../common'); -var i18n = require('../libs/api-v2/i18n'); -var buildManifest = require('../libs/api-v2/buildManifest'); +var i18n = require('../libs/i18n'); +var buildManifest = require('../libs/buildManifest'); var shared = require('../../../common'); var forceRefresh = require('./forceRefresh'); var tavernQuest = require('../models/group').tavernQuest; diff --git a/website/src/middlewares/redirects.js b/website/src/middlewares/redirects.js new file mode 100644 index 0000000000..ddc135beab --- /dev/null +++ b/website/src/middlewares/redirects.js @@ -0,0 +1,41 @@ +var nconf = require('nconf'); +var IS_PROD = nconf.get('NODE_ENV') === 'production'; +var ignoreRedirect = nconf.get('IGNORE_REDIRECT'); +var BASE_URL = nconf.get('BASE_URL'); + +function isHTTP(req) { + return ( + req.headers['x-forwarded-proto'] && + req.headers['x-forwarded-proto'] !== 'https' && + IS_PROD && + BASE_URL.indexOf('https') === 0 + ); +} + +function isProxied(req) { + return ( + req.headers['x-habitica-lb'] && + req.headers['x-habitica-lb'] === 'Yes' + ); +} + +module.exports.forceSSL = function(req, res, next){ + if(isHTTP(req) && !isProxied(req)) { + return res.redirect(BASE_URL + req.url); + } + + next(); +}; + +// Redirect to habitica for non-api urls + +function nonApiUrl(req) { + return req.url.search(/\/api\//) === -1; +} + +module.exports.forceHabitica = function(req, res, next) { + if (IS_PROD && !ignoreRedirect && !isProxied(req) && nonApiUrl(req)) { + return res.redirect(301, BASE_URL + req.url); + } + next(); +}; diff --git a/website/src/models/challenge.js b/website/src/models/challenge.js new file mode 100644 index 0000000000..d44b798bc0 --- /dev/null +++ b/website/src/models/challenge.js @@ -0,0 +1,120 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var shared = require('../../../common'); +var _ = require('lodash'); +var TaskSchemas = require('./task'); + +var ChallengeSchema = new Schema({ + _id: {type: String, 'default': shared.uuid}, + name: String, + shortName: String, + description: String, + official: {type: Boolean,'default':false}, + habits: [TaskSchemas.HabitSchema], + dailys: [TaskSchemas.DailySchema], + todos: [TaskSchemas.TodoSchema], + rewards: [TaskSchemas.RewardSchema], + leader: {type: String, ref: 'User'}, + group: {type: String, ref: 'Group'}, + timestamp: {type: Date, 'default': Date.now}, + members: [{type: String, ref: 'User'}], + memberCount: {type: Number, 'default': 0}, + prize: {type: Number, 'default': 0} +}); + +ChallengeSchema.virtual('tasks').get(function () { + var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); + var tasks = _.object(_.pluck(tasks,'id'), tasks); + return tasks; +}); + +ChallengeSchema.methods.toJSON = function(){ + var doc = this.toObject(); + doc._isMember = this._isMember; + return doc; +} + +// -------------- +// Syncing logic +// -------------- + +function syncableAttrs(task) { + var t = (task.toObject) ? task.toObject() : task; // lodash doesn't seem to like _.omit on EmbeddedDocument + // only sync/compare important attrs + var omitAttrs = 'challenge history tags completed streak notes'.split(' '); + if (t.type != 'reward') omitAttrs.push('value'); + return _.omit(t, omitAttrs); +} + +/** + * Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers + */ +function comparableData(obj) { + return JSON.stringify( + _(obj.habits.concat(obj.dailys).concat(obj.todos).concat(obj.rewards)) + .sortBy('id') // we don't want to update if they're sort-order is different + .transform(function(result, task){ + result.push(syncableAttrs(task)); + }) + .value()) +} + +ChallengeSchema.methods.isOutdated = function(newData) { + return comparableData(this) !== comparableData(newData); +} + +/** + * Syncs all new tasks, deleted tasks, etc to the user object + * @param user + * @return nothing, user is modified directly. REMEMBER to save the user! + */ +ChallengeSchema.methods.syncToUser = function(user, cb) { + if (!user) return; + var self = this; + self.shortName = self.shortName || self.name; + + // Add challenge to user.challenges + if (!_.contains(user.challenges, self._id)) { + user.challenges.push(self._id); + } + + // Sync tags + var tags = user.tags || []; + var i = _.findIndex(tags, {id: self._id}) + if (~i) { + if (tags[i].name !== self.shortName) { + // update the name - it's been changed since + user.tags[i].name = self.shortName; + } + } else { + user.tags.push({ + id: self._id, + name: self.shortName, + challenge: true + }); + } + + // Sync new tasks and updated tasks + _.each(self.tasks, function(task){ + var list = user[task.type+'s']; + var userTask = user.tasks[task.id] || (list.push(syncableAttrs(task)), list[list.length-1]); + if (!userTask.notes) userTask.notes = task.notes; // don't override the notes, but provide it if not provided + userTask.challenge = {id:self._id}; + userTask.tags = userTask.tags || {}; + userTask.tags[self._id] = true; + _.merge(userTask, syncableAttrs(task)); + }) + + // Flag deleted tasks as "broken" + _.each(user.tasks, function(task){ + if (task.challenge && task.challenge.id==self._id && !self.tasks[task.id]) { + task.challenge.broken = 'TASK_DELETED'; + } + }) + + user.save(cb); +}; + + +module.exports.schema = ChallengeSchema; +module.exports.model = mongoose.model("Challenge", ChallengeSchema); diff --git a/website/src/models/coupon.js b/website/src/models/coupon.js new file mode 100644 index 0000000000..3b0afb3f2a --- /dev/null +++ b/website/src/models/coupon.js @@ -0,0 +1,59 @@ +var mongoose = require("mongoose"); +var shared = require('../../../common'); +var _ = require('lodash'); +var async = require('async'); +var cc = require('coupon-code'); +var autoinc = require('mongoose-id-autoinc'); + +var CouponSchema = new mongoose.Schema({ + _id: {type: String, 'default': cc.generate}, + event: {type:String, enum:['wondercon','google_6mo']}, + user: {type: 'String', ref: 'User'} +}); + +CouponSchema.statics.generate = function(event, count, callback) { + async.times(count, function(n,cb){ + mongoose.model('Coupon').create({event: event}, cb); + }, callback); +} + +CouponSchema.statics.apply = function(user, code, next){ + async.auto({ + get_coupon: function (cb) { + mongoose.model('Coupon').findById(cc.validate(code), cb); + }, + apply_coupon: ['get_coupon', function (cb, results) { + if (!results.get_coupon) return cb("Invalid coupon code"); + if (results.get_coupon.user) return cb("Coupon already used"); + switch (results.get_coupon.event) { + case 'wondercon': + user.items.gear.owned.eyewear_special_wondercon_red = true; + user.items.gear.owned.eyewear_special_wondercon_black = true; + user.items.gear.owned.back_special_wondercon_black = true; + user.items.gear.owned.back_special_wondercon_red = true; + user.items.gear.owned.body_special_wondercon_red = true; + user.items.gear.owned.body_special_wondercon_black = true; + user.items.gear.owned.body_special_wondercon_gold = true; + user.extra = {signupEvent: 'wondercon'}; + user.save(cb); + break; + } + }], + expire_coupon: ['apply_coupon', function (cb, results) { + results.get_coupon.user = user._id; + results.get_coupon.save(cb); + }] + }, function(err, results){ + if (err) return next(err); + next(null,results.apply_coupon[0]); + }) +} + +CouponSchema.plugin(autoinc.plugin, { + model: 'Coupon', + field: 'seq' +}); + +module.exports.schema = CouponSchema; +module.exports.model = mongoose.model("Coupon", CouponSchema); + diff --git a/website/src/models/emailUnsubscription.js b/website/src/models/emailUnsubscription.js new file mode 100644 index 0000000000..144417f3fc --- /dev/null +++ b/website/src/models/emailUnsubscription.js @@ -0,0 +1,14 @@ +var mongoose = require("mongoose"); +var shared = require('../../../common'); + +// A collection used to store mailing list unsubscription for non registered email addresses +var EmailUnsubscriptionSchema = new mongoose.Schema({ + _id: { + type: String, + 'default': shared.uuid + }, + email: String +}); + +module.exports.schema = EmailUnsubscriptionSchema; +module.exports.model = mongoose.model('EmailUnsubscription', EmailUnsubscriptionSchema); \ No newline at end of file diff --git a/website/src/models/group.js b/website/src/models/group.js new file mode 100644 index 0000000000..caed72331e --- /dev/null +++ b/website/src/models/group.js @@ -0,0 +1,501 @@ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var User = require('./user').model; +var shared = require('../../../common'); +var _ = require('lodash'); +var async = require('async'); +var logging = require('../libs/logging'); +var Challenge = require('./../models/challenge').model; +var firebase = require('../libs/firebase'); + +// NOTE any change to groups' members in MongoDB will have to be run through the API +// changes made directly to the db will cause Firebase to get out of sync +var GroupSchema = new Schema({ + _id: {type: String, 'default': shared.uuid}, + name: String, + description: String, + leader: {type: String, ref: 'User'}, + members: [{type: String, ref: 'User'}], + invites: [{type: String, ref: 'User'}], + type: {type: String, "enum": ['guild', 'party']}, + privacy: {type: String, "enum": ['private', 'public'], 'default':'private'}, + //_v: {type: Number,'default': 0}, + chat: Array, + /* + # [{ + # timestamp: Date + # user: String + # text: String + # contributor: String + # uuid: String + # id: String + # }] + */ + leaderOnly: { // restrict group actions to leader (members can't do them) + challenges: {type:Boolean, 'default':false}, + //invites: {type:Boolean, 'default':false} + }, + memberCount: {type: Number, 'default': 0}, + challengeCount: {type: Number, 'default': 0}, + balance: Number, + logo: String, + leaderMessage: String, + challenges: [{type:'String', ref:'Challenge'}], // do we need this? could depend on back-ref instead (Challenge.find({group:GID})) + quest: { + key: String, + active: {type:Boolean, 'default':false}, + leader: {type:String, ref:'User'}, + progress:{ + hp: Number, + collect: {type:Schema.Types.Mixed, 'default':{}}, // {feather: 5, ingot: 3} + rage: Number, // limit break / "energy stored in shell", for explosion-attacks + }, + + //Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click + //'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them. + //TODO when booting user, remove from .joined and check again if we can now start the quest + members: Schema.Types.Mixed, + extra: Schema.Types.Mixed + } +}, { + strict: 'throw', + minimize: false // So empty objects are returned +}); + +/** + * Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration + * to remove duplicates, then take these fucntions out + */ +function removeDuplicates(doc){ + // Remove duplicate members + if (doc.members) { + var uniqMembers = _.uniq(doc.members); + if (uniqMembers.length != doc.members.length) { + doc.members = uniqMembers; + } + } +} + +// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate() +// @see https://github.com/LearnBoost/mongoose/issues/964 +GroupSchema.pre('save', function(next){ + removeDuplicates(this); + this.memberCount = _.size(this.members); + this.challengeCount = _.size(this.challenges); + next(); +}) + +GroupSchema.pre('remove', function(next) { + var group = this; + async.waterfall([ + function(cb) { + var invitationQuery = {}; + var groupType = group.type; + //Add an 's' to group type guild because the model has the plural version + if (group.type == "guild") groupType += "s"; + invitationQuery['invitations.' + groupType + '.id'] = group._id; + User.find(invitationQuery, cb); + }, + function(users, cb) { + if (users) { + users.forEach(function (user, index, array) { + if ( group.type == "party" ) { + user.invitations.party = {}; + } else { + var i = _.findIndex(user.invitations.guilds, {id: group._id}); + user.invitations.guilds.splice(i, 1); + } + user.save(); + }); + } + cb(); + } + ], next); +}); + +GroupSchema.post('remove', function(group) { + firebase.deleteGroup(group._id); +}); + +GroupSchema.methods.toJSON = function(){ + var doc = this.toObject(); + removeDuplicates(doc); + doc._isMember = this._isMember; + + //fix(groups): temp fix to remove chat entries stored as strings (not sure why that's happening..). + // Required as angular 1.3 is strict on dupes, and no message.id to `track by` + _.remove(doc.chat,function(msg){return !msg.id}); + + // @see pre('save') comment above + this.memberCount = _.size(this.members); + this.challengeCount = _.size(this.challenges); + + return doc; +} + +var chatDefaults = module.exports.chatDefaults = function(msg,user){ + var message = { + id: shared.uuid(), + text: msg, + timestamp: +new Date, + likes: {}, + flags: {}, + flagCount: 0 + }; + if (user) { + _.defaults(message, { + uuid: user._id, + contributor: user.contributor && user.contributor.toObject(), + backer: user.backer && user.backer.toObject(), + user: user.profile.name + }); + } else { + message.uuid = 'system'; + } + return message; +} + +var NO_CHAT_NOTIFICATIONS = ['habitrpg'] + +GroupSchema.methods.sendChat = function(message, user){ + var group = this; + group.chat.unshift(chatDefaults(message,user)); + group.chat.splice(200); + // Kick off chat notifications in the background. + var lastSeenUpdate = {$set:{}, $inc:{_v:1}}; + lastSeenUpdate['$set']['newMessages.'+group._id] = {name:group.name,value:true}; + if (NO_CHAT_NOTIFICATIONS.indexOf(group._id) !== -1 || group.memberCount > 5000) { + // TODO For Tavern, only notify them if their name was mentioned + // var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names? + // User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec(); + } else { + mongoose.model('User').update({_id:{$in:group.members, $ne: user ? user._id : ''}},lastSeenUpdate,{multi:true}).exec(); + } +} + +var cleanQuestProgress = function(merge){ + var clean = { + key: null, + progress: { + up: 0, + down: 0, + collect: {} + }, + completed: null, + RSVPNeeded: false + }; + merge = merge || {progress:{}}; + _.merge(clean, _.omit(merge,'progress')); + _.merge(clean.progress, merge.progress); + return clean; +} +GroupSchema.statics.cleanQuestProgress = cleanQuestProgress; + +// Participants: Grant rewards & achievements, finish quest +GroupSchema.methods.finishQuest = function(quest, cb) { + var group = this; + var questK = quest.key; + var updates = {$inc:{},$set:{}}; + + updates['$inc']['achievements.quests.' + questK] = 1; + updates['$inc']['stats.gp'] = +quest.drop.gp; + updates['$inc']['stats.exp'] = +quest.drop.exp; + updates['$inc']['_v'] = 1; + if (group._id == 'habitrpg') { + updates['$set']['party.quest.completed'] = questK; // Just show the notif + } else { + updates['$set']['party.quest'] = cleanQuestProgress({completed: questK}); // clear quest progress + } + + _.each(quest.drop.items, function(item){ + var dropK = item.key; + switch (item.type) { + case 'gear': + // TODO This means they can lose their new gear on death, is that what we want? + updates['$set']['items.gear.owned.'+dropK] = true; + break; + case 'eggs': + case 'food': + case 'hatchingPotions': + case 'quests': + updates['$inc']['items.'+item.type+'.'+dropK] = _.where(quest.drop.items,{type:item.type,key:item.key}).length; + break; + case 'pets': + updates['$set']['items.pets.'+dropK] = 5; + break; + case 'mounts': + updates['$set']['items.mounts.'+dropK] = true; + break; + } + }) + var q = group._id === 'habitrpg' ? {} : {_id:{$in:_.keys(group.quest.members)}}; + group.quest = {};group.markModified('quest'); + mongoose.model('User').update(q, updates, {multi:true}, cb); +} + +function isOnQuest(user,progress,group){ + return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true; +} + +GroupSchema.statics.collectQuest = function(user, progress, cb) { + this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ + if (!isOnQuest(user,progress,group)) return cb(null); + var quest = shared.content.quests[group.quest.key]; + + _.each(progress.collect,function(v,k){ + group.quest.progress.collect[k] += v; + }); + + var foundText = _.reduce(progress.collect, function(m,v,k){ + m.push(v + ' ' + quest.collect[k].text('en')); + return m; + }, []); + foundText = foundText ? foundText.join(', ') : 'nothing'; + group.sendChat("`" + user.profile.name + " found "+foundText+".`"); + group.markModified('quest.progress.collect'); + + // Still needs completing + if (_.find(shared.content.quests[group.quest.key].collect, function(v,k){ + return group.quest.progress.collect[k] < v.count; + })) return group.save(cb); + + async.series([ + function(cb2){ + group.finishQuest(quest,cb2); + }, + function(cb2){ + group.sendChat('`All items found! Party has received their rewards.`'); + group.save(cb2); + } + ],cb); + }) +} + +// to set a boss: `db.groups.update({_id:'habitrpg'},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` +module.exports.tavernQuest = {}; +var tavernQ = {_id:'habitrpg','quest.key':{$ne:null}}; +process.nextTick(function(){ + mongoose.model('Group').findOne(tavernQ, function(err,tavern){ + if (!tavern) return; // No tavern quest + + var quest = tavern.quest.toObject(); + // Using _assign so we don't lose the reference to the exported tavernQuest + _.assign(module.exports.tavernQuest, quest); + }); +}); + +GroupSchema.statics.tavernBoss = function(user,progress) { + if (!progress) return; + + // hack: prevent crazy damage to world boss + var dmg = Math.min(900, Math.abs(progress.up||0)), + rage = -Math.min(900, Math.abs(progress.down||0)); + + async.waterfall([ + function(cb){ + mongoose.model('Group').findOne(tavernQ,cb); + }, + function(tavern,cb){ + if (!(tavern && tavern.quest && tavern.quest.key)) return cb(true); + + var quest = shared.content.quests[tavern.quest.key]; + if (tavern.quest.progress.hp <= 0) { + tavern.sendChat(quest.completionChat('en')); + tavern.finishQuest(quest, function(){}); + tavern.save(cb); + _.assign(module.exports.tavernQuest, {extra: null}); + } else { + // Deal damage. Note a couple things here, str & def are calculated. If str/def are defined in the database, + // use those first - which allows us to update the boss on the go if things are too easy/hard. + if (!tavern.quest.extra) tavern.quest.extra = {}; + tavern.quest.progress.hp -= dmg / (tavern.quest.extra.def || quest.boss.def); + tavern.quest.progress.rage -= rage * (tavern.quest.extra.str || quest.boss.str); + if (tavern.quest.progress.rage >= quest.boss.rage.value) { + if (!tavern.quest.extra.worldDmg) tavern.quest.extra.worldDmg = {}; + var wd = tavern.quest.extra.worldDmg; + var scene = wd.market ? wd.stables ? wd.bailey ? false : 'bailey' : 'stables' : 'market'; // Be-Wilder attacks Alex, Matt, Bailey + if (!scene) { + tavern.sendChat('`'+quest.boss.name('en')+' tries to unleash '+quest.boss.rage.title('en')+', but is too tired.`'); + tavern.quest.progress.rage = 0 //quest.boss.rage.value; + } else { + tavern.sendChat(quest.boss.rage[scene]('en')); + tavern.quest.extra.worldDmg[scene] = true; + tavern.quest.extra.worldDmg.recent = scene; + tavern.markModified('quest.extra.worldDmg'); + tavern.quest.progress.rage = 0; + if (quest.boss.rage.healing) { + tavern.quest.progress.hp += (quest.boss.rage.healing * tavern.quest.progress.hp); + } + } + } + if (quest.boss.desperation && (tavern.quest.progress.hp < quest.boss.desperation.threshold) && !tavern.quest.extra.desperate) { + tavern.sendChat(quest.boss.desperation.text('en')); + tavern.quest.extra.desperate = true; + tavern.quest.extra.def = quest.boss.desperation.def; + tavern.quest.extra.str = quest.boss.desperation.str; + tavern.markModified('quest.extra'); + } + + _.assign(module.exports.tavernQuest, tavern.quest.toObject()); + tavern.save(cb); + } + } + ],function(err,res){ + if (err === true) return; // no current quest + if (err) return logging.error(err); + dmg = rage = null; + }) +} + +GroupSchema.statics.bossQuest = function(user, progress, cb) { + this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){ + if (!isOnQuest(user,progress,group)) return cb(null); + var quest = shared.content.quests[group.quest.key]; + if (!progress || !quest) return cb(null); // FIXME why is this ever happening, progress should be defined at this point + var down = progress.down * quest.boss.str; // multiply by boss strength + + group.quest.progress.hp -= progress.up; + group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage.` `" + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`"); //TODO Create a party preferred language option so emits like this can be localized + + // If boss has Rage, increment Rage as well + if (quest.boss.rage) { + group.quest.progress.rage += Math.abs(down); + if (group.quest.progress.rage >= quest.boss.rage.value) { + group.sendChat(quest.boss.rage.effect('en')); + group.quest.progress.rage = 0; + if (quest.boss.rage.healing) group.quest.progress.hp += (group.quest.progress.hp * quest.boss.rage.healing); //TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage + if (group.quest.progress.hp > quest.boss.hp) group.quest.progress.hp = quest.boss.hp; + } + } + // Everyone takes damage + var series = [ + function(cb2){ + mongoose.models.User.update({_id:{$in: _.keys(group.quest.members)}}, {$inc:{'stats.hp':down, _v:1}}, {multi:true}, cb2); + } + ] + + // Boss slain, finish quest + if (group.quest.progress.hp <= 0) { + group.sendChat('`You defeated ' + quest.boss.name('en') + '! Questing party members receive the rewards of victory.`'); + // Participants: Grant rewards & achievements, finish quest + series.push(function(cb2){ + group.finishQuest(quest,cb2); + }); + } + + series.push(function(cb2){group.save(cb2)}); + async.series(series,cb); + }) +} + +// Remove user from this group +GroupSchema.methods.leave = function(user, keep, mainCb){ + if(!user) return mainCb(new Error('Missing user.')); + + if(keep && typeof keep === 'function'){ + mainCb = keep; + keep = null; + } + if(typeof keep !== 'string') keep = 'keep-all'; // can be also 'remove-all' + + var group = this; + + async.parallel([ + // Remove user from group challenges + function(cb){ + async.waterfall([ + // Find relevant challenges + function(cb2) { + Challenge.find({ + _id: {$in: user.challenges}, // Challenges I am in + group: group._id // that belong to the group I am leaving + }, cb2); + }, + + // Update each challenge + function(challenges, cb2) { + Challenge.update( + {_id: {$in: _.pluck(challenges, '_id')}}, + {$pull: {members: user._id}}, + {multi: true}, + function(err) { + cb2(err, challenges); // pass `challenges` above to cb + } + ); + }, + + // Unlink the challenge tasks from user + function(challenges, cb2) { + async.waterfall(challenges.map(function(chal) { + return function(cb3) { + var i = user.challenges.indexOf(chal._id) + if (~i) user.challenges.splice(i,1); + user.unlink({cid: chal._id, keep: keep}, cb3); + } + }), cb2); + } + ], cb); + }, + + // Update the group + function(cb){ + // If user is the last one in group and group is private, delete it + if(group.members.length === 1 && ( + group.type === 'party' || + (group.type === 'guild' && group.privacy === 'private') + )){ + group.remove(cb) + }else{ // otherwise just remove a member + var update = {$pull: {members: user._id}}; + + // If the leader is leaving (or if the leader previously left, and this wasn't accounted for) + var leader = group.leader; + + if(leader == user._id || !~group.members.indexOf(leader)){ + var seniorMember = _.find(group.members, function (m) {return m != user._id}); + + // could not exist in case of public guild with 1 member who is leaving + if(seniorMember){ + if (leader == user._id || !~group.members.indexOf(leader)) { + update['$set'] = update['$set'] || {}; + update['$set'].leader = seniorMember; + } + } + } + + update['$inc'] = {memberCount: -1}; + Group.update({_id: group._id}, update, cb); + } + } + ], function(err){ + if(err) return mainCb(err); + + firebase.removeUserFromGroup(group._id, user._id); + return mainCb(); + }); +}; + + +GroupSchema.methods.toJSON = function() { + var doc = this.toObject(); + + return doc; +}; + + +module.exports.schema = GroupSchema; +var Group = module.exports.model = mongoose.model("Group", GroupSchema); + +// initialize tavern if !exists (fresh installs) +Group.count({_id: 'habitrpg'}, function(err, ct){ + if (ct > 0) return; + + new Group({ + _id: 'habitrpg', + chat: [], + leader: '9', + name: 'HabitRPG', + type: 'guild', + privacy: 'public' + }).save(); +}); diff --git a/website/src/models/task.js b/website/src/models/task.js new file mode 100644 index 0000000000..97a0e1e7c6 --- /dev/null +++ b/website/src/models/task.js @@ -0,0 +1,113 @@ +// User.js +// ======= +// Defines the user data model (schema) for use via the API. + +// Dependencies +// ------------ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var shared = require('../../../common'); +var _ = require('lodash'); +var moment = require('moment'); + +// Task Schema +// ----------- + +var TaskSchema = { + //_id:{type: String,'default': helpers.uuid}, + id: {type: String,'default': shared.uuid}, + dateCreated: {type:Date, 'default':Date.now}, + text: String, + notes: {type: String, 'default': ''}, + tags: {type: Schema.Types.Mixed, 'default': {}}, //{ "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true }, + value: {type: Number, 'default': 0}, // redness + priority: {type: Number, 'default': '1'}, + attribute: {type: String, 'default': "str", enum: ['str','con','int','per']}, + challenge: { + id: {type: 'String', ref:'Challenge'}, + broken: String, // CHALLENGE_DELETED, TASK_DELETED, UNSUBSCRIBED, CHALLENGE_CLOSED + winner: String // user.profile.name + // group: {type: 'Strign', ref: 'Group'} // if we restore this, rename `id` above to `challenge` + }, + reminders: [{ + id: {type:String,'default':shared.uuid}, + startDate: Date, + time: Date + }] +}; + +var HabitSchema = new Schema( + _.defaults({ + type: {type:String, 'default': 'habit'}, + history: Array, // [{date:Date, value:Number}], // this causes major performance problems + up: {type: Boolean, 'default': true}, + down: {type: Boolean, 'default': true} + }, TaskSchema) + , { _id: false, minimize:false } +); + +var collapseChecklist = {type:Boolean, 'default':false}; +var checklist = [{ + completed:{type:Boolean,'default':false}, + text: String, + _id:false, + id: {type:String,'default':shared.uuid} +}]; + +var DailySchema = new Schema( + _.defaults({ + type: {type: String, 'default': 'daily'}, + frequency: {type: String, 'default': 'weekly', enum: ['daily', 'weekly']}, + everyX: {type: Number, 'default': 1}, // e.g. once every X weeks + startDate: {type: Date, 'default': moment().startOf('day').toDate()}, + history: Array, + completed: {type: Boolean, 'default': false}, + repeat: { // used only for 'weekly' frequency, + m: {type: Boolean, 'default': true}, + t: {type: Boolean, 'default': true}, + w: {type: Boolean, 'default': true}, + th: {type: Boolean, 'default': true}, + f: {type: Boolean, 'default': true}, + s: {type: Boolean, 'default': true}, + su: {type: Boolean, 'default': true} + }, + collapseChecklist:collapseChecklist, + checklist:checklist, + streak: {type: Number, 'default': 0} + }, TaskSchema) + , { _id: false, minimize:false } +) + +var TodoSchema = new Schema( + _.defaults({ + type: {type:String, 'default': 'todo'}, + completed: {type: Boolean, 'default': false}, + dateCompleted: Date, + date: String, // due date for todos // FIXME we're getting parse errors, people have stored as "today" and "3/13". Need to run a migration & put this back to type: Date + collapseChecklist:collapseChecklist, + checklist:checklist + }, TaskSchema) + , { _id: false, minimize:false } +); + +var RewardSchema = new Schema( + _.defaults({ + type: {type:String, 'default': 'reward'} + }, TaskSchema) + , { _id: false, minimize:false } +); + +/** + * Workaround for bug when _id & id were out of sync, we can remove this after challenges has been running for a while + */ +//_.each([HabitSchema, DailySchema, TodoSchema, RewardSchema], function(schema){ +// schema.post('init', function(doc){ +// if (!doc.id && doc._id) doc.id = doc._id; +// }) +//}) + +module.exports.TaskSchema = TaskSchema; +module.exports.HabitSchema = HabitSchema; +module.exports.DailySchema = DailySchema; +module.exports.TodoSchema = TodoSchema; +module.exports.RewardSchema = RewardSchema; diff --git a/website/src/models/user.js b/website/src/models/user.js new file mode 100644 index 0000000000..ac5f1ba0a2 --- /dev/null +++ b/website/src/models/user.js @@ -0,0 +1,697 @@ +// User.js +// ======= +// Defines the user data model (schema) for use via the API. + +// Dependencies +// ------------ +var mongoose = require("mongoose"); +var Schema = mongoose.Schema; +var shared = require('../../../common'); +var _ = require('lodash'); +var TaskSchemas = require('./task'); +var Challenge = require('./challenge').model; +var moment = require('moment'); + +// User Schema +// ----------- + +var UserSchema = new Schema({ + // ### UUID and API Token + _id: { + type: String, + 'default': shared.uuid + }, + apiToken: { + type: String, + 'default': shared.uuid + }, + + // ### Mongoose Update Object + // We want to know *every* time an object updates. Mongoose uses __v to designate when an object contains arrays which + // have been updated (http://goo.gl/gQLz41), but we want *every* update + _v: { type: Number, 'default': 0 }, + achievements: { + originalUser: Boolean, + habitSurveys: Number, + ultimateGearSets: Schema.Types.Mixed, + beastMaster: Boolean, + beastMasterCount: Number, + mountMaster: Boolean, + mountMasterCount: Number, + triadBingo: Boolean, + triadBingoCount: Number, + veteran: Boolean, + snowball: Number, + spookDust: Number, + shinySeed: Number, + seafoam: Number, + streak: Number, + challenges: Array, + quests: Schema.Types.Mixed, + rebirths: Number, + rebirthLevel: Number, + perfect: Number, + habitBirthdays: Number, + valentine: Number, + costumeContest: Boolean, // Superseded by costumeContests + nye: Number, + habiticaDays: Number, + greeting: Number, + thankyou: Number, + costumeContests: Number, + birthday: Number, + partyUp: Boolean, + partyOn: Boolean + }, + auth: { + blocked: Boolean, + facebook: Schema.Types.Mixed, + local: { + email: String, + hashed_password: String, + salt: String, + username: String, + lowerCaseUsername: String // Store a lowercase version of username to check for duplicates + }, + timestamps: { + created: {type: Date,'default': Date.now}, + loggedin: {type: Date,'default': Date.now} + } + }, + + backer: { + tier: Number, + npc: String, + tokensApplied: Boolean + }, + + contributor: { + level: Number, // 1-9, see https://trello.com/c/wkFzONhE/277-contributor-gear https://github.com/HabitRPG/habitrpg/issues/3801 + admin: Boolean, + sudo: Boolean, + text: String, // Artisan, Friend, Blacksmith, etc + contributions: String, // a markdown textarea to list their contributions + links + critical: String + }, + + balance: {type: Number, 'default':0}, + filters: {type: Schema.Types.Mixed, 'default': {}}, + + purchased: { + ads: {type: Boolean, 'default': false}, + skin: {type: Schema.Types.Mixed, 'default': {}}, // eg, {skeleton: true, pumpkin: true, eb052b: true} + hair: {type: Schema.Types.Mixed, 'default': {}}, + shirt: {type: Schema.Types.Mixed, 'default': {}}, + background: {type: Schema.Types.Mixed, 'default': {}}, + txnCount: {type: Number, 'default':0}, + mobileChat: Boolean, + plan: { + planId: String, + paymentMethod: String, //enum: ['Paypal','Stripe', 'Gift', 'Amazon Payments', '']} + customerId: String, // Billing Agreement Id in case of Amazon Payments + dateCreated: Date, + dateTerminated: Date, + dateUpdated: Date, + extraMonths: {type:Number, 'default':0}, + gemsBought: {type: Number, 'default': 0}, + mysteryItems: {type: Array, 'default': []}, + lastBillingDate: Date, // Used only for Amazon Payments to keep track of billing date + consecutive: { + count: {type:Number, 'default':0}, + offset: {type:Number, 'default':0}, // when gifted subs, offset++ for each month. offset-- each new-month (cron). count doesn't ++ until offset==0 + gemCapExtra: {type:Number, 'default':0}, + trinkets: {type:Number, 'default':0} + } + } + }, + + flags: { + customizationsNotification: {type: Boolean, 'default': false}, + showTour: {type: Boolean, 'default': true}, + tour: { + // -1 indicates "uninitiated", -2 means "complete", any other number is the current tour step (0-index) + intro: {type: Number, 'default': -1}, + classes: {type: Number, 'default': -1}, + stats: {type: Number, 'default': -1}, + tavern: {type: Number, 'default': -1}, + party: {type: Number, 'default': -1}, + guilds: {type: Number, 'default': -1}, + challenges: {type: Number, 'default': -1}, + market: {type: Number, 'default': -1}, + pets: {type: Number, 'default': -1}, + mounts: {type: Number, 'default': -1}, + hall: {type: Number, 'default': -1}, + equipment: {type: Number, 'default': -1} + }, + tutorial: { + common: { + habits: {type: Boolean, 'default': false}, + dailies: {type: Boolean, 'default': false}, + todos: {type: Boolean, 'default': false}, + rewards: {type: Boolean, 'default': false}, + party: {type: Boolean, 'default': false}, + pets: {type: Boolean, 'default': false}, + gems: {type: Boolean, 'default': false}, + skills: {type: Boolean, 'default': false}, + classes: {type: Boolean, 'default': false}, + tavern: {type: Boolean, 'default': false}, + equipment: {type: Boolean, 'default': false}, + items: {type: Boolean, 'default': false}, + }, + ios: { + addTask: {type: Boolean, 'default': false}, + editTask: {type: Boolean, 'default': false}, + deleteTask: {type: Boolean, 'default': false}, + filterTask: {type: Boolean, 'default': false}, + groupPets: {type: Boolean, 'default': false}, + inviteParty: {type: Boolean, 'default': false}, + } + }, + dropsEnabled: {type: Boolean, 'default': false}, + itemsEnabled: {type: Boolean, 'default': false}, + newStuff: {type: Boolean, 'default': false}, + rewrite: {type: Boolean, 'default': true}, + contributor: Boolean, + classSelected: {type: Boolean, 'default': false}, + mathUpdates: Boolean, + rebirthEnabled: {type: Boolean, 'default': false}, + levelDrops: {type:Schema.Types.Mixed, 'default':{}}, + chatRevoked: Boolean, + // Used to track the status of recapture emails sent to each user, + // can be 0 - no email sent - 1, 2, 3 or 4 - 4 means no more email will be sent to the user + recaptureEmailsPhase: {type: Number, 'default': 0}, + // Needed to track the tip to send inside the email + weeklyRecapEmailsPhase: {type: Number, 'default': 0}, + // Used to track when the next weekly recap should be sent + lastWeeklyRecap: {type: Date, 'default': Date.now}, + // Used to enable weekly recap emails as users login + lastWeeklyRecapDiscriminator: Boolean, + communityGuidelinesAccepted: {type: Boolean, 'default': false}, + cronCount: {type:Number, 'default':0}, + welcomed: {type: Boolean, 'default': false}, + armoireEnabled: {type: Boolean, 'default': false}, + armoireOpened: {type: Boolean, 'default': false}, + armoireEmpty: {type: Boolean, 'default': false}, + cardReceived: {type: Boolean, 'default': false}, + warnedLowHealth: {type: Boolean, 'default': false} + }, + history: { + exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined + todos: Array //[{data: Date, value: Number}] // big peformance issues if these are defined + }, + + invitations: { + guilds: {type: Array, 'default': []}, + party: Schema.Types.Mixed + }, + items: { + gear: { + owned: _.transform(shared.content.gear.flat, function(m,v,k){ + m[v.key] = {type: Boolean}; + if (v.key.match(/[armor|head|shield]_warrior_0/) || v.gearSet === 'glasses') + m[v.key]['default'] = true; + }), + + equipped: { + weapon: String, + armor: {type: String, 'default': 'armor_base_0'}, + head: {type: String, 'default': 'head_base_0'}, + shield: {type: String, 'default': 'shield_base_0'}, + back: String, + headAccessory: String, + eyewear: String, + body: String + }, + costume: { + weapon: String, + armor: {type: String, 'default': 'armor_base_0'}, + head: {type: String, 'default': 'head_base_0'}, + shield: {type: String, 'default': 'shield_base_0'}, + back: String, + headAccessory: String, + eyewear: String, + body: String + } + }, + + special:{ + snowball: {type: Number, 'default': 0}, + spookDust: {type: Number, 'default': 0}, + shinySeed: {type: Number, 'default': 0}, + seafoam: {type: Number, 'default': 0}, + valentine: Number, + valentineReceived: Array, // array of strings, by sender name + nye: Number, + nyeReceived: Array, + greeting: Number, + greetingReceived: Array, + thankyou: Number, + thankyouReceived: Array, + birthday: Number, + birthdayReceived: Array + }, + + // -------------- Animals ------------------- + // Complex bit here. The result looks like: + // pets: { + // 'Wolf-Desert': 0, // 0 means does not own + // 'PandaCub-Red': 10, // Number represents "Growth Points" + // etc... + // } + pets: + _.defaults( + // First transform to a 1D eggs/potions mapping + _.transform(shared.content.pets, function(m,v,k){ m[k] = Number; }), + // Then add additional pets (quest, backer, contributor, premium) + _.transform(shared.content.questPets, function(m,v,k){ m[k] = Number; }), + _.transform(shared.content.specialPets, function(m,v,k){ m[k] = Number; }), + _.transform(shared.content.premiumPets, function(m,v,k){ m[k] = Number; }) + ), + currentPet: String, // Cactus-Desert + + // eggs: { + // 'PandaCub': 0, // 0 indicates "doesn't own" + // 'Wolf': 5 // Number indicates "stacking" + // } + eggs: _.transform(shared.content.eggs, function(m,v,k){ m[k] = Number; }), + + // hatchingPotions: { + // 'Desert': 0, // 0 indicates "doesn't own" + // 'CottonCandyBlue': 5 // Number indicates "stacking" + // } + hatchingPotions: _.transform(shared.content.hatchingPotions, function(m,v,k){ m[k] = Number; }), + + // Food: { + // 'Watermelon': 0, // 0 indicates "doesn't own" + // 'RottenMeat': 5 // Number indicates "stacking" + // } + food: _.transform(shared.content.food, function(m,v,k){ m[k] = Number; }), + + // mounts: { + // 'Wolf-Desert': true, + // 'PandaCub-Red': false, + // etc... + // } + mounts: _.defaults( + // First transform to a 1D eggs/potions mapping + _.transform(shared.content.pets, function(m,v,k){ m[k] = Boolean; }), + // Then add quest and premium pets + _.transform(shared.content.questPets, function(m,v,k){ m[k] = Boolean; }), + _.transform(shared.content.premiumPets, function(m,v,k){ m[k] = Boolean; }), + // Then add additional mounts (backer, contributor) + _.transform(shared.content.specialMounts, function(m,v,k){ m[k] = Boolean; }) + ), + currentMount: String, + + // Quests: { + // 'boss_0': 0, // 0 indicates "doesn't own" + // 'collection_honey': 5 // Number indicates "stacking" + // } + quests: _.transform(shared.content.quests, function(m,v,k){ m[k] = Number; }), + + lastDrop: { + date: {type: Date, 'default': Date.now}, + count: {type: Number, 'default': 0} + } + }, + + lastCron: {type: Date, 'default': Date.now}, + + // {GROUP_ID: Boolean}, represents whether they have unseen chat messages + newMessages: {type: Schema.Types.Mixed, 'default': {}}, + + party: { + // id // FIXME can we use a populated doc instead of fetching party separate from user? + order: {type:String, 'default':'level'}, + orderAscending: {type:String, 'default':'ascending'}, + quest: { + key: String, + progress: { + up: {type: Number, 'default': 0}, + down: {type: Number, 'default': 0}, + collect: {type: Schema.Types.Mixed, 'default': {}} // {feather:1, ingot:2} + }, + completed: String, // When quest is done, we move it from key => completed, and it's a one-time flag (for modal) that they unset by clicking "ok" in browser + RSVPNeeded: {type: Boolean, 'default': false} // Set to true when invite is pending, set to false when quest invite is accepted or rejected, quest starts, or quest is cancelled + } + }, + preferences: { + dayStart: {type:Number, 'default': 0, min: 0, max: 23}, + size: {type:String, enum: ['broad','slim'], 'default': 'slim'}, + hair: { + color: {type: String, 'default': 'red'}, + base: {type: Number, 'default': 3}, + bangs: {type: Number, 'default': 1}, + beard: {type: Number, 'default': 0}, + mustache: {type: Number, 'default': 0}, + flower: {type: Number, 'default': 1} + }, + chair: {type: String, 'default': 'none'}, + hideHeader: {type:Boolean, 'default':false}, + skin: {type:String, 'default':'915533'}, + shirt: {type: String, 'default': 'blue'}, + timezoneOffset: {type: Number, 'default': 0}, + timezoneOffsetAtLastCron: Number, + sound: {type:String, 'default':'off', enum: ['off', 'danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme']}, + language: String, + automaticAllocation: Boolean, + allocationMode: {type:String, enum: ['flat','classbased','taskbased'], 'default': 'flat'}, + autoEquip: {type: Boolean, 'default': true}, + costume: Boolean, + dateFormat: {type: String, enum:['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'], 'default': 'MM/dd/yyyy'}, + sleep: {type: Boolean, 'default': false}, + stickyHeader: {type: Boolean, 'default': true}, + disableClasses: {type: Boolean, 'default': false}, + newTaskEdit: {type: Boolean, 'default': false}, + dailyDueDefaultView: {type: Boolean, 'default': false}, + tagsCollapsed: {type: Boolean, 'default': false}, + advancedCollapsed: {type: Boolean, 'default': false}, + toolbarCollapsed: {type:Boolean, 'default':false}, + reverseChatOrder: {type:Boolean, 'default':false}, + background: String, + displayInviteToPartyWhenPartyIs1: { type:Boolean, 'default':true}, + webhooks: {type: Schema.Types.Mixed, 'default': {}}, + // For this fields make sure to use strict comparison when searching for falsey values (=== false) + // As users who didn't login after these were introduced may have them undefined/null + emailNotifications: { + unsubscribeFromAll: {type: Boolean, 'default': false}, + newPM: {type: Boolean, 'default': true}, + kickedGroup: {type: Boolean, 'default': true}, + wonChallenge: {type: Boolean, 'default': true}, + giftedGems: {type: Boolean, 'default': true}, + giftedSubscription: {type: Boolean, 'default': true}, + invitedParty: {type: Boolean, 'default': true}, + invitedGuild: {type: Boolean, 'default': true}, + questStarted: {type: Boolean, 'default': true}, + invitedQuest: {type: Boolean, 'default': true}, + //remindersToLogin: {type: Boolean, 'default': true}, + // Those importantAnnouncements are in fact the recapture emails + importantAnnouncements: {type: Boolean, 'default': true}, + weeklyRecaps: {type: Boolean, 'default': true} + }, + suppressModals: { + levelUp: {type: Boolean, 'default': false}, + hatchPet: {type: Boolean, 'default': false}, + raisePet: {type: Boolean, 'default': false}, + streak: {type: Boolean, 'default': false} + }, + improvementCategories: { + type: Array, + validate: (categories) => { + const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity']; + let isValidCategory = categories.every(category => validCategories.indexOf(category) !== -1); + return isValidCategory; + }} + }, + profile: { + blurb: String, + imageUrl: String, + name: String + }, + stats: { + hp: {type: Number, 'default': shared.maxHealth}, + mp: {type: Number, 'default': 10}, + exp: {type: Number, 'default': 0}, + gp: {type: Number, 'default': 0}, + lvl: {type: Number, 'default': 1}, + + // Class System + 'class': {type: String, enum: ['warrior','rogue','wizard','healer'], 'default': 'warrior'}, + points: {type: Number, 'default': 0}, + str: {type: Number, 'default': 0}, + con: {type: Number, 'default': 0}, + int: {type: Number, 'default': 0}, + per: {type: Number, 'default': 0}, + buffs: { + str: {type: Number, 'default': 0}, + int: {type: Number, 'default': 0}, + per: {type: Number, 'default': 0}, + con: {type: Number, 'default': 0}, + stealth: {type: Number, 'default': 0}, + streaks: {type: Boolean, 'default': false}, + snowball: {type: Boolean, 'default': false}, + spookDust: {type: Boolean, 'default': false}, + shinySeed: {type: Boolean, 'default': false}, + seafoam: {type: Boolean, 'default': false} + }, + training: { + int: {type: Number, 'default': 0}, + per: {type: Number, 'default': 0}, + str: {type: Number, 'default': 0}, + con: {type: Number, 'default': 0} + } + }, + + tags: {type: [{ + _id: false, + id: { type: String, 'default': shared.uuid }, + name: String, + challenge: String + }]}, + + challenges: [{type: 'String', ref:'Challenge'}], + + inbox: { + newMessages: {type:Number, 'default':0}, + blocks: {type:Array, 'default':[]}, + messages: {type:Schema.Types.Mixed, 'default':{}}, //reflist + optOut: {type:Boolean, 'default':false} + }, + + habits: {type:[TaskSchemas.HabitSchema]}, + dailys: {type:[TaskSchemas.DailySchema]}, + todos: {type:[TaskSchemas.TodoSchema]}, + rewards: {type:[TaskSchemas.RewardSchema]}, + + extra: Schema.Types.Mixed, + + pushDevices: {type: [{ + regId: {type: String}, + type: {type: String} + }],'default': []} + +}, { + strict: true, + minimize: false // So empty objects are returned +}); + +UserSchema.methods.deleteTask = function(tid) { + this.ops.deleteTask({params:{id:tid}},function(){}); // TODO remove this whole method, since it just proxies, and change all references to this method +} + +UserSchema.methods.toJSON = function() { + var doc = this.toObject(); + doc.id = doc._id; + + // FIXME? Is this a reference to `doc.filters` or just disabled code? Remove? + doc.filters = {}; + doc._tmp = this._tmp; // be sure to send down drop notifs + + return doc; +}; + +//UserSchema.virtual('tasks').get(function () { +// var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards); +// var tasks = _.object(_.pluck(tasks,'id'), tasks); +// return tasks; +//}); + +UserSchema.post('init', function(doc){ + shared.wrap(doc); +}) + +UserSchema.pre('save', function(next) { + + // Populate new users with default content + if (this.isNew){ + _populateDefaultsForNewUser(this); + } + + //this.markModified('tasks'); + if (_.isNaN(this.preferences.dayStart) || this.preferences.dayStart < 0 || this.preferences.dayStart > 23) { + this.preferences.dayStart = 0; + } + + if (!this.profile.name) { + var fb = this.auth.facebook; + this.profile.name = + (this.auth.local && this.auth.local.username) || + (fb && (fb.displayName || fb.name || fb.username || (fb.first_name && fb.first_name + ' ' + fb.last_name))) || + 'Anonymous'; + } + + // Determines if Beast Master should be awarded + var beastMasterProgress = shared.count.beastMasterProgress(this.items.pets); + if (beastMasterProgress >= 90 || this.achievements.beastMasterCount > 0) { + this.achievements.beastMaster = true; + } + + // Determines if Mount Master should be awarded + var mountMasterProgress = shared.count.mountMasterProgress(this.items.mounts); + + if (mountMasterProgress >= 90 || this.achievements.mountMasterCount > 0) { + this.achievements.mountMaster = true; + } + + // Determines if Triad Bingo should be awarded + + var dropPetCount = shared.count.dropPetsCurrentlyOwned(this.items.pets); + var qualifiesForTriad = dropPetCount >= 90 && mountMasterProgress >= 90; + + if (qualifiesForTriad || this.achievements.triadBingoCount > 0) { + this.achievements.triadBingo = true; + } + + // Enable weekly recap emails for old users who sign in + if(this.flags.lastWeeklyRecapDiscriminator){ + // Enable weekly recap emails in 24 hours + this.flags.lastWeeklyRecap = moment().subtract(6, 'days').toDate(); + // Unset the field so this is run only once + this.flags.lastWeeklyRecapDiscriminator = undefined; + } + + // EXAMPLE CODE for allowing all existing and new players to be + // automatically granted an item during a certain time period: + // if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01')) + // this.items.pets['JackOLantern-Base'] = 5; + + //our own version incrementer + if (_.isNaN(this._v) || !_.isNumber(this._v)) this._v = 0; + this._v++; + + next(); +}); + +UserSchema.methods.unlink = function(options, cb) { + var cid = options.cid, keep = options.keep, tid = options.tid; + if (!cid) { + return cb("Could not remove challenge tasks. Please delete them manually."); + } + var self = this; + switch (keep) { + case 'keep': + self.tasks[tid].challenge = {}; + break; + case 'remove': + self.deleteTask(tid); + break; + case 'keep-all': + _.each(self.tasks, function(t){ + if (t.challenge && t.challenge.id == cid) { + t.challenge = {}; + } + }); + break; + case 'remove-all': + _.each(self.tasks, function(t){ + if (t.challenge && t.challenge.id == cid) { + self.deleteTask(t.id); + } + }) + break; + } + self.markModified('habits'); + self.markModified('dailys'); + self.markModified('todos'); + self.markModified('rewards'); + self.save(cb); +} + +function _populateDefaultsForNewUser(user) { + var taskTypes; + + if (user.registeredThrough === "habitica-web" || user.registeredThrough === "habitica-android") { + taskTypes = ['habits', 'dailys', 'todos', 'rewards', 'tags']; + + var tutorialCommonSections = [ + 'habits', + 'dailies', + 'todos', + 'rewards', + 'party', + 'pets', + 'gems', + 'skills', + 'classes', + 'tavern', + 'equipment', + 'items', + 'inviteParty', + ]; + + _.each(tutorialCommonSections, function(section) { + user.flags.tutorial.common[section] = true; + }); + } else { + taskTypes = ['todos', 'tags'] + + user.flags.showTour = false; + + var tourSections = [ + 'showTour', + 'intro', + 'classes', + 'stats', + 'tavern', + 'party', + 'guilds', + 'challenges', + 'market', + 'pets', + 'mounts', + 'hall', + 'equipment', + ]; + + _.each(tourSections, function(section) { + user.flags.tour[section] = -2; + }); + } + + _populateDefaultTasks(user, taskTypes); +} + +function _populateDefaultTasks (user, taskTypes) { + _.each(taskTypes, function(taskType){ + user[taskType] = _.map(shared.content.userDefaults[taskType], function(task){ + var newTask = _.cloneDeep(task); + + // Render task's text and notes in user's language + if(taskType === 'tags'){ + // tasks automatically get id=helpers.uuid() from TaskSchema id.default, but tags are Schema.Types.Mixed - so we need to manually invoke here + newTask.id = shared.uuid(); + newTask.name = newTask.name(user.preferences.language); + }else{ + newTask.text = newTask.text(user.preferences.language); + if(newTask.notes) { + newTask.notes = newTask.notes(user.preferences.language); + } + + if(newTask.checklist){ + newTask.checklist = _.map(newTask.checklist, function(checklistItem){ + checklistItem.text = checklistItem.text(user.preferences.language); + return checklistItem; + }); + } + } + + return newTask; + }); + }); +} + +module.exports.schema = UserSchema; +module.exports.model = mongoose.model("User", UserSchema); +// Initially export an empty object so external requires will get +// the right object by reference when it's defined later +// Otherwise it would remain undefined if requested before the query executes +module.exports.mods = []; + +mongoose.model("User") + .find({'contributor.admin':true}) + .sort('-contributor.level -backer.npc profile.name') + .select('profile contributor backer') + .exec(function(err,mods){ + // Using push to maintain the reference to mods + module.exports.mods.push.apply(module.exports.mods, mods); +}); diff --git a/website/src/routes/api-v1.js b/website/src/routes/api-v1.js new file mode 100644 index 0000000000..b3408f523d --- /dev/null +++ b/website/src/routes/api-v1.js @@ -0,0 +1,173 @@ +var express = require('express'); +var router = express.Router(); +var _ = require('lodash'); +var async = require('async'); +var icalendar = require('icalendar'); +var api = require('./../controllers/api-v2/user'); +var auth = require('./../controllers/api-v2/auth'); +var logging = require('./../libs/logging'); +var i18n = require('./../libs/i18n'); +var forceRefresh = require('../middlewares/forceRefresh').middleware; + +/* ---------- Deprecated API ------------*/ + +var initDeprecated = function(req, res, next) { + req.headers['x-api-user'] = req.params.uid; + req.headers['x-api-key'] = req.body.apiToken; + return next(); +}; + +router.post('/v1/users/:uid/tasks/:taskId/:direction', initDeprecated, auth.auth, i18n.getUserLanguage, api.score); + +// FIXME add this back in +router.get('/v1/users/:uid/calendar.ics', i18n.getUserLanguage, function(req, res, next) { + return next() //disable for now + + var apiToken, model, query, uid; + uid = req.params.uid; + apiToken = req.query.apiToken; + model = req.getModel(); + query = model.query('users').withIdAndToken(uid, apiToken); + return query.fetch(function(err, result) { + var formattedIcal, ical, tasks, tasksWithDates; + if (err) { + return res.send(500, err); + } + tasks = result.get('tasks'); + /* tasks = result[0].tasks*/ + + tasksWithDates = _.filter(tasks, function(task) { + return !!task.date; + }); + if (_.isEmpty(tasksWithDates)) { + return res.send(500, "No events found"); + } + ical = new icalendar.iCalendar(); + ical.addProperty('NAME', 'HabitRPG'); + _.each(tasksWithDates, function(task) { + var d, event; + event = new icalendar.VEvent(task.id); + event.setSummary(task.text); + d = new Date(task.date); + d.date_only = true; + event.setDate(d); + ical.addComponent(event); + return true; + }); + res.type('text/calendar'); + formattedIcal = ical.toString().replace(/DTSTART\:/g, 'DTSTART;VALUE=DATE:'); + return res.send(200, formattedIcal); + }); +}); + +/* + ------------------------------------------------------------------------ + Batch Update + This is super-deprecated, and will be removed once apiv2 is running against mobile for a while + ------------------------------------------------------------------------ + */ +var batchUpdate = function(req, res, next) { + var user = res.locals.user; + var oldSend = res.send; + var oldJson = res.json; + var performAction = function(action, cb) { + + // req.body=action.data; delete action.data; _.defaults(req.params, action) + // Would require changing action.dir on mobile app + req.params.id = action.data && action.data.id; + req.params.direction = action.dir; + req.params.type = action.type; + req.body = action.data; + res.send = res.json = function(code, data) { + if (_.isNumber(code) && code >= 400) { + logging.error({ + code: code, + data: data + }); + } + //FIXME send error messages down + return cb(); + }; + switch (action.op) { + case "score": + api.score(req, res); + break; + case "addTask": + api.addTask(req, res); + break; + case "delTask": + api.deleteTask(req, res); + break; + case "revive": + api.revive(req, res); + break; + default: + cb(); + break; + } + }; + + // Setup the array of functions we're going to call in parallel with async + var actions = _.transform(req.body || [], function(result, action) { + if (!_.isEmpty(action)) { + result.push(function(cb) { + performAction(action, cb); + }); + } + }); + + // call all the operations, then return the user object to the requester + async.series(actions, function(err) { + res.json = oldJson; + res.send = oldSend; + if (err) return res.json(500, {err: err}); + var response = user.toJSON(); + response.wasModified = res.locals.wasModified; + if (response._tmp && response._tmp.drop){ + res.json(200, {_tmp: {drop: response._tmp.drop}, _v: response._v}); + }else if(response.wasModified){ + res.json(200, response); + }else{ + res.json(200, {_v: response._v}); + } + }); +}; + +/* + ------------------------------------------------------------------------ + API v1 Routes + ------------------------------------------------------------------------ + */ + + +var cron = api.cron; + +router.get('/status', i18n.getUserLanguage, function(req, res) { + return res.json({ + status: 'up' + }); +}); + +// Scoring +router.post('/user/task/:id/:direction', auth.auth, i18n.getUserLanguage, cron, api.score); +router.post('/user/tasks/:id/:direction', auth.auth, i18n.getUserLanguage, cron, api.score); + +// Tasks +router.get('/user/tasks', auth.auth, i18n.getUserLanguage, cron, api.getTasks); +router.get('/user/task/:id', auth.auth, i18n.getUserLanguage, cron, api.getTask); +router.delete('/user/task/:id', auth.auth, i18n.getUserLanguage, cron, api.deleteTask); +router.post('/user/task', auth.auth, i18n.getUserLanguage, cron, api.addTask); + +// User +router.get('/user', auth.auth, i18n.getUserLanguage, cron, api.getUser); +router.post('/user/revive', auth.auth, i18n.getUserLanguage, cron, api.revive); +router.post('/user/batch-update', forceRefresh, auth.auth, i18n.getUserLanguage, cron, batchUpdate); + +function deprecated(req, res) { + res.json(404, {err:'API v1 is no longer supported, please use API v2 instead (https://github.com/HabitRPG/habitrpg/blob/develop/API.md)'}); +} +router.get('*', i18n.getUserLanguage, deprecated); +router.post('*', i18n.getUserLanguage, deprecated); +router.put('*', i18n.getUserLanguage, deprecated); + +module.exports = router; diff --git a/website/src/routes/api-v2/auth.js b/website/src/routes/api-v2/auth.js new file mode 100644 index 0000000000..62e506d097 --- /dev/null +++ b/website/src/routes/api-v2/auth.js @@ -0,0 +1,21 @@ +var auth = require('../../controllers/api-v2/auth'); +var express = require('express'); +var i18n = require('../../libs/i18n'); +var router = express.Router(); + +/* auth.auth*/ +auth.setupPassport(router); //FIXME make this consistent with the others +router.post('/api/v2/register', i18n.getUserLanguage, auth.registerUser); +router.post('/api/v2/user/auth/local', i18n.getUserLanguage, auth.loginLocal); +router.post('/api/v2/user/auth/social', i18n.getUserLanguage, auth.loginSocial); +router.delete('/api/v2/user/auth/social', i18n.getUserLanguage, auth.auth, auth.deleteSocial); +router.post('/api/v2/user/reset-password', i18n.getUserLanguage, auth.resetPassword); +router.post('/api/v2/user/change-password', i18n.getUserLanguage, auth.auth, auth.changePassword); +router.post('/api/v2/user/change-username', i18n.getUserLanguage, auth.auth, auth.changeUsername); +router.post('/api/v2/user/change-email', i18n.getUserLanguage, auth.auth, auth.changeEmail); +router.post('/api/v2/user/auth/firebase', i18n.getUserLanguage, auth.auth, auth.getFirebaseToken); + +router.post('/api/v1/register', i18n.getUserLanguage, auth.registerUser); +router.post('/api/v1/user/auth/local', i18n.getUserLanguage, auth.loginLocal); +router.post('/api/v1/user/auth/social', i18n.getUserLanguage, auth.loginSocial); +module.exports = router; diff --git a/website/src/routes/api-v2/coupon.js b/website/src/routes/api-v2/coupon.js new file mode 100644 index 0000000000..9057758d3a --- /dev/null +++ b/website/src/routes/api-v2/coupon.js @@ -0,0 +1,12 @@ +var nconf = require('nconf'); +var express = require('express'); +var router = express.Router(); +var auth = require('../../controllers/api-v2/auth'); +var coupon = require('../../controllers/api-v2/coupon'); +var i18n = require('../../libs/i18n'); + +router.get('/api/v2/coupons', auth.authWithUrl, i18n.getUserLanguage, coupon.ensureAdmin, coupon.getCoupons); +router.post('/api/v2/coupons/generate/:event', auth.auth, i18n.getUserLanguage, coupon.ensureAdmin, coupon.generateCoupons); +router.post('/api/v2/user/coupon/:code', auth.auth, i18n.getUserLanguage, coupon.enterCode); + +module.exports = router; diff --git a/website/server/routes/api-v2/swagger.js b/website/src/routes/api-v2/swagger.js similarity index 91% rename from website/server/routes/api-v2/swagger.js rename to website/src/routes/api-v2/swagger.js index c60d8364b6..a1500ac39a 100644 --- a/website/server/routes/api-v2/swagger.js +++ b/website/src/routes/api-v2/swagger.js @@ -13,15 +13,12 @@ var members = require("../../controllers/api-v2/members"); var auth = require("../../controllers/api-v2/auth"); var hall = require("../../controllers/api-v2/hall"); var challenges = require("../../controllers/api-v2/challenges"); -var dataexport = require("../../controllers/api-v2/dataexport"); +var dataexport = require("../../controllers/dataexport"); var nconf = require("nconf"); var cron = user.cron; var _ = require('lodash'); var content = require('../../../../common').content; -var i18n = require('../../libs/api-v2/i18n'); -import { - getUserLanguage -} from '../../middlewares/api-v3/language'; +var i18n = require('../../libs/i18n'); var forceRefresh = require('../../middlewares/forceRefresh').middleware; module.exports = function(swagger, v2) { @@ -63,7 +60,7 @@ module.exports = function(swagger, v2) { description: "Export user history", method: 'GET' }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: dataexport.history }, "/user/tasks/{id}/{direction}": { @@ -137,7 +134,7 @@ module.exports = function(swagger, v2) { description: 'Unlink a task from its challenge', parameters: [path("id", "Task ID", "string"), query('keep', "When unlinking a challenge task, how to handle the orphans?", 'string', ['keep', 'keep-all', 'remove', 'remove-all'])] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.unlink }, "/user/inventory/buy": { @@ -238,7 +235,7 @@ module.exports = function(swagger, v2) { method: 'DELETE', description: "Delete a user object entirely, USE WITH CAUTION!" }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: user["delete"] }, "/user/revive": { @@ -314,7 +311,7 @@ module.exports = function(swagger, v2) { description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',parameters:{},body:{},query:{}}", parameters: [body('', 'The array of batch-operations to perform', 'object')] }, - middleware: [forceRefresh, auth.auth, getUserLanguage, cron, user.sessionPartyInvite], + middleware: [forceRefresh, auth.auth, i18n.getUserLanguage, cron, user.sessionPartyInvite], action: user.batchUpdate }, "/user/tags/{id}:GET": { @@ -409,7 +406,7 @@ module.exports = function(swagger, v2) { description: "Get a list of groups", parameters: [query('type', "Comma-separated types of groups to return, eg 'party,guilds,public,tavern'", 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: groups.list }, "/groups:POST": { @@ -419,7 +416,7 @@ module.exports = function(swagger, v2) { description: 'Create a group', parameters: [body('', 'Group object (see GroupSchema)', 'object')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: groups.create }, "/groups/{gid}:GET": { @@ -428,7 +425,7 @@ module.exports = function(swagger, v2) { description: "Get a group. The party the user currently is in can be accessed with the gid 'party'.", parameters: [path('gid', 'Group ID', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: groups.get }, "/groups/{gid}:POST": { @@ -438,7 +435,7 @@ module.exports = function(swagger, v2) { description: "Edit a group", parameters: [body('', 'Group object (see GroupSchema)', 'object')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.update }, "/groups/{gid}/join": { @@ -447,7 +444,7 @@ module.exports = function(swagger, v2) { description: 'Join a group', parameters: [path('gid', 'Id of the group to join', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.join }, "/groups/{gid}/leave": { @@ -456,7 +453,7 @@ module.exports = function(swagger, v2) { description: 'Leave a group', parameters: [path('gid', 'ID of the group to leave', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.leave }, "/groups/{gid}/invite": { @@ -465,7 +462,7 @@ module.exports = function(swagger, v2) { description: "Invite a user to a group", parameters: [path('gid', 'Group id', 'string'), body('', 'a payload of invites either under body.uuids or body.emails, only one of them!', 'object')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.invite }, "/groups/{gid}/removeMember": { @@ -474,7 +471,7 @@ module.exports = function(swagger, v2) { description: "Remove / boot a member from a group", parameters: [path('gid', 'Group id', 'string'), query('uuid', 'User id to boot', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.removeMember }, "/groups/{gid}/questAccept": { @@ -483,7 +480,7 @@ module.exports = function(swagger, v2) { description: "Accept a quest invitation", parameters: [path('gid', "Group id", 'string'), query('key', "optional. if provided, trigger new invite, if not, accept existing invite", 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.questAccept }, "/groups/{gid}/questReject": { @@ -492,7 +489,7 @@ module.exports = function(swagger, v2) { description: 'Reject quest invitation', parameters: [path('gid', 'Group id', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.questReject }, "/groups/{gid}/questCancel": { @@ -501,7 +498,7 @@ module.exports = function(swagger, v2) { description: 'Cancel quest before it starts (in invitation stage)', parameters: [path('gid', 'Group to cancel quest in', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.questCancel }, "/groups/{gid}/questAbort": { @@ -510,7 +507,7 @@ module.exports = function(swagger, v2) { description: 'Abort quest after it has started (all progress will be lost)', parameters: [path('gid', 'Group to abort quest in', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.questAbort }, "/groups/{gid}/questLeave": { @@ -519,7 +516,7 @@ module.exports = function(swagger, v2) { description: 'Leave an active quest (Quest leaders cannot leave active quests. They must abort the quest to leave)', parameters: [path('gid', 'Group to leave quest in', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.questLeave }, "/groups/{gid}/chat:GET": { @@ -528,7 +525,7 @@ module.exports = function(swagger, v2) { description: "Get all chat messages", parameters: [path('gid', 'Group to return the chat from ', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.getChat }, "/groups/{gid}/chat:POST": { @@ -538,7 +535,7 @@ module.exports = function(swagger, v2) { description: "Send a chat message", parameters: [query('message', 'Chat message', 'string'), path('gid', 'Group id', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.postChat }, "/groups/{gid}/chat/seen": { @@ -555,7 +552,7 @@ module.exports = function(swagger, v2) { description: 'Delete a chat message in a given group', parameters: [path('gid', 'ID of the group containing the message to be deleted', 'string'), path('messageId', 'ID of message to be deleted', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.deleteChatMessage }, "/groups/{gid}/chat/{mid}/like": { @@ -564,7 +561,7 @@ module.exports = function(swagger, v2) { description: "Like a chat message", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.likeChatMessage }, "/groups/{gid}/chat/{mid}/flag": { @@ -573,7 +570,7 @@ module.exports = function(swagger, v2) { description: "Flag a chat message", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.flagChatMessage }, "/groups/{gid}/chat/{mid}/clearflags": { @@ -582,7 +579,7 @@ module.exports = function(swagger, v2) { description: "Clear flag count from message and unhide it", parameters: [path('gid', 'Group id', 'string'), path('mid', 'Message id', 'string')] }, - middleware: [auth.auth, getUserLanguage, groups.attachGroup], + middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup], action: groups.clearFlagCount }, "/members/{uuid}:GET": { @@ -591,7 +588,7 @@ module.exports = function(swagger, v2) { description: "Get a member.", parameters: [path('uuid', 'Member ID', 'string')] }, - middleware: [getUserLanguage], + middleware: [i18n.getUserLanguage], action: members.getMember }, "/members/{uuid}/message": { @@ -623,14 +620,14 @@ module.exports = function(swagger, v2) { }, "/hall/heroes": { spec: {}, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: hall.getHeroes }, "/hall/heroes/{uid}:GET": { spec: { path: "/hall/heroes/{uid}" }, - middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], + middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], action: hall.getHero }, "/hall/heroes/{uid}:POST": { @@ -638,14 +635,14 @@ module.exports = function(swagger, v2) { method: 'POST', path: "/hall/heroes/{uid}" }, - middleware: [auth.auth, getUserLanguage, hall.ensureAdmin], + middleware: [auth.auth, i18n.getUserLanguage, hall.ensureAdmin], action: hall.updateHero }, "/hall/patrons": { spec: { parameters: [query('page', 'Page number to fetch (this list is long)', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: hall.getPatrons }, "/challenges:GET": { @@ -653,7 +650,7 @@ module.exports = function(swagger, v2) { path: '/challenges', description: "Get a list of challenges" }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.list }, "/challenges:POST": { @@ -663,7 +660,7 @@ module.exports = function(swagger, v2) { description: "Create a challenge", parameters: [body('', 'Challenge object (see ChallengeSchema)', 'object')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.create }, "/challenges/{cid}:GET": { @@ -672,7 +669,7 @@ module.exports = function(swagger, v2) { description: 'Get a challenge', parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.get }, "/challenges/{cid}/csv": { @@ -689,7 +686,7 @@ module.exports = function(swagger, v2) { description: "Update a challenge", parameters: [path('cid', 'Challenge id', 'string'), body('', 'Challenge object (see ChallengeSchema)', 'object')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.update }, "/challenges/{cid}:DELETE": { @@ -699,7 +696,7 @@ module.exports = function(swagger, v2) { description: "Delete a challenge", parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges["delete"] }, "/challenges/{cid}/close": { @@ -708,7 +705,7 @@ module.exports = function(swagger, v2) { description: 'Close a challenge', parameters: [path('cid', 'Challenge id', 'string'), query('uid', 'User ID of the winner', 'string', true)] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.selectWinner }, "/challenges/{cid}/join": { @@ -717,7 +714,7 @@ module.exports = function(swagger, v2) { description: "Join a challenge", parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.join }, "/challenges/{cid}/leave": { @@ -726,7 +723,7 @@ module.exports = function(swagger, v2) { description: 'Leave a challenge', parameters: [path('cid', 'Challenge id', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.leave }, "/challenges/{cid}/member/{uid}": { @@ -734,7 +731,7 @@ module.exports = function(swagger, v2) { description: "Get a member's progress in a particular challenge", parameters: [path('cid', 'Challenge id', 'string'), path('uid', 'User id', 'string')] }, - middleware: [auth.auth, getUserLanguage], + middleware: [auth.auth, i18n.getUserLanguage], action: challenges.getMember } }; @@ -768,7 +765,7 @@ module.exports = function(swagger, v2) { method: 'GET' }); if (route.middleware == null) { - route.middleware = path.indexOf('/user') === 0 ? [auth.auth, getUserLanguage, cron] : [i18n.getUserLanguage]; + route.middleware = path.indexOf('/user') === 0 ? [auth.auth, i18n.getUserLanguage, cron] : [i18n.getUserLanguage]; } swagger["add" + route.spec.method](route); return true; diff --git a/website/src/routes/api-v2/unsubscription.js b/website/src/routes/api-v2/unsubscription.js new file mode 100644 index 0000000000..882fb10392 --- /dev/null +++ b/website/src/routes/api-v2/unsubscription.js @@ -0,0 +1,8 @@ +var express = require('express'); +var router = express.Router(); +var i18n = require('../../libs/i18n'); +var unsubscription = require('../../controllers/api-v2/unsubscription'); + +router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe); + +module.exports = router; diff --git a/website/src/routes/dataexport.js b/website/src/routes/dataexport.js new file mode 100644 index 0000000000..9dd8511e3d --- /dev/null +++ b/website/src/routes/dataexport.js @@ -0,0 +1,16 @@ +var express = require('express'); +var router = express.Router(); +var dataexport = require('../controllers/dataexport'); +var auth = require('../controllers/api-v2/auth'); +var nconf = require('nconf'); +var i18n = require('../libs/i18n'); +var locals = require('../middlewares/locals'); + +/* Data export */ +router.get('/history.csv',auth.authWithSession,i18n.getUserLanguage,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes +router.get('/userdata.xml',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.xml); +router.get('/userdata.json',auth.authWithSession,i18n.getUserLanguage,dataexport.leanuser,dataexport.userdata.json); +router.get('/avatar-:uuid.html', i18n.getUserLanguage, locals, dataexport.avatarPage); +router.get('/avatar-:uuid.png', i18n.getUserLanguage, locals, dataexport.avatarImage); + +module.exports = router; diff --git a/website/server/routes/pages.js b/website/src/routes/pages.js similarity index 94% rename from website/server/routes/pages.js rename to website/src/routes/pages.js index 17818285ed..04766d9551 100644 --- a/website/server/routes/pages.js +++ b/website/src/routes/pages.js @@ -2,8 +2,8 @@ var nconf = require('nconf'); var express = require('express'); var router = express.Router(); var _ = require('lodash'); -var locals = require('../middlewares/api-v2/locals'); -var i18n = require('../libs/api-v2/i18n'); +var locals = require('../middlewares/locals'); +var i18n = require('../libs/i18n'); var md = require('markdown-it')({ html: true, }); diff --git a/website/src/routes/payments.js b/website/src/routes/payments.js new file mode 100644 index 0000000000..c385c835c7 --- /dev/null +++ b/website/src/routes/payments.js @@ -0,0 +1,31 @@ +var nconf = require('nconf'); +var express = require('express'); +var router = express.Router(); +var auth = require('../controllers/api-v2/auth'); +var payments = require('../controllers/payments'); +var i18n = require('../libs/i18n'); + +router.get('/paypal/checkout', auth.authWithUrl, i18n.getUserLanguage, payments.paypalCheckout); +router.get('/paypal/checkout/success', i18n.getUserLanguage, payments.paypalCheckoutSuccess); +router.get('/paypal/subscribe', auth.authWithUrl, i18n.getUserLanguage, payments.paypalSubscribe); +router.get('/paypal/subscribe/success', i18n.getUserLanguage, payments.paypalSubscribeSuccess); +router.get('/paypal/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.paypalSubscribeCancel); +router.post('/paypal/ipn', i18n.getUserLanguage, payments.paypalIPN); // misc ipn handling + +router.post('/stripe/checkout', auth.auth, i18n.getUserLanguage, payments.stripeCheckout); +router.post('/stripe/subscribe/edit', auth.auth, i18n.getUserLanguage, payments.stripeSubscribeEdit); +//router.get('/stripe/subscribe', auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribe); // checkout route is used (above) with ?plan= instead +router.get('/stripe/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.stripeSubscribeCancel); + +router.post('/amazon/verifyAccessToken', auth.auth, i18n.getUserLanguage, payments.amazonVerifyAccessToken); +router.post('/amazon/createOrderReferenceId', auth.auth, i18n.getUserLanguage, payments.amazonCreateOrderReferenceId); +router.post('/amazon/checkout', auth.auth, i18n.getUserLanguage, payments.amazonCheckout); +router.post('/amazon/subscribe', auth.auth, i18n.getUserLanguage, payments.amazonSubscribe); +router.get('/amazon/subscribe/cancel', auth.authWithUrl, i18n.getUserLanguage, payments.amazonSubscribeCancel); + +router.post('/iap/android/verify', auth.authWithUrl, /*i18n.getUserLanguage, */payments.iapAndroidVerify); +router.post('/iap/ios/verify', auth.auth, /*i18n.getUserLanguage, */ payments.iapIosVerify); + +router.get('/api/v2/coupons/valid-discount/:code', /*auth.authWithUrl, i18n.getUserLanguage, */ payments.validCoupon); + +module.exports = router; diff --git a/website/src/server.js b/website/src/server.js new file mode 100644 index 0000000000..7271d85579 --- /dev/null +++ b/website/src/server.js @@ -0,0 +1,177 @@ +if (process.env.NODE_ENV !== 'production') { + require('babel-register'); +} +// Only do the minimal amount of work before forking just in case of a dyno restart +var cluster = require("cluster"); +var _ = require('lodash'); +var nconf = require('nconf'); +var utils = require('./libs/utils'); +utils.setupConfig(); +var logging = require('./libs/logging'); +var isProd = nconf.get('NODE_ENV') === 'production'; +var isDev = nconf.get('NODE_ENV') === 'development'; +var DISABLE_LOGGING = nconf.get('DISABLE_REQUEST_LOGGING'); +var cores = +nconf.get("WEB_CONCURRENCY") || 0; +var moment = require('moment'); + +if (cores!==0 && cluster.isMaster && (isDev || isProd)) { + // Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production) + _.times(cores, function () { + cluster.fork(); + }); + + cluster.on('disconnect', function(worker, code, signal) { + var w = cluster.fork(); // replace the dead worker + logging.info('[%s] [master:%s] worker:%s disconnect! new worker:%s fork', new Date(), process.pid, worker.process.pid, w.process.pid); + }); + +} else { + var express = require("express"); + var bodyParser = require('body-parser'); + var session = require('cookie-session'); + var logger = require('morgan'); + var compression = require('compression'); + var favicon = require('serve-favicon'); + + var BODY_PARSER_LIMIT = '1mb'; + + var http = require("http"); + var path = require("path"); + var swagger = require("swagger-node-express"); + var autoinc = require('mongoose-id-autoinc'); + var shared = require('../../common'); + + // Setup translations + var i18n = require('./libs/i18n'); + + var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; + var app = express(); + var server = http.createServer(); + + // ------------ MongoDB Configuration ------------ + var mongoose = require('mongoose'); + var mongooseOptions = !isProd ? {} : { + replset: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, + server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } } + }; + var db = mongoose.connect(nconf.get('NODE_DB_URI'), mongooseOptions, function(err) { + if (err) throw err; + logging.info('Connected with Mongoose'); + }); + autoinc.init(db); + + require('./libs/firebase'); + + // load schemas & models + require('./models/challenge'); + require('./models/group'); + require('./models/user'); + + // ------------ Passport Configuration ------------ + var passport = require('passport') + var util = require('util') + var FacebookStrategy = require('passport-facebook').Strategy; + // Passport session setup. + // To support persistent login sessions, Passport needs to be able to + // serialize users into and deserialize users out of the session. Typically, + // this will be as simple as storing the user ID when serializing, and finding + // the user by ID when deserializing. However, since this example does not + // have a database of user records, the complete Facebook profile is serialized + // and deserialized. + passport.serializeUser(function(user, done) { + done(null, user); + }); + + passport.deserializeUser(function(obj, done) { + done(null, obj); + }); + + // FIXME + // This auth strategy is no longer used. It's just kept around for auth.js#loginFacebook() (passport._strategies.facebook.userProfile) + // The proper fix would be to move to a general OAuth module simply to verify accessTokens + passport.use(new FacebookStrategy({ + clientID: nconf.get("FACEBOOK_KEY"), + clientSecret: nconf.get("FACEBOOK_SECRET"), + //callbackURL: nconf.get("BASE_URL") + "/auth/facebook/callback" + }, + function(accessToken, refreshToken, profile, done) { + done(null, profile); + } + )); + + // ------------ Server Configuration ------------ + var publicDir = path.join(__dirname, "/../public"); + + app.set("port", nconf.get('PORT')); + + // Setup two different Express apps, one that matches everything except '/api/v3' + // and the other for /api/v3 routes, so we can keep the old an new api versions completely separate + // not sharing a single middleware if we don't want to + var oldApp = express(); // api v1 and v2, and not scoped routes + var newApp = express(); // api v3 + + // Matches all request except the ones going to /api/v3/** + app.all(/^(?!\/api\/v3).+/i, oldApp); + // Matches all requests going to /api/v3 + app.all('/api/v3', newApp); + + require('./middlewares/apiThrottle')(oldApp); + oldApp.use(require('./middlewares/domain')(server,mongoose)); + if (!isProd && !DISABLE_LOGGING) oldApp.use(logger("dev")); + oldApp.use(compression()); + oldApp.set("views", __dirname + "/../views"); + oldApp.set("view engine", "jade"); + oldApp.use(favicon(publicDir + '/favicon.ico')); + oldApp.use(require('./middlewares/cors')); + + var redirects = require('./middlewares/redirects'); + oldApp.use(redirects.forceHabitica); + oldApp.use(redirects.forceSSL); + oldApp.use(bodyParser.urlencoded({ + extended: true, + limit: BODY_PARSER_LIMIT, + })); + oldApp.use(bodyParser.json({ + limit: BODY_PARSER_LIMIT, + })); + oldApp.use(require('method-override')()); + oldApp.use(session({ + name: 'connect:sess', // Used to keep backward compatibility with Express 3 cookies + secret: nconf.get('SESSION_SECRET'), + httpOnly: false, + maxAge: TWO_WEEKS + })); + + // Initialize Passport! Also use passport.session() middleware, to support + // persistent login sessions (recommended). + oldApp.use(passport.initialize()); + oldApp.use(passport.session()); + + var maxAge = isProd ? 31536000000 : 0; + oldApp.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge })); + oldApp.use('/common/dist', express['static'](publicDir + "/../../common/dist", { maxAge: maxAge })); + oldApp.use('/common/audio', express['static'](publicDir + "/../../common/audio", { maxAge: maxAge })); + oldApp.use('/common/script/public', express['static'](publicDir + "/../../common/script/public", { maxAge: maxAge })); + oldApp.use('/common/img', express['static'](publicDir + "/../../common/img", { maxAge: maxAge })); + oldApp.use(express['static'](publicDir)); + + // Custom Directives + oldApp.use('/', require('./routes/pages')); + oldApp.use('/', require('./routes/payments')); + oldApp.use('/', require('./routes/api-v2/auth')); + oldApp.use('/', require('./routes/api-v2/coupon')); + oldApp.use('/', require('./routes/api-v2/unsubscription')); + var v2 = express(); + oldApp.use('/api/v2', v2); + oldApp.use('/api/', require('./routes/api-v1')); + oldApp.use('/export', require('./routes/dataexport')); + require('./routes/api-v2/swagger')(swagger, v2); + oldApp.use(require('./middlewares/errorHandler')); + + server.on('request', app); + server.listen(app.get("port"), function() { + return logging.info("Express server listening on port " + app.get("port")); + }); + + module.exports = server; +} diff --git a/website/views/avatar-static.jade b/website/views/avatar-static.jade index 58e11ae31a..9a3532f726 100644 --- a/website/views/avatar-static.jade +++ b/website/views/avatar-static.jade @@ -9,7 +9,7 @@ html(ng-app="habitrpg") meta(name='apple-mobile-web-app-capable', content='yes') // .slice(0).push('user') is to clone the array, - // to be surethat `user` is never available to other requests' env + // to be surethat `user` is never avalaible to other requests' env // TODO does it need only `user` in clientVars, not the others? - clientVars = env.clientVars.slice(0); diff --git a/website/views/main/filters.jade b/website/views/main/filters.jade index 40e11817a8..b72d424364 100644 --- a/website/views/main/filters.jade +++ b/website/views/main/filters.jade @@ -31,7 +31,7 @@ li.filters-edit(ng-class='{active: user.filters[tag.id]}', ng-repeat='tag in user.tags', bindonce='user.tags') form.hrpg-input-group input(type='text', ng-model='tag.name', ui-keyup="{13: 'saveOrEdit()'}") - button(type='button', ng-click='User.deleteTag({params:{id:tag.id}})') + button(type='button', ng-click='user.ops.deleteTag({params:{id:tag.id}})') span.glyphicon.glyphicon-trash ul(ng-if='!_editing', hrpg-sort-tags) li.filters-tags(ng-class='{active: user.filters[tag.id], challenge: tag.challenge}', ng-repeat='tag in user.tags', bindonce='user.tags') diff --git a/website/views/options/inventory/drops.jade b/website/views/options/inventory/drops.jade index 322a5083f5..134e9ade50 100644 --- a/website/views/options/inventory/drops.jade +++ b/website/views/options/inventory/drops.jade @@ -57,7 +57,7 @@ ng-click='castStart(Content.special.#{k})') .badge.badge-info.stack-count {{user.items.special.#{k}}} +specialItem('snowball') - +specialItem('spookySparkles') + +specialItem('spookDust') +specialItem('shinySeed') +specialItem('seafoam') @@ -65,7 +65,7 @@ button.customize-option(class='inventory_present inventory_present_{{moment().format("MM")}}', popover=env.t('subscriberItemText'), popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', - ng-click="User.openMysteryItem({})") + ng-click="user.ops.openMysteryItem({})") .badge.badge-info.stack-count {{user.purchased.plan.mysteryItems.length}} div(ng-if='user.purchased.plan.consecutive.trinkets') @@ -199,7 +199,7 @@ button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'), popover-trigger='mouseenter', popover-placement='top', popover-append-to-body='true', - ng-click='User.purchase({params:{type:"gems",key:"gem"}})') + ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})') span.Pet_Currency_Gem.inline-gems .badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}} p diff --git a/website/views/options/inventory/time-travelers.jade b/website/views/options/inventory/time-travelers.jade index be0c3ea7dd..66fc530bd0 100644 --- a/website/views/options/inventory/time-travelers.jade +++ b/website/views/options/inventory/time-travelers.jade @@ -35,4 +35,4 @@ popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', - ng-click='User.buyMysterySet({params:{key:set.key}})') + ng-click='user.ops.buyMysterySet({params:{key:set.key}})') diff --git a/website/views/options/profile.jade b/website/views/options/profile.jade index 036d0cf529..d9af640ae8 100644 --- a/website/views/options/profile.jade +++ b/website/views/options/profile.jade @@ -219,7 +219,7 @@ mixin profileStats input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})') span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation') div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)') - a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) + a.btn.btn-primary.btn-xs(ng-click='user.ops.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) span.glyphicon.glyphicon-download |  =env.t('distributePoints') @@ -227,7 +227,7 @@ mixin profileStats div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') - button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') + button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') hr(ng-if='user.preferences.disableClasses') include ../shared/profiles/achievements diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade index 34c4dff0ca..c2608126b3 100644 --- a/website/views/options/settings.jade +++ b/website/views/options/settings.jade @@ -32,7 +32,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') .form-horizontal h5=env.t('language') - select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in availableLanguages', ng-change='changeLanguage()') + select.form-control(ng-model='language.code', ng-options='lang.code as lang.name for lang in avalaibleLanguages', ng-change='changeLanguage()') small !=env.t('americanEnglishGovern') br @@ -92,7 +92,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey') button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal') - button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') + button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') hr @@ -133,11 +133,11 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') .panel-body div(ng-if='user.auth.facebook.id') button.btn.btn-primary(disabled='disabled', ng-if='!user.auth.local.username')=env.t('registeredWithFb') - button.btn.btn-danger(ng-click='http("delete", "/api/v3/user/auth/social/facebook", null, "detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook') + button.btn.btn-danger(ng-click='http("delete","/api/v2/user/auth/social",null,"detachedFacebook")', ng-if='user.auth.local.username')=env.t('detachFacebook') hr div(ng-if='!user.auth.local.username') p=env.t('addLocalAuth') - form(ng-submit='http("post", "/api/v3/user/auth/local/register", localAuth, "addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate) + form(ng-submit='http("post","/api/v2/register",localAuth,"addedLocalAuth")', ng-init='localAuth={}', name='localAuth', novalidate) //-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted')=env.t('fillAll') .form-group input.form-control(type='text', placeholder=env.t('username'), ng-model='localAuth.username', required) @@ -175,7 +175,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') h5=env.t('changeEmail') form(ng-submit='changeUser("email", emailUpdates)', ng-show='user.auth.local', name='changeEmail', novalidate) .form-group - input.form-control(type='text', placeholder=env.t('newEmail'), ng-model='emailUpdates.newEmail', required) + input.form-control(type='text', placeholder=env.t('newEmail'), ng-model='emailUpdates.email', required) .form-group input.form-control(type='password', placeholder=env.t('password'), ng-model='emailUpdates.password', required) input.btn.btn-default(type='submit', ng-disabled='changeEmail.$invalid', value=env.t('submit')) @@ -218,7 +218,7 @@ script(type='text/ng-template', id='partials/options.settings.promo.html') input.form-control(type='number',ng-model='_codes.count',placeholder="Number of codes to generate (eg, 250)") .form-group button.btn.btn-primary(type='submit')=env.t('generate') - a.btn.btn-default(href='/api/v3/coupons?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}')=env.t('getCodes') + a.btn.btn-default(href='/api/v2/coupons?_id={{user._id}}&apiToken={{user.apiToken}}')=env.t('getCodes') script(type='text/ng-template', id='partials/options.settings.api.html') .container-fluid @@ -229,9 +229,9 @@ script(type='text/ng-template', id='partials/options.settings.api.html') h6=env.t('userId') pre.prettyprint {{user.id}} h6=env.t('APIToken') - pre.prettyprint {{User.settings.auth.apiToken}} + pre.prettyprint {{user.apiToken}} h6=env.t('qrCode') - img.img-rendering-auto(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{User.settings.auth.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode') + img.img-rendering-auto(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{user.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode') br h3=env.t('thirdPartyApps') ul @@ -389,7 +389,7 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') input.form-control(type='text', ng-model='_subscription.coupon', placeholder= env.t('couponPlaceholder')) .form-group button.pull-right.btn.btn-small(type='button',ng-click='applyCoupon(_subscription.coupon)')= env.t("apply") - + div(ng-if='user.purchased.plan.customerId') .btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard') .btn.btn-sm.btn-danger(ng-if='!user.purchased.plan.dateTerminated', ng-click='Payments.cancelSubscription()')=env.t('cancelSub') @@ -400,8 +400,9 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template') .col-xs-4 a.purchase.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key')= env.t('card') .col-xs-4 - a.purchase(href='/paypal/subscribe?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') + a.purchase(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt=env.t('paypal')) .col-xs-4 a.purchase(ng-click="Payments.amazonPayments.init({type: 'subscription', subscription:_subscription.key, coupon:_subscription.coupon})") img(src='https://payments.amazon.com/gp/cba/button',alt=env.t('amazonPayments')) + diff --git a/website/views/options/social/challenge-box.jade b/website/views/options/social/challenge-box.jade index 177e049e4d..9a7df490d8 100644 --- a/website/views/options/social/challenge-box.jade +++ b/website/views/options/social/challenge-box.jade @@ -12,7 +12,7 @@ td a(ui-sref='options.social.challenges.detail({cid:challenge._id, groupIdFilter: group._id})') markdown(text='challenge.name') - div(ng-if='!group.challenges || group.challenges.length == 0') + div(ng-if='group.challenges.length == 0') p |  =env.t('noChallenges') diff --git a/website/views/options/social/challenges.jade b/website/views/options/social/challenges.jade index ab84189f6f..3fd8dba61e 100644 --- a/website/views/options/social/challenges.jade +++ b/website/views/options/social/challenges.jade @@ -55,7 +55,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.detail.ht // Member List div(bindonce='challenge', ng-if='challenge.members.length > 0') - a.btn.btn-primary.btn-sm.pull-right(ng-href='/api/v3/challenges/{{challenge._id}}/export/csv') + a.btn.btn-primary.btn-sm.pull-right(ng-href='/api/v2/challenges/{{challenge._id}}/csv') =env.t('exportChallengeCSV') h3=env.t('hows') menu @@ -145,7 +145,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') .row .form-group.col-md-6.col-sm-12 - input.form-control(type='text', + input.form-control(type='text', minlength="3", ng-model='newChallenge.shortName', placeholder=env.t('challengeTag'), required ng-disabled='insufficientGemsForTavernChallenge()') |  @@ -160,12 +160,12 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') .Pet_Currency_Gem1x input.form-control(type='number', placeholder=env.t('prize'), ng-disabled='insufficientGemsForTavernChallenge()' - min="{{newChallenge.group==TAVERN_ID ? 1 : 0}}", + min="{{newChallenge.group=='habitrpg' ? 1 : 0}}", max="{{maxPrize}}", ng-model='newChallenge.prize') - a.hint(popover="{{newChallenge.group==TAVERN_ID ? env.t('prizePopTavern') : env.t('prizePop')}}", + a.hint(popover="{{newChallenge.group=='habitrpg' ? env.t('prizePopTavern') : env.t('prizePop')}}", popover-trigger='mouseenter', popover-placement='right') =env.t('moreInfo') - div(ng-show='newChallenge.group==TAVERN_ID') + div(ng-show='newChallenge.group=="habitrpg"') !=env.t('publicChallenges') .form-group(ng-if='user.contributor.admin') @@ -178,7 +178,7 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') // Challenges list .panel-group - .panel.panel-default(ng-repeat='challenge in challenges | filter:filterChallenges track by challenge._id ') + .panel.panel-default(ng-repeat='challenge in challenges|filter:filterChallenges track by challenge._id ') .panel-heading ul.pull-right.challenge-accordion-header-specs li.bg-transparent(ng-if='challenge.official') @@ -199,10 +199,10 @@ script(type='text/ng-template', id='partials/options.social.challenges.html') p!=env.t('prizeValue', {gemcount: "{{challenge.prize}}", gemicon: ""}) li.bg-transparent // leave / join - a.btn.btn-sm.btn-danger(ng-show='isUserMemberOf(challenge)', ng-click='clickLeave(challenge, $event)') + a.btn.btn-sm.btn-danger(ng-show='challenge._isMember', ng-click='clickLeave(challenge, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-sm.btn-success(ng-hide='isUserMemberOf(challenge)', ng-click='join(challenge)') + a.btn.btn-sm.btn-success(ng-hide='challenge._isMember', ng-click='join(challenge)') span.glyphicon.glyphicon-ok =env.t('join') a.accordion-toggle(id="{{challenge._id}}" ng-click='toggle(challenge._id)') diff --git a/website/views/options/social/chat-box.jade b/website/views/options/social/chat-box.jade index 3792070706..74fc071dfe 100644 --- a/website/views/options/social/chat-box.jade +++ b/website/views/options/social/chat-box.jade @@ -8,7 +8,7 @@ div.chat-form.guidelines-not-accepted(ng-if='!user.flags.communityGuidelinesAcce form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postChat(group,message.content)') div(ng-controller='AutocompleteCtrl') - textarea.form-control(rows=4, ui-keydown='{"meta-enter":"postChat(group,message.content)"}', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete placeholder="{{group._id == TAVERN_ID ? env.t('tavernCommunityGuidelinesPlaceholder') : ''}}", ng-disabled='_sending == true') + textarea.form-control(rows=4, ui-keydown='{"meta-enter":"postChat(group,message.content)"}', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete placeholder="{{group._id == 'habitrpg' ? env.t('tavernCommunityGuidelinesPlaceholder') : ''}}", ng-disabled='_sending == true') span.user-list ul.list-at-user(ng-show="query") li(ng-repeat='msg in response | filter:filterUser | limitTo: 5', ng-click='performCompletion(msg)') diff --git a/website/views/options/social/chat-message.jade b/website/views/options/social/chat-message.jade index 07447b483c..3231726e82 100644 --- a/website/views/options/social/chat-message.jade +++ b/website/views/options/social/chat-message.jade @@ -29,7 +29,7 @@ mixin chatMessages(inbox) a(ng-click="quickReply(message.uuid)") span.glyphicon.glyphicon-share-alt(tooltip=env.t('pm-reply')) span(ng-if='#{inbox ? "true" : ":: user.contributor.admin || message.uuid == user.id"}')     - a(ng-click='#{inbox? "User.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}') + a(ng-click='#{inbox? "user.ops.deletePM({params:{id:message.$key}})" : "deleteChatMessage(group, message)"}') span.glyphicon.glyphicon-trash(tooltip=env.t('delete')) span(ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')     a(ng-click="flagChatMessage(group._id, message)") diff --git a/website/views/options/social/group.jade b/website/views/options/social/group.jade index 6232990915..3630ca3d2e 100644 --- a/website/views/options/social/group.jade +++ b/website/views/options/social/group.jade @@ -18,10 +18,10 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter h3.panel-title span {{group.name}} span.group-leave-join(ng-if='group') - a.btn.btn-sm.btn-danger.pull-right(ng-if="isMemberOfGroup(User.user._id, group)", ng-hide='group._editing', ng-click='clickLeave(group, $event)') + a.btn.btn-sm.btn-danger.pull-right(ng-if=":: isMemberOfGroup(User.user._id, group)", ng-hide='group._editing', ng-click='clickLeave(group, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-success.pull-right(ng-if='!isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join') + a.btn.btn-success.pull-right(ng-if=':: !isMemberOfGroup(User.user._id, group)', ng-click='join(group)')=env.t('join') span(ng-if='group.leader._id == user.id') button.btn.btn-sm.btn-primary.pull-right(ng-click='cancelEdit(group)', ng-hide='!group._editing')=env.t('cancel') button.btn.btn-sm.btn-primary.pull-right(ng-click='saveEdit(group)', ng-show='group._editing')=env.t('save') @@ -60,7 +60,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter .text-center(ng-if='group.type === "party"') .row.row-margin: .col-sm-6.col-sm-offset-3 button.btn.btn-success.btn-block( - ng-if='!group.quest.key', + ng-if='!party.quest.key', ng-click='clickStartQuest();' )=env.t('startAQuest') diff --git a/website/views/options/social/hall.jade b/website/views/options/social/hall.jade index 381e5ec8fd..7e17980117 100644 --- a/website/views/options/social/hall.jade +++ b/website/views/options/social/hall.jade @@ -50,7 +50,7 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html') h4 Update Item .form-group.well input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',ng-model='hero.itemPath') - small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. See all paths here. When in doubt, ask Tyler. + small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. See all paths here. When in doubt, ask Tyler. br input.form-control(type='text',placeholder='Value (eg, 5)',ng-model='hero.itemVal') small.muted Enter the item value. E.g., 5 or false or head_warrior_3 (respectively from above examples). diff --git a/website/views/options/social/index.jade b/website/views/options/social/index.jade index 754d3d08f6..f4c3a7043a 100644 --- a/website/views/options/social/index.jade +++ b/website/views/options/social/index.jade @@ -45,10 +45,10 @@ script(type='text/ng-template', id='partials/options.social.guilds.public.html') li='{{::group.memberCount}} ' + env.t('members') // join / leave li.bg-transparent - a.btn.btn-sm.btn-danger(ng-if="isMemberOfGroup(User.user._id, group)", ng-click='clickLeave(group, $event)') + a.btn.btn-sm.btn-danger(ng-if="::group._isMember", ng-click='clickLeave(group, $event)') span.glyphicon.glyphicon-ban-circle =env.t('leave') - a.btn.btn-sm.btn-success(ng-if="!isMemberOfGroup(User.user._id, group)", ng-click='join(group)') + a.btn.btn-sm.btn-success(ng-if="::!group._isMember", ng-click='join(group)') span.glyphicon.glyphicon-ok =env.t('join') h4: a(href='/#/options/groups/guilds/{{::group._id}}') {{::group.name}} diff --git a/website/views/options/social/party/leave-party-and-join-another.jade b/website/views/options/social/party/leave-party-and-join-another.jade index b6ff6c7862..438feb3786 100644 --- a/website/views/options/social/party/leave-party-and-join-another.jade +++ b/website/views/options/social/party/leave-party-and-join-another.jade @@ -1,5 +1,5 @@ - var newParty = 'User.user.invitations.party' -.containter-fluid(ng-if='#{newParty}.id && group._id') +.containter-fluid(ng-if='#{newParty}.id && party._id') .row.text-center .col-sm-6.col-sm-offset-3.alert.alert-warning p {{::env.t('invitedToNewParty', { partyName: #{newParty}.name })}} diff --git a/website/views/options/social/party/party-invitation.jade b/website/views/options/social/party/party-invitation.jade index 0bfd72b6d8..f4972e6e41 100644 --- a/website/views/options/social/party/party-invitation.jade +++ b/website/views/options/social/party/party-invitation.jade @@ -5,4 +5,5 @@ data-type='party', ng-click='join(user.invitations.party)' )=env.t('accept') - a.btn.btn-danger(ng-click='reject(user.invitations.party)')=env.t('reject') + a.btn.btn-danger(ng-click='reject()')=env.t('reject') + diff --git a/website/views/options/social/quests/questActive.jade b/website/views/options/social/quests/questActive.jade index b457f8d0e3..969861e8c1 100644 --- a/website/views/options/social/quests/questActive.jade +++ b/website/views/options/social/quests/questActive.jade @@ -23,7 +23,7 @@ div(ng-if='group.quest.active===true') include ./ianQuestInfo unless tavern - button.btn.btn-sm.btn-warning(ng-if='::canEditQuest()', + button.btn.btn-sm.btn-warning(ng-if='::canEditQuest(party)', ng-click='questAbort()')=env.t('abort') button.btn.btn-sm.btn-warning(ng-if='!(group.quest.leader && group.quest.leader === user._id) && isMemberOfRunningQuest(user._id,group)', ng-click='questLeave()')=env.t('leaveQuest') diff --git a/website/views/options/social/quests/questNotActive.jade b/website/views/options/social/quests/questNotActive.jade index c20ae5a226..3472011e29 100644 --- a/website/views/options/social/quests/questNotActive.jade +++ b/website/views/options/social/quests/questNotActive.jade @@ -26,6 +26,6 @@ div(ng-if='group.quest.active===false') button.btn.btn-sm.btn-success(ng-click='questAccept()')=env.t('accept') button.btn.btn-sm.btn-danger(ng-click='questReject()')=env.t('reject') - span(ng-if='::canEditQuest()') - button.btn.btn-sm.btn-warning(ng-click='questForceStart()')=env.t('begin') + span(ng-if='::canEditQuest(party)') + button.btn.btn-sm.btn-warning(ng-click='party.$startQuest({"force":true})')=env.t('begin') button.btn.btn-sm.btn-danger(ng-click='questCancel()')=env.t('cancel') diff --git a/website/views/options/social/tavern.jade b/website/views/options/social/tavern.jade index d0f16c187d..ed7e4ef6c3 100644 --- a/website/views/options/social/tavern.jade +++ b/website/views/options/social/tavern.jade @@ -16,7 +16,7 @@ .popover-content span(ng-if='!env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innText',{name: user.profile.name}) : env.t('danielText')}} span(ng-if='env.worldDmg.tavern') {{user.preferences.sleep ? env.t('innTextBroken',{name: user.profile.name}) : env.t('danielTextBroken')}} - button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') | {{user.preferences.sleep ? env.t('innCheckOut') : env.t('innCheckIn')}} span(ng-if='!user.preferences.sleep && !env.worldDmg.tavern')=env.t('danielText2') span(ng-if='!user.preferences.sleep && env.worldDmg.tavern')=env.t('danielText2Broken') diff --git a/website/views/shared/avatar/appearance.jade b/website/views/shared/avatar/appearance.jade index 14f69414a0..7b28273996 100644 --- a/website/views/shared/avatar/appearance.jade +++ b/website/views/shared/avatar/appearance.jade @@ -11,13 +11,13 @@ mixin avatar(opts) span(ng-if='profile.items.currentMount', class='Mount_Body_{{profile.items.currentMount}}') // Buffs that cause visual changes to avatar: Snowman, Ghost, Flower, etc - - var visualBuffs = { snowball: 'snowman', spookySparkles: 'ghost', shinySeed: 'avatar_floral_{{profile.stats.class}}', seafoam: 'seafoam_star' } + - var visualBuffs = { snowball: 'snowman', spookDust: 'spookman', shinySeed: 'avatar_floral_{{profile.stats.class}}', seafoam: 'seafoam_star' } each klass, item in visualBuffs span(ng-if='profile.stats.buffs.#{item}', class='#{klass}') // Show avatar only if not currently affected by visual buff - var buffs = '!profile.stats.buffs' - span(ng-if='#{buffs}.snowball && #{buffs}.spookySparkles && #{buffs}.shinySeed && #{buffs}.seafoam') + span(ng-if='#{buffs}.snowball && #{buffs}.spookDust && #{buffs}.shinySeed && #{buffs}.seafoam') +generatedAvatar // Mount Head diff --git a/website/views/shared/footer.jade b/website/views/shared/footer.jade index 0164b16783..0f5bc5c041 100644 --- a/website/views/shared/footer.jade +++ b/website/views/shared/footer.jade @@ -47,6 +47,8 @@ footer.footer(ng-controller='FooterCtrl') a(target='_blank', href='/static/clear-browser-data')=env.t('communityBug') li a(target='_blank', href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')=env.t('communityFeature') + li + a(target='_blank', href='https://habitica.com/static/api')=env.t('API') li a(href='http://habitica.wikia.com/wiki/App_and_Extension_Integrations', target='_blank')=env.t('communityExtensions') li @@ -57,14 +59,6 @@ footer.footer(ng-controller='FooterCtrl') a(target='_blank', href='https://www.facebook.com/Habitica')=env.t('communityFacebook') li a(target='_blank', href='http://www.reddit.com/r/habitrpg/')=env.t('communityReddit') - h4=env.t('footerDevs') - ul.list-unstyled - li - a(target='_blank', href='http://devs.habitica.com')=env.t('devBlog') + ' - The Forge' - li - a(target='_blank', href='/apidoc')=env.t('APIv3') - li - a(target='_blank', href='/static/api-v2')=env.t('APIv2') .col-sm-3 if (env.NODE_ENV === 'production' && !env.IS_MOBILE) h4=env.t('footerSocial') @@ -85,7 +79,7 @@ footer.footer(ng-controller='FooterCtrl') tr td iframe(src='/bower_components/github-buttons/github-btn.html?user=habitrpg&repo=habitrpg&type=watch&count=true', allowtransparency='true', frameborder='0', scrolling='0', width='85px', height='20px') - if (env.NODE_ENV==='development' || env.NODE_ENV==='test') && !env.isStaticPage + else if (env.NODE_ENV==='development' || env.NODE_ENV==='test') && !env.isStaticPage h4 Debug .btn-group-vertical a.btn.btn-default(ng-click='setHealthLow()') Health = 1 @@ -97,10 +91,7 @@ footer.footer(ng-controller='FooterCtrl') a.btn.btn-default(ng-click='addMana()') +MP a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP +MP a.btn.btn-default(ng-click='addOneLevel()') +1 Level - a.btn.btn-default(ng-click='addQuestProgress()' tooltip="+1000 to boss quests. 300 items to collection quests") Quest Progress Up - // TODO Re-enable after v3 prod testing - // a.btn.btn-default(ng-click='makeAdmin()') Make Admin - a.btn.btn-default(ng-click='openModifyInventoryModal()') Modify Inventory + a.btn.btn-default(ng-click='addBossQuestProgressUp()') +1000 Boss Quest Progress Up div(ng-init='deferredScripts()') diff --git a/website/views/shared/header/header.jade b/website/views/shared/header/header.jade index 6535bb217f..5c0b847849 100644 --- a/website/views/shared/header/header.jade +++ b/website/views/shared/header/header.jade @@ -25,13 +25,13 @@ .meter-label(tooltip='Mana', ng-if='user.flags.classSelected && !user.preferences.disableClasses') span.glyphicon.glyphicon-fire .meter.mana(ng-if='user.flags.classSelected && !user.preferences.disableClasses', tooltip='{{Math.round(user.stats.mp * 100) / 100}}') - .bar(ng-style='{"width": (user.stats.mp / user.fns.statsComputed().maxMP * 100) + "%"}') + .bar(ng-style='{"width": (user.stats.mp / user._statsComputed.maxMP * 100) + "%"}') span.meter-text.value span - | {{Math.floor(user.stats.mp)}} / {{user.fns.statsComputed().maxMP}} + | {{Math.floor(user.stats.mp)}} / {{user._statsComputed.maxMP}} // party .party(ng-controller='PartyCtrl') - button.party-invite.btn.btn-primary(ng-click="inviteOrStartParty(party)", + button.party-invite.btn.btn-primary(ng-click="inviteOrStartParty(group)", ng-if="(!party.members || party.memberCount === 1) && user.preferences.displayInviteToPartyWhenPartyIs1", popover="{{!party.members ? env.t('startAParty') : env.t('addToParty')}}", popover-placement="left", popover-trigger="mouseenter") span=env.t("battleWithFriends") diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade index d009ebdd4d..78f60557ca 100644 --- a/website/views/shared/header/menu.jade +++ b/website/views/shared/header/menu.jade @@ -205,7 +205,7 @@ nav.toolbar(ng-controller='MenuCtrl') span.glyphicon.glyphicon-plus-sign span=env.t('haveUnallocated', {points: '{{user.stats.points}}'}) li(ng-repeat='(k,v) in user.newMessages', ng-if='v.value') - a(ng-click='(k === party._id || k === user.party._id) ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k}); ', data-close-menu) + a(ng-click='k === party._id ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k}); ', data-close-menu) span.glyphicon.glyphicon-comment span {{v.name}} a(ng-click='clearMessages(k)', popover=env.t('clear'),popover-placement='right',popover-trigger='mouseenter',popover-append-to-body='true') diff --git a/website/views/shared/modals/buy-gems.jade b/website/views/shared/modals/buy-gems.jade index 4ebebf57ea..d071450cab 100644 --- a/website/views/shared/modals/buy-gems.jade +++ b/website/views/shared/modals/buy-gems.jade @@ -10,7 +10,7 @@ mixin buyGemsDropdown() p small.muted=env.t('paymentMethods') a.purchase.btn.btn-primary(ng-click='Payments.showStripe({})')=env.t('card') - a.purchase(href='/paypal/checkout?_id={{user._id}}&apiToken={{User.settings.auth.apiToken}}') + a.purchase(href='/paypal/checkout?_id={{user._id}}&apiToken={{user.apiToken}}') img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt='Pay now with Paypal') a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})") img(src='https://payments.amazon.com/gp/cba/button',alt='Pay now with Amazon Payments') @@ -34,7 +34,7 @@ script(id='modals/buyGems.html', type='text/ng-template') .container-fluid .row .col-md-3 - button.customize-option(ng-click='User.purchase({params:{type:"gems",key:"gem"}})') + button.customize-option(ng-click='user.ops.purchase({params:{type:"gems",key:"gem"}})') span.Pet_Currency_Gem.inline-gems .badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}} p diff --git a/website/views/shared/modals/classes.jade b/website/views/shared/modals/classes.jade index 754d6f31f0..6484626318 100644 --- a/website/views/shared/modals/classes.jade +++ b/website/views/shared/modals/classes.jade @@ -68,6 +68,6 @@ script(type='text/ng-template', id='modals/chooseClass.html') .modal-footer span(popover-placement='left', popover-trigger='mouseenter', popover=env.t('optOutOfClassesText')) - button.btn.btn-danger(ng-click='User.disableClasses({}); $close()')=env.t('optOutOfClasses') + button.btn.btn-danger(ng-click='user.ops.disableClasses({}); $close()')=env.t('optOutOfClasses') button.btn.btn-primary(ng-disabled='!selectedClass' ng-click='changeClass(selectedClass); $close()')=env.t('select') .pull-left!=env.t('chooseClassLearn') diff --git a/website/views/shared/modals/death.jade b/website/views/shared/modals/death.jade index 60a92c3802..cfdbced16e 100644 --- a/website/views/shared/modals/death.jade +++ b/website/views/shared/modals/death.jade @@ -21,5 +21,5 @@ script(type='text/ng-template', id='modals/death.html') h4(style='margin-top:1.5em')=env.t('dontDespair') p(style='margin-top:1.5em')=env.t('deathPenaltyDetails') .modal-footer - a.btn.btn-danger.btn-lg.flex-column(ng-click='User.revive(); $close()')=env.t('refillHealthTryAgain') + a.btn.btn-danger.btn-lg.flex-column(ng-click='user.ops.revive({}); $close()')=env.t('refillHealthTryAgain') h4.text-center!=env.t('dyingOftenTips') diff --git a/website/views/shared/modals/index.jade b/website/views/shared/modals/index.jade index c492e09ee1..a57ee62189 100644 --- a/website/views/shared/modals/index.jade +++ b/website/views/shared/modals/index.jade @@ -19,7 +19,6 @@ include ./level-up.jade include ./hatch-pet.jade include ./raise-pet.jade include ./won-challenge.jade -include ./modify-inventory.jade //- Settings script(type='text/ng-template', id='modals/change-day-start.html') diff --git a/website/views/shared/modals/limited.jade b/website/views/shared/modals/limited.jade index 0f68a2dcad..158fd5a647 100644 --- a/website/views/shared/modals/limited.jade +++ b/website/views/shared/modals/limited.jade @@ -9,4 +9,4 @@ script(id='modals/cards.html', type='text/ng-template') markdown(text='::cardMessage') .modal-footer small.pull-left {{::env.t(cardType + 'CardExplanation')}} - button.btn.btn-default(ng-click='User.readCard({params: {cardType: cardType}}); $close()')=env.t('ok') + button.btn.btn-default(ng-click='user.ops.readCard({params: {cardType: cardType}}); $close()')=env.t('ok') diff --git a/website/views/shared/modals/members.jade b/website/views/shared/modals/members.jade index c4741156f8..b40a56eea5 100644 --- a/website/views/shared/modals/members.jade +++ b/website/views/shared/modals/members.jade @@ -33,9 +33,9 @@ script(type='text/ng-template', id='modals/member.html') include ../profiles/achievements .modal-footer .btn-group.pull-left(ng-if='::user') - button.btn.btn-md.btn-default(ng-if='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') + button.btn.btn-md.btn-default(ng-if='user.inbox.blocks | contains:profile._id', tooltip=env.t('unblock'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') span.glyphicon.glyphicon-plus - button.btn.btn-md.btn-default(ng-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip=env.t('block'), ng-click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') + button.btn.btn-md.btn-default(ng-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip=env.t('block'), ng-click="user.ops.blockUser({params:{uuid:profile._id}})", tooltip-placement='right') span.glyphicon.glyphicon-ban-circle button.btn.btn-md.btn-default(tooltip=env.t('sendPM'), ng-click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right') span.glyphicon.glyphicon-envelope @@ -94,9 +94,9 @@ script(type='text/ng-template', id='modals/send-gift.html') .modal-footer - var fromBal = "gift.type=='gems' && gift.gems.fromBalance" - button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id)')=env.t("send") + button.btn.btn-primary(ng-show=fromBal, ng-click='sendGift(profile._id, gift)')=env.t("send") a.btn.btn-primary(ng-hide=fromBal, ng-click='Payments.showStripe({gift:gift, uuid:profile._id})')=env.t('card') - a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::User.settings.auth.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal + a.btn.btn-warning(ng-hide=fromBal, href='/paypal/checkout?_id={{::user._id}}&apiToken={{::user.apiToken}}&gift={{Payments.encodeGift(profile._id, gift)}}') PayPal .btn.btn-success(ng-hide=fromBal, ng-click="Payments.amazonPayments.init({type: 'single', gift: gift, giftedTo: profile._id})") Amazon Payments button.btn.btn-default(ng-click='$close()')=env.t('cancel') diff --git a/website/views/shared/modals/modify-inventory.jade b/website/views/shared/modals/modify-inventory.jade deleted file mode 100644 index fd7ae0c61f..0000000000 --- a/website/views/shared/modals/modify-inventory.jade +++ /dev/null @@ -1,252 +0,0 @@ -script(type='text/ng-template', id='modals/modify-inventory.html') - .modal-header - h4 Modify Inventory for {{::user.profile.name}} - .modal-body - .container-fluid - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.gear", ng-click="showInv.gear = true") Show Gear - button.btn.btn-default.pull-right(ng-if="showInv.gear", ng-click="showInv.gear = false") Hide Gear - h4 Gear - div(ng-if="showInv.gear") - button.btn.btn-default(ng-click="setAllItems('gear', true)") Own All - button.btn.btn-default(ng-click="setAllItems('gear', false)") Previously Own All - button.btn.btn-default(ng-click="setAllItems('gear', undefined)") Never Own All - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.gear.flat" ng-init="inv.gear[item.key] = user.items.gear.owned[item.key]") - .pull-left(class="shop_{{::item.key}}" style="margin-right: 10px") - | {{::item.text()}} - - .clearfix - label.radio-inline - input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="true") - | Owned - label.radio-inline - input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="false") - | Previously Owned - label.radio-inline - input(type="radio" name="gear-{{::item.key}}" ng-model="inv.gear[item.key]" ng-value="undefined") - | Never Owned - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.special", ng-click="showInv.special = true") Show Special Items - button.btn.btn-default.pull-right(ng-if="showInv.special", ng-click="showInv.special = false") Hide Special Items - h4 Special Items - div(ng-if="showInv.special") - button.btn.btn-default(ng-click="setAllItems('special', 999)") Set All to 999 - button.btn.btn-default(ng-click="setAllItems('special', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('special', undefined)") Set All to undefined - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.special" ng-init="inv.special[item.key] = user.items.special[item.key]" ng-if="item.value === 15") - .form-inline.clearfix - .pull-left(class="inventory_special_{{::item.key}}" style="margin-right: 10px") - p {{::item.text()}} - input.form-control(type="number" ng-model="inv.special[item.key]") - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.pets", ng-click="showInv.pets = true") Show Pets - button.btn.btn-default.pull-right(ng-if="showInv.pets", ng-click="showInv.pets = false") Hide Pets - h4 Pets - div(ng-if="showInv.pets") - button.btn.btn-default(ng-click="setAllItems('pets', 45)") Set All to 45 - button.btn.btn-default(ng-click="setAllItems('pets', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('pets', -1)") Set All to -1 - button.btn.btn-default(ng-click="setAllItems('pets', undefined)") Set All to undefined - - hr - - h5 Drop Pets - ul.list-group - li.list-group-item(ng-repeat="(pet, value) in Content.pets" ng-init="inv.pets[pet] = user.items.pets[pet]") - .form-inline.clearfix - .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") - p {{::pet}} - input.form-control(type="number" ng-model="inv.pets[pet]") - - h5 Quest Pets - ul.list-group - li.list-group-item(ng-repeat="(pet, value) in Content.questPets" ng-init="inv.pets[pet] = user.items.pets[pet]") - .form-inline.clearfix - .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") - p {{::pet}} - input.form-control(type="number" ng-model="inv.pets[pet]") - - h5 Special Pets - ul.list-group - li.list-group-item(ng-repeat="(pet, value) in Content.specialPets" ng-init="inv.pets[pet] = user.items.pets[pet]") - .form-inline.clearfix - .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") - p {{::pet}} - input.form-control(type="number" ng-model="inv.pets[pet]") - - h5 Premium Pets - ul.list-group - li.list-group-item(ng-repeat="(pet, value) in Content.premiumPets" ng-init="inv.pets[pet] = user.items.pets[pet]") - .form-inline.clearfix - .pull-left(class="Pet-{{::pet}}" style="margin-right: 10px") - p {{::pet}} - input.form-control(type="number" ng-model="inv.pets[pet]") - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.mounts", ng-click="showInv.mounts = true") Show Mounts - button.btn.btn-default.pull-right(ng-if="showInv.mounts", ng-click="showInv.mounts = false") Hide Mounts - h4 Mounts - div(ng-if="showInv.mounts") - button.btn.btn-default(ng-click="setAllItems('mounts', true)") Set all to Owned - button.btn.btn-default(ng-click="setAllItems('mounts', undefined)") Set all to Not Owned - - hr - - h5 Drop Mounts - ul.list-group - li.list-group-item(ng-repeat="(mount, value) in Content.mounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") - .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") - | {{::mount}} - .clearfix - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") - | Owned - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") - | Not Owned - - h5 Quest Mounts - ul.list-group - li.list-group-item(ng-repeat="(mount, value) in Content.questMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") - .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") - | {{::mount}} - .clearfix - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") - | Owned - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") - | Not Owned - - h5 Special Mounts - ul.list-group - li.list-group-item(ng-repeat="(mount, value) in Content.specialMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") - .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") - | {{::mount}} - .clearfix - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") - | Owned - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") - | Not Owned - - h5 Premium Mounts - ul.list-group - li.list-group-item(ng-repeat="(mount, value) in Content.premiumMounts" ng-init="inv.mounts[mount] = user.items.mounts[mount]") - .pull-left(class="Mount_Icon_{{::mount}}" style="margin-right: 10px") - | {{::mount}} - .clearfix - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="true") - | Owned - label.radio-inline - input(type="radio" name="mounts-{{::mount}}" ng-model="inv.mounts[mount]" ng-value="undefined") - | Not Owned - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.hatchingPotions", ng-click="showInv.hatchingPotions = true") Show Hatching Potions - button.btn.btn-default.pull-right(ng-if="showInv.hatchingPotions", ng-click="showInv.hatchingPotions = false") Hide Hatching Potions - h4 Hatching Potions - div(ng-if="showInv.hatchingPotions") - button.btn.btn-default(ng-click="setAllItems('hatchingPotions', 999)") Set All to 999 - button.btn.btn-default(ng-click="setAllItems('hatchingPotions', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('hatchingPotions', undefined)") Set All to undefined - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.hatchingPotions" ng-init="inv.hatchingPotions[item.key] = user.items.hatchingPotions[item.key]") - .form-inline.clearfix - .pull-left(class="Pet_HatchingPotion_{{::item.key}}" style="margin-right: 10px") - p {{::item.text()}} - input.form-control(type="number" ng-model="inv.hatchingPotions[item.key]") - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.eggs", ng-click="showInv.eggs = true") Show Eggs - button.btn.btn-default.pull-right(ng-if="showInv.eggs", ng-click="showInv.eggs = false") Hide Eggs - h4 Eggs - div(ng-if="showInv.eggs") - button.btn.btn-default(ng-click="setAllItems('eggs', 999)") Set All to 999 - button.btn.btn-default(ng-click="setAllItems('eggs', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('eggs', undefined)") Set All to undefined - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.eggs" ng-init="inv.eggs[item.key] = user.items.eggs[item.key]") - .form-inline.clearfix - .pull-left(class="Pet_Egg_{{::item.key}}" style="margin-right: 10px") - p {{::item.text()}} - input.form-control(type="number" ng-model="inv.eggs[item.key]") - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.food", ng-click="showInv.food = true") Show Food - button.btn.btn-default.pull-right(ng-if="showInv.food", ng-click="showInv.food = false") Hide Food - h4 Food - div(ng-if="showInv.food") - button.btn.btn-default(ng-click="setAllItems('food', 999)") Set All to 999 - button.btn.btn-default(ng-click="setAllItems('food', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('food', undefined)") Set All to undefined - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.food" ng-init="inv.food[item.key] = user.items.food[item.key]") - .form-inline.clearfix - .pull-left(class="Pet_Food_{{::item.key}}" style="margin-right: 10px") - p {{::item.text()}} - input.form-control(type="number" ng-model="inv.food[item.key]") - - hr - - .row - .col-xs-12 - button.btn.btn-default.pull-right(ng-if="!showInv.quests", ng-click="showInv.quests = true") Show Quests - button.btn.btn-default.pull-right(ng-if="showInv.quests", ng-click="showInv.quests = false") Hide Quests - h4 Quests - div(ng-if="showInv.quests") - button.btn.btn-default(ng-click="setAllItems('quests', 999)") Set All to 999 - button.btn.btn-default(ng-click="setAllItems('quests', 0)") Set All to 0 - button.btn.btn-default(ng-click="setAllItems('quests', undefined)") Set All to undefined - - hr - - ul.list-group - li.list-group-item(ng-repeat="item in Content.quests" ng-init="inv.quests[item.key] = user.items.quests[item.key]" ng-if="item.category !== 'world'") - .form-inline.clearfix - .pull-left(class="inventory_quest_scroll_{{::item.key}}" style="margin-right: 10px") - p {{::item.text()}} - input.form-control(type="number" ng-model="inv.quests[item.key]") - .modal-footer - button.btn.btn-default(ng-click="$close()")=env.t('close') - button.btn.btn-primary(ng-click="$close();modifyInventory()") Apply Changes diff --git a/website/views/shared/modals/quests.jade b/website/views/shared/modals/quests.jade index 40efa3b5c4..a9dbf6a991 100644 --- a/website/views/shared/modals/quests.jade +++ b/website/views/shared/modals/quests.jade @@ -57,7 +57,7 @@ script(type='text/ng-template', id='modals/buyQuest.html') .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('neverMind') button.btn.btn-primary(ng-if='::selectedQuest.category !== "gold"', ng-click='purchase("quests", quest); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.value}} ' + env.t('gems') - button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='User.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold') + button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='user.ops.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold') script(type='text/ng-template', id='modals/questInvitation.html') .modal-header @@ -104,8 +104,7 @@ script(type='text/ng-template', id='modals/questDrop.html') .quest-icon(class='inventory_quest_scroll_{{::selectedQuest.key}}') h4!=env.t('leveledUpReceivedQuest', {level:'{{user.stats.lvl}}'}) .row(style='margin-top:2em') - button.btn.btn-primary(ng-click='inviteOrStartParty(party); $close()', ng-if='!User.user.party._id')=env.t('startAParty') - button.btn.btn-primary(ng-click='inviteOrStartParty(party); $close()', ng-if='!User.user.party._id && !party.members')=env.t('battleWithFriends') + button.btn.btn-primary(ng-click='inviteOrStartParty(group); $close()', ng-if='!party.members')=env.t('startAParty') button.btn.btn-primary(ng-click='questInit(); $close()', ng-if='party.members')=env.t('inviteParty') button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('questLater') .modal-footer(style='margin-top:0', ng-init='loadWidgets()') diff --git a/website/views/shared/modals/settings.jade b/website/views/shared/modals/settings.jade index 5063173a96..8a263f2df0 100644 --- a/website/views/shared/modals/settings.jade +++ b/website/views/shared/modals/settings.jade @@ -55,11 +55,11 @@ script(type='text/ng-template', id='modals/delete.html') .modal-header h4=env.t('deleteAccount') .modal-body - p!=env.t('deleteText', {deleteWord: 'Your password'}) + p!=env.t('deleteText', {deleteWord: 'DELETE'}) br .row .col-md-6 - input.form-control(type='password', ng-model='_deleteAccount') + input.form-control(type='text', ng-model='_deleteAccount') .modal-footer button.btn.btn-default(ng-click='$close()')=env.t('neverMind') - button.btn.btn-danger(ng-disabled='!_deleteAccount', ng-click='$close(); delete(_deleteAccount)')=env.t('deleteDo') + button.btn.btn-danger(ng-disabled='_deleteAccount != "DELETE"', ng-click='$close(); delete()')=env.t('deleteDo') diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index c59174b701..dfd4cbfcaf 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,44 +1,26 @@ -h2 5/21/2016 - WELCOME BACK, HABITICA! +h2 5/19/2016 - IMPORTANT: UPCOMING MAINTENANCE! hr tr td - h3 Welcome Back, Everyone! - p Hurrah! After many hours of toil, our valiant blacksmiths were able to complete our planned maintenance ahead of schedule. The site should be working normally again! If you notice any issues or have any questions, please feel free to email us at admin@habitica.com and we will be happy to help. - tr - td - h3 Important Mobile App Updates - p We’ve released an iOS update and an Android update that contain the new code. It’s very important to download these updates immediately, or you may encounter significant bugs! - tr - td - .Pet-Wolf-Veteran.pull-right - h3 Veteran Pets - p To thank you for your patience during the maintenance, we have awarded everyone a special Veteran Pet! You can see it under Inventory > Pets, at the bottom of the screen. If it hasn’t appeared yet, never fear: because there are so many Habiticans, it can sometimes take an hour or two for everyone to receive their pet. You will have it soon! Thanks again for bearing with us during the downtime. - tr - td - h3 Daily Safe Mode - p To protect the accounts of Habiticans in different time zones across the world, we enabled Cron Daily Safe Mode during the maintenance, which will prevent you from taking any damage or losing any streaks for the rest of the weekend. Let us know at admin@habitica.com if you have any questions or concerns! + h3 Maintenance to Take Place May 21 + p This Saturday, we will be performing important maintenance on Habitica to build out the groundwork for some exciting upcoming features! We'll be doing everything we can to make this as smooth as possible, but unfortunately, there will be significant downtime for much of the day. + br + p.strong We expect that on Saturday, May 21st, Habitica will be unavailable between 1 PM and 10 PM Pacific Time (8 pm - 5 am UTC). + ul + li Don't worry, you will NOT lose any streaks or take any damage during this weekend, not even from Bosses! This maintenance will not harm your accounts. + li If you will need to see your task list on Saturday, we recommend taking a screenshot of your tasks before the maintenance begins so that you can use them as a reference during downtime. + li At the end of the maintenance, to thank people for their patience, everyone will receive a rare Veteran pet! + li This maintenance should not result in any major visible differences to the site; it's all behind-the-scenes work. However, at the end of it, we will release new updates to the mobile apps, which will be required in order for the apps to work properly with the new changes! Be sure to download those updates on Saturday as soon as they are released. + li For more information, please check out our detailed info page about the maintenance! And if you have any further questions or concerns, feel free to reach out to Leslie (leslie@habitica.com), and she will be happy to help you. + p We understand that it's very frustrating to have Habitica unavailable for such a long part of the day. Rest assured that we'll be doing everything we can to make the maintenance go as quickly as possible, but with over a million Habitican accounts to migrate, this is a hefty task! During the maintenance on Saturday we will be posting regular status reports on our Twitter account, so you can follow us for the most accurate updates. + br + p Thank you for your patience, and for using Habitica! if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews - h2 5/19/2016 - IMPORTANT: UPCOMING MAINTENANCE! - tr - td - h3 Maintenance to Take Place May 21 - p This Saturday, we will be performing important maintenance on Habitica to build out the groundwork for some exciting upcoming features! We'll be doing everything we can to make this as smooth as possible, but unfortunately, there will be significant downtime for much of the day. - br - p.strong We expect that on Saturday, May 21st, Habitica will be unavailable between 1 PM and 10 PM Pacific Time (8 pm - 5 am UTC). - ul - li Don't worry, you will NOT lose any streaks or take any damage during this weekend, not even from Bosses! This maintenance will not harm your accounts. - li If you will need to see your task list on Saturday, we recommend taking a screenshot of your tasks before the maintenance begins so that you can use them as a reference during downtime. - li At the end of the maintenance, to thank people for their patience, everyone will receive a rare Veteran pet! - li This maintenance should not result in any major visible differences to the site; it's all behind-the-scenes work. However, at the end of it, we will release new updates to the mobile apps, which will be required in order for the apps to work properly with the new changes! Be sure to download those updates on Saturday as soon as they are released. - li For more information, please check out our detailed info page about the maintenance! And if you have any further questions or concerns, feel free to reach out to Leslie (leslie@habitica.com), and she will be happy to help you. - p We understand that it's very frustrating to have Habitica unavailable for such a long part of the day. Rest assured that we'll be doing everything we can to make the maintenance go as quickly as possible, but with over a million Habitican accounts to migrate, this is a hefty task! During the maintenance on Saturday we will be posting regular status reports on our Twitter account, so you can follow us for the most accurate updates. - br - p Thank you for your patience, and for using Habitica! h2 5/17/2016 - TREELING PET QUEST AND CHALLENGE SPOTLIGHT! tr td @@ -343,7 +325,7 @@ mixin oldNews tr td .promo_spring_classes_2016.pull-right - h3 Limited Edition Class Outfits + h3 Limited Edition Class Outfits p From now until April 30th, limited edition outfits are available in the Rewards column! Depending on your class, you can be a Springing Bunny, Clever Dog, Grand Malkin, or Brave Mouse. You'd better get productive to earn enough Gold before your time runs out... p.small.muted by PainterProphet and Balduranne tr @@ -897,7 +879,7 @@ mixin oldNews p Exciting news - for the next three weeks, we are offering Habitica T-shirts via Teespring! Show your Habitica pride in purple or black. We are also offering an EU run for cheaper shipping to Europe! br p Whether you're getting them for yourself or as a holiday gift, we hope you enjoy these limited-run T-shirts! As always, thanks for supporting Habitica. - + h2 11/5/2015 - HUGE IOS UPDATE AND ANDROID MAILING LIST tr td @@ -927,7 +909,7 @@ mixin oldNews tr td h3 Android Mailing List - p For those of you anxious for news about the new native Android app, we've created a mailing list so that you can be notified about important updates for the Android app. You can sign up here! + p For those of you anxious for news about the new native Android app, we've created a mailing list so that you can be notified about important updates for the Android app. You can sign up here! br p Our staff has been working very hard on it and testing out a new build each week, so progress is definitely advancing. When the beta is ready we will announce it on social media and on the site, but the mailing list is the easiest way to make sure you don't miss it! Thanks very much for your patience. h2 11/3/2015 - NOVEMBER BACKGROUNDS AND ARMOIRE ITEMS, AND AUTO-EQUIP NEW GEAR @@ -988,14 +970,14 @@ mixin oldNews td .promo_mystery_201510.pull-right h3 Last Chance for Horned Goblin Set - p Reminder: this is the final day to subscribe and receive the Horned Goblin Item Set! If you want the Goblin Horns or the Goblin Tail, now's the time! + p Reminder: this is the final day to subscribe and receive the Horned Goblin Item Set! If you want the Goblin Horns or the Goblin Tail, now's the time! br - p Thanks so much for your supporting the site -- you're helping us keep Habitica alive. + p Thanks so much for your supporting the site -- you're helping us keep Habitica alive. tr td .npc_justin.pull-right h3 Happy Habitoween! - p Burnout is nearly defeated, so what could be a better way to speed the celebration than to have some fun? In honor of Habitoween and defiance of the looming threat, all of the remaining NPCs have dressed up as monsters from the Flourishing Fields! Be sure to visit them on the site to admire their outfits. If only the three Exhaust Spirits could join them... + p Burnout is nearly defeated, so what could be a better way to speed the celebration than to have some fun? In honor of Habitoween and defiance of the looming threat, all of the remaining NPCs have dressed up as monsters from the Flourishing Fields! Be sure to visit them on the site to admire their outfits. If only the three Exhaust Spirits could join them... h2 10/27/2015 - BURNOUT STRIKES AGAIN! PLUS, SPOOKY POTIONS VANISHING SOON tr @@ -2699,9 +2681,9 @@ mixin oldNews td h3 Spooky Sparkles .pull-right - .inventory_special_spookySparkles - .achievement-spookySparkles - .ghost + .inventory_special_spookDust + .achievement-spookDust + .spookman p There's a new gold-purchasable item in the Market: Spooky Sparkles! Buy some and then cast it on your friends. I wonder what it will do? br p If you have Spooky Sparkles cast on you, you will receive the "Alarming Friends" badge! Don't worry, any mysterious effects will wear off the next day.... or you can cancel them early by buying an Opaque Potion! diff --git a/website/views/shared/profiles/achievements.jade b/website/views/shared/profiles/achievements.jade index cb75e0db32..1b3659a863 100644 --- a/website/views/shared/profiles/achievements.jade +++ b/website/views/shared/profiles/achievements.jade @@ -183,11 +183,11 @@ div(ng-if='::profile.achievements.snowball') =env.t('annoyingFriendsText', {snowballs: "{{::profile.achievements.snowball}}"}) hr -div(ng-if='::profile.achievements.spookySparkles') - .achievement.achievement-spookySparkles +div(ng-if='::profile.achievements.spookDust') + .achievement.achievement-spookDust h5=env.t('alarmingFriends') small - =env.t('alarmingFriendsText', {spookySparkles: "{{::profile.achievements.spookySparkles}}"}) + =env.t('alarmingFriendsText', {spookDust: "{{::profile.achievements.spookDust}}"}) hr div(ng-if='::profile.achievements.shinySeed') diff --git a/website/views/shared/profiles/stats/attributes.jade b/website/views/shared/profiles/stats/attributes.jade index bf5b1dbcba..0d222a42b4 100644 --- a/website/views/shared/profiles/stats/attributes.jade +++ b/website/views/shared/profiles/stats/attributes.jade @@ -7,7 +7,7 @@ table.table.table-striped span.hint(popover-title=env.t(statInfo.title), popover-placement='right', popover=env.t(statInfo.popover), popover-trigger='mouseenter') strong=env.t(statInfo.title) - strong : {{profile.fns.statsComputed().#{stat}}} + strong : {{profile._statsComputed.#{stat}}} td: ul.list-unstyled +statList('statCalc.levelBonus(profile.stats.lvl)', 'levelBonus', 'level', true) diff --git a/website/views/shared/tasks/edit/habits/plus_minus.jade b/website/views/shared/tasks/edit/habits/plus_minus.jade index 0a67ff9001..bbe17b7d4e 100644 --- a/website/views/shared/tasks/edit/habits/plus_minus.jade +++ b/website/views/shared/tasks/edit/habits/plus_minus.jade @@ -1,8 +1,8 @@ fieldset.option-group.plusminus(ng-if='task.type=="habit" && !task.challenge.id') legend.option-title=env.t('direction/Actions') span.task-checker - input.visuallyhidden.focusable(id='{{obj._id}}_{{task._id}}-option-plus', type='checkbox', ng-model='task.up') - label(for='{{obj._id}}_{{task._id}}-option-plus') + input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-plus', type='checkbox', ng-model='task.up') + label(for='{{obj._id}}_{{task.id}}-option-plus') span.task-checker - input.visuallyhidden.focusable(id='{{obj._id}}_{{task._id}}-option-minus', type='checkbox', ng-model='task.down') - label(for='{{obj._id}}_{{task._id}}-option-minus') + input.visuallyhidden.focusable(id='{{obj._id}}_{{task.id}}-option-minus', type='checkbox', ng-model='task.down') + label(for='{{obj._id}}_{{task.id}}-option-minus') diff --git a/website/views/shared/tasks/edit/index.jade b/website/views/shared/tasks/edit/index.jade index d7bc217598..ce29ee8896 100644 --- a/website/views/shared/tasks/edit/index.jade +++ b/website/views/shared/tasks/edit/index.jade @@ -3,12 +3,12 @@ div(ng-if='task._editing') // Broken Challenge .well(ng-if='task.challenge.broken') - div(ng-if='task.challenge.broken=="TASK_DELETED" || task.challenge.broken=="CHALLENGE_TASK_NOT_FOUND') + div(ng-if='task.challenge.broken=="TASK_DELETED"') p=env.t('brokenTask') p a(ng-click='unlink(task, "keep")')=env.t('keepIt') |    - a(ng-click="removeTask(task, obj)")=env.t('removeIt') + a(ng-click="removeTask(task, obj[list.type+'s'])")=env.t('removeIt') div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"') p |  diff --git a/website/views/shared/tasks/edit/tags.jade b/website/views/shared/tasks/edit/tags.jade index 8abcedc355..089aa0c408 100644 --- a/website/views/shared/tasks/edit/tags.jade +++ b/website/views/shared/tasks/edit/tags.jade @@ -1,5 +1,5 @@ fieldset.option-group(ng-if='!$state.includes("options.social.challenges")') p.option-title.mega(ng-class='{active: task._tags}', ng-click='task._tags = !task._tags', tooltip=env.t('expandCollapse'))=env.t('tags') label.checkbox(ng-repeat='tag in user.tags', ng-if='task._tags') - input(type='checkbox', ng-checked="task.tags.indexOf(tag.id) !== -1", ng-click="updateTaskTags(tag.id, task)") + input(type='checkbox', ng-model='task.tags[tag.id]') markdown(text='tag.name') diff --git a/website/views/shared/tasks/index.jade b/website/views/shared/tasks/index.jade index f128b0b322..fc3223b888 100644 --- a/website/views/shared/tasks/index.jade +++ b/website/views/shared/tasks/index.jade @@ -23,7 +23,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") i.glyphicon.glyphicon-warning-sign   =env.t('dailiesRestingInInn') - button.btn-block.btn.btn-lg.btn-success(ng-click='User.sleep({})') + button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})') | {{env.t('innCheckOut')}} +taskColumnTabs('top') diff --git a/website/views/shared/tasks/meta_controls.jade b/website/views/shared/tasks/meta_controls.jade index 3feaef0d87..733b37d23a 100644 --- a/website/views/shared/tasks/meta_controls.jade +++ b/website/views/shared/tasks/meta_controls.jade @@ -23,15 +23,15 @@ |{{checklistCompletion(task.checklist)}}/{{task.checklist.length}} span.glyphicon.glyphicon-tags(tooltip='{{Shared.appliedTags(user.tags, task.tags)}}', ng-hide='Shared.noTags(task.tags)') // edit - a(ng-hide='task._editing', ng-click='editTask(task, user)', tooltip=env.t('edit')) + a(ng-hide='task._editing', ng-click='editTask(task)', tooltip=env.t('edit')) |   span.glyphicon.glyphicon-pencil(ng-hide='task._editing') |   - a(ng-hide='!task._editing', ng-click='editTask(task, user)', tooltip=env.t('cancel')) + a(ng-hide='!task._editing', ng-click='editTask(task)', tooltip=env.t('cancel')) span.glyphicon.glyphicon-remove(ng-hide='!task._editing') |   // save - a(ng-hide='!task._editing', ng-click='editTask(task, user);saveTask(task)', tooltip=env.t('save')) + a(ng-hide='!task._editing', ng-click='editTask(task);saveTask(task)', tooltip=env.t('save')) span.glyphicon.glyphicon-ok(ng-hide='!task._editing') |   //challenges @@ -43,12 +43,12 @@ span.glyphicon.glyphicon-bullhorn(tooltip=env.t('challenge')) |   // delete - a(ng-if='!task.challenge.id || obj.leader._id === User.user._id', ng-click='removeTask(task, obj)', tooltip=env.t('delete')) + a(ng-if='!task.challenge.id', ng-click='removeTask(task, obj[list.type+"s"])', tooltip=env.t('delete')) span.glyphicon.glyphicon-trash |   // chart - a(ng-show='task.history', ng-click='toggleChart(obj._id+task._id, task)', tooltip=env.t('progress')) + a(ng-show='task.history', ng-click='toggleChart(obj._id+task.id, task)', tooltip=env.t('progress')) span.glyphicon.glyphicon-signal |   // notes diff --git a/website/views/shared/tasks/task.jade b/website/views/shared/tasks/task.jade index ad8ee4ffe5..3a69e1f760 100644 --- a/website/views/shared/tasks/task.jade +++ b/website/views/shared/tasks/task.jade @@ -1,4 +1,4 @@ -li(id='task-{{::task._id}}', +li(id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | filterByTaskInfo: obj.filterQuery | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', @@ -14,4 +14,4 @@ li(id='task-{{::task._id}}', include ./edit/index - div(class='{{obj._id}}{{task._id}}-chart', ng-show='charts[obj._id+task._id]') + div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]') diff --git a/website/views/shared/tasks/task_view/add_new.jade b/website/views/shared/tasks/task_view/add_new.jade index 2d4fd1c869..f9baf76499 100644 --- a/website/views/shared/tasks/task_view/add_new.jade +++ b/website/views/shared/tasks/task_view/add_new.jade @@ -1,4 +1,4 @@ -form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"], list, obj)', novalidate) +form.task-add(name='new{{list.type}}form', ng-hide='obj._locked', ng-submit='addTask(obj[list.type+"s"],list)', novalidate) textarea(rows='6', focus-element='list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolderBulk}}', ng-if='list.bulk', ui-keydown='{"meta-enter ctrl-enter":"addTask(obj[list.type+\'s\'],list)"}', required) input(type='text', focus-element='!list.bulk && list.focus', ng-model='list.newTask', placeholder='{{list.placeHolder}}', ng-if='!list.bulk', required) button(type='submit', ng-disabled='new{{list.type}}form.$invalid') diff --git a/website/views/shared/tasks/task_view/graph.jade b/website/views/shared/tasks/task_view/graph.jade index be2fba13a8..82f062dfde 100644 --- a/website/views/shared/tasks/task_view/graph.jade +++ b/website/views/shared/tasks/task_view/graph.jade @@ -1,7 +1,7 @@ span.option-box.pull-right(ng-if='::main') a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;') span.glyphicon.glyphicon-signal - //a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{User.settings.auth.apiToken}}', tooltip='iCal') + //a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal') //-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false') span.glyphicon.glyphicon-calendar // diff --git a/website/views/shared/tasks/task_view/index.jade b/website/views/shared/tasks/task_view/index.jade index 16628bfa5d..1d151ebd07 100644 --- a/website/views/shared/tasks/task_view/index.jade +++ b/website/views/shared/tasks/task_view/index.jade @@ -28,13 +28,13 @@ // Daily & Todos span.task-checker.action-yesno(ng-if='::task.type=="daily" || task.type=="todo"') - input.task-input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task._id}}', type='checkbox', + input.task-input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox', ng-model='task.completed', ng-if='$state.includes("tasks")', - ng-change='changeCheck(task)' + ng-change='task.type=="todo" && pushTask(task,$index,"bottom"); changeCheck(task)' ui-keypress='{13:"task.completed = !task.completed; changeCheck(task)"}' ) - input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task._id}}', type='checkbox', + input.visuallyhidden.focusable(id='box-{{::obj._id}}_{{::task.id}}', type='checkbox', ng-if='!$state.includes("tasks")') - label(for='box-{{::obj._id}}_{{::task._id}}') + label(for='box-{{::obj._id}}_{{::task.id}}') // main content .task-text(ng-dblclick='task._editing ? saveTask(task) : editTask(task)') diff --git a/website/views/shared/tasks/task_view/mixins.jade b/website/views/shared/tasks/task_view/mixins.jade index 2c71074d71..398813d5ad 100644 --- a/website/views/shared/tasks/task_view/mixins.jade +++ b/website/views/shared/tasks/task_view/mixins.jade @@ -24,7 +24,7 @@ mixin taskColumnTabs(position) div(ng-show='list.view == "complete"') .alert =env.t('lotOfToDos') - button.task-action-btn.tile.spacious.bright(ng-click='User.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted') + button.task-action-btn.tile.spacious.bright(ng-click='user.ops.clearCompleted({})',popover=env.t('deleteToDosExplanation'),popover-trigger='mouseenter')=env.t('clearCompleted') // remaining/completed tabs ul.task-filter li(ng-class='{active: list.view == "remaining"}') @@ -32,7 +32,7 @@ mixin taskColumnTabs(position) li(ng-class='{active: list.view == "dated"}') a(ng-click='list.view = "dated"')=env.t('dated') li(ng-class='{active: list.view == "complete"}') - a(ng-click='list.view = "complete";loadedCompletedTodos()')=env.t('complete') + a(ng-click='list.view = "complete"')=env.t('complete') // Rewards Tabs div(ng-if='::main && list.type=="reward"', class='tabbable tabs-below') ul.task-filter diff --git a/website/views/shared/tasks/task_view/skills.jade b/website/views/shared/tasks/task_view/skills.jade index dccd6d1233..da8cabef47 100644 --- a/website/views/shared/tasks/task_view/skills.jade +++ b/website/views/shared/tasks/task_view/skills.jade @@ -1,5 +1,5 @@ // Events -- var seasonalSkills = {'snowball':'salt', 'spookySparkles':'opaquePotion', 'shinySeed':'petalFreePotion', 'seafoam':'sand'} +- var seasonalSkills = {'snowball':'salt', 'spookDust':'opaquePotion', 'shinySeed':'petalFreePotion', 'seafoam':'sand'} ul.items.rewards each dispel,skill in seasonalSkills span(ng-if='main && list.type=="reward" && (user.items.special.#{skill}>0 || user.stats.buffs.#{skill})') diff --git a/website/views/static/api-v2.jade b/website/views/static/api.jade similarity index 91% rename from website/views/static/api-v2.jade rename to website/views/static/api.jade index c6e5c034f0..8c7ebcce8a 100644 --- a/website/views/static/api-v2.jade +++ b/website/views/static/api.jade @@ -75,10 +75,6 @@ html //.input a#explore(href='#') Explore br - h2 API v3 - p This page contains documentation for version 2 of Habitica's API. A new API version, the third, has been released and its documentation can be found here and an introductory blog post with the most important changes here. - p API v2 is still available to give time to developers to port their apps and integration to the new API but it's considered deprecated and should not be used for new projects. It'll be completely retired shortly. - br h2 Two API Types p Habitica's API is meant for two different audiences: (1) extensions and scripts, and (2) full-fledged applications. Extensions and scripts can utilize Habitica's up/down scoring for individual tasks. An example of this in action is the Chrome Extension, which up-scores you for visiting productive websites, and down-scores you for visiting procrastination websites. Other examples currently in use are Pomodoro, Anki, and Github scripts - which up-score you for good behavior and downscore you for bad behavior - see the list. The second API consumer is for full-fledge applications, which need read / write access to the entire user document. An example of this would be Mobile Apps or Desktop application. h2 Extensions / Scripts @@ -97,7 +93,7 @@ html p All API requests should be prefaced by https://habitica.com. Every authenticated request should include two headers. Your api key (x-api-key) and your user id (x-api-user). Do not include {} braces in your header (-H 'x-api-user: a94b6d9d-6b64-43ae-856c-2c3f211bd426') h2 Requirements: p The base-url for all routes is /api/v2. So /user actions will be at https://habitica.com/api/v2/*. You need to send x-api-user and x-api-key headers for each request. - p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here + p For create & edit paths (PUT & POST), you'll need to know the schema of the object you're trying to create or edit. See Schema definitions here p If any of the documentation is lacking or you're having trouble with it, please post an issue to Github #message-bar.swagger-ui-wrap #swagger-ui-container.swagger-ui-wrap diff --git a/website/views/static/front.jade b/website/views/static/front.jade index 3a3a06f37f..a77527c549 100644 --- a/website/views/static/front.jade +++ b/website/views/static/front.jade @@ -34,7 +34,6 @@ html(ng-app='habitrpg', ng-controller='RootCtrl') script(type='text/javascript'). window.env = !{JSON.stringify(env._.pick(env, env.clientVars))}; - != env.getManifestFiles("tmp_static_front") script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/js/bootstrap.min.js') diff --git a/website/views/static/maintenance-info.jade b/website/views/static/maintenance-info.jade index 241aafde3a..deba47297e 100644 --- a/website/views/static/maintenance-info.jade +++ b/website/views/static/maintenance-info.jade @@ -1,4 +1,4 @@ -- var t = env ? env.t : translation; +- var t = t || env.t; title Habitica |  =t('maintenance') diff --git a/website/views/static/maintenance.jade b/website/views/static/maintenance.jade deleted file mode 100644 index 0537913219..0000000000 --- a/website/views/static/maintenance.jade +++ /dev/null @@ -1,18 +0,0 @@ -- var t = env ? env.t : translation; - -title Habitica |  - =t('maintenance') - -head - link(rel='stylesheet', type='text/css', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.4/css/bootstrap.min.css') - -body.text-center - h1=t('habiticaBackSoon') - img.img-rendering-auto.center-block.img-responsive(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/scene_maintenance.png') - p!=t('importantMaintenance') - p!=t('twitterMaintenanceUpdates') - ul.lead(style='list-style-position:inside') - li=t('noDamageKeepStreaks') - li=t('veteranPetAward') - p!=t('maintenanceMoreInfo', {linkStart: '', linkEnd: '',}) - p.lead=t('thanksForPatience') From 763429402bd26312b1aa012005764421fc78cf5f Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 09:51:47 -0500 Subject: [PATCH 007/152] chore(i18n): update locales --- common/locales/cs/groups.json | 4 +- common/locales/cs/limited.json | 2 +- common/locales/cs/pets.json | 1 + common/locales/cs/settings.json | 1 + common/locales/cs/spells.json | 6 +- common/locales/cs/subscriber.json | 2 +- common/locales/cs/tasks.json | 1 + common/locales/da/groups.json | 4 +- common/locales/da/limited.json | 2 +- common/locales/da/pets.json | 1 + common/locales/da/settings.json | 1 + common/locales/da/spells.json | 6 +- common/locales/da/subscriber.json | 2 +- common/locales/da/tasks.json | 1 + common/locales/de/groups.json | 4 +- common/locales/de/limited.json | 2 +- common/locales/de/maintenance.json | 4 +- common/locales/de/pets.json | 1 + common/locales/de/settings.json | 1 + common/locales/de/spells.json | 6 +- common/locales/de/subscriber.json | 2 +- common/locales/de/tasks.json | 1 + common/locales/en@pirate/character.json | 4 +- common/locales/en@pirate/groups.json | 4 +- common/locales/en@pirate/limited.json | 2 +- common/locales/en@pirate/pets.json | 1 + common/locales/en@pirate/settings.json | 1 + common/locales/en@pirate/spells.json | 6 +- common/locales/en@pirate/subscriber.json | 2 +- common/locales/en@pirate/tasks.json | 1 + common/locales/en_GB/challenge.json | 5 +- common/locales/en_GB/groups.json | 18 ++-- common/locales/en_GB/limited.json | 2 +- common/locales/en_GB/pets.json | 1 + common/locales/en_GB/settings.json | 1 + common/locales/en_GB/spells.json | 6 +- common/locales/en_GB/subscriber.json | 14 +-- common/locales/en_GB/tasks.json | 5 +- common/locales/es/backgrounds.json | 14 +-- common/locales/es/character.json | 2 +- common/locales/es/front.json | 132 +++++++++++------------ common/locales/es/generic.json | 110 +++++++++---------- common/locales/es/groups.json | 4 +- common/locales/es/limited.json | 2 +- common/locales/es/messages.json | 2 +- common/locales/es/npc.json | 22 ++-- common/locales/es/pets.json | 9 +- common/locales/es/settings.json | 1 + common/locales/es/spells.json | 6 +- common/locales/es/subscriber.json | 2 +- common/locales/es/tasks.json | 25 ++--- common/locales/es_419/groups.json | 4 +- common/locales/es_419/limited.json | 2 +- common/locales/es_419/maintenance.json | 62 +++++------ common/locales/es_419/pets.json | 1 + common/locales/es_419/settings.json | 1 + common/locales/es_419/spells.json | 6 +- common/locales/es_419/subscriber.json | 2 +- common/locales/es_419/tasks.json | 1 + common/locales/fr/content.json | 4 +- common/locales/fr/front.json | 2 +- common/locales/fr/gear.json | 24 ++--- common/locales/fr/groups.json | 34 +++--- common/locales/fr/limited.json | 2 +- common/locales/fr/maintenance.json | 6 +- common/locales/fr/npc.json | 10 +- common/locales/fr/pets.json | 5 +- common/locales/fr/quests.json | 10 +- common/locales/fr/questscontent.json | 6 +- common/locales/fr/rebirth.json | 2 +- common/locales/fr/settings.json | 5 +- common/locales/fr/spells.json | 10 +- common/locales/fr/subscriber.json | 16 +-- common/locales/fr/tasks.json | 13 +-- common/locales/he/backgrounds.json | 14 +-- common/locales/he/challenge.json | 31 +++--- common/locales/he/character.json | 6 +- common/locales/he/groups.json | 4 +- common/locales/he/limited.json | 2 +- common/locales/he/maintenance.json | 26 ++--- common/locales/he/pets.json | 1 + common/locales/he/settings.json | 1 + common/locales/he/spells.json | 6 +- common/locales/he/subscriber.json | 2 +- common/locales/he/tasks.json | 1 + common/locales/hu/groups.json | 4 +- common/locales/hu/limited.json | 2 +- common/locales/hu/pets.json | 1 + common/locales/hu/settings.json | 1 + common/locales/hu/spells.json | 6 +- common/locales/hu/subscriber.json | 2 +- common/locales/hu/tasks.json | 1 + common/locales/it/challenge.json | 15 +-- common/locales/it/groups.json | 4 +- common/locales/it/limited.json | 2 +- common/locales/it/maintenance.json | 16 +-- common/locales/it/pets.json | 1 + common/locales/it/quests.json | 2 +- common/locales/it/rebirth.json | 2 +- common/locales/it/settings.json | 9 +- common/locales/it/spells.json | 6 +- common/locales/it/subscriber.json | 24 ++--- common/locales/it/tasks.json | 29 ++--- common/locales/ja/content.json | 8 +- common/locales/ja/front.json | 8 +- common/locales/ja/groups.json | 4 +- common/locales/ja/limited.json | 4 +- common/locales/ja/maintenance.json | 2 +- common/locales/ja/pets.json | 1 + common/locales/ja/settings.json | 1 + common/locales/ja/spells.json | 6 +- common/locales/ja/subscriber.json | 2 +- common/locales/ja/tasks.json | 1 + common/locales/nl/backgrounds.json | 12 +-- common/locales/nl/challenge.json | 17 +-- common/locales/nl/character.json | 6 +- common/locales/nl/contrib.json | 10 +- common/locales/nl/death.json | 2 +- common/locales/nl/front.json | 30 +++--- common/locales/nl/gear.json | 8 +- common/locales/nl/groups.json | 10 +- common/locales/nl/limited.json | 4 +- common/locales/nl/maintenance.json | 18 ++-- common/locales/nl/pets.json | 1 + common/locales/nl/rebirth.json | 2 +- common/locales/nl/settings.json | 3 +- common/locales/nl/spells.json | 10 +- common/locales/nl/subscriber.json | 6 +- common/locales/nl/tasks.json | 3 +- common/locales/pl/backgrounds.json | 6 +- common/locales/pl/challenge.json | 17 +-- common/locales/pl/character.json | 6 +- common/locales/pl/content.json | 10 +- common/locales/pl/contrib.json | 6 +- common/locales/pl/death.json | 2 +- common/locales/pl/front.json | 32 +++--- common/locales/pl/groups.json | 30 +++--- common/locales/pl/limited.json | 4 +- common/locales/pl/npc.json | 12 +-- common/locales/pl/pets.json | 3 +- common/locales/pl/settings.json | 3 +- common/locales/pl/spells.json | 6 +- common/locales/pl/subscriber.json | 2 +- common/locales/pl/tasks.json | 1 + common/locales/pt/groups.json | 4 +- common/locales/pt/limited.json | 2 +- common/locales/pt/maintenance.json | 8 +- common/locales/pt/pets.json | 1 + common/locales/pt/settings.json | 1 + common/locales/pt/spells.json | 6 +- common/locales/pt/subscriber.json | 10 +- common/locales/pt/tasks.json | 1 + common/locales/ro/groups.json | 4 +- common/locales/ro/limited.json | 2 +- common/locales/ro/pets.json | 1 + common/locales/ro/settings.json | 1 + common/locales/ro/spells.json | 6 +- common/locales/ro/subscriber.json | 2 +- common/locales/ro/tasks.json | 1 + common/locales/ru/groups.json | 4 +- common/locales/ru/limited.json | 2 +- common/locales/ru/pets.json | 1 + common/locales/ru/settings.json | 1 + common/locales/ru/spells.json | 6 +- common/locales/ru/subscriber.json | 2 +- common/locales/ru/tasks.json | 1 + common/locales/sk/backgrounds.json | 14 +-- common/locales/sk/character.json | 4 +- common/locales/sk/defaulttasks.json | 4 +- common/locales/sk/groups.json | 4 +- common/locales/sk/limited.json | 2 +- common/locales/sk/maintenance.json | 30 +++--- common/locales/sk/pets.json | 9 +- common/locales/sk/questscontent.json | 4 +- common/locales/sk/rebirth.json | 2 +- common/locales/sk/settings.json | 27 ++--- common/locales/sk/spells.json | 6 +- common/locales/sk/subscriber.json | 2 +- common/locales/sk/tasks.json | 17 +-- common/locales/sr/groups.json | 4 +- common/locales/sr/limited.json | 2 +- common/locales/sr/pets.json | 1 + common/locales/sr/settings.json | 1 + common/locales/sr/spells.json | 6 +- common/locales/sr/subscriber.json | 2 +- common/locales/sr/tasks.json | 1 + common/locales/sv/backgrounds.json | 20 ++-- common/locales/sv/groups.json | 4 +- common/locales/sv/limited.json | 2 +- common/locales/sv/pets.json | 1 + common/locales/sv/settings.json | 1 + common/locales/sv/spells.json | 6 +- common/locales/sv/subscriber.json | 2 +- common/locales/sv/tasks.json | 1 + common/locales/uk/groups.json | 4 +- common/locales/uk/limited.json | 2 +- common/locales/uk/pets.json | 1 + common/locales/uk/settings.json | 1 + common/locales/uk/spells.json | 6 +- common/locales/uk/subscriber.json | 2 +- common/locales/uk/tasks.json | 1 + common/locales/zh/challenge.json | 31 +++--- common/locales/zh/contrib.json | 10 +- common/locales/zh/death.json | 2 +- common/locales/zh/front.json | 4 +- common/locales/zh/groups.json | 18 ++-- common/locales/zh/limited.json | 4 +- common/locales/zh/maintenance.json | 50 ++++----- common/locales/zh/npc.json | 40 +++---- common/locales/zh/pets.json | 9 +- common/locales/zh/quests.json | 20 ++-- common/locales/zh/rebirth.json | 2 +- common/locales/zh/settings.json | 5 +- common/locales/zh/spells.json | 16 +-- common/locales/zh/subscriber.json | 14 +-- common/locales/zh/tasks.json | 1 + common/locales/zh_TW/groups.json | 4 +- common/locales/zh_TW/limited.json | 2 +- common/locales/zh_TW/pets.json | 1 + common/locales/zh_TW/settings.json | 1 + common/locales/zh_TW/spells.json | 6 +- common/locales/zh_TW/subscriber.json | 2 +- common/locales/zh_TW/tasks.json | 1 + 223 files changed, 904 insertions(+), 829 deletions(-) diff --git a/common/locales/cs/groups.json b/common/locales/cs/groups.json index 5dea22f538..c534fd1aa5 100644 --- a/common/locales/cs/groups.json +++ b/common/locales/cs/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "Uživatel již je v družině.", "userWithIDNotFound": "Uživatel s id „<%= userId %>\" nenalezen.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "Můžete pozvat pouze „<%= maxInvites %>\" najednou.", "onlyCreatorOrAdminCanDeleteChat": "Nemáte oprávnění k smazání této zprávy." } \ No newline at end of file diff --git a/common/locales/cs/limited.json b/common/locales/cs/limited.json index 72428915d9..54f54d82eb 100644 --- a/common/locales/cs/limited.json +++ b/common/locales/cs/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Otravní přátelé", "annoyingFriendsText": "Byl členy družiny zkoulován <%= snowballs %> krát.", "alarmingFriends": "Strašliví přátelé", - "alarmingFriendsText": "Byl <%= spookDust %>krát vyděšen členy družiny.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Zemědělští přátelé", "agriculturalFriendsText": "Byl kamarády transformován <%= seeds %>krát na kytku.", "aquaticFriends": "Podvodní přátelé", diff --git a/common/locales/cs/pets.json b/common/locales/cs/pets.json index a2d6d36ea0..1ef2ea83bd 100644 --- a/common/locales/cs/pets.json +++ b/common/locales/cs/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Éterický lev", "veteranWolf": "Vlk veterán", "veteranTiger": "Tygr veterán", + "veteranLion": "Veteran Lion", "cerberusPup": "Štěně Kerbera", "hydra": "Hydra", "mantisShrimp": "Strašek paví", diff --git a/common/locales/cs/settings.json b/common/locales/cs/settings.json index e2d49ec93c..00bf8bec68 100644 --- a/common/locales/cs/settings.json +++ b/common/locales/cs/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Vlastní začátek dne", "changeCustomDayStart": "Změnit vlastní začátek dne?", "sureChangeCustomDayStart": "Jsi si jistý, že chceš změnit svůj vlastní začátek dne?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Tve Denní úkoly se resetují ve chvíli, kdy se po <%= time %> vrátíš do země Habitica. Ujisti se, že jsi je všechny stihnul dokončit před tímto časem!", "customDayStartInfo1": "Při základním nastavení program Habitica kontroluje a resetuje tvé Denní úkoly každý den o půlnoci ve tvé časové zóně. Tento čas můžeš změnit zde.", "misc": "Ostatní", diff --git a/common/locales/cs/spells.json b/common/locales/cs/spells.json index 15baf84dd3..1fdc4e499b 100644 --- a/common/locales/cs/spells.json +++ b/common/locales/cs/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Hoď sněhovou koulí po členu družiny, co by se tak mohlo stát? Vydrží mu až do druhého dne.", "spellSpecialSaltText": "Sůl", "spellSpecialSaltNotes": "Někdo po tobě hodil sněhovou kouli. Ha ha, strašně vtipné. A teď ze mě dostaňte ten sníh!", - "spellSpecialSpookDustText": "Strašidelné jiskry", - "spellSpecialSpookDustNotes": "Změň kamaráda na poletující deku s očima!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Neprůhledný lektvar", "spellSpecialOpaquePotionNotes": "Zruš účinky strašidelných jisker.", "spellSpecialShinySeedText": "Třpytivé semínko", @@ -50,7 +50,7 @@ "spellNotFound": "Schopnost „<%= spellId %>\" nenalezena.", "partyNotFound": "Družina nenalezena", "targetIdUUID": "„cílovéId\" musí být platné ID uživatele. ", - "challengeTasksNoCast": "Použití schopností na výzvu není povoleno.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "Nevlastníte tuto schopnost.", "spellLevelTooHigh": "Pro použití této schopnosti musíte být úroveň <%= level %>." } \ No newline at end of file diff --git a/common/locales/cs/subscriber.json b/common/locales/cs/subscriber.json index eb956eb26e..9fdbe4a3f0 100644 --- a/common/locales/cs/subscriber.json +++ b/common/locales/cs/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/cs/tasks.json b/common/locales/cs/tasks.json index 5efcd94b20..2a64f1522a 100644 --- a/common/locales/cs/tasks.json +++ b/common/locales/cs/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Vyčistit", "hideTags": "Schovat", "showTags": "Ukázat", + "toRequired": "You must supply a to value", "startDate": "Datum začátku", "startDateHelpTitle": "Kdy by měl tento úkol začít?", "startDateHelp": "Nastav datum, ve kterém začne úkol platit. Nebude muset být splněn dříve.", diff --git a/common/locales/da/groups.json b/common/locales/da/groups.json index c3eadb944d..615fb94513 100644 --- a/common/locales/da/groups.json +++ b/common/locales/da/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/da/limited.json b/common/locales/da/limited.json index 3c62c98c6b..5b7f3c63e7 100644 --- a/common/locales/da/limited.json +++ b/common/locales/da/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Irriterende venner", "annoyingFriendsText": "Blev ramt af <%= snowballs %> snebolde af gruppemedlemmer.", "alarmingFriends": "Foruroligende Venner", - "alarmingFriendsText": "Blev skræmt <%= spookDust %> gange af gruppemedlemmer.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Landbrugsvenner", "agriculturalFriendsText": "Blev lavet om til en blomst <%= seeds %> gange af gruppemedlemmer.", "aquaticFriends": "Våde Venner", diff --git a/common/locales/da/pets.json b/common/locales/da/pets.json index 8ce3c56023..7da511fc21 100644 --- a/common/locales/da/pets.json +++ b/common/locales/da/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Æterisk Løve", "veteranWolf": "Veteranulv", "veteranTiger": "Veterantiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Kerberoshvalp", "hydra": "Hydra", "mantisShrimp": "Knælerreje", diff --git a/common/locales/da/settings.json b/common/locales/da/settings.json index 5d80d99bb2..4f48dd224f 100644 --- a/common/locales/da/settings.json +++ b/common/locales/da/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Brugerdefineret Dagstart", "changeCustomDayStart": "Skift Brugerdefineret Dagstart?", "sureChangeCustomDayStart": "Er du sikker på, at du vil ændre din brugerdefinerede dagsstart?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Dine Daglige vil blive nulstillet første gang du bruger Habitica efter <%= time %>. Vær sikker på, at du har færdiggjort dine Daglige før dette tidspunkt!", "customDayStartInfo1": "Som udgangspunkt vil Habitica tjekke og nulstille dine Daglige ved midnat hver dag. Du kan ændre dette her.", "misc": "Diverse", diff --git a/common/locales/da/spells.json b/common/locales/da/spells.json index fbc36fb207..54be25f1c5 100644 --- a/common/locales/da/spells.json +++ b/common/locales/da/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Kast en snebold efter et gruppemedlem! Kom nu, hvad kan der ske? Holder indtil medlemmet skifter dag.", "spellSpecialSaltText": "Salt", "spellSpecialSaltNotes": "Nogen har kastet en snebold på dig! Haha, skideskægt... Fjern nu bare sneen fra mig!", - "spellSpecialSpookDustText": "Skræmmende Gnister", - "spellSpecialSpookDustNotes": "Gør din ven til et svævende lagen med øjne!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Uigennemsigtig Eliksir", "spellSpecialOpaquePotionNotes": "Fjerner effekterne af Skræmmende Gnister.", "spellSpecialShinySeedText": "Skinnende Frø", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/da/subscriber.json b/common/locales/da/subscriber.json index 701be59720..488d7173df 100644 --- a/common/locales/da/subscriber.json +++ b/common/locales/da/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/da/tasks.json b/common/locales/da/tasks.json index dc825fb8c4..eb797ec9af 100644 --- a/common/locales/da/tasks.json +++ b/common/locales/da/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Ryd", "hideTags": "Skjul", "showTags": "Vis", + "toRequired": "You must supply a to value", "startDate": "Startdato", "startDateHelpTitle": "Hvornår skal denne opgave starte?", "startDateHelp": "Sæt datoen for, hvornår denne opgave begynder. Opgaven er ikke aktuel før den valgte dag.", diff --git a/common/locales/de/groups.json b/common/locales/de/groups.json index 766217328f..2baca7563d 100644 --- a/common/locales/de/groups.json +++ b/common/locales/de/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "Benutzer bereits in einer Gruppe.", "userWithIDNotFound": "Benutzer mit ID \"<%= userId %>\" nicht gefunden", "userHasNoLocalRegistration": "Benutzer ist lokal nicht registriert (Benutzername, E-Mail, Passwort).", - "uuidsMustBeAnArray": "UUID-Einladungen müssen ein Array sein.", - "emailsMustBeAnArray": "E-Mail-Einladungen müssen ein Array sein.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "Du kannst nur \"<%= maxInvites %>\" Benutzer gleichzeitig einladen", "onlyCreatorOrAdminCanDeleteChat": "Löschen der Nachricht nicht erlaubt!" } \ No newline at end of file diff --git a/common/locales/de/limited.json b/common/locales/de/limited.json index 4abd571bc5..76d0f4e8d1 100644 --- a/common/locales/de/limited.json +++ b/common/locales/de/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Nervige Freunde", "annoyingFriendsText": "Hat sich <%= snowballs %> Schneebälle von Gruppenmitgliedern eingefangen.", "alarmingFriends": "Unheimliche Freunde", - "alarmingFriendsText": "Wurde <%= spookDust %> mal von Gruppenmitgliedern erschreckt.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Gartenbegeisterte Freunde", "agriculturalFriendsText": "Wurde <%= seeds %> mal von Gruppenmitgliedern in eine Blume verwandelt.", "aquaticFriends": "Wasserliebende Freunde", diff --git a/common/locales/de/maintenance.json b/common/locales/de/maintenance.json index 1efd9be5bb..2d357319d4 100644 --- a/common/locales/de/maintenance.json +++ b/common/locales/de/maintenance.json @@ -12,12 +12,12 @@ "maintenanceInfoWhat": "Was passiert?", "maintenanceInfoWhatText": "Am 21. Mai wird Habitica für eine Weile wegen Wartungsarbeiten unerreichbar sein. Du wirst keinen Schaden erleiden und Dein Konto wird nicht negativ beeinflusst, selbst wenn Du Dich nicht rechtzeitig einloggen kannst, um Deine täglichen Aufgaben abzuhaken! Wir arbeiten hart daran, die Auszeit so kurz wie möglich zu halten und werden regelmäßige Updates auf unserem Twitter Account posten. Am Ende der Wartungszeit wirst Du als Dankeschön für Deine Geduld ein seltenes Haustier erhalten!", "maintenanceInfoWhy": "Warum passiert das?", - "maintenanceInfoWhyText": "In den letzten Monaten haben wir Habitica hinter den Kulissen gründlich aufgeräumt. Wir haben vor allem die API neu geschrieben. Obwohl es auf der Oberfläche nicht wirklich anders aussieht, findet sich eine komplett neue Welt unter der Haube. Das wird uns SEHR viel mehr Flexibilität zum Erstellen neuer Features bringen und führt zu verbesserter Performance!", + "maintenanceInfoWhyText": "In den letzten Monaten haben wir Habitica hinter den Kulissen gründlich aufgeräumt. Wir haben vor allem die API neu geschrieben. Obwohl es auf der Oberfläche nicht wirklich anders aussieht, findet sich darunter eine komplett neue Welt. Das wird uns SEHR viel mehr Flexibilität beim Erstellen neuer Features bringen und führt zu verbesserter Performance!", "maintenanceInfoTechDetails": "Möchtest Du mehr über die technische Seite des Prozesses erfahren? Besuche Die Schmiede, unseren Entwicklerblog.", "maintenanceInfoMore": "Mehr Informationen", "maintenanceInfoAccountChanges": "Welche Änderungen werde ich in meinem Account sehen, wenn die Wartungsarbeiten fertig sind?", "maintenanceInfoAccountChangesText": "Anfangs wird es keine merklichen Änderungen außer den Performanceverbesserungen für Features wie Wettbewerbe geben. Falls Du Änderungen bemerkst, die es nicht geben sollte, schreib uns eine E-Mail unter admin@habitica.com und wir werden sie für Dich untersuchen!", - "maintenanceInfoAddFeatures": "Welche Feature werden dadurch auf Habitica möglich?", + "maintenanceInfoAddFeatures": "Welche Features werden dadurch auf Habitica möglich?", "maintenanceInfoAddFeaturesText": "Mit Abschluss dieser Wartungen wird es möglich, den Chat und die Gilden zu verbessern, die Pläne für Organisationen und Familien und mehr Produktivitätsfeatures umzusetzen, wie monatliche Aufgaben und die Möglichkeit, gestrige Aktivitäten aufzuzeichnen! Das alles sind große Features, es wird also etwas Dauern, sie umzusetzen, aber bis zum Ende dieser Wartungsarbeiten gab es keinen Weg, sie überhaupt anzufangen.", "maintenanceInfoHowLong": "Wie lange werden die Wartungsarbeiten dauern?", "maintenanceInfoHowLongText": "Wir müssen die Aufgaben und Daten aller 1,3 Millionen Habitica-Benutzer migrieren -- keine einfache Aufgabe! Wir nehmen an dass wir etwa von 13:00 Pacific Time (20:00 UTC) and 22:00 Pacific Time (05:00 UTC) benötigen. Wir werden alles tun um das so schnell wie möglich über die Bühne zu bringen! Du kannst den Updates auf Twitter folgen.", diff --git a/common/locales/de/pets.json b/common/locales/de/pets.json index 2a65a188f0..e502467914 100644 --- a/common/locales/de/pets.json +++ b/common/locales/de/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Ätherischer Löwe", "veteranWolf": "Veteranwolf", "veteranTiger": "Veterantiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Zerberuswelpe", "hydra": "Hydra", "mantisShrimp": "Fangschreckenkrebs", diff --git a/common/locales/de/settings.json b/common/locales/de/settings.json index 4607189f8e..9bc58f3684 100644 --- a/common/locales/de/settings.json +++ b/common/locales/de/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Tageswechsel einstellen", "changeCustomDayStart": "Tageswechsel ändern?", "sureChangeCustomDayStart": "Willst Du Deinen Tageswechsel wirklich ändern?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Deine täglichen Aufgaben werden das nächste Mal überprüft und zurückgesetzt, wenn Du Habitica das erste Mal nach <%= time %> nutzt. Stelle sicher, dass Du Deine täglichen Aufgaben vor diesem Zeitpunkt erledigt hast.", "customDayStartInfo1": "Normalerweise werden Deine täglichen Aufgaben jeden Tag um Mitternacht Deiner Ortszeit von Habitica überprüft und zurückgesetzt. Du kannst diesen Zeitpunkt hier ändern.", "misc": "Verschiedenes", diff --git a/common/locales/de/spells.json b/common/locales/de/spells.json index a4aac37312..59a3ac5301 100644 --- a/common/locales/de/spells.json +++ b/common/locales/de/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Wirf einen Schneeball auf ein Gruppenmitglied! Was kann dabei schon schief gehen? Dauert bis zum Tageswechsel des Gruppenmitglieds an.", "spellSpecialSaltText": "Salz", "spellSpecialSaltNotes": "Jemand hat Dich mit Schnee eingeseift. Haha, sehr lustig. Jetzt ist aber Schluss!", - "spellSpecialSpookDustText": "Spukglitzer", - "spellSpecialSpookDustNotes": "Verwandle einen Freund in ein schwebendes Bettlaken mit Augen!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Trank der Entgeisterung", "spellSpecialOpaquePotionNotes": "Beendet den Effekt von Spukglitzer.", "spellSpecialShinySeedText": "Schimmernde Saat", @@ -50,7 +50,7 @@ "spellNotFound": "Fähigkeit \"<%= spellId %>\" nicht gefunden.", "partyNotFound": "Gruppe nicht gefunden", "targetIdUUID": "\"targetId\" muss eine gültige Benutzer-ID sein.", - "challengeTasksNoCast": "Eine Fähigkeit auf Wettbewerbs-Aufgaben anzuwenden wird nicht unterstützt.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "Du besitzt diese Fähigkeit nicht.", "spellLevelTooHigh": "Du musst Level <%= level %> sein, um diese Fähigkeit zu benutzen." } \ No newline at end of file diff --git a/common/locales/de/subscriber.json b/common/locales/de/subscriber.json index 4c653a5d0a..9efa500bbd 100644 --- a/common/locales/de/subscriber.json +++ b/common/locales/de/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Haustier/Reittier nicht zum Kauf mit mystischen Sanduhren verfügbar.", "readCard": "<%= cardType %> wurde gelesen", "cardTypeRequired": "Kartentyp erforderlich", - "cardTypeNotAllowed": "Unbekannter Kartentyp.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Ungültiger Gutschein-Code", "couponUsed": "Gutschein wurde bereits eingelöst.", "noSudoAccess": "Du hast keine sudo-Rechte.", diff --git a/common/locales/de/tasks.json b/common/locales/de/tasks.json index d23417040a..a431a2bd17 100644 --- a/common/locales/de/tasks.json +++ b/common/locales/de/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Aufräumen", "hideTags": "Verbergen", "showTags": "Anzeigen", + "toRequired": "You must supply a to value", "startDate": "Starttermin", "startDateHelpTitle": "Wann soll diese Aufgabe beginnen?", "startDateHelp": "Setze das Datum fest, ab dem diese Aufgabe in Kraft tritt. Davor wird sie nicht fällig werden.", diff --git a/common/locales/en@pirate/character.json b/common/locales/en@pirate/character.json index 9c6cdd3202..c54e3123b7 100644 --- a/common/locales/en@pirate/character.json +++ b/common/locales/en@pirate/character.json @@ -110,7 +110,7 @@ "mage": "Magician", "mystery": "Mystery", "changeClass": "Change Class, Refund Attribute Points", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "T'\u001d change class ye must be at least level 10.", "levelPopover": "Each level earns ye one point to assign to an attribute 'o ye choice. ye can do so manually, or let th' game decide fer ye usin' one 'o th' Automatic Allocation options.", "unallocated": "Unallocated Attribute Points", "haveUnallocated": "Ye have <%= points %> unallocated Attribute Point(s)", @@ -168,5 +168,5 @@ "hideQuickAllocation": "Hide stat allocation", "quickAllocationLevelPopover": "Each level earns ye one point t' assign t' an attribute o' yer choice. Ye can do so manually, or let th' game decide for ye using one o' th' Automatic Allocation options found in User -> Stats.", "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "notEnoughAttrPoints": "Ye don't have enough attribute points." } \ No newline at end of file diff --git a/common/locales/en@pirate/groups.json b/common/locales/en@pirate/groups.json index ce02be8a12..091abcb25b 100644 --- a/common/locales/en@pirate/groups.json +++ b/common/locales/en@pirate/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/en@pirate/limited.json b/common/locales/en@pirate/limited.json index 01bb9ec12e..60f4260ced 100644 --- a/common/locales/en@pirate/limited.json +++ b/common/locales/en@pirate/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Annoyin' Mates", "annoyingFriendsText": "Got snowballed <%= snowballs %> times by crew-mates.", "alarmingFriends": "Alarmin' Buckos", - "alarmingFriendsText": "Got spooked <%= spookDust %> times by crew mates.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Agricultural Friends", "agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.", "aquaticFriends": "Friends o' th' Sea", diff --git a/common/locales/en@pirate/pets.json b/common/locales/en@pirate/pets.json index 18554f398c..0eefe31c8f 100644 --- a/common/locales/en@pirate/pets.json +++ b/common/locales/en@pirate/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Ethereal Lion", "veteranWolf": "Veteran Wolf", "veteranTiger": "Veteran Tiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberus Pup", "hydra": "Hydra", "mantisShrimp": "Mantis Shrimp", diff --git a/common/locales/en@pirate/settings.json b/common/locales/en@pirate/settings.json index e86515261f..39201b964d 100644 --- a/common/locales/en@pirate/settings.json +++ b/common/locales/en@pirate/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Custom Day Start", "changeCustomDayStart": "Change Custom Day Start, matey?", "sureChangeCustomDayStart": "Arr ye sure ye want t' change yer custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Yer Dailies will next reset the first time you use Habitica after <%= time %>. Ye better be sure ye've completed yer Dailies by that time!", "customDayStartInfo1": "Habitica defaults t' check an' reset yer Dailies at midnight in yer own time zone ev'ry day. Ye c'n customize that time here.", "misc": "Misc", diff --git a/common/locales/en@pirate/spells.json b/common/locales/en@pirate/spells.json index 784a2d6ead..e850c5644c 100644 --- a/common/locales/en@pirate/spells.json +++ b/common/locales/en@pirate/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Throw a snowball at a crew mate! Could anythin' go wrong? Lasts 'til mate's new day.", "spellSpecialSaltText": "Sea Salt", "spellSpecialSaltNotes": "Someone has snowballed ye. Ha ha, extra hardyharhar. Now get this snow off me!", - "spellSpecialSpookDustText": "Spooky Sparkles", - "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Murky Potion", "spellSpecialOpaquePotionNotes": "Cancel th' effects 'o Spooky Sparkles.", "spellSpecialShinySeedText": "Shiny Seed", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/en@pirate/subscriber.json b/common/locales/en@pirate/subscriber.json index f9d9c49905..66cf091577 100644 --- a/common/locales/en@pirate/subscriber.json +++ b/common/locales/en@pirate/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/en@pirate/tasks.json b/common/locales/en@pirate/tasks.json index 5934135f6d..dc4efa86f5 100644 --- a/common/locales/en@pirate/tasks.json +++ b/common/locales/en@pirate/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Clear", "hideTags": "Hide", "showTags": "Show", + "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set th' date for which this task takes effect. Will not be due on earlier days.", diff --git a/common/locales/en_GB/challenge.json b/common/locales/en_GB/challenge.json index 62c2792252..d2522308e2 100644 --- a/common/locales/en_GB/challenge.json +++ b/common/locales/en_GB/challenge.json @@ -74,9 +74,10 @@ "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", "onlyLeaderUpdateChal": "Only the challenge leader can update it.", "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be un-linked. ", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/en_GB/groups.json b/common/locales/en_GB/groups.json index e207638acb..d5109da655 100644 --- a/common/locales/en_GB/groups.json +++ b/common/locales/en_GB/groups.json @@ -167,23 +167,23 @@ "groupTypesRequired": "You must supply a valid \"type\" query string.", "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", - "onlyLeaderCanRemoveMember": "Only group leader can remove a member!", + "onlyLeaderCanRemoveMember": "Only the group leader can remove a member!", "memberCannotRemoveYourself": "You cannot remove yourself!", - "groupMemberNotFound": "User not found among group's members", + "groupMemberNotFound": "User not found among group's members.", "mustBeGroupMember": "Must be member of the group.", "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", - "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", + "canOnlyInviteEmailUuid": "Can only invite using UUIDs or emails.", "inviteMissingEmail": "Missing email address in invite.", - "partyMustbePrivate": "Parties must be private", + "partyMustbePrivate": "Parties must be private.", "userAlreadyInGroup": "User already in that group.", "userAlreadyInvitedToGroup": "User already invited to that group.", "userAlreadyPendingInvitation": "User already pending invitation.", "userAlreadyInAParty": "User already in a party.", - "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", + "userWithIDNotFound": "User with ID \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", - "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", - "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", + "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time.", + "onlyCreatorOrAdminCanDeleteChat": "Not authorised to delete this message!" } \ No newline at end of file diff --git a/common/locales/en_GB/limited.json b/common/locales/en_GB/limited.json index 4afc1acaa2..f6e200f995 100644 --- a/common/locales/en_GB/limited.json +++ b/common/locales/en_GB/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Annoying Friends", "annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.", "alarmingFriends": "Alarming Friends", - "alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Agricultural Mates", "agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.", "aquaticFriends": "Aquatic Friends", diff --git a/common/locales/en_GB/pets.json b/common/locales/en_GB/pets.json index 351ab54871..1a7d7ce97b 100644 --- a/common/locales/en_GB/pets.json +++ b/common/locales/en_GB/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Ethereal Lion", "veteranWolf": "Veteran Wolf", "veteranTiger": "Veteran Tiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberus Pup", "hydra": "Hydra", "mantisShrimp": "Mantis Shrimp", diff --git a/common/locales/en_GB/settings.json b/common/locales/en_GB/settings.json index b1ed787ab0..adb566d6e4 100644 --- a/common/locales/en_GB/settings.json +++ b/common/locales/en_GB/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Custom Day Start", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customise that time here.", "misc": "Misc", diff --git a/common/locales/en_GB/spells.json b/common/locales/en_GB/spells.json index f6213ab057..95821167bc 100644 --- a/common/locales/en_GB/spells.json +++ b/common/locales/en_GB/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Throw a snowball at a party member! What could possibly go wrong? Lasts until member's new day.", "spellSpecialSaltText": "Salt", "spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!", - "spellSpecialSpookDustText": "Spooky Sparkles", - "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Opaque Potion", "spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.", "spellSpecialShinySeedText": "Shiny Seed", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/en_GB/subscriber.json b/common/locales/en_GB/subscriber.json index e115944d72..fa4b5ae867 100644 --- a/common/locales/en_GB/subscriber.json +++ b/common/locales/en_GB/subscriber.json @@ -122,19 +122,19 @@ "mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.", "hourglassPurchase": "Purchased an item using a Mystic Hourglass!", "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!", - "missingUnsubscriptionCode": "Missing unsubscription code.", - "missingSubscription": "User does not have a plan subscription", + "missingUnsubscriptionCode": "Missing un-subscribe code.", + "missingSubscription": "User does not have a subscription plan.", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", - "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", - "paymentNotSuccessful": "The payment was not successful", - "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", + "cannotDeleteActiveAccount": "You have an active subscription. Cancel your plan before deleting your account.", + "paymentNotSuccessful": "The payment was not successful.", + "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits.", "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", - "noSudoAccess": "You don't have sudo access.", + "noSudoAccess": "You don't have 'sudo' access.", "couponCodeRequired": "The coupon code is required.", "eventRequired": "\"req.params.event\" is required.", "countRequired": "\"req.query.count\" is required." diff --git a/common/locales/en_GB/tasks.json b/common/locales/en_GB/tasks.json index 5c13a9a544..0c81c4ef46 100644 --- a/common/locales/en_GB/tasks.json +++ b/common/locales/en_GB/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Clear", "hideTags": "Hide", "showTags": "Show", + "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.", @@ -121,10 +122,10 @@ "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", "checklistItemNotFound": "No checklist item was found with given id.", "itemIdRequired": "\"itemId\" must be a valid UUID.", - "tagNotFound": "No tag item was found with given id.", + "tagNotFound": "No tag item was found with given ID.", "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.", "positionRequired": "\"position\" is required and must be a number.", - "cantMoveCompletedTodo": "Can't move a completed todo.", + "cantMoveCompletedTodo": "Can't move a completed to-do.", "directionUpDown": "\"direction\" is required and must be 'up' or 'down'", "alreadyTagged": "The task is already tagged with given tag." } \ No newline at end of file diff --git a/common/locales/es/backgrounds.json b/common/locales/es/backgrounds.json index b13c48fb58..b1bfb7eef4 100644 --- a/common/locales/es/backgrounds.json +++ b/common/locales/es/backgrounds.json @@ -161,11 +161,11 @@ "backgroundGiantFlowersNotes": "Juguetear encima de Flores Gigantes.", "backgroundRainbowsEndText": "Fin del Arcoíris", "backgroundRainbowsEndNotes": "Descubre oro al Fin del Arcoíris.", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", - "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", - "backgroundGazeboText": "Gazebo", - "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgrounds052016": "JUEGO 24: Lanzado en Mayo de 2016", + "backgroundBeehiveText": "Colmena", + "backgroundBeehiveNotes": "Zumba y baila en una Colmena.", + "backgroundGazeboText": "Kiosko", + "backgroundGazeboNotes": "Pelea en un Kiosko.", + "backgroundTreeRootsText": "Raíces de árbol", + "backgroundTreeRootsNotes": "Explora las raíces del árbol." } \ No newline at end of file diff --git a/common/locales/es/character.json b/common/locales/es/character.json index 077b1a2a81..3a3c6c6c5e 100644 --- a/common/locales/es/character.json +++ b/common/locales/es/character.json @@ -67,7 +67,7 @@ "ultimGearText": "Has conseguido el conjunto de armas y armadura de mayor nivel en las siguientes clases:", "level": "Nivel", "levelUp": "¡Subiste de Nivel!", - "gainedLevel": "Has ganado un nivel!", + "gainedLevel": "¡Has subido de nivel!", "leveledUp": "Por cumplir tus tareas en la vida real has subido al Nivel <%= level %>!", "fullyHealed": "Has recobrado tu salud!", "huzzah": "Oh si!", diff --git a/common/locales/es/front.json b/common/locales/es/front.json index 8009a44069..fc74f0511b 100644 --- a/common/locales/es/front.json +++ b/common/locales/es/front.json @@ -1,104 +1,104 @@ { - "FAQ": "Preguntas Frecuentes", - "accept1Terms": "Al hacer clíck en el botón de abajo, acepto los", + "FAQ": "Preguntas frecuentes", + "accept1Terms": "Al hacer clic en el botón de abajo, acepto los", "accept2Terms": "y la", - "alexandraQuote": "No pude NO hablar de [Habitica] durante mi conferencia en Madrid. Herramienta imprescindible para autónomos que todavía necesitan un jefe.", + "alexandraQuote": "No pude NO hablar de [Habitica] durante mi conferencia en Madrid. Una herramienta imprescindible para los autónomos que aún necesitan un jefe.", "althaireQuote": "Estar siempre en una misión realmente me motiva a hacer todas mis tareas diarias y pendientes. Mi mayor motivación es no decepcionar a mi grupo.", - "andeeliaoQuote": "Genial producto, empecé hace unos pocos días y ya soy más productivo y consciente de mi tiempo!", - "autumnesquirrelQuote": "Estoy postergando menos en el trabajo en las tareas domésticas y pago las cuentas a tiempo.", - "businessSample1": "Confirmar 1 página de Inventario", - "businessSample2": "20 minutos Archivando", - "businessSample3": "Ordenar y Procesar Bandeja de Entrada", - "businessSample4": "Preparar 1 Documento para el Cliente", - "businessSample5": "Llamar Clientes/Postponer llamadas de teléfono", - "businessText": "Usa Habitica en tu negocio", - "choreSample1": "Poner Ropa Sucia en el Cesto", - "choreSample2": "20 minutos de Tareas del Hogar", - "choreSample3": "Lavar los Platos", - "choreSample4": "Recoger Una Habitación", - "choreSample5": "Lavar y Secar la Colada", + "andeeliaoQuote": "Es genial, empecé hace unos pocos días y ¡ya soy más productivo y más consciente de mi tiempo!", + "autumnesquirrelQuote": "Vagueo menos en el trabajo y las tareas domésticas, y llevo todos los pagos al día.", + "businessSample1": "Confirmar 1 página de inventario", + "businessSample2": "Archivar 20 minutos", + "businessSample3": "Ordenar y procesar la bandeja de entrada", + "businessSample4": "Preparar 1 documento para un cliente", + "businessSample5": "Llamar a clientes / posponer llamadas de teléfono", + "businessText": "Usa Habitica en tu empresa", + "choreSample1": "Poner ropa sucia en el cesto", + "choreSample2": "20 minutos de tareas del hogar", + "choreSample3": "Lavar los platos", + "choreSample4": "Recoger una habitación", + "choreSample5": "Poner una lavadora y tenderla", "chores": "Tareas de casa", "clearBrowserData": "Borrar datos del navegador", - "communityBug": "Enviar un Informe de Error", - "communityExtensions": "Complementos y Extensiones", + "communityBug": "Enviar un informe de error", + "communityExtensions": "Complementos y extensiones", "communityFacebook": "Facebook", - "communityFeature": "Solicitar una Característica", + "communityFeature": "Solicitar una función", "communityForum": "Foro", "communityKickstarter": "Kickstarter", "communityReddit": "Reddit", - "companyAbout": "Cómo Funciona", + "companyAbout": "Cómo funciona", "companyBlog": "Blog", - "devBlog": "Developer Blog", + "devBlog": "Blog de desarrolladores", "companyDonate": "Donar", "companyExtensions": "Extensiones", "companyPrivacy": "Privacidad", "companyTerms": "Condiciones", "companyVideos": "Vídeos", "contribUse": "Los colaboradores de Habitica utilizan:", - "dragonsilverQuote": "No puedo decir cuánto tiempo y sistemas de seguimiento de tareas he probado a lo largo de las décadas...[Habitica] es lo único que he utilizado que realmente me ayuda a terminar mis tareas más allá de simplemente listarlas.", + "dragonsilverQuote": "Ya no sé cuánto tiempo ni cuántos sistemas para guardar tareas he probado a lo largo de décadas... [Habitica] es el único que me ayuda realmente a terminar mis tareas y no solo apuntarlas.", "dreimQuote": "Cuando descubrí [Habitica] el verano pasado, acababa de suspender la mitad de mis exámenes. Gracias a las tareas diarias, conseguí organizarme y tener disciplina y, hace un mes, aprobé todo.", - "elmiQuote": "¡Cada mañana estoy deseando levantarme para ganar un poco de oro!", - "email": "Correo Electrónico", - "emailNewPass": "Enviar Nueva Contraseña", - "evagantzQuote": "La primera cita con mi dentista en la que el higienista estaba realmente excitado con mis hábitos de uso del hilo dental. ¡Gracias [Habitica]!", - "examplesHeading": "Los Jugadores usan Habitica para gestionar...", - "featureAchievementByline": "¿Haces algo totalmente genial? ¡Consigue una insignia y muéstrala!", - "featureAchievementHeading": "Logro de Insignias", - "featureEquipByline": "¡Compra ediciones limitadas de equipo, pociones, y otras golosinas virtuales en nuestro Mercado con tus premios de tareas!", + "elmiQuote": "Todas las mañanas me apetece levantarme para ganar un poco de oro.", + "email": "Correo electrónico", + "emailNewPass": "Enviar nueva contraseña", + "evagantzQuote": "Es la primera vez que voy al dentista y no me echa la bronca por no usar hilo dental. ¡Gracias, [Habitica]!", + "examplesHeading": "Los jugadores usan Habitica para gestionar...", + "featureAchievementByline": "¿Has hecho algo increíble? ¡Consigue una insignia y exhíbela!", + "featureAchievementHeading": "Insignias de logros", + "featureEquipByline": "Compra ediciones limitadas de equipo, pociones y otros objetos virtuales en nuestro Mercado, gracias a las recompensas que obtienes por cumplir tus tareas.", "featureEquipHeading": "Equipo y extras", - "featurePetByline": "Huevos y artículos aparecen cuando completas tus tareas. ¡Se lo más productivo posible para coleccionar mascotas y monturas!", - "featurePetHeading": "Mascotas y Monturas", - "featureSocialByline": "Únete a grupos con intereses comunes que compartan tu opinión. Crea Desafios para competir contra otros usuarios.", + "featurePetByline": "Cuando completas tus tareas, aparecen huevos y artículos. Para coleccionar mascotas y monturas, ¡sé tan productivo como puedas!", + "featurePetHeading": "Mascotas y monturas", + "featureSocialByline": "Únete a grupos con intereses comunes. Crea desafíos para competir contra otros usuarios.", "featureSocialHeading": "Juego social", - "featuredIn": "Destacado", - "featuresHeading": "También contamos con...", - "footerDevs": "Developers", + "featuredIn": "Hablan de nosotros en", + "featuresHeading": "También tenemos...", + "footerDevs": "Desarrolladores", "footerCommunity": "Comunidad", - "footerCompany": "Compañía", + "footerCompany": "Empresa", "footerMobile": "Móvil", "footerSocial": "Social", - "forgotPass": "Olvidé mi Contraseña", - "frabjabulousQuote": "[Habitica] es la razón por la que conseguí un trabajo genial y bien pagado... y algo más milagroso todavía ¡ahora uso el hilo dental a diario!", + "forgotPass": "Olvidé mi contraseña", + "frabjabulousQuote": "[Habitica] es la razón por la que conseguí un trabajo genial y bien pagado... y algo más milagroso todavía: ahora no me olvido ni un solo día de usar hilo dental.", "free": "Únete gratis", - "gamifyButton": "¡Convierte tu vida en un juego hoy!", - "goalSample1": "Practicar Piano durante 1 Hora", - "goalSample2": "Trabajar en un artículo para su publicación", - "goalSample3": "Trabajar en artículos del blog", + "gamifyButton": "Convierte tu vida en un juego", + "goalSample1": "Practicar piano 1 hora", + "goalSample2": "Trabajar en un artículo para publicar", + "goalSample3": "Escribir entrada del blog", "goalSample4": "Lección de japonés en Duolingo", - "goalSample5": "Leer un Artículo Informativo", + "goalSample5": "Leer un artículo informativo", "goals": "Metas", "health": "Salud", - "healthSample1": "Beber Agua/Refresco", - "healthSample2": "Masticar Chicle/Fumar", + "healthSample1": "Beber agua/refresco", + "healthSample2": "Masticar chicle / fumar", "healthSample3": "Utilizar escaleras/ascensor", "healthSample4": "Comer comida saludable/basura", - "healthSample5": "Romper a sudar durante 1 hora", + "healthSample5": "Hacer ejercicio 1 hora", "history": "Historia", - "infhQuote": "[Habitica] realmente me ha ayudado a estructurar mi vida universitaria", - "invalidEmail": "Se requiere una dirección de correo electrónico válida para resetear la contraseña.", - "irishfeet123Quote": "He tenido terribles hábitos limpiando mi casa completamente tras las comidas y dejándome tazas por todas partes. ¡[Habitica] ha curado eso!", - "joinOthers": "¡Únete a las <%= userCount %> personas que hacen que completar objetivos sea divertido!", - "kazuiQuote": "Antes de [Habitica], estaba atascado con mi tesis, así como insatisfecho con mi disciplina personal en cuanto a tareas domésticas y cosas como aprender vocabulario y estudiar teoría de Go. Resulta que romper estas tareas en pequeñas y manejables listas es exactamente lo que me mantiene motivado y constantemente trabajando.", + "infhQuote": "[Habitica] me ha ayudado muchísimo a organizarme para terminar mi posgrado.", + "invalidEmail": "Para restablecer la contraseña, se necesita una dirección de correo electrónico válida.", + "irishfeet123Quote": "Antes llevaba fatal la limpieza de la casa: después de comer, dejaba todo tirado y había vasos por todas partes. Con [Habitica], ya no soy así.", + "joinOthers": "Alcanzar tus metas puede ser divertido: únete a las <%= userCount %> personas que ya lo han comprobado.", + "kazuiQuote": "Antes de usar [Habitica], estaba atascado con mi tesis e insatisfecho con mi disciplina personal: las tareas domésticas, aprender vocabulario, estudiar teoría de Go... Parece que dividir esas tareas en listas más pequeñas y manejables es la forma de mantenerme motivado y trabajar con constancia.", "landingadminlink": "paquetes administrativos", "landingend": "¿Todavía no estás convencido?", - "landingend2": "Vea una lista más detallada de", - "landingend3": "¿Estás buscando una experiencia más privada? Echale un vistazo a nuestros", - "landingend4": "que son perfectos para familias, maestros, grupos de apoyo, y negocios.", + "landingend2": "Mira una lista más detallada de", + "landingend3": ". ¿Buscas algo menos público? Echa un vistazo a nuestros", + "landingend4": "que son perfectos para familias, maestros, grupos de apoyo y empresas.", "landingfeatureslink": "nuestras características", - "landingp1": "El problema con la mayoría de las aplicaciones de productividad en el mercado es que no ofrecen ningún incentivo para seguir usándolas. ¡Habitica soluciona esto haciendo que crear hábitos sea divertido! Premiándote por tus éxitos y penalizándote por tus despistes, Habitica proporciona motivación externa para completar tus tareas diarias.", - "landingp2": "Cada vez que refuerces un hábito positivo, completes una tarea diaria o te encargues de una antigua tarea pendiente, HabitPRG te recompensa con puntos de experiencia y oro. Conforme vas ganando experiencia, subes de nivel, mejorando tus estadísticas y desbloqueando más características, como clases, mascotas... El oro se puede gastar en objetos que cambian tu experiencia de juego o en recompensas personalizadas que tu has creado para motivarte. Cuando los más pequeños éxitos te premian con una recompensa inmediata, tienes menos tendencia a dejar cosas sin hacer.", - "landingp2header": "Recompensa Inmediata", - "landingp3": "Siempre que realices un mal hábito o falles al completar una de tus tareas diarias, perderás salud. Si tu salud cae demasiado, perderás alguno de los progresos realizados. Proporcionando consecuencias inmediatas, Habitica puede ayudar a romper malos hábitos y ciclos de procrastinación antes de que causen problemas en el mundo real.", + "landingp1": "El problema con la mayoría de las aplicaciones de productividad que hay en el mercado es que no ofrecen ningún incentivo para seguir usándolas. Habitica sí, ya que hace que crear hábitos sea divertido. Te premia por tus éxitos y te penaliza por tus deslices, y es una motivación externa para completar tus tareas cotidianas.", + "landingp2": "Cada vez que refuerzas un hábito positivo, completas una tarea diaria o te encargas de una tarea que lleva mucho tiempo pendiente, Habitica te recompensa con puntos de experiencia y oro. Conforme vas ganando experiencia, subes de nivel, con lo que mejoran tus estadísticas y desbloqueas más funciones, como las clases, las mascotas... El oro se puede gastar en objetos que cambian tu forma de jugar o en las recompensas personalizadas que crees para motivarte. Cuando cualquier éxito, por pequeño que sea, te ofrece una recompensa inmediata, tienes menos tendencia a dejar las cosas sin hacer.", + "landingp2header": "Recompensa inmediata", + "landingp3": "Siempre que realices un mal hábito o no completes una de tus tareas diarias, perderás salud. Si tu salud empeora demasiado, perderás parte de tus progresos. En Habitica, las consecuencias de tus acciones son inmediatas, lo que puede ayudar a romper malos hábitos y ciclos de procrastinación antes de que causen problemas en el mundo real.", "landingp3header": "Consecuencias", - "landingp4": "Con una comunidad activa, Habitica proporciona la responsabilidad que necesitas para seguir tus tareas. Con el sistema de grupos, puedes traer a un grupo de amigos más cercanos para animarte. El sistema de gremios te permite encontrar gente con intereses u obstáculos similares, así puedes compartir tus metas e intercambiar trucos de cómo abordar tus problemas- En Habitica, la comunidad significa que tienes el apoyo y la responsabilidad que necesitar para triunfar.", + "landingp4": "En Habitica, con una comunidad de usuarios activos, te proporciona la responsabilidad que necesitas para no dejar de lado tus tareas. Con el sistema de grupos, puedes traer a un grupo de buenos amigos para animarte. El sistema de gremios te permite encontrar a gente con intereses u obstáculos similares, para que puedas compartir tus metas e intercambiar trucos sobre cómo abordar los problemas. En Habitica, gracias a la comunidad de usuarios, tienes el apoyo y la responsabilidad que necesitas para hacer las cosas bien.", "landingp4header": "Responsabilidad", - "leadText": "Habitica es un creador de hábitos gratuito y una aplicación de productividad que trata tu vida real como un juego. Con recompensas dentro del juego y castigos para motivarte y una sólida red social para inspirarte, Habitica puede ayudarte a alcanzar tus objetivos de volverte saludable, trabajador y feliz.", - "login": "Entrar", - "loginAndReg": "Entrar / Crear cuenta", - "loginFacebookAlt": "Entrar / Regístrate con Facebook", - "logout": "Cerrar Sesión", + "leadText": "Habitica es una aplicación gratuita de productividad y formación de hábitos que trata tu vida real como un juego. Con recompensas dentro del juego y castigos para motivarte, además de una sólida red social para inspirarte, Habitica te ayuda a alcanzar tus objetivos y llevar una vida más sana, responsable y feliz.", + "login": "Iniciar sesión", + "loginAndReg": "Iniciar sesión o registrarse", + "loginFacebookAlt": "Iniciar sesión o registrarse con Facebook", + "logout": "Cerrar sesión", "marketing1Header": "Mejora tus hábitos jugando", - "marketing1Lead1": "Habitica es un videojuego pensado para mejorar tus hábitos en la vida real que \"gamifica\" tu vida, convirtiendo todas tus tareas (hábitos, tareas diarias y pendientes) en pequeños monstruos que debes conquistar. Cuanto mejor eres en esto, más progresas en el juego. Si fallas en la vida real, tu personaje empezará a sufrir las consecuencias en el juego.", + "marketing1Lead1": "Habitica es un videojuego pensado para mejorar tus hábitos en la vida real que \"gamifica\" tu vida, convirtiendo todas tus tareas (hábitos, tareas diarias y pendientes) en pequeños monstruos a los que debes vencer. Cuanto mejor lo hagas, más progresarás en el juego. Si no cumples algo en la vida real, tu personaje empezará a sufrir las consecuencias en el juego.", "marketing1Lead2": "Obtén increíbles equipos. Mejora tus hábitos para construir tu avatar. Muestra el increíble armamento que has conseguido", "marketing1Lead2Title": "Obtén increíbles equipos", "marketing1Lead3": "Encuentra premios aleatorios. Para algunos, las apuestas son las que los motiva, en un sistema llamado \"recompensa estocástica\". Habitica se acomoda a todos los estilos de refuerzo: positivo, negativo, predictivo y aleatorio.", diff --git a/common/locales/es/generic.json b/common/locales/es/generic.json index 33098f3cb1..b3248b658e 100644 --- a/common/locales/es/generic.json +++ b/common/locales/es/generic.json @@ -1,14 +1,14 @@ { "languageName": "Español", - "stringNotFound": "Cadena de caracteres '<%= string %>' no se ha encontrado.", + "stringNotFound": "No se encontró la cadena '<%= string %>'.", "titleIndex": "Habitica | El juego de rol de tu vida", "habitica": "Habitica", - "titleTasks": "Quehaceres", + "titleTasks": "Tareas", "titleAvatar": "Avatar", - "titleBackgrounds": "Ambientes", - "titleStats": "Estadísticas y Logros", + "titleBackgrounds": "Fondos", + "titleStats": "Estadísticas y logros", "titleProfile": "Perfil", - "titleInbox": "Bandeja", + "titleInbox": "Bandeja de entrada", "titleTavern": "Taberna", "titleParty": "Grupo", "titleHeroes": "Salón de los Héroes", @@ -17,18 +17,18 @@ "titleChallenges": "Desafíos", "titleDrops": "Mercado", "titleQuests": "Misiones", - "titlePets": "Mascotes", + "titlePets": "Mascotas", "titleMounts": "Monturas", "titleEquipment": "Equipo", "titleTimeTravelers": "Viajeros del tiempo", "titleSeasonalShop": "Tienda de temporada", "titleSettings": "Configuración", - "expandToolbar": "Abrir barra de herramientas", - "collapseToolbar": "Cerrar barra de herramientas", + "expandToolbar": "Expandir barra de herramientas", + "collapseToolbar": "Contraer barra de herramientas", "markdownBlurb": "Habitica emplea el formato Markdown en los mensajes. Para obtener más información, consulta el resumen del formato Markdown.", "showFormattingHelp": "Mostrar ayuda de formato", "hideFormattingHelp": "Ocultar ayuda de formato", - "youType": "Escribes:", + "youType": "Si escribes:", "youSee": "Ves:", "italics": "*Cursiva*", "bold": "**Negrita**", @@ -36,90 +36,90 @@ "emojiExample": ":smile:", "markdownLinkEx": "[¡Habitica es genial!](https://habitica.com)", "markdownImageEx": "![texto alt obligatorio](https://habitica.com/cake.png \"título opcional al pasar el ratón\")", - "unorderedListHTML": "+ Primer objeto
+ Segundo objeto
+ Tercer objeto", - "unorderedListMarkdown": "+ Primer objeto\n+ Segundo objeto\n+ Tercer objeto", + "unorderedListHTML": "+ Primer elemento
+ Segundo elemento
+ Tercer elemento", + "unorderedListMarkdown": "+ Primer elemento\n+ Segundo elemento\n+ Tercer elemento", "code": "`código`", "achievements": "Logros", "modalAchievement": "¡Logro!", "special": "Especial", - "site": "Sitio", + "site": "Sitio web", "help": "Ayuda", "user": "Usuario", "market": "Mercado", - "subscriberItem": "Objeto Misterioso", - "newSubscriberItem": "Nuevo Objeto Misterioso", + "subscriberItem": "Objeto misterioso", + "newSubscriberItem": "Nuevo objeto misterioso", "subscriberItemText": "Cada mes, los suscriptores reciben un objeto misterioso, que se suele publicar una semana antes de cada fin de mes, aproximadamente. Para más información, consulta la página de la wiki sobre los objetos misteriosos.", "all": "Todo", "none": "Ninguno", "or": "O", "and": "y", "loginSuccess": "Has iniciado sesión.", - "youSure": "¿Estás seguro?", + "youSure": "¿Seguro?", "submit": "Enviar", "close": "Cerrar", - "saveAndClose": "Guardar y Cerrar", + "saveAndClose": "Guardar y cerrar", "cancel": "Cancelar", - "ok": "OK", + "ok": "Aceptar", "add": "Añadir", "undo": "Deshacer", "continue": "Continuar", "accept": "Aceptar", "reject": "Rechazar", "neverMind": "No importa", - "buyMoreGems": "Comprar más Gemas.", - "notEnoughGems": "No hay suficientes gemas.", - "alreadyHave": "Ups! Ya tienes este artículo. No necesitas comprarlo otra vez!", + "buyMoreGems": "Comprar más gemas", + "notEnoughGems": "No tienes suficientes gemas", + "alreadyHave": "Vaya, ya tienes este artículo. No necesitas comprarlo otra vez.", "delete": "Eliminar", "gemsPopoverTitle": "Gemas", "gems": "Gemas", - "gemButton": "Tienes <%= number %> Gemas.", + "gemButton": "Tienes <%= number %> gemas.", "moreInfo": "Más información", "showMoreMore": "(mostrar más)", "showMoreLess": "(mostrar menos)", - "gemsWhatFor": "¡Haz clic para comprar Gemas! Las Gemas te permiten comprar artículos especiales como Misiones, personalizaciones del avatar y equipo estacional.", + "gemsWhatFor": "¡Haz clic para comprar gemas! Las gemas te permiten comprar artículos especiales como misiones, personalizaciones del avatar y equipos de temporada.", "veteran": "Veterano", "veteranText": "Ha sobrevivido a Habit The Grey (nuestro sito web pre-Angular) y se ha ganado muchas cicatrices por sus fallos.", "originalUser": "¡Usuario original!", - "originalUserText": "Uno de los primeros usuarios... ¡digamos que fue un alpha tester!", - "habitBirthday": "Festejo de Cumpleaños de Habitica", - "habitBirthdayText": "Participó en el Festejo de Cumpleaños de Habitica", - "habitBirthdayPluralText": "¡Participó en <%= number %> Festejo(s) de Cumpleaños de Habitica!", + "originalUserText": "Uno de los primerísimos usuarios... ¡Este sí que es un alpha tester!", + "habitBirthday": "Fiesta de cumpleaños de Habitica", + "habitBirthdayText": "Participó en la fiesta de compleaños de Habitica", + "habitBirthdayPluralText": "¡Participó en <%= number %> fiestas de cumpleaños de Habitica!", "habiticaDay": "Bautizo de Habitica", - "habiticaDaySingularText": "¡Celebró el Bautizo de Habitica! Gracias por ser un magnífico usuario.", - "habiticaDayPluralText": "¡ Celebró <%= number %> Bautizos! Gracias por ser un magnífico usuario.", + "habiticaDaySingularText": "¡Celebró el bautizo de Habitica! Gracias por ser un usuario magnífico.", + "habiticaDayPluralText": "¡Celebró <%= number %> bautizos! Gracias por ser un usuario magnífico.", "achievementDilatory": "Salvador de Dilatoria", "achievementDilatoryText": "¡Ayudó a derrotar al Dread Drag'on de Dilatoria durante el evento Summer Splash del 2014!", "costumeContest": "Participante disfrazado", - "costumeContestText": "Ha participado en el Concurso de Disfraces de Habitoween. Puedes ver algunas de las candidaturas en el blog de Habitica.", + "costumeContestText": "Ha participado en el concurso de disfraces de Habitoween. Puedes ver algunas de las candidaturas en el blog de Habitica.", "costumeContestTextPlural": "Ha participado en <%= number %> concursos de disfraces de Habitoween. Mira algunas de las candidaturas en el blog de Habitica.", "memberSince": "- Miembro desde", - "lastLoggedIn": "- Última conexión", - "notPorted": "Esta función no se ha traido del sitio original", - "buyThis": "¿Comprar este <%= text %> con <%= price %> de tus <%= gems %> Gemas?", - "noReachServer": "Error en la conexión al servidor, intentalo más tarde.", + "lastLoggedIn": "- Última conexión el", + "notPorted": "Esta función aún no se ha traído del sitio original.", + "buyThis": "¿Quieres comprar este <%= text %> con <%= price %> de tus <%= gems %> gemas?", + "noReachServer": "No se puede acceder al servidor. Inténtalo de nuevo más tarde.", "errorUpCase": "ERROR:", "newPassSent": "Nueva contraseña enviada.", - "serverUnreach": "Error en la conexión al servidor.", + "serverUnreach": "No se puede acceder al servidor.", "requestError": "Vaya, se ha producido un error. Actualiza la página; puede que tu última acción no se haya guardado correctamente.", - "seeConsole": "Si el eror persiste, por favor reportelo en Ayuda> Notificar un error. Si eres familiar con la consola de tu navegador, por favor incluye los mensajes de error.", + "seeConsole": "Si el error persiste, informa de él en Ayuda > Notificar un error. Si sabes cómo usar la consola del navegador, incluye los mensajes de error.", "error": "Error", "menu": "Menú", "notifications": "Notificaciones", "noNotifications": "No tienes mensajes nuevos.", - "clear": "Cerrar", - "endTour": "Terminar Tour", + "clear": "Borrar", + "endTour": "Terminar visita guiada", "audioTheme": "Tema de audio", - "audioTheme_off": "Apagar", + "audioTheme_off": "Desactivado", "audioTheme_danielTheBard": "Daniel el Bardo", "audioTheme_wattsTheme": "Tema Watts", "audioTheme_gokulTheme": "Tema Gokul", "audioTheme_luneFoxTheme": "Tema LuneFox", - "askQuestion": "Hacer una Pregunta", + "askQuestion": "Hacer una pregunta", "reportBug": "Notificar un error", - "HabiticaWiki": "La Wiki de Habitica", - "HabiticaWikiFrontPage": "http://habitica.wikia.com/wiki/Habitica_Wiki", - "contributeToHRPG": "Contribuir con Habitica", - "overview": "Resumen para Nuevos Usuarios", + "HabiticaWiki": "La wiki de Habitica", + "HabiticaWikiFrontPage": "http://es.habitica.wikia.com/wiki/Habitica_Wiki", + "contributeToHRPG": "Colaborar con Habitica", + "overview": "Introducción para nuevos usuarios", "January": "Enero", "February": "Febrero", "March": "Marzo", @@ -132,44 +132,44 @@ "October": "Octubre", "November": "Noviembre", "December": "Diciembre", - "dateFormat": "Formato de Fecha", + "dateFormat": "Formato de fecha", "achievementStressbeast": "Salvador de Stoïkalm", "achievementStressbeastText": "¡Ayudó a derrotar a la Abominable Bestia del Estrés durante el evento Winter Wonderland de 2014!", "achievementBurnout": "Salvador de los Campos Florecientes", "achievementBurnoutText": "¡Ayudó a derrotar al Burnout y restaurar los Espíritus del Cansancio durante el evento Fall Festival de 2015!", - "achievementBewilder": "Salvador/a de Calavuelos", + "achievementBewilder": "Salvador de Calavuelos", "achievementBewilderText": "¡Ayudó a derrotar al Apa-bullador durante el Evento de Primavera de 2016!", - "checkOutProgress": "¡Comprobar mi progreso en Habitica!", + "checkOutProgress": "Consultar mi progreso en Habitica", "cardReceived": "¡Recibiste una tarjeta!", "cardReceivedFrom": "<%= cardType %> de <%= userName %>", - "greetingCard": "Tarjeta de Felicitación", + "greetingCard": "Tarjeta de saludo", "greetingCardExplanation": "Ambos recibís el logro Alegres Amigotes", - "greetingCardNotes": "Enviar una tarjeta de felicitación a un miembro del grupo.", - "greeting0": "¡Hola, tú!", + "greetingCardNotes": "Enviar una tarjeta para mandar un saludo a un miembro del grupo.", + "greeting0": "¡Hola!", "greeting1": "Solo quería saludar :)", "greeting2": "(saluda con emoción)", "greeting3": "¡Ey!", "greetingCardAchievementTitle": "Alegre Amigote", - "greetingCardAchievementText": "¡Felicidades! Envió o recibió <%= cards %> tarjetas de felicitación.", - "thankyouCard": "Tarjeta de Agradecimiento", - "thankyouCardExplanation": "Los dos recibiréis el logro Agradablemente Agradecido", + "greetingCardAchievementText": "¿Qué tal? Envió o recibió <%= cards %> tarjetas de saludo.", + "thankyouCard": "Tarjeta de agradecimiento", + "thankyouCardExplanation": "Los dos recibís el logro Agradablemente Agradecido", "thankyouCardNotes": "Envía una tarjeta de agradecimiento a un miembro de tu grupo.", "thankyou0": "¡Muchas gracias!", "thankyou1": "¡Gracias, gracias, gracias!", "thankyou2": "¡Un millón de gracias!", - "thankyou3": "Estoy muy agradecido - ¡gracias!", + "thankyou3": "Estoy muy agradecido: ¡gracias!", "thankyouCardAchievementTitle": "Agradablemente Agradecido", "thankyouCardAchievementText": "¡Gracias por las gracias! Envió o recibió <%= cards %> tarjetas de agradecimiento.", "birthdayCard": "Tarjeta de cumpleaños", "birthdayCardExplanation": "Ambos recibís el logro Prosperidad Cumpleañera", - "birthdayCardNotes": "Enviar una tarjeta de cumpleaños a un miembro del grupo.", + "birthdayCardNotes": "Envía una tarjeta de cumpleaños a un miembro del grupo.", "birthday0": "¡Feliz cumpleaños!", "birthdayCardAchievementTitle": "Prosperidad Cumpleañera", "birthdayCardAchievementText": "¡Que cumplas muchos más! Ha enviado o recibido <%= cards %> felicitaciones de cumpleaños.", "streakAchievement": "¡Has obtenido un logro de racha!", "firstStreakAchievement": "Racha de 21 días", "streakAchievementCount": "<%= streaks %> rachas de 21 días", - "twentyOneDays": "¡Has completado tus Tareas Diarias durante 21 días seguidos!", + "twentyOneDays": "¡Has completado tus tareas diarias durante 21 días seguidos!", "dontBreakStreak": "Excelente trabajo. ¡No estropees la racha!", "dontStop": "¡No te detengas ahora!", "levelUpShare": "¡Subí de nivel en Habitica al mejorar en mis hábitos en la vida real!", diff --git a/common/locales/es/groups.json b/common/locales/es/groups.json index fec6b4cc1c..5a9b518c7a 100644 --- a/common/locales/es/groups.json +++ b/common/locales/es/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/es/limited.json b/common/locales/es/limited.json index ce4961ca2a..f031e52134 100644 --- a/common/locales/es/limited.json +++ b/common/locales/es/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amigos Molestos", "annoyingFriendsText": "Tus compañeros te han lanzado <%= snowballs %> bolas de nieve.", "alarmingFriends": "Amigos Alarmantes", - "alarmingFriendsText": "Tus compañeros the han asustado <%= spookDust %> veces.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Amigos Agrícolas", "agriculturalFriendsText": "Tus compañeros te han transformado en flor <%= seeds %> veces.", "aquaticFriends": "Amigos Acuáticos", diff --git a/common/locales/es/messages.json b/common/locales/es/messages.json index 5774aaf4e1..f3676b9118 100644 --- a/common/locales/es/messages.json +++ b/common/locales/es/messages.json @@ -10,7 +10,7 @@ "messageAlreadyMount": "Ya tienes esa montura. Intenta alimentar a otra mascota.", "messageEvolve": "Has dominado a <%= egg %>, ¡vamos a dar una vuelta!", "messageLikesFood": "¡A <%= egg %> le encanta <%= foodText %>!", - "messageDontEnjoyFood": "<%= egg %> come <%= foodText %> pero no parece disfrutarlo.", + "messageDontEnjoyFood": "Tu <%= egg %> come <%= foodText %>, pero no parece que le guste.", "messageBought": "Has comprado <%= itemText %>", "messageEquipped": "<%= itemText %> equipado.", "messageUnEquipped": "Te has quitado <%= itemText %>.", diff --git a/common/locales/es/npc.json b/common/locales/es/npc.json index 56cdd5220d..cd1804d290 100644 --- a/common/locales/es/npc.json +++ b/common/locales/es/npc.json @@ -14,7 +14,7 @@ "displayItemForGold": "¿Quieres vender tu <%= itemType %>?", "displayEggForGold": "¿Quieres vender un huevo <%= itemType %>?", "displayPotionForGold": "¿Quieres vender una poción <%= itemType %>?", - "sellForGold": "Véndelo por <%= gold %> de oro", + "sellForGold": "Venderlo por <%= gold %> de oro", "buyGems": "Comprar Gemas", "purchaseGems": "Comprar gemas", "justin": "Justin", @@ -42,31 +42,31 @@ "alreadyUnlocked": "Full set already unlocked.", "alreadyUnlockedPart": "Full set already partially unlocked.", "USD": "(USD)", - "newStuff": "Cosas Nuevas", - "cool": "Ya me lo dirás", - "dismissAlert": "Ignora Esta Alerta", + "newStuff": "Cosas nuevas", + "cool": "Posponer", + "dismissAlert": "Descartar este aviso", "donateText1": "Añade 20 Gemas a tu cuenta. Las Gemas se usan para comprar objetos especiales en el juego, como camisetas y peinados.", "donateText2": "Ayuda a Habitica", "donateText3": "Habitica es un proyecto de código abierto que depende del soporte de sus usuarios. El dinero que gastes en gemas nos ayuda a mantener activos los servidores, mantener al pequeño grupo de personal, desarrollar nuevas caracteristicas y proveer incentivos para nuestros programadores voluntarios. ¡Gracias por tu generosidad!", - "donationDesc": "20 Gemas, Donación a Habitica", + "donationDesc": "20 gemas, donación a Habitica", "payWithCard": "Pagar con una tarjeta de crédito", "payNote": "Nota: Paypal a veces toma mucho tiempo autorizando. Recomendamos pagar con tarjeta de crédito.", "card": "Tarjeta de crédito (a través de Stripe)", - "amazonInstructions": "Haz click en el botón para pagar con Amazon Payments", + "amazonInstructions": "Haz clic en el botón para pagar con Amazon Payments", "paymentMethods": "Comprar con", "classGear": "Equipo de clase", "classGearText": "Antes que nada, no te preocupes. El equipamiento que tenías está en tu inventario y ahora llevas el equipo de aprendiz de tu nueva clase. Si llevas objetos pertenecientes a tu clase, obtendrás una bonificación del 50% en tus estadísticas. Pero, si quieres, puedes volver a usar el equipamiento anterior sin ningún problema.", "classStats": "Éstas son las estadísticas de tu clase; afectan al gameplay. Cada vez que subas de nivel obtendrás un punto para asignar a una estadística en concreto. Mueve el puntero sobre cada estadística para más información.", - "autoAllocate": "Asignación Automática", + "autoAllocate": "Asignación automática", "autoAllocateText": "Si 'distribución automática' está seleccionado, tu avatar gana estadísticas automáticamente basado en los atributos de sus tareas, que puedes encontrar en TAREA > Editar > Avanzado > Atributos Por ejemplo, si haces ejercicio con frecuencia, y si tu Diaria de 'Ejercicio' está fijado en 'Físico', vas a ganar Fuerza automáticamente.", "spells": "Hechizos", "spellsText": "Ahora podrás desbloquear hechizos especificos para tu clase. El primero se desbloquea a nivel 11. Tu maná se rellena 10 puntos al día, mas 1 punto por cada completado", - "toDo": "Tarea Pendiente", - "moreClass": "Para mas información sobre el sistema de clase, mira en", + "toDo": "Tarea pendiente", + "moreClass": "Para ver más información sobre el sistema de clases, consulta", "tourWelcome": "¡Bienvenido a Habitica! Esta es tu lista de Tareas Pendientes. ¡Tacha alguna tarea realizada para continuar!", "tourExp": "¡Buen trabajo! ¡Tachar tareas te da Experiencia y Oro!", "tourDailies": "Esta columna es la de las tareas diarias. Para continuar, añade una tarea que debas hacer todos los días. Ejemplos de tareas diarias: hacer la cama, usar hilo dental o consultar el correo del trabajo", - "tourCron": "¡Espléndido! Tus Tareas Diarias se restaurarán cada día.", + "tourCron": "¡Espléndido! Tus tareas diarias se restaurarán cada día.", "tourHP": "¡Atención! Si no completas tus Tareas Diarias antes de medianoche, ¡te dañarán!", "tourHabits": "¡Esta columna es para los buenos y malos hábitos que haces varias veces al día! Para continuar haz click al lápiz para editar los nombres, después haz click en el tick para guardarlo.", "tourStats": "¡Los Buenos Hábitos dan Experiencia y Oro! Los Malos Hábitos quitan vida.", @@ -89,7 +89,7 @@ "tourAwesome": "¡Estupendo!", "tourSplendid": "¡Esplendido!", "tourNifty": "¡Ingenioso!", - "tourAvatarProceed": "¡Muestrame mis tareas!", + "tourAvatarProceed": "¡Muéstrame mis tareas!", "tourToDosBrief": "Lista de Tareas Pendientes
  • ¡Marca las Tareas Pendientes para ganar Oro y Experiencia!
  • Las Tareas Pendientes nunca hacen que tu avatar pierda Salud.
", "tourDailiesBrief": "Tareas Diarias
  • Las Tareas Diarias se repiten cada día.
  • Pierdes Salud si te saltas las Tareas Diarias.
", "tourDailiesProceed": "¡Tendré cuidado!", diff --git a/common/locales/es/pets.json b/common/locales/es/pets.json index 8ba712dda2..c9a4ae19bf 100644 --- a/common/locales/es/pets.json +++ b/common/locales/es/pets.json @@ -12,6 +12,7 @@ "etherealLion": "León Etéreo", "veteranWolf": "Lobo Veterano", "veteranTiger": "Tigre veterano", + "veteranLion": "Veteran Lion", "cerberusPup": "Cachorro de Cerbero", "hydra": "Hidra", "mantisShrimp": "Mantis marina", @@ -62,7 +63,7 @@ "hatchedPet": "¡Ha nacido un/a <%= egg %> <%= potion %>!", "displayNow": "Llevar ahora", "displayLater": "Llevar más adelante", - "petNotOwned": "You do not own this pet.", + "petNotOwned": "No eres dueño de esta mascota.", "earnedCompanion": "Tu productividad te ha granjeado un nuevo acompañante. Aliméntalo y crecerá.", "feedPet": "¿Dar de comer <%= article %><%= text %>a su <%= name %>?", "useSaddle": "¿Ensillar <%= pet %>?", @@ -84,8 +85,8 @@ "petKeyBoth": "Soltar ambas", "confirmPetKey": "¿Está seguro?", "petKeyNeverMind": "Aún no", - "petsReleased": "Pets released.", - "mountsAndPetsReleased": "Mounts and pets released", - "mountsReleased": "Mounts released", + "petsReleased": "Mascotas liberadas.", + "mountsAndPetsReleased": "Monturas y mascotas liberadas.", + "mountsReleased": "Monturas liberadas.", "gemsEach": "gemas cada uno" } \ No newline at end of file diff --git a/common/locales/es/settings.json b/common/locales/es/settings.json index 67f8c06853..53ba76f0ed 100644 --- a/common/locales/es/settings.json +++ b/common/locales/es/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Comienzo de Día Personalizado", "changeCustomDayStart": "¿Cambiar inicio de día?", "sureChangeCustomDayStart": "¿Seguro que quieres cambiar la hora a la que empieza cada día?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Tus tareas diarias se restablecerán la próxima vez que uses Habitica a partir del <%= time %>. ¡No te olvides de completar las tareas diarias antes de esa hora!", "customDayStartInfo1": "De forma predeterminada, Habitica comprueba si has realizado las tareas diarias y las restablece todos los días a medianoche, de acuerdo con tu zona horaria. Aquí puedes cambiar la hora a la que lo hace.", "misc": "Varios", diff --git a/common/locales/es/spells.json b/common/locales/es/spells.json index 0470f636dc..6f2783ea06 100644 --- a/common/locales/es/spells.json +++ b/common/locales/es/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "¡Lanza una bola de nieve a un miembro de tu grupo! ¿Que podría salir mal? Dura hasta el final del día de ese miembro.", "spellSpecialSaltText": "Sal", "spellSpecialSaltNotes": "Alguien te ha lanzado una bola de nieve. Ja Ja, muy gracioso. ¡Ahora quítame esto de encima!", - "spellSpecialSpookDustText": "Brillantina espeluznante", - "spellSpecialSpookDustNotes": "¡Convierte un amigo en una manta flotante con ojos!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Poción Opaco", "spellSpecialOpaquePotionNotes": "Cancela los efectos de la brillantina espeluznante.", "spellSpecialShinySeedText": "Semilla Brillante", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/es/subscriber.json b/common/locales/es/subscriber.json index 6451f8f383..4c07e05c64 100644 --- a/common/locales/es/subscriber.json +++ b/common/locales/es/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/es/tasks.json b/common/locales/es/tasks.json index 965d4f5e1a..d35e8c9e6b 100644 --- a/common/locales/es/tasks.json +++ b/common/locales/es/tasks.json @@ -4,36 +4,36 @@ "deleteToDosExplanation": "Si le das clic al botón de abajo, todas sus tareas completadas y tareas archivadas se borrarán permanentemente. Expórtalas primero si quieres llevar un registro de ellas.", "addmultiple": "Añade varias", "addsingle": "Añade una", - "habit": "hábito", + "habit": "Hábito", "habits": "Hábitos", - "newHabit": "Nuevo Hábito", - "newHabitBulk": "Hábitos Nuevos (uno por línea)", + "newHabit": "Nuevo hábito", + "newHabitBulk": "Nuevos hábitos (uno por línea)", "yellowred": "Débil", "greenblue": "Fuerte", "edit": "Editar", "save": "Guardar", - "addChecklist": "Aňadir Lista", + "addChecklist": "Aňadir lista", "checklist": "Lista", "checklistText": "¡Divide una tarea en pedazos más pequeños! Las listas aumentan la Experiencia y el Oro conseguidos por una Tarea Pendiente y reducen el daño causado por una Diaria.", - "expandCollapse": "Expandir/Colapsar", + "expandCollapse": "Expandir/contraer", "text": "Título", - "extraNotes": "Notas Extra", - "direction/Actions": "Dirección/Acciones", - "advancedOptions": "Opciones Avanzadas", + "extraNotes": "Notas adicionales", + "direction/Actions": "Dirección/acciones", + "advancedOptions": "Opciones avanzadas", "difficulty": "Dificultad", "difficultyHelpTitle": "¿Qué dificultad tiene esta tarea?", "difficultyHelpContent": "Cuanto más dura sea una tarea, más Experiencia y Oro ganas al completarla... ¡pero más daño hace si es una Diaria o un Mal Hábito!", "trivial": "Trivial", "easy": "Fácil", - "medium": "Intermedio", + "medium": "Intermedia", "hard": "Difícil", "attributes": "Atributos", - "physical": "Físicos", + "physical": "Físicas", "mental": "Mentales", "otherExamples": "Ej, actividades profesional, pasatiempos, finanzas, etc.", "progress": "Progreso", - "daily": "tarea diaria", - "dailies": "tareas Diarias", + "daily": "Tarea diaria", + "dailies": "Tareas diarias", "newDaily": "Nueva tarea Diaria", "newDailyBulk": "Nuevas Diarias (una por línea)", "streakCounter": "Contador de Rachas", @@ -73,6 +73,7 @@ "clearTags": "Limpiar", "hideTags": "Ocultar", "showTags": "Mostrar", + "toRequired": "You must supply a to value", "startDate": "Fecha de Inicio", "startDateHelpTitle": "¿Cuándo debería empezar esta tarea?", "startDateHelp": "Fija la fecha para la que esta tarea toma efecto. No se acabará el plazo en días anteriores.", diff --git a/common/locales/es_419/groups.json b/common/locales/es_419/groups.json index e091d764a9..a8171a95da 100644 --- a/common/locales/es_419/groups.json +++ b/common/locales/es_419/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/es_419/limited.json b/common/locales/es_419/limited.json index 1bfe1cbca8..ba5e9828da 100644 --- a/common/locales/es_419/limited.json +++ b/common/locales/es_419/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amigos Molestos", "annoyingFriendsText": "Fue alcanzado por <%= snowballs %> bolas de nieve lanzadas por sus compañeros de equipo.", "alarmingFriends": "Amigos Alarmantes", - "alarmingFriendsText": "Fue espantado <%= spookDust %> veces por sus compañeros de equipo.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Amigos Agrícolas", "agriculturalFriendsText": "Fue transformado en una flor <%= seeds %> veces por sus compañeros de equipo.", "aquaticFriends": "Amigos Acuáticos", diff --git a/common/locales/es_419/maintenance.json b/common/locales/es_419/maintenance.json index efdb524cd2..a13177bc4d 100644 --- a/common/locales/es_419/maintenance.json +++ b/common/locales/es_419/maintenance.json @@ -1,34 +1,34 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", - "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "habiticaBackSoon": "No te preocupes, ¡Habitica volverá pronto!", + "importantMaintenance": "Estamos realizando tareas importantes de mantenimiento que creemos que durarán hasta el <%= localDate %> en tu zona horaria.", + "maintenance": "Mantenimiento", + "maintenanceMoreInfo": "¿Quieres saber más sobre el mantenimiento? <%= linkStart %>Echa un vistazo a nuestra página de información<%= linkEnd %>.", + "noDamageKeepStreaks": "¡NO sufrirás ningún daño ni perderás tus rachas!", + "thanksForPatience": "¡Gracias por tu paciencia!", + "twitterMaintenanceUpdates": "Para las actualizaciones más recientes, sigue nuestro Twitter, donde estaremos publicando información sobre el estado del mantenimiento.", + "veteranPetAward": "Cuando terminemos, ¡recibirás una mascota veterana!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", - "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", - "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", - "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", - "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", - "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", - "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", - "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", - "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", - "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", - "maintenanceInfoSeeTasks": "What if I need to see my task list?", - "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", - "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", - "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", - "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." + "maintenanceInfoTitle": "Información sobre próximas tareas de mantenimiento en Habitica", + "maintenanceInfoWhat": "¿Qué va a ocurrir?", + "maintenanceInfoWhatText": "El 21 de mayo, Habitica estará fuera de servicio por mantenimiento la mayor parte del día. No sufrirás ningún daño ni nada perjudicará a tu cuenta durante ese fin de semana, ¡incluso aunque no puedas iniciar sesión para marcar tus Diarias a tiempo! Nosotros estaremos trabajando mucho para que esta interrupción dure lo menos posible e iremos publicando los avances en nuestra cuenta de Twitter. Cuando finalice este proceso, ¡todos recibirán una mascota rara como gesto de agradecimiento por su paciencia!", + "maintenanceInfoWhy": "¿Por qué ocurrirá esto?", + "maintenanceInfoWhyText": "Durante varios meses estuvimos renovando Habitica tras bambalinas. Para ser específicos, hemos reescrito la API. Aunque tal vez por fuera no parezca muy distinta, por dentro es un mundo completamente nuevo. ¡Esto nos permitirá tener MUCHÍSIMA más flexibilidad para añadir funciones de ahora en adelante, además de mejorar el rendimiento!", + "maintenanceInfoTechDetails": "¿Quieres más detalles sobre el lado técnico del proceso? Visita La Forja, nuestro blog de desarrolladores.", + "maintenanceInfoMore": "Más información", + "maintenanceInfoAccountChanges": "¿Qué cambios notaré en mi cuenta cuando el mantenimiento termine?", + "maintenanceInfoAccountChangesText": "Al principio, no habrá cambios importantes aparte de las mejoras en el rendimiento de algunas funciones, como los desafíos. Si observas algún cambio que no debería haber aparecido, escríbenos a admin@habitica.com ¡y nos encargaremos de investigarlo!", + "maintenanceInfoAddFeatures": "¿Qué tipo de funciones se podrán añadir a Habitica después de esto?", + "maintenanceInfoAddFeaturesText": "Al completar el mantenimiento, podremos empezar a crear un chat y unos gremios mejorados, planes para organizaciones y familias, y funciones de productividad adicionales, ¡como tareas mensuales y la posibilidad de registrar lo que hiciste el día anterior! Todas estas funciones son bastante complejas, de modo que llevará tiempo desarrollarlas, pero sin tener terminada esta reescritura, es imposible empezar con ello.", + "maintenanceInfoHowLong": "¿Cuánto durará el mantenimiento?", + "maintenanceInfoHowLongText": "Tenemos que migrar las tareas y los datos de 1,3 millones de usuarios de Habitica -- ¡no es una tarea fácil! Calculamos que esto tendrá lugar aproximadamente entre la 1 pm (8 pm UTC) y las 10 pm (5 am UTC), Hora Estándar del Pacífico. Por supuesto, ¡haremos todo lo que esté en nuestras manos por terminar lo antes posible! Puedes seguir los avances en nuestro Twitter.", + "maintenanceInfoStatsAffected": "¿Cómo serán afectadas mis Diarias, rachas, mejoras y misiones?", + "maintenanceInfoStatsAffectedText1": "NO sufrirás ningún daño ni perderás ninguna racha ese fin de semana, pero aparte de eso, ¡el día se reiniciará normalmente! Las Diarias que marcaste se desmarcarán, las mejoras se restablecerán, etc. Si estás participando en una misión de recolección, seguirás encontrando objetos. Si te encuentras en una batalla contra un Jefe, seguirás infligiéndole daño, pero él no te hará daño a ti. (¡Hasta los monstruos necesitan un descanso!)", + "maintenanceInfoStatsAffectedText2": "Después de mucho pensar, llegamos a la conclusión de que ésta era la forma más justa de actuar ante el hecho de que muchos usuarios no podrán marcar sus Diarias normalmente durante el mantenimiento. ¡Sentimos las molestias que esto pueda causar!", + "maintenanceInfoSeeTasks": "¿Qué hago si necesito ver mi lista de tareas?", + "maintenanceInfoSeeTasksText": "Si sabes que vas a necesitar tu lista de tareas el sábado para recordar lo que tienes que hacer, te recomendamos que antes de que comience el mantenimiento hagas una captura de pantalla de tus tareas para poder consultarla luego.", + "maintenanceInfoRarePet": "¿Qué tipo de mascota rara recibiré?", + "maintenanceInfoRarePetText": "Como agradecimiento por su paciencia durante este proceso, todos recibirán una mascota rara veterana. Si nunca has recibido una mascota veterana, obtendrás un Lobo Veterano. Si ya tienes uno, recibirás un Tigre Veterano. Y si ya tienes ambos, ¡obtendrás una mascota veterana nunca antes vista! Completada la migración, es posible que la mascota tarde unas horas en aparecer, pero no temas, todo el mundo recibirá una.", + "maintenanceInfoWho": "¿Quién trabajó en este enorme proyecto?", + "maintenanceInfoWhoText": "¡Nos alegra que preguntes! El cabecilla de todo esto es nuestro increíble colaborador paglias, con la inmensa ayuda de Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown y Alys.", + "maintenanceInfoTesting": "La nueva versión también fue testeada de forma incansable por un grupo de nuestros excelentes voluntarios de código abierto. Gracias -- no podríamos haber hecho esto sin ustedes." } diff --git a/common/locales/es_419/pets.json b/common/locales/es_419/pets.json index 995d6e92a5..8a91740ab2 100644 --- a/common/locales/es_419/pets.json +++ b/common/locales/es_419/pets.json @@ -12,6 +12,7 @@ "etherealLion": "León Etéreo", "veteranWolf": "Lobo Veterano", "veteranTiger": "Tigre Veterano ", + "veteranLion": "Veteran Lion", "cerberusPup": "Cachorro Cerbero", "hydra": "Hidra", "mantisShrimp": "Mantis Marina", diff --git a/common/locales/es_419/settings.json b/common/locales/es_419/settings.json index 880effc4f8..cae930985e 100644 --- a/common/locales/es_419/settings.json +++ b/common/locales/es_419/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Comienzo de día personalizado", "changeCustomDayStart": "¿Cambiar día de inicio personalizado?", "sureChangeCustomDayStart": "¿Estás seguro de que quieres cambiar tu día de inicio personalizado?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Tus Diarias se reiniciarán la primera vez que uses Habitica después de <%= time %>. ¡Asegúrate de haber completado tus Diarias antes de esa hora!", "customDayStartInfo1": "Habitica por defecto chequea y reinicia tus Diarias a la medianoche en tu propia zona horaria cada día. Puedes personalizar ese horario aquí.", "misc": "Varios", diff --git a/common/locales/es_419/spells.json b/common/locales/es_419/spells.json index 75dd41e535..d82e355a3b 100644 --- a/common/locales/es_419/spells.json +++ b/common/locales/es_419/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "¡Lanza una bola de nieve a un miembro de tu equipo! ¿Qué podría salir mal? Dura hasta el nuevo día de tu compañero.", "spellSpecialSaltText": "Sal", "spellSpecialSaltNotes": "Alguien te ha lanzado una bola de nieve. Ja ja, muy gracioso. ¡Ahora quítame esta nieve de encima!", - "spellSpecialSpookDustText": "Brillantina Espeluznante", - "spellSpecialSpookDustNotes": "¡Convierte a un amigo en una manta flotante con ojos!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Poción Opaca", "spellSpecialOpaquePotionNotes": "Cancela los efectos de la Brillantina Espeluznante.", "spellSpecialShinySeedText": "Semilla Radiante", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/es_419/subscriber.json b/common/locales/es_419/subscriber.json index 54211c9084..db8b583c47 100644 --- a/common/locales/es_419/subscriber.json +++ b/common/locales/es_419/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/es_419/tasks.json b/common/locales/es_419/tasks.json index d49ed5fdba..83bdbbafd6 100644 --- a/common/locales/es_419/tasks.json +++ b/common/locales/es_419/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Borrar", "hideTags": "Ocultar", "showTags": "Mostrar", + "toRequired": "You must supply a to value", "startDate": "Día de inicio", "startDateHelpTitle": "¿Cuándo debería comenzar esta tarea?", "startDateHelp": "Ajusta la fecha en la que esta tarea comenzará a tener efecto. No estará prevista para días anteriores.", diff --git a/common/locales/fr/content.json b/common/locales/fr/content.json index 58c87201e7..e23935ffc9 100644 --- a/common/locales/fr/content.json +++ b/common/locales/fr/content.json @@ -116,8 +116,8 @@ "questEggFalconText": "Faucon", "questEggFalconMountText": "Faucon", "questEggFalconAdjective": "un rapide", - "questEggTreelingText": "Arbuste", - "questEggTreelingMountText": "Arbuste", + "questEggTreelingText": "Arbrisseau", + "questEggTreelingMountText": "Arbrisseau", "questEggTreelingAdjective": "un feuillu", "eggNotes": "Trouvez une potion d’éclosion à verser sur cet œuf et il en sortira <%= eggAdjective(locale) %> <%= eggText(locale) %>.", "hatchingPotionBase": "de Base", diff --git a/common/locales/fr/front.json b/common/locales/fr/front.json index 976a5b92f7..e07c058643 100644 --- a/common/locales/fr/front.json +++ b/common/locales/fr/front.json @@ -184,7 +184,7 @@ "zelahQuote": "Avec [Habitica], je me suis persuadé d'aller au lit à l'heure avec la simple idée de gagner des points en me couchant tôt ou de perdre de la santé en me couchant tard !", "reportAccountProblems": "Signaler un problème sur votre compte", "reportCommunityIssues": "Signaler un problème au niveau de la Communauté", - "subscriptionPaymentIssues": "Problèmes d'inscription et de paiement", + "subscriptionPaymentIssues": "Problèmes d'abonnement et de paiement", "generalQuestionsSite": "Questions générales sur le Site", "businessInquiries": "Demandes pour les entreprises", "merchandiseInquiries": "Demandes commerciales", diff --git a/common/locales/fr/gear.json b/common/locales/fr/gear.json index 4a907d7155..790a9e9374 100644 --- a/common/locales/fr/gear.json +++ b/common/locales/fr/gear.json @@ -190,7 +190,7 @@ "weaponArmoireBasicLongbowText": "Arc long de base", "weaponArmoireBasicLongbowNotes": "Un arc facile à prendre en main. Augmente la Force de <%= str %>. Armoire enchantée : Ensemble de l'Archer de base (Objet 1 sur 3).", "weaponArmoireHabiticanDiplomaText": "Diplôme d'Habiticien·ne", - "weaponArmoireHabiticanDiplomaNotes": "Le certificat d'un succès significatif -- Bravo ! Augmente l'intelligence de <%= int %>. Armoire enchantée : Ensemble du diplomé (objet 1 de 3)", + "weaponArmoireHabiticanDiplomaNotes": "Le certificat d'un succès significatif. Bravo ! Augmente l'intelligence de <%= int %>. Armoire enchantée : Ensemble du diplomé (objet 1 de 3)", "armor": "armure", "armorBase0Text": "Habit simple", "armorBase0Notes": "Un vêtement ordinaire. N'apporte aucun avantage.", @@ -367,7 +367,7 @@ "armorMystery201603Text": "Costume Chanceux", "armorMystery201603Notes": "Ce costume est cousu avec des milliers de trèfles à quatre feuilles ! N'apporte aucun bonus. Équipement d'abonné de mars 2016.", "armorMystery201604Text": "Armure de feuilles", - "armorMystery201604Notes": "Vous aussi, vous pouvez être un petit mais effroyable petit bouquet de feuilles. Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.", + "armorMystery201604Notes": "Vous aussi, vous pouvez être un petit mais effroyable bouquet de feuilles. Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.", "armorMystery301404Text": "Tenue Steampunk", "armorMystery301404Notes": "Pimpant et fringuant ! N'apporte aucun bonus. Équipement d'abonné·e de février 3015.", "armorArmoireLunarArmorText": "Armure Lunaire Apaisante", @@ -398,8 +398,8 @@ "armorArmoireMinerOverallsNotes": "Elle semble peut-être usée, mais elle contient un enchantement qui repousse la poussière. Augmente la Constitution de <%= con %> points. Armoire Enchantée : Ensemble du Mineur (Objet 2 sur 3).", "armorArmoireBasicArcherArmorText": "Armure d'Archer de base", "armorArmoireBasicArcherArmorNotes": "Cette veste de camouflage vous permet de vous faufiler inaperçu dans les forêts. Augmente la Perception de <%= per %>. Armoire enchantée : Ensemble de l'Archer de base (Objet 2 sur 3).", - "armorArmoireGraduateRobeText": "Robe de diplômé", - "armorArmoireGraduateRobeNotes": "Félicitations ! Cette épaisse robe pèse lourd de toutes les connaissances que vous avez acquises. Augmente l'Intelligence de <%= int %>. Armoire enchantée : Ensemble du diplômé (objet 2 de 3).", + "armorArmoireGraduateRobeText": "Toge de diplômé", + "armorArmoireGraduateRobeNotes": "Félicitations ! Cette lourde toge porte toutes les connaissances que vous avez acquises. Augmente l'Intelligence de <%= int %>. Armoire enchantée : Ensemble du diplômé (objet 2 de 3).", "headgear": "coiffe", "headBase0Text": "Pas de casque", "headBase0Notes": "Pas de couvre-chef.", @@ -683,7 +683,7 @@ "shieldSpecialWinter2015HealerText": "Bouclier Apaisant", "shieldSpecialWinter2015HealerNotes": "Ce bouclier dévie le vent glacial. Augmente la Constitution de <%= con %> points. Équipement en édition limitée de l'hiver 2014-2015.", "shieldSpecialSpring2015RogueText": "Couinement explosif", - "shieldSpecialSpring2015RogueNotes": "Ne vous laissez pas duper par le son - ces explosifs font mal. Augmente la Force de <%= str %> points. Équipement en édition limitée du printemps 2015.", + "shieldSpecialSpring2015RogueNotes": "Ne vous laissez pas duper par le son – ces explosifs font mal. Augmente la Force de <%= str %> points. Équipement en édition limitée du printemps 2015.", "shieldSpecialSpring2015WarriorText": "Disque gamelle", "shieldSpecialSpring2015WarriorNotes": "Lancez-le vers vos ennemis... ou ne faites que le tenir, parce qu'il va se remplir de délicieuse nourriture pour chien à l'heure du repas. Augmente la Constitution de <%= con %> points. Équipement en édition limitée du printemps 2015.", "shieldSpecialSpring2015HealerText": "Coussin décoré", @@ -727,7 +727,7 @@ "shieldArmoireMysticLampText": "Lampe Mystique", "shieldArmoireMysticLampNotes": "Illuminez les cavernes les plus sombres grâce à cette lampe mystique ! Augmente la Perception de <%= per %> points. Armoire Enchantée : Objet Indépendant.", "shieldArmoireFloralBouquetText": "Bouquet de fleurs", - "shieldArmoireFloralBouquetNotes": "Ne sont pas très utiles dans une bataille mais ne sont-elles pas belles ? Augmente la Constitution de <%= con %> points. Armoire Enchantée : Objet Indépendant.", + "shieldArmoireFloralBouquetNotes": "Ne sont pas très utiles dans une bataille, mais ne sont-elles pas belles ? Augmente la Constitution de <%= con %> points. Armoire Enchantée : Objet Indépendant.", "back": "Accessoire de Dos", "backBase0Text": "Pas d’accessoire dorsal", "backBase0Notes": "Pas d’accessoire dorsal.", @@ -833,19 +833,19 @@ "eyewearBase0Text": "Pas de Lunettes", "eyewearBase0Notes": "Pas de Lunettes.", "eyewearSpecialBlackTopFrameText": "Lunettes normales noires", - "eyewearSpecialBlackTopFrameNotes": "Des lunettes cerclées de noir. Ne confère aucun bonus.", + "eyewearSpecialBlackTopFrameNotes": "Des lunettes à monture noire. Ne confère aucun bonus.", "eyewearSpecialBlueTopFrameText": "Lunettes normales bleues", - "eyewearSpecialBlueTopFrameNotes": "Des lunettes cerclées de bleu. Ne confère aucun bonus.", + "eyewearSpecialBlueTopFrameNotes": "Des lunettes à monture bleue. Ne confère aucun bonus.", "eyewearSpecialGreenTopFrameText": "Lunettes normales vertes", - "eyewearSpecialGreenTopFrameNotes": "Des lunettes cerclées de vert. Ne confère aucun bonus.", + "eyewearSpecialGreenTopFrameNotes": "Des lunettes à monture verte. Ne confère aucun bonus.", "eyewearSpecialPinkTopFrameText": "Lunettes normales roses", - "eyewearSpecialPinkTopFrameNotes": "Des lunettes cerclées de rose. Ne confère aucune bonus.", + "eyewearSpecialPinkTopFrameNotes": "Des lunettes à monture rose. Ne confère aucune bonus.", "eyewearSpecialRedTopFrameText": "Lunettes normales rouges", "eyewearSpecialRedTopFrameNotes": "Des lunettes cerclées de rouge. Ne confère aucun bonus.", "eyewearSpecialWhiteTopFrameText": "Lunettes normales blanches", - "eyewearSpecialWhiteTopFrameNotes": "Des lunettes cerclées de blanc. Ne confère aucun bonus.", + "eyewearSpecialWhiteTopFrameNotes": "Des lunettes à monture blanche. Ne confère aucun bonus.", "eyewearSpecialYellowTopFrameText": "Lunettes normales jaunes", - "eyewearSpecialYellowTopFrameNotes": "Des lunettes cerclées de jaune. Ne confère aucun bonus.", + "eyewearSpecialYellowTopFrameNotes": "Des lunettes à monture jaune. Ne confère aucun bonus.", "eyewearSpecialSummerRogueText": "Cache-œil Espiègle", "eyewearSpecialSummerRogueNotes": "Même un voyou peut voir à quel point c'est élégant ! N'apporte aucun bonus. Équipement en édition limitée de l’été 2014.", "eyewearSpecialSummerWarriorText": "Cache-œil Fringuant", diff --git a/common/locales/fr/groups.json b/common/locales/fr/groups.json index cd76ca98f5..9c9a31031c 100644 --- a/common/locales/fr/groups.json +++ b/common/locales/fr/groups.json @@ -106,9 +106,9 @@ "notAuthorizedToSendMessageToThisUser": "Impossible d'envoyer un message à cet utilisateur.", "privateMessageGiftIntro": "Bonjour <%= receiverName %>, <%= senderName %> vous a envoyé", "privateMessageGiftGemsMessage": "<%= gemAmount %> gemmes!", - "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> mois de souscription !", - "cannotSendGemsToYourself": "Vous ne pouvez pas vous envoyer des gemmes. Essayez la souscription à la place.", - "badAmountOfGemsToSend": "Le montant doit être compris entre 1 et votre nombre actuel de gemmes.", + "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> mois d'abonnement !", + "cannotSendGemsToYourself": "Vous ne pouvez pas vous envoyer de gemmes. Essayez un abonnement à la place.", + "badAmountOfGemsToSend": "Le total doit être compris entre 1 et votre nombre actuel de gemmes.", "abuseFlag": "Signaler une infraction aux Règles de la Communauté", "abuseFlagModalHeading": "Signaler <%= name %> pour infraction ?", "abuseFlagModalBody": "Êtes vous sûr de vouloir signaler ce post ? Vous ne devez reporter UNIQUEMENT les posts qui sont contraîre les <%= firstLinkStart %>Règles de la Communauté<%= linkEnd %> et/ou les <%= secondLinkStart %>Conditions Générales d'Utilisation <%= linkEnd%>. Signaler inutilement un post est une violation des Règles de la Communauté et peux vous faire recevoir un avertissement. Les raisons qui justifient le signalement d'un post incluent mais ne se limitent pas :

  • aux insultes ou la diffamation
  • à l'intolérance ou propos à caractères religieux
  • aux sujets 'réservés aux adultes'
  • à la violence, même sous forme de plaisanterie
  • au spamming et aux messages incompréhensibles ou volontairement sans aucun sens
", @@ -165,25 +165,25 @@ "groupIdRequired": "\"groupId\" doit être un UUID valide", "groupNotFound": "Groupe non trouvé.", "groupTypesRequired": "Vous devez fournir une requête \"type\" valide.", - "questLeaderCannotLeaveGroup": "Vous ne pouvez pas quitter votre groupe alors que vous avez démarré une quête. Abandonnez la quête d'abord.", + "questLeaderCannotLeaveGroup": "Vous ne pouvez pas quitter une équipe si vous avez démarré une quête. Abandonnez la quête d'abord.", "cannotLeaveWhileActiveQuest": "Vous ne pouvez pas quitter une équipe pendant une quête active. Veuillez d'abord quitter la quête.", - "onlyLeaderCanRemoveMember": "Seul le chef d'équipe peut supprimer un membre!", - "memberCannotRemoveYourself": "Vous ne pouvez pas vous supprimer vous-même!", - "groupMemberNotFound": "Utilisateur non trouvé dans les membres du groupe", + "onlyLeaderCanRemoveMember": "Seul le responsable d'équipe peut supprimer un membre !", + "memberCannotRemoveYourself": "Vous ne pouvez pas vous supprimer vous-même !", + "groupMemberNotFound": "Utilisateur non trouvé parmi les membres du groupe", "mustBeGroupMember": "Doit être un membre du groupe.", - "keepOrRemoveAll": "req.query.keep doit être soit \"keep-all\" ou \"remove-all\"", + "keepOrRemoveAll": "req.query.keep doit être soit \"keep-all\" soit \"remove-all\"", "keepOrRemove": "req.query.keep doit être soit \"keep\" soit \"remove\"", - "canOnlyInviteEmailUuid": "Vous ne pouvez inviter qu'avec des UUIDs ou des emails.", - "inviteMissingEmail": "L'adresse mail est manquante dans l'invitation.", + "canOnlyInviteEmailUuid": "Vous ne pouvez inviter qu'avec des UUIDs ou des courriels.", + "inviteMissingEmail": "L'adresse courriel est manquante dans l'invitation.", "partyMustbePrivate": "Les équipes doivent être privées", - "userAlreadyInGroup": "L'utilisateur est déjà dans cette équipe.", - "userAlreadyInvitedToGroup": "L'utilisateur a déjà été invité dans cette équipe.", + "userAlreadyInGroup": "Utilisateur déjà dans cette équipe.", + "userAlreadyInvitedToGroup": "Utilisateur déjà invité à joindre cette équipe.", "userAlreadyPendingInvitation": "Utilisateur déjà en cours d'invitation.", - "userAlreadyInAParty": "L'utilisateur est déjà dans une équipe.", + "userAlreadyInAParty": "Utilisateur déjà dans une équipe.", "userWithIDNotFound": "Utilisateur avec l'ID \"<%= userId %>\" non trouvé.", - "userHasNoLocalRegistration": "L'utilisateur n'a pas un enregistrement local (nom d'utilisateur, email, mot de passe).", - "uuidsMustBeAnArray": "Les UUIDs de l'invitation doivent être un tableau.", - "emailsMustBeAnArray": "Les emails de l'invitation doivent être un tableau.", + "userHasNoLocalRegistration": "L'utilisateur n'a pas un enregistrement local (nom d'utilisateur, courriel, mot de passe).", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "Vous ne pouvez envoyer que \"<%= maxInvites %>\" invitations à la fois", - "onlyCreatorOrAdminCanDeleteChat": "Vous n'êtes pas autorisé à supprimer ce message!" + "onlyCreatorOrAdminCanDeleteChat": "Vous n'êtes pas autorisé à supprimer ce message !" } \ No newline at end of file diff --git a/common/locales/fr/limited.json b/common/locales/fr/limited.json index 3b21a30267..3df9d2a08f 100644 --- a/common/locales/fr/limited.json +++ b/common/locales/fr/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amis Pénibles", "annoyingFriendsText": "A été touché·e <%= snowballs %> fois par des boules de neige lancées par un membre de l'équipe.", "alarmingFriends": "Amis Inquiétants", - "alarmingFriendsText": "A été transformé·e en fantôme <%= spookDust %> fois par des membres de l'équipe.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Amis Agricoles", "agriculturalFriendsText": "A été transformé·e en fleur <%= seeds %> fois par un membre de son équipe.", "aquaticFriends": "Amis Aquatiques", diff --git a/common/locales/fr/maintenance.json b/common/locales/fr/maintenance.json index d1693543dd..7e391e2b47 100644 --- a/common/locales/fr/maintenance.json +++ b/common/locales/fr/maintenance.json @@ -3,9 +3,9 @@ "importantMaintenance": "Nous sommes en train d'effectuer d'importantes tâches de maintenance qui devraient être terminées à <%= localDate %> dans votre fuseau horaire.", "maintenance": "Maintenance", "maintenanceMoreInfo": "Vous voulez plus d'information sur la maintenance ? <%= linkStart %>Allez voir notre page d'information<%= linkEnd %>.", - "noDamageKeepStreaks": "Vous ne prendrez AUCUN dommage et ne perdrez aucun de combos !", + "noDamageKeepStreaks": "Vous ne prendrez AUCUN dommage et ne perdrez aucun combo !", "thanksForPatience": "Merci pour votre patience !", - "twitterMaintenanceUpdates": "Pour vous tenir informeé des mises à jours récentes, suivez notre compte Twitter, où nous posterons toutes les informations.", + "twitterMaintenanceUpdates": "Pour vous tenir informé des mises à jours récentes, suivez notre compte Twitter, où nous posterons toutes les informations.", "veteranPetAward": "À la fin, vous recevrez un familier Vétéran !", "maintenanceInfoTitle": "Information à propos des maintenances à venir sur Habitica", @@ -18,7 +18,7 @@ "maintenanceInfoAccountChanges": "Quels changements vais-je trouver sur mon compte une fois que la réécriture sera terminée ?", "maintenanceInfoAccountChangesText": "Au début, vous ne percevrez aucun changement notable, à l'exception de l'amélioration des performances de certaines fonctionnalités comme les Défis. Si vous remarquez un changement qui n'a pas lieu d'être, communiquez avec nous à admin@habitica.com pour que nous cherchions le problème pour vous !", "maintenanceInfoAddFeatures": "Quel genre de fonctionnalités pourront alors être ajoutées à Habitica ?", - "maintenanceInfoAddFeaturesText": "Grâce à cette réécriture, nous pourrons commencer à créer des améliorations aux forum de discussion et aux Guildes, des forfaits pour les organisations et les familles ainsi que d'autres fonctions de productivité comme les Mensuelles et la possibilité de valider les tâches de la veille ! Chaque fonction est aussi complexe que la suivante, alors les créer prendra du temps. Toutefois, nous ne pouvions tout simplement pas commencer sans cette réécriture.", + "maintenanceInfoAddFeaturesText": "Grâce à cette réécriture, nous pourrons commencer à créer des améliorations aux forum de discussion et aux Guildes, des forfaits pour les organisations et les familles ainsi que d'autres fonctionnalités de productivité comme les Mensuelles et la possibilité de valider les tâches de la veille ! Chaque fonctionnalité est aussi complexe que la suivante, alors les créer prendra du temps. Toutefois, nous ne pouvions tout simplement pas commencer sans cette réécriture.", "maintenanceInfoHowLong": "Combien de temps durera la maintenance ?", "maintenanceInfoHowLongText": "Nous devons migrer les tâches et les données des 1,3 millions d'utilisateurs d'Habitica et ce n'est pas une mince affaire ! Nous prévoyons que cette migration se déroulera environ de 13 h à 22 h, heure du Pacifique (soit de 20 h à 5 h UTC). Soyez assuré, nous faisons notre possible pour la terminer aussi vite que possible ! Vous pouvez suivre les mises à jour sur notre compte Twitter.", "maintenanceInfoStatsAffected": "Comment seront affectées mes tâches Quotidiennes, mes combos, mes bonus et mes quêtes ?", diff --git a/common/locales/fr/npc.json b/common/locales/fr/npc.json index ed4fcca464..4d4df6f2e9 100644 --- a/common/locales/fr/npc.json +++ b/common/locales/fr/npc.json @@ -25,22 +25,22 @@ "itemNotFound": "Objet \"<%= key %>\" non trouvé.", "cannotBuyItem": "Vous ne pouvez pas acheter cet objet.", "missingTypeKeyEquip": "\"key\" et \"type\" sont des paramètres requis.", - "missingPetFoodFeed": "\"pet\" et \"food\" sont des paramètres requis", + "missingPetFoodFeed": "\"pet\" et \"food\" sont des paramètres requis.", "invalidPetName": "Nom de familier invalide.", "missingEggHatchingPotionHatch": "\"egg\" et \"hatchingPotion\" sont des paramètres requis.", - "invalidTypeEquip": "\"type\" doit être faire partie de 'equipped', 'pet', 'mount', 'costume'.", + "invalidTypeEquip": "\"type\" doit faire partie de 'equipped', 'pet', 'mount', 'costume'.", "mustPurchaseToSet": "Il faut acheter <%= val %> pour le mettre en <%= key %>.", "typeRequired": "Type est requis", "keyRequired": "La clé est requise", - "notAccteptedType": "Le Type doit être compris dans [eggs, hatchingPotions, food, quests, gear]", + "notAccteptedType": "Le Type doit faire partie de [eggs, hatchingPotions, food, quests, gear]", "contentKeyNotFound": "Clé non trouvée pour le contenu <%= type %>", "plusOneGem": "+1 Gemme", "typeNotSellable": "Type n'est pas vendable. Il doit faire partie des types suivants <%= acceptedTypes %>", "userItemsKeyNotFound": "Clé non trouvée pour user.items <%= type %>", - "pathRequired": "Le chemin est requis", + "pathRequired": "La chaîne de chemin d'accès est requise", "unlocked": "Les objets ont été débloqués.", "alreadyUnlocked": "Ensemble complet déjà débloqué.", - "alreadyUnlockedPart": "L'ensemble est déjà partiellement débloqué.", + "alreadyUnlockedPart": "Ensemble déjà partiellement débloqué.", "USD": "(USD)", "newStuff": "Nouveauté", "cool": "Rappelez-le moi plus tard", diff --git a/common/locales/fr/pets.json b/common/locales/fr/pets.json index aa4137f2f4..23bc3865d5 100644 --- a/common/locales/fr/pets.json +++ b/common/locales/fr/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Lion Éthéré", "veteranWolf": "Loup de Vétéran", "veteranTiger": "Tigre de Vétéran", + "veteranLion": "Veteran Lion", "cerberusPup": "Chiot Cerbère", "hydra": "Hydre", "mantisShrimp": "Crevette-mante", @@ -84,8 +85,8 @@ "petKeyBoth": "Relâcher les Deux", "confirmPetKey": "Êtes-vous sur·e ?", "petKeyNeverMind": "Pas Encore", - "petsReleased": "Familiers libérées.", - "mountsAndPetsReleased": "Montures et familiers libérées", + "petsReleased": "Familiers libérés.", + "mountsAndPetsReleased": "Montures et familiers libérés", "mountsReleased": "Montures libérées", "gemsEach": "gemmes chacun" } \ No newline at end of file diff --git a/common/locales/fr/quests.json b/common/locales/fr/quests.json index 613517fa19..bffaf8c0fa 100644 --- a/common/locales/fr/quests.json +++ b/common/locales/fr/quests.json @@ -81,10 +81,10 @@ "leveledUpReceivedQuest": "Vous avez atteint le niveau <%= level %> et reçu un parchemin de quête !", "questInvitationDoesNotExist": "Aucune invitation à la quête n'a été envoyée pour l'instant.", "questInviteNotFound": "Aucune invitation à la quête trouvée.", - "guildQuestsNotSupported": "Les guildes ne peuvent pas être invitées dans des quêtes.", + "guildQuestsNotSupported": "Les guildes ne peuvent pas être invitées à joindre une quête.", "questNotFound": "Quête \"<%= key %>\" introuvable.", "questNotOwned": "Vous ne possédez pas ce parchemin de quête.", - "questNotGoldPurchasable": "La quête \"<%= key %>\" n'est pas une quête achetable avec de l'or.", + "questNotGoldPurchasable": "La quête \"<%= key %>\" n'est pas achetable avec de l'or.", "questLevelTooHigh": "Vous devez être au moins au niveau <%= level %> pour commencer cette quête.", "questAlreadyUnderway": "Votre équipe est déjà en quête. Essayez à nouveau lorsque la quête actuelle sera terminée.", "questAlreadyAccepted": "Vous avez déjà accepté l'invitation à la quête.", @@ -94,8 +94,8 @@ "noActiveQuestToAbort": "Il n'y a pas de quête active à interrompre.", "onlyLeaderAbortQuest": "Seul le responsable du groupe ou de la quête peut interrompre une quête.", "questAlreadyRejected": "Vous avez déjà rejeté l'invitation à la quête.", - "cantCancelActiveQuest": "Vous ne pouvez pas annuler une quête active, utilisez la fonctionnalité interrompre à la place.", + "cantCancelActiveQuest": "Vous ne pouvez pas annuler une quête active, utilisez la fonction interrompre à la place.", "onlyLeaderCancelQuest": "Seul le responsable du groupe ou de la quête peut annuler la quête.", - "questNotPending": "Il n'y a pas de quête à démarrer.", - "questOrGroupLeaderOnlyStartQuest": "Seul le responsable de quête du groupe peut forcer le début de la quête" + "questNotPending": "Il n'y a aucune quête à commencer.", + "questOrGroupLeaderOnlyStartQuest": "Seul le responsable du groupe ou de la quête peut forcer le début de la quête" } \ No newline at end of file diff --git a/common/locales/fr/questscontent.json b/common/locales/fr/questscontent.json index f05bd6d68b..4a398042e8 100644 --- a/common/locales/fr/questscontent.json +++ b/common/locales/fr/questscontent.json @@ -317,8 +317,8 @@ "questFalconUnlockText": "Déverrouille l'achat d’œufs de faucon au Marché", "questTreelingText": "L'Arbre tortueux", "questTreelingNotes": "C'est la Compétition annuelle de jardinage et tout le monde parle du mystérieux projet qu'@aurakami a promis de révélé. Vous vous joignez à la foule le jour de la grande annonce et vous émerveillez devant l'arbre animé. @fuzzytrees explique que cet arbre aidera à la maintenance des jardins, en montrant qu'il peut tondre la pelouse, couper la haie et tailler les rosiers en même temps... jusqu'à ce l'arbre devienne sauvage et retourne son sécateur contre son créateur ! La foule est en panique. Alors que tous essaient de s'enfuir, vous vous avancez d'un bond, sans peur et prêt au combat.", - "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"", + "questTreelingCompletion": "Vous vous dépoussiérez alors que les toutes dernières feuilles tombent au sol. Malgré les mécontents, la Compétition de jardinage est maintenant hors de danger – quoique l'arbre que vous venez de réduire en un tas de copeaux ne gagnera pas le moindre prix ! « Il reste quelques petits défauts à travailler, dit @PainterProphet. Quelqu'un saura peut-être mieux entraîner ces arbrisseaux. Voulez-vous vous lancer ? »", "questTreelingBoss": "Arbre tortueux", - "questTreelingDropTreelingEgg": "Arbuste (œuf)", - "questTreelingUnlockText": "Déverrouille l'achat d’œufs d'arbuste au Marché" + "questTreelingDropTreelingEgg": "Arbrisseau (œuf)", + "questTreelingUnlockText": "Déverrouille l'achat d’œufs d'arbrisseau au Marché" } \ No newline at end of file diff --git a/common/locales/fr/rebirth.json b/common/locales/fr/rebirth.json index 8eff6518f4..f4a50c2221 100644 --- a/common/locales/fr/rebirth.json +++ b/common/locales/fr/rebirth.json @@ -5,7 +5,7 @@ "rebirthStartOver": "Renaissance fait recommencer votre personnage au Niveau 1.", "rebirthAdvList1": "Vous repartez avec une Santé complète.", "rebirthAdvList2": "Vous n'avez ni Expérience, ni Or, ni Équipement (à l'exception d'objets gratuits comme les objets Mystère).", - "rebirthAdvList3": "Vos Habitudes, Quotidiennes et Tâches sont remises au jaune et les combos sont remis à zéro, à l'exception des tâches relatives à un défi.", + "rebirthAdvList3": "Vos Habitudes, Quotidiennes et Tâches À Faire sont remises au jaune et les combos sont remis à zéro, à l'exception des tâches de Défis.", "rebirthAdvList4": "Vous avez la classe de départ de Guerrier jusqu'à ce que vous obteniez une nouvelle classe.", "rebirthInherit": "Votre nouveau personnage a hérité de quelques petites choses de son prédécesseur :", "rebirthInList1": "Les tâches, l'historique et les réglages sont conservés.", diff --git a/common/locales/fr/settings.json b/common/locales/fr/settings.json index 46b9313e89..67fe35a8dc 100644 --- a/common/locales/fr/settings.json +++ b/common/locales/fr/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Heure personnalisée de début de journée", "changeCustomDayStart": "Changer l'Heure personnalisée de début de journée ?", "sureChangeCustomDayStart": "Êtes-vous sûr•e de vouloir changer votre heure personnalisée de début de journée ?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Vos tâches Quotidiennes seront réinitialisées la prochaine fois que vous utiliserez Habitica après <%= time %>. Faites attention à compléter vos tâches Quotidiennes avant cette heure !", "customDayStartInfo1": "Par défaut, Habitica vérifie et réinitialise vos tâches Quotidiennes à minuit dans votre fuseau horaire. Vous pouvez personnaliser cette heure ici.", "misc": "Divers", @@ -61,7 +62,7 @@ "newUsername": "Nouveau nom d'utilisateur", "dangerZone": "Zone de Danger", "resetText1": "ATTENTION ! Cette action va réinitialiser une grand partie de votre compte. Ceci est fortement déconseillé, mais certaines personnes y trouvent une utilité dans les premiers temps, après une courte utilisation de l'application.", - "resetText2": "Vous perdrez tous vos niveaux, or et points d'expérience. Toutes vos tâches (à l'exception des tâches de Défis) seront supprimées de façon permanente et vous perdrez tout l'historique associé aux tâches. Vous perdrez tout votre équipement mais il vous sera possible de l'acheter à nouveau, y compris les équipements en Edition Limitée et les Equipements Mystères d'abonné (vous devrez cependant être de la classe correspondante pour racheter les équipements de classe). Vous conserverez votre classe actuelle, ainsi que vos familiers et montures. Vous préférerez peut-être utiliser un Orbe de Renaissance à la place, une option bien plus sûre qui vous permettra de conserver toutes vos tâches.", + "resetText2": "Vous perdrez tous vos niveaux, or et points d'expérience. Toutes vos tâches, à l'exception des tâches de Défis, seront supprimées de façon permanente et vous perdrez tout l'historique associé aux tâches. Vous perdrez tout votre équipement, mais il vous sera possible de l'acheter à nouveau, y compris les équipements en Édition Limitée et les Équipements Mystères d'abonné. Vous devrez cependant être de la classe correspondante pour racheter les équipements de classe. Vous conserverez votre classe actuelle, ainsi que vos familiers et montures. Vous préférerez peut-être utiliser un Orbe de Renaissance à la place, une option bien plus sûre qui vous permettra de conserver toutes vos tâches.", "deleteText": "Êtes-vous sûr•e ? Cela va supprimer votre compte Habitica définitivement et il ne pourra pas être restauré ! Vous serez obligé de créer un nouveau compte pour ré-utiliser Habitica. Les Gemmes sur votre compte ou celles dépensées ne seront pas remboursées. Si vous êtes absolument certain, tapez <%= deleteWord %> dans le champ de texte ci-dessous.", "API": "API", "APIv3": "API v3", @@ -148,7 +149,7 @@ "enabled": "Activé", "webhookURL": "URL du webhook", "invalidUrl": "URL invalide", - "invalidEnabled": "le paramètre \"enabled' doit être booléien", + "invalidEnabled": "le paramètre \"enabled' doit être booléen", "regIdRequired": "RegId est requis", "pushDeviceAdded": "Appareil push ajouté avec succès", "pushDeviceAlreadyAdded": "L'utilisateur a déjà l'appareil push", diff --git a/common/locales/fr/spells.json b/common/locales/fr/spells.json index 2de1e8414f..20a82d3b5a 100644 --- a/common/locales/fr/spells.json +++ b/common/locales/fr/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Jetez une boule de neige à un membre de votre équipe ! Qu'est-ce qui pourrait mal tourner ? L'effet dure jusqu'à la fin du jour pour ce membre.", "spellSpecialSaltText": "Sel", "spellSpecialSaltNotes": "Quelqu'un vous a envoyé une boule de neige. Ha, ha, ha, très drôle. Enlevez-moi cette neige de là, maintenant !", - "spellSpecialSpookDustText": "Poudre Poltergeist", - "spellSpecialSpookDustNotes": "Change un ami en drap flottant avec des yeux !", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Potion Opaque", "spellSpecialOpaquePotionNotes": "Annule les effets de la Poudre Poltergeist.", "spellSpecialShinySeedText": "Graine Brillante", @@ -50,7 +50,7 @@ "spellNotFound": "Compétence \"<%= spellId %>\" introuvable.", "partyNotFound": "Équipe non trouvée.", "targetIdUUID": "\"targetId\" doit être un nom d'utilisateur valide.", - "challengeTasksNoCast": "Impossible de lancer une compétence sur une tâche de Défis", - "spellNotOwned": "Vous n'avez pas obtenu cette compétence.", - "spellLevelTooHigh": "Vous devez être niveau <%= level %> pour utiliser cette compétence." + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "spellNotOwned": "Vous ne possédez pas cette compétence.", + "spellLevelTooHigh": "Vous devez être au niveau <%= level %> pour utiliser cette compétence." } \ No newline at end of file diff --git a/common/locales/fr/subscriber.json b/common/locales/fr/subscriber.json index ffe0bf3e08..1b9b8f7b6b 100644 --- a/common/locales/fr/subscriber.json +++ b/common/locales/fr/subscriber.json @@ -4,16 +4,16 @@ "subDescription": "Utilisez l'or pour acheter des gemmes, gagnez des objets-mystères chaque mois, conservez l'historique de vos progrès, doublez le butin quotidien, soutenez les développeurs. Cliquez pour plus d'informations.", "buyGemsGold": "Acheter des Gemmes avec de l'Or", "buyGemsGoldText": "Alexander le Marchand vous vendra les gemmes pour <%= gemCost%> or par gemme. Ses envois mensuels sont initialement plafonnés à <%= gemLimit%> gemmes par mois, mais cette limite augmente de 5 gemmes tous les trois mois d'abonnement consécutifs, jusqu'à un maximum de 50 gemmes par mois !", - "mustSubscribeToPurchaseGems": "Vous devez être abonné pour acheter des gemmes avec des GP.", - "reachedGoldToGemCap": "Vous avez atteint la limite de conversion Or => Gemme, <%= convCap %>, pour ce mois. Elle existe pour limité l'abus / le farm. La limite sera remise à zéros dans les 3 premiers jours du mois suivant.", + "mustSubscribeToPurchaseGems": "Vous devez être abonné pour acheter des gemmes avec de l'or.", + "reachedGoldToGemCap": "Vous avez atteint la limite de conversion Or => Gemme, <%= convCap %> pour ce mois. Elle existe pour limiter l'abus / le farming. La limite sera remise à zéro dans les 3 premiers jours du mois suivant.", "retainHistory": "Conservation de plus d'entrées de l'historique", "retainHistoryText": "Rend les Tâches à Faire effectuées et l'historique des tâches disponibles plus longtemps.", "doubleDrops": "Limite journalière de butin doublée", "doubleDropsText": "Complétez votre écurie encore plus vite !", "mysteryItem": "Des objets uniques tous les mois", - "mysteryItemText": "Chaque mois, vous recevrez un objet cosmétique unique pour votre avatar ! En plus, pour chaque période de trois mois consécutifs d'inscription, les Mystérieux Voyageurs Temporels vous donneront accès aux objets cosmétiques historiques (et futurs !)", + "mysteryItemText": "Chaque mois, vous recevrez un objet cosmétique unique pour votre avatar ! En plus, pour chaque période de trois mois consécutifs d'abonnement, les Mystérieux Voyageurs Temporels vous donneront accès aux objets cosmétiques historiques (et futurs !)", "supportDevs": "Soutenez les développeurs", - "supportDevsText": "Votre inscription aide à garder Habitica prospère et aide à financer le développement de nouvelles fonctionnalités. Merci de vie générosité !", + "supportDevsText": "Votre abonnement aide à garder Habitica prospère et aide à financer le développement de nouvelles fonctionnalités. Merci de vie générosité !", "monthUSD": "USD / Mois", "organization": "Organisation", "groupPlans": "Offres d'Entreprise", @@ -123,15 +123,15 @@ "hourglassPurchase": "A acheté un objet avec un Sablier Mystique !", "hourglassPurchaseSet": "A acheté un ensemble d'objets avec un Sablier Mystique !", "missingUnsubscriptionCode": "Il manque le code de désabonnement.", - "missingSubscription": "L'utilisateur n'a pas de plan d'abonnement", + "missingSubscription": "L'utilisateur n'a pas de forfait d'abonnement", "missingSubscriptionCode": "Il manque le code d'abonnement. Valeurs possibles : basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", - "cannotDeleteActiveAccount": "Vous avez un abonnement actif, annulez votre plan avant de supprimer votre compte.", + "cannotDeleteActiveAccount": "Vous avez un abonnement actif, annulez le forfait avant de supprimer votre compte.", "paymentNotSuccessful": "Le paiement n'a pas abouti", "planNotActive": "Le plan n'a pas encore été activé (à cause d'un bug PayPal). Il commencera le <%= nextBillingDate %>, après quoi vous pourrez annuler pour en garder tous les bénéfices.", - "notAllowedHourglass": "Familier/monture non disponible à l'achat avec un sablier mystique.", + "notAllowedHourglass": "Familier/monture non disponible pour l'achat avec un Sablier Mystique.", "readCard": "<%= cardType %> a été lue", "cardTypeRequired": "Type de carte requis", - "cardTypeNotAllowed": "Type de carte inconnu.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Code du coupon invalide.", "couponUsed": "Code du coupon déjà utilisé.", "noSudoAccess": "Vous n'avez pas d'accès sudo.", diff --git a/common/locales/fr/tasks.json b/common/locales/fr/tasks.json index fa63172912..95777dcd2e 100644 --- a/common/locales/fr/tasks.json +++ b/common/locales/fr/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Nettoyer", "hideTags": "Masquer", "showTags": "Afficher", + "toRequired": "You must supply a to value", "startDate": "Date de début", "startDateHelpTitle": "Quand cette tâche devrait-elle démarrer ?", "startDateHelp": "Définissez la date à laquelle cette tâche prendra effet. Elle ne sera pas active avant cette date.", @@ -88,7 +89,7 @@ "fortifyName": "Potion de Fortification", "fortifyPop": "Fait revenir toutes les tâches à une valeur neutre (couleur jaune) et restaure tous les points de santé que vous aviez perdus.", "fortify": "Fortification", - "fortifyText": "La potion de fortification ramènera toutes vos tâches, à l'exception des tâches de Défis, à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. C'est utile si vos tâches rouges rendent le jeu trop dur, ou si vos tâches bleues le rendent trop facile. Si cela vous motive de retrouver des bases saines, dépensez vos gemmes et accordez vous un sursis !", + "fortifyText": "La potion de fortification ramènera toutes vos tâches, à l'exception des tâches de Défis, à un niveau neutre (jaune), comme si vous veniez de les ajouter, et remplira votre barre de santé. C'est utile si vos tâches rouges rendent le jeu trop dur, ou si vos tâches bleues le rendent trop facile. Si cela vous motive de retrouver des bases saines, dépensez vos gemmes et accordez-vous un sursis !", "confirmFortify": "Êtes-vous sûr•e ?", "fortifyComplete": "Fortification terminée !", "sureDelete": "Êtes-vous sûr•e de vouloir effacer cette <%= taskType %> avec le texte \"<%= taskText %>\"?", @@ -115,13 +116,13 @@ "rewardHelp4": "Ne soyez pas effrayé de créer des récompenses personnalisées ! Regardez quelques exemples ici.", "clickForHelp": "Cliquez pour obtenir de l'aide.", "taskIdRequired": "\"taskId\" doit être un UUID valide.", - "taskNotFound": "Tâche non-trouvée.", + "taskNotFound": "Tâche non trouvée.", "invalidTaskType": "Task type doit être l'un des suivants : \"habit\", \"daily\", \"todo\", \"reward\".", - "cantDeleteChallengeTasks": "Une tâche appartenant à un Défi ne peut être supprimée.", - "checklistOnlyDailyTodo": "Les checklists ne sont disponibles que sur les tâches Quotidiennes et À faire", - "checklistItemNotFound": "Aucune liste de vérification n'a été trouvée avec l'id fourni.", + "cantDeleteChallengeTasks": "Une tâche de Défi ne peut être supprimée.", + "checklistOnlyDailyTodo": "Les listes de vérification ne sont disponibles que sur les tâches Quotidiennes et À faire", + "checklistItemNotFound": "Aucune liste de vérification n'a été trouvée pour l'ID fourni.", "itemIdRequired": "\"itemId\" doit être un UUID valide.", - "tagNotFound": "Aucune étiquette n'a été trouvée avec l'id fourni.", + "tagNotFound": "Aucune étiquette n'a été trouvée pour l'ID fourni.", "tagIdRequired": "\"tagId\" doit être un UUID valide correspondant à une étiquette appartenant à l'utilisateur.", "positionRequired": "\"position\" est requis et doit être un nombre.", "cantMoveCompletedTodo": "Impossible de déplacer une tâche À Faire complétée.", diff --git a/common/locales/he/backgrounds.json b/common/locales/he/backgrounds.json index deb6add151..68ee1fe3da 100644 --- a/common/locales/he/backgrounds.json +++ b/common/locales/he/backgrounds.json @@ -161,11 +161,11 @@ "backgroundGiantFlowersNotes": "השתובבו על פרחי ענק.", "backgroundRainbowsEndText": "קצה הקשת", "backgroundRainbowsEndNotes": "גלו זהב בקצה הקשת.", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", - "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", - "backgroundGazeboText": "Gazebo", - "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgrounds052016": "סט 24: פורסם במאי 2016", + "backgroundBeehiveText": "כוורת דבורים", + "backgroundBeehiveNotes": "זמזמ/י ורקוד/ריקדי בכוורת דבורים.", + "backgroundGazeboText": "גזיבו", + "backgroundGazeboNotes": "הילחמ/י נגד גזיבו.", + "backgroundTreeRootsText": "שורשי עץ", + "backgroundTreeRootsNotes": "סקור/סיקרי את שורשי העץ." } \ No newline at end of file diff --git a/common/locales/he/challenge.json b/common/locales/he/challenge.json index 018bc5c22a..f6f994b912 100644 --- a/common/locales/he/challenge.json +++ b/common/locales/he/challenge.json @@ -64,19 +64,20 @@ "hurray": "יש!!", "noChallengeOwner": "אין בעלים", "noChallengeOwnerPopover": "אין לאתגר זה בעלים כיוון שמי שיצר את האתגר מחק את החשבון שלו.", - "challengeMemberNotFound": "User not found among challenge's members", - "onlyGroupLeaderChal": "Only the group leader can create challenges", - "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", - "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", - "challengeIdRequired": "\"challengeId\" must be a valid UUID.", - "winnerIdRequired": "\"winnerId\" must be a valid UUID.", - "challengeNotFound": "Challenge not found.", - "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", - "onlyLeaderUpdateChal": "Only the challenge leader can update it.", - "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", - "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", - "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", - "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "challengeMemberNotFound": "המשתמש לא נמצא בין חברי האתגר", + "onlyGroupLeaderChal": "רק מנהיג/ת הקבוצה יכול/ה ליצור אתגרים", + "tavChalsMinPrize": "הפרס חייב להיות לפחות אבן חן 1 עבור אתגרי הפונדק.", + "cantAfford": "הפרס הזה יקר מידי עבורך. יש לרכוש עוד אבני חן, או להנמיך את הכמות בפרס.", + "challengeIdRequired": "\"challengeId\" חייב להיות UUID - זהות משתמש ייחודי - תקף.", + "winnerIdRequired": "\"winnerId\" חייב להיות UUID - זהות משתמש ייחודי - תקף.", + "challengeNotFound": "האתגר לא נמצא.", + "onlyLeaderDeleteChal": "רק מנהיג/ת האתגר יכול/ה למחוק אותו.", + "onlyLeaderUpdateChal": "רק מנהיג/ת האתגר יכול/ה לעדכן אותו.", + "winnerNotFound": "מנצח/ת עם זהות משתמש \"<%= userId %>\" לא נמצא או לא חלק מהאתגר.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", + "userTasksNoChallengeId": "כאשר \"tasksOwner\" הוא/היא \"user\" \"challengeId\" איננו יכול לעבור.", + "onlyChalLeaderEditTasks": "מטלות השייכות לאתגר יכולות לעבור עריכה רק על ידי המנהיג/ה.", + "userAlreadyInChallenge": "המשתמש/ת כבר משתתפ/ת באתגר הזה.", + "cantOnlyUnlinkChalTask": "רק מטלות אתגרים שבורים יכולות להיות לא מקושרות.", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/he/character.json b/common/locales/he/character.json index 140f0838cb..68ff904398 100644 --- a/common/locales/he/character.json +++ b/common/locales/he/character.json @@ -110,7 +110,7 @@ "mage": "מכשף", "mystery": "מסתורין", "changeClass": "שינוי מקצוע, החזרת נקודות תכונה", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "כדי לשנות מקצוע עליך להיות לפחות ברמה 10.", "levelPopover": "כל דרגה מעניקה לך נקודה אחת להקצות לתכונה לפי בחירתך. ניתן לעשות זאת באופן ידני, או לתת למשחק להחליט בשבילך באמצעות אחת האופציות להקצאה אוטומטית.", "unallocated": "נקודות תכונה שטרם הוקצו", "haveUnallocated": "יש לך <%= points %> נקודות תכונה שטרם הקצית.", @@ -167,6 +167,6 @@ "showQuickAllocation": "הצגת הקצאת תכונות", "hideQuickAllocation": "הסתרת הקצאת תכונות", "quickAllocationLevelPopover": "כל דרגה מקנה לכם נקודה אחת לטובת תכונה כרצונכם. תוכלו לעשות זאת ידנית, או לתת למשחק להחליט בשבילכם באמצעות אופציית הקצאת הנקודות האוטומטית שניתן למצוא תחת משתמש -> תכונות דמות.", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "invalidAttribute": "\"<%= attr %>\" אינה תכונה תקפה.", + "notEnoughAttrPoints": "אין לך מספיק נקודות תכונה." } \ No newline at end of file diff --git a/common/locales/he/groups.json b/common/locales/he/groups.json index e5d3e68b93..6a45cab186 100644 --- a/common/locales/he/groups.json +++ b/common/locales/he/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/he/limited.json b/common/locales/he/limited.json index a8b353cf2f..c3532c1029 100644 --- a/common/locales/he/limited.json +++ b/common/locales/he/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "חברים מרגיזים", "annoyingFriendsText": "חטף <%= snowballs %> כדורי שלג לפרצוף משאר החבורה.", "alarmingFriends": "חברים מפחידים", - "alarmingFriendsText": "הופחד/ה <%= spookDust %> פעמים ע\"י שאר החבורה שלו/ה.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "חברים חקלאיים", "agriculturalFriendsText": "הוחלף לפרח <%= seeds %> פעמים על-ידי חברי חבורה.", "aquaticFriends": "חברים ימיים", diff --git a/common/locales/he/maintenance.json b/common/locales/he/maintenance.json index efdb524cd2..e1afe60a76 100644 --- a/common/locales/he/maintenance.json +++ b/common/locales/he/maintenance.json @@ -1,18 +1,18 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", - "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "habiticaBackSoon": "אל דאגה, Habitica תחזור בקרוב!", + "importantMaintenance": "אנו עוסקים בתחזוקה חשובה שאנו מעריכים כי תימשך עד <%= localDate %> באזור הזמן שלך.", + "maintenance": "תחזוקה", + "maintenanceMoreInfo": "רוצה עוד מידע בנוגע לתחזוקה? <%= linkStart %>תנ/י מבט בדף המידע שלנו<%= linkEnd %>.", + "noDamageKeepStreaks": "לא יהיה לך שום אובדן של רצפים או פגיעה בנקודות החיים שלך!", + "thanksForPatience": "תודה לך על סבלנותך!", + "twitterMaintenanceUpdates": "לעדכונים החדשים ביותר, צפה/י בחשבון הטוויטר שלנו, שבו נפרסם מידע על המצב.", + "veteranPetAward": "בסוף התחזוקה, את/ה תקבל/י חיית מחמד ותיקה!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", - "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", + "maintenanceInfoTitle": "מידע בנוגע לתחזוקה צפויה בHabitica", + "maintenanceInfoWhat": "מה קורה?", + "maintenanceInfoWhatText": "ב21 במאי, Habitica תרד לתחזוקה למשך רוב היום. לא תהיה שום פגיעה בנקודות החיים שלך או כל נזק לחשבון שלך במהלך אותו סוף השבוע, אפילו אם לא תוכל/י להיכנס לחשבון כדי לסמן את היומיות שלך בזמן! אנחנו נעבוד קשה מאוד כדי לעשות את פרק הזמן הזה קצר ככל האפשר, ונפרסם עדכונים שוטפים בחשבון הטוויטר שלנו. בתום תקופת התחזוקה כולכם/ן תקבלו חיית מחמד נדירה!", + "maintenanceInfoWhy": "מדוע זה קורה?", + "maintenanceInfoWhyText": "במהלך מספר החודשים האחרונים, אנחנו הכנסנו אויר מחודש מאחורי הקלעים של Habitica. למשל, כתבנו מחדש את הAPI. למרות שלמראית עין על פני השטח לא התרחש כמעט שינוי, יש עולם חדש שלם מתחת. זה יאפשר לנו הרבה יותר גמישות כאשר נרצה לבנות מאפיינים בעתיד, ויוביל לביצועים משופרים!", "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", "maintenanceInfoMore": "More Information", "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", diff --git a/common/locales/he/pets.json b/common/locales/he/pets.json index b60e1c7f4e..a9bd7ce5b7 100644 --- a/common/locales/he/pets.json +++ b/common/locales/he/pets.json @@ -12,6 +12,7 @@ "etherealLion": "אריה אתרי", "veteranWolf": "זאב ותיק", "veteranTiger": "נמר ותיק", + "veteranLion": "Veteran Lion", "cerberusPup": "גור קרברוס", "hydra": "הידרה", "mantisShrimp": "חסילון-גמל-שלמה", diff --git a/common/locales/he/settings.json b/common/locales/he/settings.json index b7c675cae5..06ca87a73f 100644 --- a/common/locales/he/settings.json +++ b/common/locales/he/settings.json @@ -47,6 +47,7 @@ "customDayStart": "התחלת יום מותאמת אישית", "changeCustomDayStart": "שנה את מועד תחילת היום?", "sureChangeCustomDayStart": "האם אתם בטוחים שאתם מעוניינים לשנות את מועד תחילת היום?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "שמור מועד תחילת יום מותאם-אישית", "customDayStartInfo1": "האביטיקה מכוונת לבדוק ולאפס את המטלות היומיות בחצות של אזור הזמן שלכם, מידי יום. תוכלו לשנות זאת כאן.", "misc": "שונות", diff --git a/common/locales/he/spells.json b/common/locales/he/spells.json index e3859fe8e5..f855704a1e 100644 --- a/common/locales/he/spells.json +++ b/common/locales/he/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "השליכו כדור שלג על החבר שלכם, מה כבר יקרה? נמשך עד היום הבא של החבר הפגוע.", "spellSpecialSaltText": "מלח", "spellSpecialSaltNotes": "מישהו/י זרק/ה עלייך כדור שלג. פפחחחחחח, זה קורע, משהו היסטרי. טוב, נו, בוא נוריד את השלג הזה ממך!", - "spellSpecialSpookDustText": "נצנצים מלחיצים", - "spellSpecialSpookDustNotes": "הפכו חבר/ה לשמיכה מעופפת עם עיניים!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "שיקוי עכור", "spellSpecialOpaquePotionNotes": "מבטלת את השפעת הנצנצים המלחיצים.", "spellSpecialShinySeedText": "זרע מנצנץ", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/he/subscriber.json b/common/locales/he/subscriber.json index 5b0b086e95..9616808714 100644 --- a/common/locales/he/subscriber.json +++ b/common/locales/he/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/he/tasks.json b/common/locales/he/tasks.json index 13cff6e201..4c248259e9 100644 --- a/common/locales/he/tasks.json +++ b/common/locales/he/tasks.json @@ -73,6 +73,7 @@ "clearTags": "נקה", "hideTags": "הסתר", "showTags": "הראה", + "toRequired": "You must supply a to value", "startDate": "תאריך התחלה", "startDateHelpTitle": "מתי כדאי שמשימה תתחיל?", "startDateHelp": "קבעו תאריך שבו משימה זו נכנסת לתוקף. משימה זו לא תחול בימים מוקדמים יותר.", diff --git a/common/locales/hu/groups.json b/common/locales/hu/groups.json index 8108f28c21..0125fe3b98 100644 --- a/common/locales/hu/groups.json +++ b/common/locales/hu/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/hu/limited.json b/common/locales/hu/limited.json index eba33a92c2..cdb9f34015 100644 --- a/common/locales/hu/limited.json +++ b/common/locales/hu/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Idegesítő barátok", "annoyingFriendsText": "Eltaláltak <%= snowballs %> hógolyóval a csapattársaid.", "alarmingFriends": "Ijesztő barátok", - "alarmingFriendsText": "A csapattagjaid <%= spookDust %>-szor kísértettek.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Mezőgazdasági Barátok", "agriculturalFriendsText": "Csapattársaid <%= seeds %> alkalommal varázsoltak virággá.", "aquaticFriends": "Vizi Barátok", diff --git a/common/locales/hu/pets.json b/common/locales/hu/pets.json index deef2663de..ea192391e3 100644 --- a/common/locales/hu/pets.json +++ b/common/locales/hu/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Földöntúli oroszlán", "veteranWolf": "Veterán farkas", "veteranTiger": "Veterán Tigris", + "veteranLion": "Veteran Lion", "cerberusPup": "Kerberosz kölyök", "hydra": "Hidra", "mantisShrimp": "Sáskarák", diff --git a/common/locales/hu/settings.json b/common/locales/hu/settings.json index b33068f3bf..f74545a0b7 100644 --- a/common/locales/hu/settings.json +++ b/common/locales/hu/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Egyedi nap kezdet", "changeCustomDayStart": "Megváltoztatod az egyedi nap kezdetet?", "sureChangeCustomDayStart": "Biztosan meg akarod változtatni, mikor kezdődjön a nap?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "A napi feladataid legközelebb akkor indulnak újra, amikor a Habiticat <%= time %> után használod. Bizonyosodj meg róla, hogy ez előtt az idő előtt befejezed a napi feladataid.", "customDayStartInfo1": "A Habitican a napi feladataid újraindítása a saját időzónádbeli éjfélre van alapértelmezve. Ezt az időt itt tudod tesreszabni", "misc": "Egyéb", diff --git a/common/locales/hu/spells.json b/common/locales/hu/spells.json index ca942f7fde..e4911b6dc6 100644 --- a/common/locales/hu/spells.json +++ b/common/locales/hu/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Megdobsz egy csapttagot hógolyóval, ugyan mi rossz történhet? A csapattag új napjáig marad érvényben.", "spellSpecialSaltText": "Só", "spellSpecialSaltNotes": "Valaki megdobott egy hógolyóval. Ha ha, nagyon vicces. Leszedné valaki rólam a havat?!", - "spellSpecialSpookDustText": "Kísérteties sziporkák", - "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Opálos főzet", "spellSpecialOpaquePotionNotes": "Szűntesd meg a Kísérteties sziporkák hatását.", "spellSpecialShinySeedText": "Fényes mag", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/hu/subscriber.json b/common/locales/hu/subscriber.json index ffa7413b41..22116bf6c2 100644 --- a/common/locales/hu/subscriber.json +++ b/common/locales/hu/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/hu/tasks.json b/common/locales/hu/tasks.json index d61dc640c9..e8ec2a51a5 100644 --- a/common/locales/hu/tasks.json +++ b/common/locales/hu/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Törlés", "hideTags": "Elrejt", "showTags": "Mutat", + "toRequired": "You must supply a to value", "startDate": "Kezdő dátum", "startDateHelpTitle": "Mikortól induljon a feladat?", "startDateHelp": "Állítsd be a feladat dátumát, amikortól aktív lesz. Előtte inaktív marad.", diff --git a/common/locales/it/challenge.json b/common/locales/it/challenge.json index 7f979be9c0..b4b90e625e 100644 --- a/common/locales/it/challenge.json +++ b/common/locales/it/challenge.json @@ -68,15 +68,16 @@ "onlyGroupLeaderChal": "Only the group leader can create challenges", "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", - "challengeIdRequired": "\"challengeId\" must be a valid UUID.", - "winnerIdRequired": "\"winnerId\" must be a valid UUID.", - "challengeNotFound": "Challenge not found.", + "challengeIdRequired": "\"challengeId\" deve essere un UUID valido.", + "winnerIdRequired": "\"winnerId\" deve essere un UUID valido.", + "challengeNotFound": "Sfida non trovata.", "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", "onlyLeaderUpdateChal": "Only the challenge leader can update it.", - "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "winnerNotFound": "Vincitore con id \"<%= userId %>\" non trovato o non ha partecipato a questa sfida.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", - "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "userAlreadyInChallenge": "L'utente sta già partecipando a questa sfida.", + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/it/groups.json b/common/locales/it/groups.json index 2d8bd4d746..8609f0b3d6 100644 --- a/common/locales/it/groups.json +++ b/common/locales/it/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/it/limited.json b/common/locales/it/limited.json index f594c21290..0b75f92002 100644 --- a/common/locales/it/limited.json +++ b/common/locales/it/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amici Fastidiosi", "annoyingFriendsText": "I compagni di squadra gli hanno tirato <%= snowballs %> palle di neve.", "alarmingFriends": "Amici Inquietanti", - "alarmingFriendsText": "Sei stato spaventato <%= spookDust %> volte dai tuoi compagni di squadra.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Amici Floreali", "agriculturalFriendsText": "E' stato trasformato in un fiore <%= seeds %> volte dai compagni di squadra.", "aquaticFriends": "Amici acquatici", diff --git a/common/locales/it/maintenance.json b/common/locales/it/maintenance.json index 7eacca6446..2eeb0c0d9c 100644 --- a/common/locales/it/maintenance.json +++ b/common/locales/it/maintenance.json @@ -1,12 +1,12 @@ { "habiticaBackSoon": "Non ti preoccupare, Habitica tornerà presto!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", + "importantMaintenance": "Stiamo effettuando delle importanti operazioni di manutenzione, stimiamo che dureranno fino al <%= localDate %> nel tuo fuso orario.", "maintenance": "Manutenzione", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", + "noDamageKeepStreaks": "NON riceverai alcun danno o perderai le tue Serie!", "thanksForPatience": "Grazie per la tua pazienza!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "twitterMaintenanceUpdates": "Per gli aggiornamenti più recenti, tieni d'occhio il nostro Twitter, dove pubblicheremo informazioni sullo stato della manutenzione.", + "veteranPetAward": "Alla fine, riceverai un raro animale veterano!", "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", "maintenanceInfoWhat": "Cosa sta succedendo?", @@ -15,20 +15,20 @@ "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", "maintenanceInfoMore": "Maggiori informazioni", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", + "maintenanceInfoAccountChanges": "Che cambiamenti vedrò nel mio account dopo che la riscrittura sarà completa?", "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", + "maintenanceInfoHowLong": "Quanto durerà la manutenzione?", "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", "maintenanceInfoSeeTasks": "What if I need to see my task list?", "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", + "maintenanceInfoRarePet": "Che tipo di animale raro riceverò?", "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", + "maintenanceInfoWho": "Chi ha lavorato su questo progetto immenso?", "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." } diff --git a/common/locales/it/pets.json b/common/locales/it/pets.json index 745467e332..e5143bf1c4 100644 --- a/common/locales/it/pets.json +++ b/common/locales/it/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Leone Etereo", "veteranWolf": "Lupo Veterano", "veteranTiger": "Tigre Veterana", + "veteranLion": "Veteran Lion", "cerberusPup": "Cucciolo di Cerbero", "hydra": "Idra", "mantisShrimp": "Canocchia", diff --git a/common/locales/it/quests.json b/common/locales/it/quests.json index 463f5643e2..597135bc0c 100644 --- a/common/locales/it/quests.json +++ b/common/locales/it/quests.json @@ -91,7 +91,7 @@ "noActiveQuestToLeave": "Nessuna missione attiva da abbandonare", "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", "notPartOfQuest": "You are not part of the quest", - "noActiveQuestToAbort": "There is no active quest to abort.", + "noActiveQuestToAbort": "Non c'è una missione attiva da annullare.", "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.", "questAlreadyRejected": "You already rejected the quest invitation.", "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.", diff --git a/common/locales/it/rebirth.json b/common/locales/it/rebirth.json index 4fd8def364..75c9b6e428 100644 --- a/common/locales/it/rebirth.json +++ b/common/locales/it/rebirth.json @@ -5,7 +5,7 @@ "rebirthStartOver": "La rinascita riporta il tuo personaggio al livello 1.", "rebirthAdvList1": "Torni ad avere tutti i punti vita.", "rebirthAdvList2": "Non hai punti esperienza, oro o equipaggiamento (ad eccezione degli oggetti gratuiti come gli Oggetti Misteriosi)", - "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.", + "rebirthAdvList3": "Le tue Habit, le Daily e i To-Do si resettano tornando al colore giallo e le serie si azzerano, eccetto per le attività delle sfide.", "rebirthAdvList4": "La tua classe iniziale sarà Guerriero fino a quando non otterrai una nuova classe.", "rebirthInherit": "Il tuo nuovo personaggio eredita delle cose dal suo predecessore:", "rebirthInList1": "Attività, cronologia e impostazioni rimangono.", diff --git a/common/locales/it/settings.json b/common/locales/it/settings.json index 7280f865e4..b948362553 100644 --- a/common/locales/it/settings.json +++ b/common/locales/it/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Data di inizio giorno personalizzata", "changeCustomDayStart": "Cambiare l'ora di inizio della giornata?", "sureChangeCustomDayStart": "Sei sicuro di voler cambiare l'inizio della giornata personalizzata?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Le tue Daily verranno ripristinate la prima volta che usi Habitica dopo <%= time %>. Assicurati di aver completato le tue Daily prima di allora!", "customDayStartInfo1": "Habitica è impostato per resettare le tue Daily a mezzanotte nel tuo fuso orario ogni giorno. Qui puoi modificare l'ora di \"reset\".", "misc": "Altro", @@ -65,7 +66,7 @@ "deleteText": "Vuoi davvero procedere? In questo modo eliminerai il tuo account per sempre, non potrà mai più essere recuperato! Dovrai registrare un nuovo account per continuare ad usare Habitica. Le gemme accumulate o spese non verranno risarcite. Se sei assolutamente sicuro, digita <%= deleteWord %> nella casella di testo qui sotto.", "API": "API", "APIv3": "API v3", - "APIv2": "API v2 - Deprecated", + "APIv2": "API v2 - Deprecata", "APIText": "Copia questi valori per utilizzarli in applicazioni di terze parti. Ad ogni modo, pensa al tuo API Token come a una password: tienilo segreto. Occasionalmente potrebbe venirti richiesto l'ID Utente, ma non scrivere mai il tuo API Token dove può essere visto da altre persone, nemmeno su GitHub.", "APIToken": "API Token (questa è una password - leggi la nota sopra!)", "thirdPartyApps": "Applicazioni di terze parti", @@ -77,7 +78,7 @@ "otherExtensions": "Altre estensioni", "otherDesc": "Puoi trovare altre app, strumenti ed estensioni nella wiki di Habitica.", "resetDo": "Sì, resetta il mio account!", - "resetComplete": "Reset complete!", + "resetComplete": "Reset completato!", "fixValues": "Sistema valori", "fixValuesText1": "Se hai avuto a che fare con un bug o commesso un errore che ha ingiustamente \"cambiato\" il tuo personaggio (danni che non avresti dovuto ricevere, Oro che non hai guadagnato, ecc.), qui puoi sistemare manualmente quei valori. Sì, questo rende possibile barare: usa questa funzionalità con saggezza, o finirai per rovinare la tua stessa costruzione di Habit!", "fixValuesText2": "Tieni presente che qui non puoi modificare le serie delle singole attività. Per fare quello, modifica una Daily e vai nelle sue opzioni avanzate, troverai un campo \"Ripristina serie\".", @@ -107,7 +108,7 @@ "emailNotifications": "Notifiche via email", "wonChallenge": "Hai vinto una Sfida!", "newPM": "Hai ricevuto un messaggio privato", - "sentGems": "Sent gems!", + "sentGems": "Gemme inviate!", "giftedGems": "Ti vengono regalate delle Gemme", "giftedGemsInfo": "<%= amount %> Gemme - da <%= name %>", "giftedSubscription": "Ti viene regalato un abbonamento", @@ -149,7 +150,7 @@ "webhookURL": "URL Webhook", "invalidUrl": "url non valido", "invalidEnabled": "the \"enabled\" parameter should be a boolean", - "regIdRequired": "RegId is required", + "regIdRequired": "RegId è richiesto", "pushDeviceAdded": "Push device added successfully", "pushDeviceAlreadyAdded": "The user already has the push device", "add": "Aggiungi", diff --git a/common/locales/it/spells.json b/common/locales/it/spells.json index 75f359e450..3b35e348e8 100644 --- a/common/locales/it/spells.json +++ b/common/locales/it/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Tira una palla di neve a un compagno di squadra! Cosa potrebbe mai andare storto? L'effetto scomparirà quando inizierà la nuova giornata del tuo \"bersaglio\".", "spellSpecialSaltText": "Sale", "spellSpecialSaltNotes": "Qualcuno ti ha tirato una palla di neve. Ah ah, divertente. Ora però toglimi questa neve di dosso!", - "spellSpecialSpookDustText": "Luci Sinistre", - "spellSpecialSpookDustNotes": "Trasforma un amico in un lenzuolo fluttuante con due occhi!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Pozione Opaca", "spellSpecialOpaquePotionNotes": "Annulla l'effetto di Luci Sinistre", "spellSpecialShinySeedText": "Seme Brillante", @@ -50,7 +50,7 @@ "spellNotFound": "Abilità \"<%= spellId %>\" non trovata.", "partyNotFound": "Squadra non trovata.", "targetIdUUID": "\"targetId\" deve essere un ID Utente valido.", - "challengeTasksNoCast": "Usare un'abilità sulle attività delle sfide non è supportato.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "Non possiedi questa abilità.", "spellLevelTooHigh": "Devi aver raggiunto il livello <%= level %> per usare questa abilità." } \ No newline at end of file diff --git a/common/locales/it/subscriber.json b/common/locales/it/subscriber.json index 3ab04f69d5..db3640c226 100644 --- a/common/locales/it/subscriber.json +++ b/common/locales/it/subscriber.json @@ -31,9 +31,9 @@ "manageSub": "Clicca per gestire l'abbonamento", "cancelSub": "Annulla abbonamento", "canceledSubscription": "Abbonamento annullato", - "cancelingSubscription": "Canceling the subscription", + "cancelingSubscription": "Annullamento dell'abbonamento", "adminSub": "Abbonamento per amministratori", - "morePlans": "Altri Piani
In Arrivo", + "morePlans": "Altri piani
In arrivo", "organizationSub": "Organizzazione privata", "organizationSubText": "I membri dell'organizzazione partecipano separatamente dall'Habitica pubblico, permettendo maggiore concentrazione ai tuoi partecipanti.", "hostingType": "Tipo di hosting", @@ -126,16 +126,16 @@ "missingSubscription": "User does not have a plan subscription", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", - "paymentNotSuccessful": "The payment was not successful", + "paymentNotSuccessful": "Il pagamento non è andato a buon fine", "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", - "readCard": "<%= cardType %> has been read", - "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", - "invalidCoupon": "Invalid coupon code.", - "couponUsed": "Coupon code already used.", - "noSudoAccess": "You don't have sudo access.", - "couponCodeRequired": "The coupon code is required.", - "eventRequired": "\"req.params.event\" is required.", - "countRequired": "\"req.query.count\" is required." + "readCard": "<%= cardType %> è stato letto", + "cardTypeRequired": "Tipo di carta richiesto", + "cardTypeNotAllowed": "Unknown card type.", + "invalidCoupon": "Codice coupon non valido.", + "couponUsed": "Codice coupon già utilizzato.", + "noSudoAccess": "Non hai accesso sudo.", + "couponCodeRequired": "Il codice coupon è richiesto.", + "eventRequired": "\"req.params.event\" è richiesto.", + "countRequired": "\"req.query.count\" è richiesto." } \ No newline at end of file diff --git a/common/locales/it/tasks.json b/common/locales/it/tasks.json index 96f404287f..a837f3bddc 100644 --- a/common/locales/it/tasks.json +++ b/common/locales/it/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Deseleziona", "hideTags": "Nascondi", "showTags": "Mostra", + "toRequired": "You must supply a to value", "startDate": "Data di inizio", "startDateHelpTitle": "Quando dovrebbe cominciare questa attività?", "startDateHelp": "Imposta la data in cui l'attività sarà \"attiva\". Non sarà necessario completarla prima di quel giorno.", @@ -83,14 +84,14 @@ "perfectName": "Giorni Perfetti", "perfectText": "Ha completato tutte le Daily attive <%= perfects %> volte. Grazie a questa medaglia ottieni un bonus a tutti gli attributi pari a +livello/2 per il giorno successivo. Per i livelli superiori a 100 non c'è alcun effetto aggiuntivo oltre al bonus.", "perfectSingular": "Giorno Perfetto", - "perfectSingularText": "Completate tutte le Daily attive in un giorno. Grazie a questa medaglia ottieni un bonus a tutti gli attributi pari a livello/2 per il giorno successivo. Per i livelli superiori al 100 non ci sono effetti aggiuntivi.", + "perfectSingularText": "Completate tutte le Daily attive in un giorno. Grazie a questa medaglia ottieni un bonus per tutti gli attributi (pari a +livello/2) per il giorno successivo. Per i livelli superiori al 100 non ci sono effetti aggiuntivi oltre al bonus.", "streakerAchievement": "Hai ottenuto la medaglia \"Perfezionista\"! I 21 giorni consecutivi sono un importante traguardo per formare abitudini. Continua ad ottenere questa medaglia per ogni altri 21 giorni addizionali, su questa Daily o su qualunque altra!", "fortifyName": "Pozione di Fortificazione", "fortifyPop": "Fa tornare tutte le attività al valore neutro (colore giallo) e ripristina tutti i punti vita persi.", "fortify": "Fortifica", - "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", + "fortifyText": "La fortificazione farà tornare tutte le attività, eccetto quelle delle sfide, allo stato neutro (colore giallo), proprio come se le avessi appena create, e riempirà la tua barra della Salute. Questo è un ottimo aiuto se le tue attività rosse stanno rendendo il gioco troppo difficile, o se quelle blu stanno rendendo il gioco troppo semplice. Se ripartire con calma suona come una buona idea, spendi qualche gemma e fai vedere alle tue attività chi comanda!", "confirmFortify": "Sei sicuro?", - "fortifyComplete": "Fortify complete!", + "fortifyComplete": "Fortificazione completa!", "sureDelete": "Vuoi davvero eliminare la <%= taskType %> con il testo \"<%= taskText %>\"?", "streakCoins": "Bonus serie!", "pushTaskToTop": "Metti in cima alla lista. Tieni premuto CTRL o cmd per mettere in fondo.", @@ -114,17 +115,17 @@ "rewardHelp3": "Gli equipaggiamenti speciali compariranno qui durante gli Eventi Globali.", "rewardHelp4": "Non aver paura di creare le tue ricompense! Guarda qualche esempio qui.", "clickForHelp": "Fai click per suggerimenti", - "taskIdRequired": "\"taskId\" must be a valid UUID.", + "taskIdRequired": "\"taskId\" deve essere un UUID valido.", "taskNotFound": "Attività non trovata.", - "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", - "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", - "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", - "checklistItemNotFound": "No checklist item was found with given id.", - "itemIdRequired": "\"itemId\" must be a valid UUID.", + "invalidTaskType": "Il tipo di attività deve essere uno tra \"habit\", \"daily\", \"todo\", \"reward\".", + "cantDeleteChallengeTasks": "Un'attività che appartiene ad una sfida non può essere eliminata.", + "checklistOnlyDailyTodo": "Le checklist sono supportate solo su Daily e To-Do.", + "checklistItemNotFound": "Non è stato trovato alcun elemento checklist con l'id specificato.", + "itemIdRequired": "\"itemId\" deve essere un UUID valido.", "tagNotFound": "No tag item was found with given id.", - "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.", - "positionRequired": "\"position\" is required and must be a number.", - "cantMoveCompletedTodo": "Can't move a completed todo.", - "directionUpDown": "\"direction\" is required and must be 'up' or 'down'", - "alreadyTagged": "The task is already tagged with given tag." + "tagIdRequired": "\"tagId\" deve essere un UUID valido e deve corrispondere a un'etichetta appartenente all'utente.", + "positionRequired": "\"position\" è richiesto e deve essere un numero.", + "cantMoveCompletedTodo": "Non è possibile spostare una To-Do completata.", + "directionUpDown": "\"direction\" è richiesto è deve essere 'up' oppure 'down'.", + "alreadyTagged": "L'attività ha già l'etichetta specificata." } \ No newline at end of file diff --git a/common/locales/ja/content.json b/common/locales/ja/content.json index 1b9f3c761a..8148df1fa4 100644 --- a/common/locales/ja/content.json +++ b/common/locales/ja/content.json @@ -116,9 +116,9 @@ "questEggFalconText": "たか", "questEggFalconMountText": "たか", "questEggFalconAdjective": "敏速な", - "questEggTreelingText": "Treeling", - "questEggTreelingMountText": "Treeling", - "questEggTreelingAdjective": "a leafy", + "questEggTreelingText": "トリーリング", + "questEggTreelingMountText": "トリーリング", + "questEggTreelingAdjective": "リーフリ", "eggNotes": "たまごがえしの薬を見つけて、たまごにかけると、<%= eggAdjective(locale) %> <%= eggText(locale) %>が生まれます。", "hatchingPotionBase": "普通の", "hatchingPotionWhite": "白い", @@ -132,7 +132,7 @@ "hatchingPotionGolden": "黄金の", "hatchingPotionSpooky": "不気味な", "hatchingPotionPeppermint": "薄緑色の", - "hatchingPotionFloral": "Floral", + "hatchingPotionFloral": "フローラル", "hatchingPotionNotes": "これをたまごにかけると、<%= potText(locale) %> ペットが生まれます。", "premiumPotionAddlNotes": "ペットのたまごはクエストに使えません。", "foodMeat": "肉", diff --git a/common/locales/ja/front.json b/common/locales/ja/front.json index 0fad8119f1..62ee3d411b 100644 --- a/common/locales/ja/front.json +++ b/common/locales/ja/front.json @@ -184,7 +184,7 @@ "zelahQuote": "[Habitica] は、早く寝てポイントを増やすか、夜ふかしして体力を減らすかと考えさせることで、ぼくを定刻にベッドに行くよう説得してくれたよ。", "reportAccountProblems": "アカウントの問題を報告する", "reportCommunityIssues": "コミュニティの問題を報告する", - "subscriptionPaymentIssues": "Subscription and Payment Issues", + "subscriptionPaymentIssues": "寄付や支払いの問題", "generalQuestionsSite": "サイトについての一般的なご質問", "businessInquiries": "ビジネスのお問い合わせ", "merchandiseInquiries": "商品化についてのお問い合わせ", @@ -226,9 +226,9 @@ "altAttrGithub": "GitHub", "altAttrTrello": "Trello", "altAttrSlack": "Slack", - "missingAuthHeaders": "Missing authentication headers.", - "missingAuthParams": "Missing authentication parameters.", - "missingUsernameEmail": "Missing username or email.", + "missingAuthHeaders": "認証ヘッダーが見つかりません。", + "missingAuthParams": "認証パラメーターが見つかりません。", + "missingUsernameEmail": "ユーザー名またはメールアドレスが見つかりません。", "missingEmail": "Missing email.", "missingUsername": "Missing username.", "missingPassword": "Missing password.", diff --git a/common/locales/ja/groups.json b/common/locales/ja/groups.json index 4c948a60ea..c902704316 100644 --- a/common/locales/ja/groups.json +++ b/common/locales/ja/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/ja/limited.json b/common/locales/ja/limited.json index 160e0065f5..982229d95f 100644 --- a/common/locales/ja/limited.json +++ b/common/locales/ja/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "困った友達", "annoyingFriendsText": "パーティーの仲間から <%= snowballs %> 回、雪玉を投げられました。", "alarmingFriends": "要注意の友達", - "alarmingFriendsText": "パーティーの仲間から <%= spookDust %> 回、オバケにされました。", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "ぶざまな友達", "agriculturalFriendsText": "パーティーの仲間から <%= seeds %> 回、花にされました。", "aquaticFriends": "水の中の仲間たち", @@ -73,5 +73,5 @@ "sneakySqueakerSet": "こそこそしたピヨピヨ (盗賊)", "fallEventAvailability": "10月31日まで有効", "winterEventAvailability": "12月31日まで有効", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "5月31日まで有効" } \ No newline at end of file diff --git a/common/locales/ja/maintenance.json b/common/locales/ja/maintenance.json index efdb524cd2..6c1bfdae41 100644 --- a/common/locales/ja/maintenance.json +++ b/common/locales/ja/maintenance.json @@ -1,5 +1,5 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", + "habiticaBackSoon": "心配しないでください、ハビティカもすぐに換えてします!", "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", "maintenance": "Maintenance", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", diff --git a/common/locales/ja/pets.json b/common/locales/ja/pets.json index 6e5d4928e3..f500d5f049 100644 --- a/common/locales/ja/pets.json +++ b/common/locales/ja/pets.json @@ -12,6 +12,7 @@ "etherealLion": "天上のライオン", "veteranWolf": "百戦錬磨のオオカミ", "veteranTiger": "百戦錬磨のトラ", + "veteranLion": "Veteran Lion", "cerberusPup": "子ケルベロス", "hydra": "ヒドラ", "mantisShrimp": "シャコ", diff --git a/common/locales/ja/settings.json b/common/locales/ja/settings.json index 6b667f39c2..a3acacd9c5 100644 --- a/common/locales/ja/settings.json +++ b/common/locales/ja/settings.json @@ -47,6 +47,7 @@ "customDayStart": "日付更新の設定", "changeCustomDayStart": "日付更新する時間を変更しますか?", "sureChangeCustomDayStart": "日付更新する時間を変更します。よろしいですか?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "あなたの日課が次に更新されるのは <%= time %> です。この時間までに日課を片付けておきましょう!", "customDayStartInfo1": "Habitica の標準設定では、日課は、毎日あなたのタイム ゾーンでの深夜に更新されます。その時間をカスタマイズします。", "misc": "その他", diff --git a/common/locales/ja/spells.json b/common/locales/ja/spells.json index fecff5f0ec..8ac178ea14 100644 --- a/common/locales/ja/spells.json +++ b/common/locales/ja/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "パーティーの仲間に雪玉を投げましょう! 何か問題でも? 翌日まで効果がつづきます。", "spellSpecialSaltText": "塩", "spellSpecialSaltNotes": "だれかに雪玉を投げつけられた。ハハ、とてもおもしろい。だれかこの雪を拭いてくれ!", - "spellSpecialSpookDustText": "不気味なきらめき", - "spellSpecialSpookDustNotes": "友達を目が付いた空飛ぶ毛布に変えよう!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "不透明ポーション", "spellSpecialOpaquePotionNotes": "不気味なきらめきの効果を取り消す。", "spellSpecialShinySeedText": "輝く種", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/ja/subscriber.json b/common/locales/ja/subscriber.json index 86f3efc40d..75a0ec952f 100644 --- a/common/locales/ja/subscriber.json +++ b/common/locales/ja/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/ja/tasks.json b/common/locales/ja/tasks.json index 54004bc778..62cddd866f 100644 --- a/common/locales/ja/tasks.json +++ b/common/locales/ja/tasks.json @@ -73,6 +73,7 @@ "clearTags": "クリア", "hideTags": "非表示にする", "showTags": "表示する", + "toRequired": "You must supply a to value", "startDate": "開始日", "startDateHelpTitle": "このタスクはいつ始める?", "startDateHelp": "タスクが効果を発する開始日を設定します。ただし、過去の日付を設定することができません。", diff --git a/common/locales/nl/backgrounds.json b/common/locales/nl/backgrounds.json index 68994431f7..33eaca699e 100644 --- a/common/locales/nl/backgrounds.json +++ b/common/locales/nl/backgrounds.json @@ -161,11 +161,11 @@ "backgroundGiantFlowersNotes": "Dartel op Reusachtige Bloemen", "backgroundRainbowsEndText": "Einde van de regenboog", "backgroundRainbowsEndNotes": "Vind goud aan het Einde van de Regenboog", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", - "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", - "backgroundGazeboText": "Gazebo", + "backgrounds052016": "SET 24: Uitgebracht in februari 2016", + "backgroundBeehiveText": "Bijenkorf", + "backgroundBeehiveNotes": "Zoem en dans in een Bijenkorf", + "backgroundGazeboText": "Prieel", "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgroundTreeRootsText": "Boomwortels", + "backgroundTreeRootsNotes": "Ontdek de Boomwortels" } \ No newline at end of file diff --git a/common/locales/nl/challenge.json b/common/locales/nl/challenge.json index 2463a9d1a1..32195e8346 100644 --- a/common/locales/nl/challenge.json +++ b/common/locales/nl/challenge.json @@ -64,19 +64,20 @@ "hurray": "Hoera!", "noChallengeOwner": "geen eigenaar", "noChallengeOwnerPopover": "Deze uitdaging heeft geen eigenaar omdat de eigenaar zijn of haar account heeft verwijderd.", - "challengeMemberNotFound": "User not found among challenge's members", - "onlyGroupLeaderChal": "Only the group leader can create challenges", - "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", + "challengeMemberNotFound": "Gebruiker niet gevonden onder deelnemers aan de uitdaging", + "onlyGroupLeaderChal": "Alleen de groepsleider kan uitdagingen creëren", + "tavChalsMinPrize": "Prijs moet ten minste 1 Juweel zijn voor Herberg uitdagingen", "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", "challengeIdRequired": "\"challengeId\" must be a valid UUID.", "winnerIdRequired": "\"winnerId\" must be a valid UUID.", - "challengeNotFound": "Challenge not found.", - "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", - "onlyLeaderUpdateChal": "Only the challenge leader can update it.", + "challengeNotFound": "Uitdaging niet gevonden", + "onlyLeaderDeleteChal": "Alleen de uitdagingsleider kan het verwijderen.", + "onlyLeaderUpdateChal": "Alleen de uitdagingsleider kan het updaten.", "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/nl/character.json b/common/locales/nl/character.json index 55a05a780e..fcb9c02e1a 100644 --- a/common/locales/nl/character.json +++ b/common/locales/nl/character.json @@ -110,7 +110,7 @@ "mage": "Magiër", "mystery": "Verrassingsartikelen", "changeClass": "Van klasse veranderen en eigenschapspunten terugkrijgen", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "Om van klasse te veranderen moet je ten minste niveau 10 zijn.", "levelPopover": "Elk niveau geeft je één punt om toe te wijzen aan een eigenschap van jouw keuze. Je kunt dit handmatig doen of het spel voor jou laten beslissen door gebruik te maken van één van de automatische verdelingsopties.", "unallocated": "nog niet toegewezen eigenschapspunten", "haveUnallocated": "Je hebt <%= points %> eigenschapspunt(en) nog niet toegewezen.", @@ -167,6 +167,6 @@ "showQuickAllocation": "Toon verdeling eigenschapspunten", "hideQuickAllocation": "Verberg verdeling eigenschapspunten", "quickAllocationLevelPopover": "Met ieder level verdien je een punt om toe te wijzen aan een eigenschap van jouw keuze. Je kunt dit eigenhandig doen of het spel voor je laten bepalen door een van de automatische toewijzingsopties te kiezen in Gebruiker -> Statistieken gebruiker.", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "invalidAttribute": "\"<%= attr %>\" is geen geldige eigenschap.", + "notEnoughAttrPoints": "Je hebt niet genoeg eigenschap punten" } \ No newline at end of file diff --git a/common/locales/nl/contrib.json b/common/locales/nl/contrib.json index 90e64c13e2..daeeb4c53c 100644 --- a/common/locales/nl/contrib.json +++ b/common/locales/nl/contrib.json @@ -35,12 +35,12 @@ "hallContributors": "Hal der Bijdragers", "hallPatrons": "Hal der Weldoeners", "rewardUser": "Gebruiker belonen", - "UUID": "User ID", + "UUID": "Gebruikers ID", "loadUser": "Gebruiker laden", - "noAdminAccess": "You don't have admin access.", - "pageMustBeNumber": "req.query.page must be a number", - "userNotFound": "User not found.", - "invalidUUID": "UUID must be valid", + "noAdminAccess": "Je hebt geen beheerders toegang.", + "pageMustBeNumber": "req.query.page moet een nummer zijn", + "userNotFound": "Gebruiker niet gevonden.", + "invalidUUID": "UUID moet geldig zijn", "title": "Titel", "moreDetails": "Meer details (1-7)", "moreDetails2": "meer details (8-9)", diff --git a/common/locales/nl/death.json b/common/locales/nl/death.json index ef2f35ed99..328e7dee89 100644 --- a/common/locales/nl/death.json +++ b/common/locales/nl/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "Onvolledige dagelijkse taken doen je 's nachts pijn, dus wees voorzichtig en voeg in eerste instantie niet te veel toe!", "lowHealthTips4": "Als een dagelijkse taak op een bepaalde dag niet gedaan hoeft te worden kun je die dag uitschakelen door op het potlood te klikken.", "goodLuck": "Veel plezier!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "Kan niet herrijzen als je niet dod bent" } \ No newline at end of file diff --git a/common/locales/nl/front.json b/common/locales/nl/front.json index 8642845db2..513427a4a0 100644 --- a/common/locales/nl/front.json +++ b/common/locales/nl/front.json @@ -28,7 +28,7 @@ "communityReddit": "Reddit", "companyAbout": "Hoe het werkt", "companyBlog": "Blog", - "devBlog": "Developer Blog", + "devBlog": "Ontwikkelaars Blog", "companyDonate": "Doneer", "companyExtensions": "Extensies", "companyPrivacy": "Privacy", @@ -52,7 +52,7 @@ "featureSocialHeading": "Samenspel", "featuredIn": "Vermeld in", "featuresHeading": "We bieden ook...", - "footerDevs": "Developers", + "footerDevs": "Ontwikkelaars", "footerCommunity": "Gemeenschap", "footerCompany": "Bedrijf", "footerMobile": "Mobiel", @@ -184,7 +184,7 @@ "zelahQuote": "Door [Habitica] kan ik overgehaald worden om op tijd naar bed te gaan, omdat ik eraan denk dat ik punten verdien als ik vroeg in bed kruip, en gezondheid verlies als ik lang opblijf!", "reportAccountProblems": "Problemen met je account melden", "reportCommunityIssues": "Problemen met de gemeenschap melden", - "subscriptionPaymentIssues": "Subscription and Payment Issues", + "subscriptionPaymentIssues": "Abonnements- en Betalingsproblemen", "generalQuestionsSite": "Algemene vragen over de site", "businessInquiries": "Zakelijke aanvragen", "merchandiseInquiries": "Vragen over merchandise", @@ -228,18 +228,18 @@ "altAttrSlack": "Slack", "missingAuthHeaders": "Missing authentication headers.", "missingAuthParams": "Missing authentication parameters.", - "missingUsernameEmail": "Missing username or email.", - "missingEmail": "Missing email.", - "missingUsername": "Missing username.", - "missingPassword": "Missing password.", - "missingNewPassword": "Missing new password.", - "wrongPassword": "Wrong password.", - "notAnEmail": "Invalid email address.", - "emailTaken": "Email address is already used in an account.", - "newEmailRequired": "Missing new email address.", - "usernameTaken": "Username already taken.", - "passwordConfirmationMatch": "Password confirmation doesn't match password.", - "invalidLoginCredentials": "Incorrect username and/or email and/or password.", + "missingUsernameEmail": "Ontbrekende gebruikersnaam of email.", + "missingEmail": "Ontbrekende e-mail.", + "missingUsername": "Ontbrekende gebruikersnaam.", + "missingPassword": "Ontbrekend wachtwoord.", + "missingNewPassword": "Ontbrekend nieuw wachtwoord.", + "wrongPassword": "Verkeerd wachtwoord.", + "notAnEmail": "Ongeldig e-mail adres.", + "emailTaken": "E-mail adres is al in gebruik door een account.", + "newEmailRequired": "Ontbrekend nieuw e-mail adres.", + "usernameTaken": "Gebruikersnaam al in gebruik.", + "passwordConfirmationMatch": "Wachtwoord confirmatie komt niet overeen met wachtwoord.", + "invalidLoginCredentials": "Incorrecte gebruikersnaam en/of e-mail en/of wachtwoord.", "passwordReset": "If we have your email on file, your password reset link has been sent to your email.", "passwordResetEmailSubject": "Password Reset for Habitica", "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.", diff --git a/common/locales/nl/gear.json b/common/locales/nl/gear.json index edcbc23fac..3bbc893f11 100644 --- a/common/locales/nl/gear.json +++ b/common/locales/nl/gear.json @@ -189,7 +189,7 @@ "weaponArmoireMiningPickaxNotes": "Houw de maximale hoeveelheid goud uit je taken! Verhoogt perceptie met <%= per %>. Betoverd kabinet: Mijnwerkersset (Voorwerp 3 van 3).", "weaponArmoireBasicLongbowText": "Simpele handboog", "weaponArmoireBasicLongbowNotes": "Een bruikbare tweedehandse boog. Vergroot de kracht met <%= str %>. Betoverd kabinet: Boogschuttersset (Voorwerp 1 van 3)", - "weaponArmoireHabiticanDiplomaText": "Habitican Diploma", + "weaponArmoireHabiticanDiplomaText": "Habiticaan Diploma", "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).", "armor": "wapenrusting", "armorBase0Text": "Eenvoudige kleding", @@ -366,7 +366,7 @@ "armorMystery201512Notes": "Ontbied de ijzige vlammen van de winter! Verleent geen voordelen. Abonnee-uitrusting december 2015.", "armorMystery201603Text": "Gelukzalig Pak", "armorMystery201603Notes": "Dit pak is geweven uit duizenden klavertjes vier! Verleent geen voordelen. Abonnee-uitrusting maart 2016.", - "armorMystery201604Text": "Armor o' Leaves", + "armorMystery201604Text": "Harnas van Bladeren", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", "armorMystery301404Text": "Steampunkpak", "armorMystery301404Notes": "Net en zwierig, niet? Verleent geen voordelen. Abonnee-uitrusting februari 3015.", @@ -571,7 +571,7 @@ "headMystery201602Notes": "Bescherm je identiteit voor al je aanbidders. Verleent geen voordelen. Abonnee-uitrusting februari 2016.", "headMystery201603Text": "Gelukzalige Hoed", "headMystery201603Notes": "Deze hoge hoed is een magische geluksbedel. Verleent geen voordelen. Abonnee-uitrusting maart 2016.", - "headMystery201604Text": "Crown o' Flowers", + "headMystery201604Text": "Kroon van Bloemen", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", "headMystery301404Text": "Chique hoge hoed", "headMystery301404Notes": "Een chique hoge hoed voor lieden van deftigen huize! Abonnee-uitrusting januari 3015. Verleent geen voordelen.", @@ -726,7 +726,7 @@ "shieldArmoireDragonTamerShieldNotes": "Leid vijanden af met dit schild in de vorm van een draak. Verhoogt Perceptie met <%= per %>. Betoverd kabinet: Drakentemmer set (Voorwerp 2 van 3).", "shieldArmoireMysticLampText": "Mystieke Lamp", "shieldArmoireMysticLampNotes": "Verlicht de donkerste grotten met deze Mystieke Lamp! Verhoogt perceptie met <%= per %>. Betoverd kabinet: Onafhankelijk voorwerp. ", - "shieldArmoireFloralBouquetText": "Bouquet o' Flowers", + "shieldArmoireFloralBouquetText": "Boeket Bloemen", "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.", "back": "Lichaamsaccessoire", "backBase0Text": "Geen rugaccessoire", diff --git a/common/locales/nl/groups.json b/common/locales/nl/groups.json index 9218d8c2db..abe7649e55 100644 --- a/common/locales/nl/groups.json +++ b/common/locales/nl/groups.json @@ -103,7 +103,7 @@ "messageRequired": "A message is required.", "toUserIDRequired": "A User ID is required", "gemAmountRequired": "A number of gems is required", - "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.", + "notAuthorizedToSendMessageToThisUser": "Kan geen bericht versturen naar deze gebruiker.", "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you", "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!", "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!", @@ -163,7 +163,7 @@ "partyUpAchievement": "Is bij een groep gegaan met een andere persoon! Veel plezier met het vechten tegen monsters en het ondersteunen van elkaar.", "partyOnAchievement": "Is bij een groep gegaan met minstens vier mensen! Geniet van je verhoogde verantwoordelijkheid wanneer je je bij je vrienden voegt om je vijanden te verslaan!", "groupIdRequired": "\"groupId\" must be a valid UUID", - "groupNotFound": "Group not found.", + "groupNotFound": "Groep niet gevonden.", "groupTypesRequired": "You must supply a valid \"type\" query string.", "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", @@ -179,11 +179,11 @@ "userAlreadyInGroup": "User already in that group.", "userAlreadyInvitedToGroup": "User already invited to that group.", "userAlreadyPendingInvitation": "User already pending invitation.", - "userAlreadyInAParty": "User already in a party.", + "userAlreadyInAParty": "Gebruiker zit al in een groep.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/nl/limited.json b/common/locales/nl/limited.json index c4d328b43b..2bba166e67 100644 --- a/common/locales/nl/limited.json +++ b/common/locales/nl/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Irritante vrienden", "annoyingFriendsText": "Is <%= snowballs %> keer geraakt door een sneeuwbal door groepsleden.", "alarmingFriends": "Verontrustende vrienden", - "alarmingFriendsText": "Is <%= spookDust %> keer bespookt door groepsleden.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Vrienden met groene vingers", "agriculturalFriendsText": "Is <%= seeds %> keer door groepsgenoten in een bloem omgetoverd.", "aquaticFriends": "Spetterende vrienden", @@ -73,5 +73,5 @@ "sneakySqueakerSet": "Sneaky Pieper (Dief)", "fallEventAvailability": "Beschikbaar tot 31 oktober", "winterEventAvailability": "Beschikbaar tot 31 december", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "Beschikbaar tot 31 mei" } \ No newline at end of file diff --git a/common/locales/nl/maintenance.json b/common/locales/nl/maintenance.json index efdb524cd2..7ec78446a7 100644 --- a/common/locales/nl/maintenance.json +++ b/common/locales/nl/maintenance.json @@ -1,30 +1,30 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", + "habiticaBackSoon": "Maak je geen zorgen, Habitica is snel weer terug!", "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", + "maintenance": "Onderhoud", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", + "noDamageKeepStreaks": "Je zal GEEN schade ontvangen of series verliezen!", + "thanksForPatience": "Bedankt voor je geduld!", "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", "veteranPetAward": "At the end, you will receive a Veteran pet!", "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", + "maintenanceInfoWhat": "Wat gebeurt er?", "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", + "maintenanceInfoWhy": "Waarom gebeurt dit?", "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", + "maintenanceInfoMore": "Meer informatie", "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", + "maintenanceInfoHowLong": "Hoe lang zal dit onderhoud duren?", "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", - "maintenanceInfoSeeTasks": "What if I need to see my task list?", + "maintenanceInfoSeeTasks": "Wat als ik mijn taken lijst moet zien?", "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", "maintenanceInfoRarePet": "What kind of rare pet will I receive?", "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", diff --git a/common/locales/nl/pets.json b/common/locales/nl/pets.json index 53a694d3b0..8b9d62023a 100644 --- a/common/locales/nl/pets.json +++ b/common/locales/nl/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Etherische Leeuw", "veteranWolf": "Veteranenwolf", "veteranTiger": "Veteranentijger", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberuspup", "hydra": "Hydra", "mantisShrimp": "Bidsprinkhaankreeft", diff --git a/common/locales/nl/rebirth.json b/common/locales/nl/rebirth.json index 6b5503484f..1b991589c0 100644 --- a/common/locales/nl/rebirth.json +++ b/common/locales/nl/rebirth.json @@ -25,5 +25,5 @@ "rebirthName": "Bol der Hergeboorte", "reborn": "Herboren, maximale niveau <%= reLevel %>", "confirmReborn": "Weet je het zeker?", - "rebirthComplete": "You have been reborn!" + "rebirthComplete": "Je bent herboren!" } \ No newline at end of file diff --git a/common/locales/nl/settings.json b/common/locales/nl/settings.json index 95233c0355..ed28bcebd5 100644 --- a/common/locales/nl/settings.json +++ b/common/locales/nl/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Begin van de dag aanpassen", "changeCustomDayStart": "Begin van de dag aanpassen? ", "sureChangeCustomDayStart": "Weet je zeker dat je het begin van de dag wilt aanpassen?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Je dagelijkse taken worden gereset de eerste keer dat je Habitica gebruikt na <%= time %>. Zorg ervoor dat je je dagelijkse taken voor die tijd afrondt!", "customDayStartInfo1": "Standaard controleert en reset Habitica je dagelijkse taken elke dag om middernacht. Je kunt dit hier aanpassen.", "misc": "Diversen", @@ -147,7 +148,7 @@ "webhooks": "Webhooks", "enabled": "Ingeschakeld", "webhookURL": "Webhook-URL", - "invalidUrl": "invalid url", + "invalidUrl": "ongeldige url", "invalidEnabled": "the \"enabled\" parameter should be a boolean", "regIdRequired": "RegId is required", "pushDeviceAdded": "Push device added successfully", diff --git a/common/locales/nl/spells.json b/common/locales/nl/spells.json index 928b3c3393..c477664cd2 100644 --- a/common/locales/nl/spells.json +++ b/common/locales/nl/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Gooi een sneeuwbal naar een groepslid! Wat kan er nou misgaan? Duurt tot een nieuwe dag aanbreekt voor dit groepslid.", "spellSpecialSaltText": "Zout", "spellSpecialSaltNotes": "Iemand heeft een sneeuwbal naar je gegooid. Ha ha, heel grappig. Help me nu die sneeuw van me af te krijgen!", - "spellSpecialSpookDustText": "Spookachtige Glitters", - "spellSpecialSpookDustNotes": "Verander een vriend in een zwevend laken met ogen!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Ondoorzichtig toverdrankje", "spellSpecialOpaquePotionNotes": "Verwijder het effect van Spookachtige Glitters.", "spellSpecialShinySeedText": "Glanzend zaadje", @@ -47,10 +47,10 @@ "spellSpecialSeafoamNotes": "Verander een vriend in een zeedier!", "spellSpecialSandText": "Zand", "spellSpecialSandNotes": "Verwijder de effecten van Zeeschuim.", - "spellNotFound": "Skill \"<%= spellId %>\" not found.", - "partyNotFound": "Party not found", + "spellNotFound": "Vaardigheid \"<%= spellId %>\" niet gevonden.", + "partyNotFound": "Groep niet gevonden", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/nl/subscriber.json b/common/locales/nl/subscriber.json index ceabf65b2c..59070b5ec0 100644 --- a/common/locales/nl/subscriber.json +++ b/common/locales/nl/subscriber.json @@ -131,9 +131,9 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", - "invalidCoupon": "Invalid coupon code.", - "couponUsed": "Coupon code already used.", + "cardTypeNotAllowed": "Unknown card type.", + "invalidCoupon": "Ongeldige coupon code.", + "couponUsed": "Coupon code al in gebruik", "noSudoAccess": "You don't have sudo access.", "couponCodeRequired": "The coupon code is required.", "eventRequired": "\"req.params.event\" is required.", diff --git a/common/locales/nl/tasks.json b/common/locales/nl/tasks.json index bb89d30866..4426b2126a 100644 --- a/common/locales/nl/tasks.json +++ b/common/locales/nl/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Wissen", "hideTags": "Verbergen", "showTags": "Weergeven", + "toRequired": "You must supply a to value", "startDate": "Begindatum", "startDateHelpTitle": "Wanneer moet deze taak beginnen?", "startDateHelp": "Selecteer de datum waarop deze taak actief wordt. Hij hoeft vóór deze datum niet voltooid te worden.", @@ -115,7 +116,7 @@ "rewardHelp4": "Wees niet bang om je eigen beloningen toe te voegen! Kijk ook eens naar de voorbeelden hier.", "clickForHelp": "Klik hier voor hulp", "taskIdRequired": "\"taskId\" must be a valid UUID.", - "taskNotFound": "Task not found.", + "taskNotFound": "Taak niet gevonden.", "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", diff --git a/common/locales/pl/backgrounds.json b/common/locales/pl/backgrounds.json index 8e18523529..18b09bb5bf 100644 --- a/common/locales/pl/backgrounds.json +++ b/common/locales/pl/backgrounds.json @@ -158,14 +158,14 @@ "backgroundArcheryRangeText": "Strzelnica łuczników", "backgroundArcheryRangeNotes": "Ćwicz łucznictwo na strzelnicy.", "backgroundGiantFlowersText": "Gigantyczne kwiaty", - "backgroundGiantFlowersNotes": "Hasaj wśród Gigantycznych Kwiatów.", + "backgroundGiantFlowersNotes": "Hasaj wśród gigantycznych kwiatów.", "backgroundRainbowsEndText": "Koniec tęczy", "backgroundRainbowsEndNotes": "Znajdź złoto na końcu tęczy.", - "backgrounds052016": "SET 24: Released May 2016", + "backgrounds052016": "ZESTAW 24: Opublikowany w maju 2016", "backgroundBeehiveText": "Ul", "backgroundBeehiveNotes": "Bzycz i tańcz w ulu.", "backgroundGazeboText": "Altana", "backgroundGazeboNotes": "Battle a Gazebo.", "backgroundTreeRootsText": "Korzenie drzewa", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgroundTreeRootsNotes": "Zbadaj korzenie drzewa" } \ No newline at end of file diff --git a/common/locales/pl/challenge.json b/common/locales/pl/challenge.json index d21f7455e6..0149a06b48 100644 --- a/common/locales/pl/challenge.json +++ b/common/locales/pl/challenge.json @@ -64,19 +64,20 @@ "hurray": "Hurra!", "noChallengeOwner": "brak właściciela", "noChallengeOwnerPopover": "To wyzwanie nie posiada właściciela, ponieważ osoba zakładająca to wyzwanie usunęła swoje konto.", - "challengeMemberNotFound": "User not found among challenge's members", + "challengeMemberNotFound": "Nie znaleziono użytkownika wśród uczestników wyzwania", "onlyGroupLeaderChal": "Tylko przywódca grupy może tworzyć wyzwania", - "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", - "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", + "tavChalsMinPrize": "Nagroda za wyzwania karczmy musi wynosić co najmniej jeden klejnot.", + "cantAfford": "Nie stać cię na tę nagrodę. Kup więcej klejnotów lub obniż jej wysokość.", "challengeIdRequired": "\"challengeId\" must be a valid UUID.", "winnerIdRequired": "\"winnerId\" must be a valid UUID.", "challengeNotFound": "Nie znaleziono wyzwania.", - "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", - "onlyLeaderUpdateChal": "Only the challenge leader can update it.", + "onlyLeaderDeleteChal": "Tylko przywódca wyzwania może je usunąć.", + "onlyLeaderUpdateChal": "Tylko przywódca wyzwania może je zaktualizować.", "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", "onlyChalLeaderEditTasks": "Zadanie należące do wyzwania może być zmieniane jedynie przez przywódcę.", - "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "userAlreadyInChallenge": "Użytkownik bierze już udział w tym wyzwaniu.", + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/pl/character.json b/common/locales/pl/character.json index 29b8569894..0e114de957 100644 --- a/common/locales/pl/character.json +++ b/common/locales/pl/character.json @@ -110,7 +110,7 @@ "mage": "Mag", "mystery": "Tajemniczy", "changeClass": "Zmień klasę i przywróć punkty atrybutów", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "Aby zmienić klasę musisz mieć co najmniej 10 level. ", "levelPopover": "Każdy zdobyty poziom daje Ci jeden punkt, który możesz przydzielić do wybranego przez siebie atrybutu. Możesz zrobić to ręcznie lub pozwolić, by gra zdecydowała za Ciebie, używając jednej z opcji automatycznego przydzielania.", "unallocated": "Nieprzydzielone punkty atrybutów", "haveUnallocated": "Nieprzydzielone Punkty Atrybutów: <%= points %>", @@ -167,6 +167,6 @@ "showQuickAllocation": "Pokaż przydział atrybutów", "hideQuickAllocation": "Ukryj przydział atrybutów", "quickAllocationLevelPopover": "Z każdym poziomem otrzymujesz jeden punkt, który możesz przydzielić wybranemu atrybutowi. Możesz to zrobić ręcznie, lub zdać się na jedną z możliwych opcji Automatycznej Alokacji, dostępnych w Użytkownik -> Statystyki Awatara.", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "invalidAttribute": "\"<%= attr %>\" nie jest poprawnym atrybutem.", + "notEnoughAttrPoints": "Nie masz wystarczająco punktów atrybutów." } \ No newline at end of file diff --git a/common/locales/pl/content.json b/common/locales/pl/content.json index e48b962844..963104a9ac 100644 --- a/common/locales/pl/content.json +++ b/common/locales/pl/content.json @@ -115,10 +115,10 @@ "questEggSnailAdjective": "powolny ale wytrwały", "questEggFalconText": "Sokół", "questEggFalconMountText": "Sokół", - "questEggFalconAdjective": "jerzyk", - "questEggTreelingText": "Treeling", - "questEggTreelingMountText": "Treeling", - "questEggTreelingAdjective": "a leafy", + "questEggFalconAdjective": "szybki", + "questEggTreelingText": "Drzewo", + "questEggTreelingMountText": "Drzewo", + "questEggTreelingAdjective": "liściaste", "eggNotes": "Znajdź eliksir wyklucia i wylej go na to jajo, a wykluje się z niego <%= eggAdjective(locale) %> <%= eggText(locale) %>. ", "hatchingPotionBase": "Zwyczajny", "hatchingPotionWhite": "Biały", @@ -132,7 +132,7 @@ "hatchingPotionGolden": "Złoty", "hatchingPotionSpooky": "Upiorny", "hatchingPotionPeppermint": "Miętowy", - "hatchingPotionFloral": "Floral", + "hatchingPotionFloral": "Kwiecisty", "hatchingPotionNotes": "Wylej go na jajko, a wykluje się z niego <%=potText(locale)%>.", "premiumPotionAddlNotes": "Nie nadaje się do użytku na jajach otrzymanych za misje.", "foodMeat": "Mięso", diff --git a/common/locales/pl/contrib.json b/common/locales/pl/contrib.json index 1bb11eb1a0..f223b61116 100644 --- a/common/locales/pl/contrib.json +++ b/common/locales/pl/contrib.json @@ -35,11 +35,11 @@ "hallContributors": "Sala Współtwórców", "hallPatrons": "Sala patronów", "rewardUser": "Nagrodź użytkownika", - "UUID": "User ID", + "UUID": "ID Użytkownika", "loadUser": "Wczytaj użytkownika", - "noAdminAccess": "You don't have admin access.", + "noAdminAccess": "Nie masz dostępu administratora.", "pageMustBeNumber": "req.query.page must be a number", - "userNotFound": "User not found.", + "userNotFound": "Nie znaleziono użytkownika.", "invalidUUID": "UUID must be valid", "title": "Tytuł", "moreDetails": "Więcej szczegółów (1-7)", diff --git a/common/locales/pl/death.json b/common/locales/pl/death.json index 55929f6427..23a074e6d1 100644 --- a/common/locales/pl/death.json +++ b/common/locales/pl/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "Niewykonane Codzienne ranią ciebie w nocy, więc uważaj, by na początku nie dodać ich zbyt dużo!", "lowHealthTips4": "Jeśli Codzienne nie jest zaplanowane na konkretny dzień, możesz je wyłączyć klikając na ikonę ołówka.", "goodLuck": "Powodzenia!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "Nie można ożywić kogoś, kto nie jest martwy" } \ No newline at end of file diff --git a/common/locales/pl/front.json b/common/locales/pl/front.json index 07b89d0032..79194b5730 100644 --- a/common/locales/pl/front.json +++ b/common/locales/pl/front.json @@ -184,7 +184,7 @@ "zelahQuote": "Z [Habitiką], myśl o zdobytych punktach (za dłuższy sen) lub straconym zdrowiu (za zarwanie nocy), skłania mnie do wcześniejszego pójścia do łóżka!", "reportAccountProblems": "Zgłoś problem z kontem", "reportCommunityIssues": "Zgłoś problem społecznościowy", - "subscriptionPaymentIssues": "Subscription and Payment Issues", + "subscriptionPaymentIssues": "Problemy z abonamentem i opłatami", "generalQuestionsSite": "Ogólne pytania na temat strony", "businessInquiries": "Zapytania biznesowe", "merchandiseInquiries": "Zapytania handlowe", @@ -228,25 +228,25 @@ "altAttrSlack": "Slack", "missingAuthHeaders": "Missing authentication headers.", "missingAuthParams": "Missing authentication parameters.", - "missingUsernameEmail": "Missing username or email.", - "missingEmail": "Missing email.", - "missingUsername": "Missing username.", - "missingPassword": "Missing password.", + "missingUsernameEmail": "Zapomniałem nazwy użytkownika lub maila.", + "missingEmail": "Zapomniałem maila.", + "missingUsername": "Zapomniałem nazwy użytkownika.", + "missingPassword": "Zapomniałem hasło.", "missingNewPassword": "Missing new password.", - "wrongPassword": "Wrong password.", - "notAnEmail": "Invalid email address.", - "emailTaken": "Email address is already used in an account.", + "wrongPassword": "Nieprawidłowe hasło.", + "notAnEmail": "Nieprawidłowy adres e-mail. ", + "emailTaken": "Adres e-mail jest już używany.", "newEmailRequired": "Missing new email address.", - "usernameTaken": "Username already taken.", - "passwordConfirmationMatch": "Password confirmation doesn't match password.", - "invalidLoginCredentials": "Incorrect username and/or email and/or password.", + "usernameTaken": "Nazwa użytkownika jest już zajęta", + "passwordConfirmationMatch": "Potwierdzenie hasła nie jest identyczne z hasłem.", + "invalidLoginCredentials": "Błędna nazwa użytkownika i/lub e-mail i/lub hasło", "passwordReset": "If we have your email on file, your password reset link has been sent to your email.", - "passwordResetEmailSubject": "Password Reset for Habitica", - "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.", - "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.

Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.

Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.", - "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".", + "passwordResetEmailSubject": "Reset hasła do Habitiki", + "passwordResetEmailText": "Hasło użytkownika <%= username %> zostało zmienione na <%= newPassword %>. Uwaga! Wielkość liter ma znaczenie – zarówno nazwę użytkownika, jak i hasło musisz wprowadzić w dokładnie takiej samej formie jak tu. Radzimy skopiować je zamiast wpisywać samodzielnie. Zaloguj się na <%= baseUrl %>. Po zalogowaniu udaj się do <%= baseUrl %>/#/options/settings/settings i zmień hasło.", + "passwordResetEmailHtml": "Hasło użytkownika <%= username %> zostało zmienione na <%= newPassword %>.

Uwaga! Wielkość liter ma znaczenie – zarówno nazwę użytkownika, jak i hasło musisz wprowadzić w dokładnie takiej samej formie jak tu. Radzimy skopiować je zamiast wpisywać samodzielnie.

Zaloguj się na <%= baseUrl %>. Po zalogowaniu udaj się do <%= baseUrl %>/#/options/settings/settings i zmień hasło.", + "invalidLoginCredentialsLong": "Ojej, twoja nazwa użytkownika lub hasło są błędne.\n– Upewnij się, że poprawnie wpisałeś nazwę użytkownika lub e-mail.\n– Być może rejestrowałeś się za pomocą Facebooka, a nie e-maila. Upewnij się, że tak nie było, próbując logowania przez Facebook.\n– Jeśli zapomniałeś hasła, wybierz \"zapomniałem hasła\".", "invalidCredentials": "There is no account that uses those credentials.", - "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.", + "accountSuspended": "Konto zostało zawieszone. Aby uzyskać pomoc, napisz na leslie@habitica.com, zawierając w treści swoje ID użytkownika – „<%= userId %>”.", "onlyFbSupported": "Only Facebook is supported currently.", "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.", "onlySocialAttachLocal": "Local authentication can be added to only a social account.", diff --git a/common/locales/pl/groups.json b/common/locales/pl/groups.json index 5415ab006c..e425b96132 100644 --- a/common/locales/pl/groups.json +++ b/common/locales/pl/groups.json @@ -102,11 +102,11 @@ "inbox": "Skrzynka odbiorcza", "messageRequired": "A message is required.", "toUserIDRequired": "A User ID is required", - "gemAmountRequired": "A number of gems is required", + "gemAmountRequired": "Wymagana liczba klejnotów", "notAuthorizedToSendMessageToThisUser": "Nie można wysłać wiadomości do tego użytkownika.", "privateMessageGiftIntro": "Hej <%= receiverName %>, <%= senderName %> wysłał Tobie", "privateMessageGiftGemsMessage": "<%= gemAmount %> klejnotów!", - "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!", + "privateMessageGiftSubscriptionMessage": "Miesięcy abonamentu: <%= numberOfMonths %>!", "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.", "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.", "abuseFlag": "Zgłoś naruszenie regulaminu społeczności.", @@ -165,25 +165,25 @@ "groupIdRequired": "\"groupId\" must be a valid UUID", "groupNotFound": "Grupa nie znaleziona.", "groupTypesRequired": "You must supply a valid \"type\" query string.", - "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", - "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", - "onlyLeaderCanRemoveMember": "Only group leader can remove a member!", + "questLeaderCannotLeaveGroup": "Nie możesz odejść z drużyny, jeśli zacząłeś misję – musisz ją najpierw przerwać.", + "cannotLeaveWhileActiveQuest": "Nie możesz odejść z drużyny w trakcie trwania misji. Najpierw powinieneś opuścić misję.", + "onlyLeaderCanRemoveMember": "Tylko przywódca grupy może usuwać jej członków!", "memberCannotRemoveYourself": "Nie możesz usunąć siebie!", "groupMemberNotFound": "Użytkownik nie znaleziony wśród członków grupy", - "mustBeGroupMember": "Must be member of the group.", + "mustBeGroupMember": "Musi być członkiem grupy.", "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", "inviteMissingEmail": "Missing email address in invite.", - "partyMustbePrivate": "Parties must be private", - "userAlreadyInGroup": "User already in that group.", - "userAlreadyInvitedToGroup": "User already invited to that group.", + "partyMustbePrivate": "Drużyny muszą być prywatne.", + "userAlreadyInGroup": "Użytkownik jest już członkiem tej grupy.", + "userAlreadyInvitedToGroup": "Użytkownik został już zaproszony do tej grupy.", "userAlreadyPendingInvitation": "User already pending invitation.", - "userAlreadyInAParty": "User already in a party.", - "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", + "userAlreadyInAParty": "Użytkownik jest już w drużynie.", + "userWithIDNotFound": "Nie znaleziono użytkownika o numerze ID „<%= userId %>”.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", - "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", - "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", + "canOnlyInviteMaxInvites": "Możesz zaprosić jednocześnie nie więcej niż <%= maxInvites %> osób.", + "onlyCreatorOrAdminCanDeleteChat": "Nie masz uprawnień do usunięcia tej wiadomości!" } \ No newline at end of file diff --git a/common/locales/pl/limited.json b/common/locales/pl/limited.json index 357b22a0bf..db038351dd 100644 --- a/common/locales/pl/limited.json +++ b/common/locales/pl/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Złośliwi znajomi", "annoyingFriendsText": "Dostał <%= snowballs %> razy śnieżką od członków Drużyny.", "alarmingFriends": "Niepokojący przyjaciele", - "alarmingFriendsText": "Przestraszony <%= spookDust %> razy przez członków drużyny.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Rolniczy przyjaciele", "agriculturalFriendsText": "Przemieniony w kwiat <%= seeds %> razy przez członków drużyny.", "aquaticFriends": "Wodni przyjaciele", @@ -73,5 +73,5 @@ "sneakySqueakerSet": "Przebiegły Kwikacz (Łotrzyk)", "fallEventAvailability": "Dostępne do 31 października", "winterEventAvailability": "Dostępne do 31 grudnia", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "Dostępne do 31 maja" } \ No newline at end of file diff --git a/common/locales/pl/npc.json b/common/locales/pl/npc.json index b73a1e7d43..3e85c6e11e 100644 --- a/common/locales/pl/npc.json +++ b/common/locales/pl/npc.json @@ -23,7 +23,7 @@ "ianBrokenText": "Witaj w sklepie z misjami... Możesz tutaj wykorzystać zwoje misji, by wraz z przyjaciółmi toczyć boje z potworami... Już teraz koniecznie sprawdź naszą wspaniałą tablicę ze zwojami misji możliwymi do zakupu...", "missingKeyParam": "\"req.params.key\" is required.", "itemNotFound": "Item \"<%= key %>\" not found.", - "cannotBuyItem": "You can't buy this item.", + "cannotBuyItem": "Nie możesz tego kupić.", "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.", "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.", "invalidPetName": "Invalid pet name supplied.", @@ -34,13 +34,13 @@ "keyRequired": "Key is required", "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]", "contentKeyNotFound": "Key not found for Content <%= type %>", - "plusOneGem": "+1 Gem", + "plusOneGem": "+1 klejnot", "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", "userItemsKeyNotFound": "Key not found for user.items <%= type %>", "pathRequired": "Path string is required", - "unlocked": "Items have been unlocked", - "alreadyUnlocked": "Full set already unlocked.", - "alreadyUnlockedPart": "Full set already partially unlocked.", + "unlocked": "Przedmioty zostały odblokowane", + "alreadyUnlocked": "Pełen zestaw jest już odblokowany.", + "alreadyUnlockedPart": "Zestaw jest już częściowo odblokowany.", "USD": "(USD)", "newStuff": "Nowości", "cool": "Przypomnij mi później", @@ -84,7 +84,7 @@ "tourPetsPage": "Oto i stajnia! Po uzyskaniu 3 poziomu można, przy użyciu jaj i eliksirów, wykluwać chowańce. Chowaniec wykluty na targu pojawi się tutaj! Kliknij na obrazku przedstawiającym chowańca, aby dołączył do Twojego awatara. Racz chowańce karmą, która będzie dostępna od poziomu 3, a wyrosną na potężne wierzchowce.", "tourMountsPage": "Kiedy ofiarujesz chowańcowi wystarczającą ilość karmy, by zmienił się w wierzchowca, pojawi się tutaj. (Chowańce, wierzchowce i karmę odblokowuje uzyskanie poziomu 3.) Kliknij na wierzchowcu, aby go osiodłać!", "tourEquipmentPage": "Tutaj znajduje się Twoje wyposażenie! Twój rynsztunek wpływa na statystyki. Jeśli chcesz, by Twój awatar posiadał inny rynsztunek bez zmiany statystyk, kliknij \"Dostosuj strój\".", - "equipmentAlreadyOwned": "You already own that piece of equipment", + "equipmentAlreadyOwned": "Już posiadasz tę część ekwipunku.", "tourOkay": "Oki!", "tourAwesome": "Świetnie!", "tourSplendid": "Wspaniale!", diff --git a/common/locales/pl/pets.json b/common/locales/pl/pets.json index b2dcc41aeb..cab4bb2036 100644 --- a/common/locales/pl/pets.json +++ b/common/locales/pl/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Eteryczny lew", "veteranWolf": "Wilk weteran", "veteranTiger": "Tygrys weteran", + "veteranLion": "Veteran Lion", "cerberusPup": "Szczenię Cerbera", "hydra": "Hydra", "mantisShrimp": "Krewetka modliszkowa", @@ -62,7 +63,7 @@ "hatchedPet": "<%= potion %> <%= egg %> się wykluło!", "displayNow": "Wyświetl teraz", "displayLater": "Wyświetl później", - "petNotOwned": "You do not own this pet.", + "petNotOwned": "Nie posiadasz tego chowańca.", "earnedCompanion": "Dzięki swej produktywności masz nowego towarzysza. Nakarm go by urósł!", "feedPet": "Nakarmić <%= text %> twojego <%= name %>?", "useSaddle": "Osiodłać <%= pet %>?", diff --git a/common/locales/pl/settings.json b/common/locales/pl/settings.json index 97ef5eaf0b..4deebbbaec 100644 --- a/common/locales/pl/settings.json +++ b/common/locales/pl/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Własny początek dnia", "changeCustomDayStart": "Zmienić własny początek dnia?", "sureChangeCustomDayStart": "Czy na pewno chcesz zmienić własny początek dnia?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Twoje Codzienne zresetują się po pierwszym użyciu Habitiki po <%= time %>. Upewnij się, że ukończyłeś swoje Codzienne przed tym czasem!", "customDayStartInfo1": "Habitica domyślnie sprawdza i resetuje twoje Codzienne o północy każdego dnia w twojej strefie czasowej. Tutaj możesz zmienić ten czas.", "misc": "Pozostałe", @@ -166,5 +167,5 @@ "amazonPayments": "Płatności Amazon", "timezone": "Strefa czasowa", "timezoneUTC": "Habitica korzysta ze strefy czasowej ustawionej na twoim komputerze, to znaczy: <%= utc %>", - "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.

If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices." + "timezoneInfo": "Jeśli strefa czasowa jest błędna, spróbuj najpierw odświeżyć stronę przyciskiem odświeżania w przeglądarce, aby upewnić się, że Habitica ma dostęp do najnowszych danych. Jeśli strefa wciąż jest nieprawidłowa, ustaw ją na swoim komputerze i znów odśwież stronę.

Jeśli używasz Habitiki na innych komputerach lub urządzeniach mobilnych, strefa czasowa na każdym z nich musi być taka sama. Jeśli twoje codzienne zadania resetują się o niewłaściwej porze, sprawdź w ten sam sposób inne komputery oraz przeglądarki w twoich urządzeniach mobilnych." } \ No newline at end of file diff --git a/common/locales/pl/spells.json b/common/locales/pl/spells.json index 3a36644d77..d3633eb829 100644 --- a/common/locales/pl/spells.json +++ b/common/locales/pl/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Rzuć w członka drużyny śnieżną pigułą! Co może pójść nie tak? Trwa aż do jego następnego dnia.", "spellSpecialSaltText": "Sól", "spellSpecialSaltNotes": "Ktoś rzucił w ciebie kulą śnieżną. Ha ha, bardzo śmieszne. A teraz otrzepcie mnie z tego śniegu!", - "spellSpecialSpookDustText": "Straszne iskierki", - "spellSpecialSpookDustNotes": "Zamień przyjaciela w latający koc z oczami!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Nieprzejrzysty eliksir", "spellSpecialOpaquePotionNotes": "Anuluje efekt strasznych iskierek.", "spellSpecialShinySeedText": "Lśniące nasiono", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/pl/subscriber.json b/common/locales/pl/subscriber.json index 493c14a5f0..041bd248d5 100644 --- a/common/locales/pl/subscriber.json +++ b/common/locales/pl/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/pl/tasks.json b/common/locales/pl/tasks.json index 9379729cf9..33394743db 100644 --- a/common/locales/pl/tasks.json +++ b/common/locales/pl/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Wyczyść", "hideTags": "Ukryj", "showTags": "Pokaż", + "toRequired": "You must supply a to value", "startDate": "Data rozpoczęcia", "startDateHelpTitle": "Kiedy to zadanie powinno się rozpocząć?", "startDateHelp": "Wyznacz termin wykonania tego zadania. Przed wybraną datą nie będą z nim związane żadne efekty.", diff --git a/common/locales/pt/groups.json b/common/locales/pt/groups.json index cf1924b0b9..ae932b1b31 100644 --- a/common/locales/pt/groups.json +++ b/common/locales/pt/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/pt/limited.json b/common/locales/pt/limited.json index cf9fa99f82..6f5d433599 100644 --- a/common/locales/pt/limited.json +++ b/common/locales/pt/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amigos Irritantes", "annoyingFriendsText": "Membros da equipe jogaram bolas de neve em você <%= snowballs %> vezes.", "alarmingFriends": "Amigos Alarmantes", - "alarmingFriendsText": "Membros da equipe te assustaram <%= spookDust %> vezes.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Amigos Agrícolas", "agriculturalFriendsText": "Membros da equipe te transformaram em flor <%= seeds %> vezes.", "aquaticFriends": "Amigos aquáticos", diff --git a/common/locales/pt/maintenance.json b/common/locales/pt/maintenance.json index efdb524cd2..493454405d 100644 --- a/common/locales/pt/maintenance.json +++ b/common/locales/pt/maintenance.json @@ -1,10 +1,10 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", + "habiticaBackSoon": "Não se preocupe. Habitica estará de volta em breve!", "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", + "maintenance": "Manutenção", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", + "noDamageKeepStreaks": "NÃO receberá dano ou perderá a sua sequência!", + "thanksForPatience": "Obrigado pela sua paciência!", "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", "veteranPetAward": "At the end, you will receive a Veteran pet!", diff --git a/common/locales/pt/pets.json b/common/locales/pt/pets.json index cdbce79733..64df4f0b16 100644 --- a/common/locales/pt/pets.json +++ b/common/locales/pt/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Leão Etéreo", "veteranWolf": "Lobo Veterano", "veteranTiger": "Tigre Veterano", + "veteranLion": "Veteran Lion", "cerberusPup": "Filhote de Cérbero", "hydra": "Hidra", "mantisShrimp": "Camarão Mantis", diff --git a/common/locales/pt/settings.json b/common/locales/pt/settings.json index d72c5c2173..8816d5aa67 100644 --- a/common/locales/pt/settings.json +++ b/common/locales/pt/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Início do Dia Personalizado", "changeCustomDayStart": "Deseja mudar o Início de dia customizado?", "sureChangeCustomDayStart": "Você tem certeza que quer mudar o início customizado de dia?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "As suas tarefas diárias serão reiniciadas ao utilizar Habitica depois de <%= time %>. Certifique-se de que completou as suas tarefas diárias antes deste horário!", "customDayStartInfo1": "Habitica está predefinido para verificar e reiniciar as suas tarefas diárias à meia noite de seu fuso horária a cada dia. Você pode personalizar esse horário aqui.", "misc": "Variados", diff --git a/common/locales/pt/spells.json b/common/locales/pt/spells.json index d5adfca845..1a48277e6b 100644 --- a/common/locales/pt/spells.json +++ b/common/locales/pt/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Jogue uma bola de neve em um membro da equipe! O que poderia dar errado? Dura até o novo dia do membro.", "spellSpecialSaltText": "Sal", "spellSpecialSaltNotes": "Alguém jogou uma bola de neve em você. Ha ha, muito engraçado. Agora tire essa neve de mim!", - "spellSpecialSpookDustText": "Brilhos Assustadores", - "spellSpecialSpookDustNotes": "Transforme um amigo em um lençol flutuante com olhos!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Poção Opaca", "spellSpecialOpaquePotionNotes": "Cancela os efeitos de Brilhos Assustadores.", "spellSpecialShinySeedText": "Semente Cintilante", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/pt/subscriber.json b/common/locales/pt/subscriber.json index d3afbf5fae..5282895be9 100644 --- a/common/locales/pt/subscriber.json +++ b/common/locales/pt/subscriber.json @@ -125,17 +125,17 @@ "missingUnsubscriptionCode": "Missing unsubscription code.", "missingSubscription": "User does not have a plan subscription", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", - "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", + "cannotDeleteActiveAccount": "Você possui um cadastro ativo, cancele seu plano antes de deletar sua conta.", "paymentNotSuccessful": "The payment was not successful", "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", - "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeRequired": "Tipo de cartão requerido.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", - "noSudoAccess": "You don't have sudo access.", - "couponCodeRequired": "The coupon code is required.", + "noSudoAccess": "Você não possui acesso de administrador.", + "couponCodeRequired": "O código do cupom é requerido.", "eventRequired": "\"req.params.event\" is required.", "countRequired": "\"req.query.count\" is required." } \ No newline at end of file diff --git a/common/locales/pt/tasks.json b/common/locales/pt/tasks.json index 918d8c6e44..28dd55621f 100644 --- a/common/locales/pt/tasks.json +++ b/common/locales/pt/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Limpar", "hideTags": "Ocultar", "showTags": "Mostrar", + "toRequired": "You must supply a to value", "startDate": "Data Inicial", "startDateHelpTitle": "Quando esta tarefa deve começar?", "startDateHelp": "Defina a data para a qual esta tarefa entra em vigor. Não será debitada nos dias anteriores.", diff --git a/common/locales/ro/groups.json b/common/locales/ro/groups.json index d79a184a06..89946fc398 100644 --- a/common/locales/ro/groups.json +++ b/common/locales/ro/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/ro/limited.json b/common/locales/ro/limited.json index 12d369d842..279543e61f 100644 --- a/common/locales/ro/limited.json +++ b/common/locales/ro/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Prieteni enervanți", "annoyingFriendsText": "Lovit de <%= snowballs %> cu bulgări de zăpadă de coechipieri.", "alarmingFriends": "Prieteni Alarmanți", - "alarmingFriendsText": "Speriat de <%= spookDust %> ori de colegii din echipă", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Prieteni agricoli", "agriculturalFriendsText": "Am fost transformat(ă) într-o floare de <%= seeds %> ori de coechipieri.", "aquaticFriends": "Prieteni acvatici", diff --git a/common/locales/ro/pets.json b/common/locales/ro/pets.json index e0ad7f96da..3d61dbc10b 100644 --- a/common/locales/ro/pets.json +++ b/common/locales/ro/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Leu eteric", "veteranWolf": "Lup veteran", "veteranTiger": "Veteran Tiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Pui de Cerber", "hydra": "Hidră", "mantisShrimp": "Crevete călugăr", diff --git a/common/locales/ro/settings.json b/common/locales/ro/settings.json index a584961059..cad9e6df41 100644 --- a/common/locales/ro/settings.json +++ b/common/locales/ro/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Când începe ziua", "changeCustomDayStart": "Dorești schimbarea orei de început a zilei?", "sureChangeCustomDayStart": "Sigur dorești schimbarea orei de început a zilei?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Sarcinile tale zilnice se vor reseta prima dată când folosești Habitica după ora <%= time %>. Ai grijă să completezi sarcinile zilnice înainte de această oră!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Altele", diff --git a/common/locales/ro/spells.json b/common/locales/ro/spells.json index b6d10e810d..a513e6f9cf 100644 --- a/common/locales/ro/spells.json +++ b/common/locales/ro/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Throw a snowball at a party member! What could possibly go wrong? Lasts until member's new day.", "spellSpecialSaltText": "Sare", "spellSpecialSaltNotes": "Cineva te-a lovit cu un bulgăre de zăpadă. Ha ha, foarte amuzant. Acum dă zăpada asta jos de pe mine!", - "spellSpecialSpookDustText": "Spooky Sparkles", - "spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Poțiune opacă", "spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.", "spellSpecialShinySeedText": "Shiny Seed", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/ro/subscriber.json b/common/locales/ro/subscriber.json index aa0ee2ee85..88bf2be5d6 100644 --- a/common/locales/ro/subscriber.json +++ b/common/locales/ro/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/ro/tasks.json b/common/locales/ro/tasks.json index a45220d404..ce7b1d6799 100644 --- a/common/locales/ro/tasks.json +++ b/common/locales/ro/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Șterge", "hideTags": "Ascunde", "showTags": "Arată", + "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.", diff --git a/common/locales/ru/groups.json b/common/locales/ru/groups.json index 1067170ace..c74a8d6d6a 100644 --- a/common/locales/ru/groups.json +++ b/common/locales/ru/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/ru/limited.json b/common/locales/ru/limited.json index ae1538f2e5..6261836ec3 100644 --- a/common/locales/ru/limited.json +++ b/common/locales/ru/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Вредные друзья", "annoyingFriendsText": "Прилетело снежков от членов команды: <%= snowballs %>.", "alarmingFriends": "Пугающие друзья", - "alarmingFriendsText": "Напуган членами команды <%= spookDust %> раз(а).", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Сельскохозяйственные друзья", "agriculturalFriendsText": "Превращен в цветок членами команды <%= seeds %> раз(а).", "aquaticFriends": "Водяные друзья", diff --git a/common/locales/ru/pets.json b/common/locales/ru/pets.json index ff6552591a..3ddf405ca0 100644 --- a/common/locales/ru/pets.json +++ b/common/locales/ru/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Бесплотный лев", "veteranWolf": "Волк-ветеран", "veteranTiger": "Тигр-ветеран", + "veteranLion": "Veteran Lion", "cerberusPup": "Щенок цербера", "hydra": "Гидра", "mantisShrimp": "Рак-богомол", diff --git a/common/locales/ru/settings.json b/common/locales/ru/settings.json index d64ed6ead2..24abe566a5 100644 --- a/common/locales/ru/settings.json +++ b/common/locales/ru/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Персональное начало суток", "changeCustomDayStart": "Изменить персональное начало суток?", "sureChangeCustomDayStart": "Вы уверены, что хотите изменить персональное начало суток?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Ваши ежедневные задания будут обнулены при первом же посещении сайта после <%= time %>. Убедитесь, что вы выполнили ежедневные задания до этого времени!", "customDayStartInfo1": "Habitica по умолчанию проверяет и сбрасывает ваши ежедневные задачи каждый день в полночь по вашему часовому поясу. Вы можете настроить это время здесь.", "misc": "Разное", diff --git a/common/locales/ru/spells.json b/common/locales/ru/spells.json index 927d6fc9ad..9a6ec57186 100644 --- a/common/locales/ru/spells.json +++ b/common/locales/ru/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Бросьте снежок в товарища по команде! Что может пойти не так? Эффект длится до наступления нового дня компаньона.", "spellSpecialSaltText": "Соль", "spellSpecialSaltNotes": "Кто-то кинул в вас снежок. Ха-ха, очень смешно. А теперь стряхните с меня снег!", - "spellSpecialSpookDustText": "Зловещие искры", - "spellSpecialSpookDustNotes": "Превратите друга в парящую простыню с глазами!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Зелье Непроницаемости", "spellSpecialOpaquePotionNotes": "Отменить эффект Зловещих искр", "spellSpecialShinySeedText": "Солнечное семя", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/ru/subscriber.json b/common/locales/ru/subscriber.json index 2fe5355aa7..e380649b97 100644 --- a/common/locales/ru/subscriber.json +++ b/common/locales/ru/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/ru/tasks.json b/common/locales/ru/tasks.json index 0395d6d48e..01d64edd52 100644 --- a/common/locales/ru/tasks.json +++ b/common/locales/ru/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Очистить", "hideTags": "Скрыть", "showTags": "Показать", + "toRequired": "You must supply a to value", "startDate": "Дата начала", "startDateHelpTitle": "Когда это задание должно начаться?", "startDateHelp": "Задайте дату, в этот день задание вступит в силу. До этого оно будет необязательным.", diff --git a/common/locales/sk/backgrounds.json b/common/locales/sk/backgrounds.json index 60a5252122..8bfd63bfac 100644 --- a/common/locales/sk/backgrounds.json +++ b/common/locales/sk/backgrounds.json @@ -161,11 +161,11 @@ "backgroundGiantFlowersNotes": "Šanti navrchu obrovských kvetov.", "backgroundRainbowsEndText": "Koniec dúhy", "backgroundRainbowsEndNotes": "Nájdi zlato na konci dúhy", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", - "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", - "backgroundGazeboText": "Gazebo", - "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgrounds052016": "24. sada: Vydaná v máji 2016", + "backgroundBeehiveText": "Včelí úľ", + "backgroundBeehiveNotes": "Bzuč a tancuj vo včeľom úli. ", + "backgroundGazeboText": "Terasa", + "backgroundGazeboNotes": "Bojuj na terase.", + "backgroundTreeRootsText": "Korene stromov", + "backgroundTreeRootsNotes": "Preskúmaj korene stromov." } \ No newline at end of file diff --git a/common/locales/sk/character.json b/common/locales/sk/character.json index 9fccd83e58..c00fecd300 100644 --- a/common/locales/sk/character.json +++ b/common/locales/sk/character.json @@ -110,7 +110,7 @@ "mage": "Mág", "mystery": "Záhada", "changeClass": "Zmeniť povolanie, vrátiť pridelené body atribútov", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "Aby si mohol zmeniť povolanie, musíš byť aspoň level 10.", "levelPopover": "S každým levelom získaš jeden bod, ktorý môžeš priradiť do atribútu. Môžeš tak robiť ručne alebo nechať hru, aby rozhodla za teba, použitím možnosti \"automatické pridelenie\".", "unallocated": "Nepridelené body atribútov", "haveUnallocated": "Máš <%= points %> nepriradených bodov atribútov", @@ -167,6 +167,6 @@ "showQuickAllocation": "Zobraziť rozdelenie štatistík", "hideQuickAllocation": "Skryť rozdelenie štatistík", "quickAllocationLevelPopover": "S každým levelom získaš jeden bod, ktorý môžeš priradiť do atribútu podľa svojho výberu. Môžeš tak robiť ručne alebo nechať hru, aby rozhodla za teba, použitím možnosti \"automatické pridelenie\", ktorú môžeš nájsť v časti Užívateľ -> Štatistiky.", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", + "invalidAttribute": "\"<%= attr %>\" nie je platný atribút.", "notEnoughAttrPoints": "You don't have enough attribute points." } \ No newline at end of file diff --git a/common/locales/sk/defaulttasks.json b/common/locales/sk/defaulttasks.json index b7ffa68d11..61b198380a 100644 --- a/common/locales/sk/defaulttasks.json +++ b/common/locales/sk/defaulttasks.json @@ -1,5 +1,5 @@ { - "defaultHabit1Text": "Productive Work (Click the pencil to edit)", + "defaultHabit1Text": "Produktívna práca (Pre úpravu klikni na ceruzku)", "defaultHabit1Notes": "Sample Good Habits: + Eat a vegetable + 15 minutes productive work", "defaultHabit2Text": "Eat Junk Food (Click the pencil to edit)", "defaultHabit2Notes": "Sample Bad Habits: - Smoke - Procrastinate", @@ -7,7 +7,7 @@ "defaultHabit3Notes": "Sample Good or Bad Habits: +/- Took Stairs/Elevator ; +/- Drank Water/Soda", "defaultTodoNotes": "You can either complete this To-Do, edit it, or remove it.", "defaultTodo1Text": "Join Habitica (Check me off!)", - "defaultReward1Text": "15 minute break", + "defaultReward1Text": "15 minútová prestávka", "defaultReward1Notes": "Vlastné odmeny môžu mať rôzne formy. Niektorí ľudia odkladajú sledovanie svojho obľúbeného seriálu, kým nemajú dosť zlata, aby si to mohli dovoliť.", "defaultTag1": "ráno", "defaultTag2": "poobede", diff --git a/common/locales/sk/groups.json b/common/locales/sk/groups.json index 6377903a67..c9815caa36 100644 --- a/common/locales/sk/groups.json +++ b/common/locales/sk/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/sk/limited.json b/common/locales/sk/limited.json index ae8cffb3db..19fb8f9d59 100644 --- a/common/locales/sk/limited.json +++ b/common/locales/sk/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Otravní kamaráti", "annoyingFriendsText": "<%= snowballs %>-krát schytal snehovou guľou od člena družiny.", "alarmingFriends": "Znepokojujúci priatelia", - "alarmingFriendsText": "Členovia družiny ťa vystrašili <%= spookDust %> krát.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Poľnohospodárski priatelia", "agriculturalFriendsText": "Členovia družiny ťa premenili na kvetinu <%= seeds %> krát.", "aquaticFriends": "Vodní priatelia", diff --git a/common/locales/sk/maintenance.json b/common/locales/sk/maintenance.json index efdb524cd2..ac1c49b19b 100644 --- a/common/locales/sk/maintenance.json +++ b/common/locales/sk/maintenance.json @@ -1,25 +1,25 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", - "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "habiticaBackSoon": "Nerob paniku, Habitica bude čoskoro späť!", + "importantMaintenance": "Robíme dôležitú údržbu, ktorá sa bude konať pravdepodobne do <%= localDate %> tvojej časovej zóny.", + "maintenance": "Údržba", + "maintenanceMoreInfo": "Chceš viac informácií o údržbe? <%= linkStart %>Pozri si našu info stránku<%= linkEnd %>.", + "noDamageKeepStreaks": "Neuberie sa ti zo života, ani nestratíš série!", + "thanksForPatience": "Vďaka za Vašu trpezlivosť!", + "twitterMaintenanceUpdates": "Pre najnovšie informácie sledujte náš Twitter, kde budeme zverejňovať informácie o stave údržby.", + "veteranPetAward": "Na konci dostaneš zvieratko veterán!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", + "maintenanceInfoTitle": "Informácie o blížiacej sa údržbe", + "maintenanceInfoWhat": "Čo sa to deje?", + "maintenanceInfoWhatText": "Habitica nebude 21. mája spustená kvôli údržbe väčšinu dňa. Počas tohto víkendu ti nebude spôsobené žiadne poškodenie ani nebude tvoj účet zranený napriek tomu, že sa nebudeš môcť prihlásiť a skontrolovať svoje denné úlohy včas! Budeme sa veľmi snažiť, aby doba údržby trvala čo najkratšie a budeme zverejňovať pravidelné aktualizácie na našom Twitterovom účte. Aby sme všetkým poďakovali za vašu trpezlivosť, na konci doby údržby všetci dostanú nezvyčajné zvieratko!", + "maintenanceInfoWhy": "Prečo sa to robí?", "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", + "maintenanceInfoMore": "Viac Informácií", + "maintenanceInfoAccountChanges": "Aké zmeny uvidím na mojom účte po skončení prepracovania?", "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", + "maintenanceInfoHowLong": "Ako dlho bude prebiehať údržba?", "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", diff --git a/common/locales/sk/pets.json b/common/locales/sk/pets.json index 1b29bf5186..a43eedf660 100644 --- a/common/locales/sk/pets.json +++ b/common/locales/sk/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Éterický lev", "veteranWolf": "Vlk veterán", "veteranTiger": "Tiger Veterán", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberusove šteniatko", "hydra": "Hydra", "mantisShrimp": "Garnát", @@ -62,7 +63,7 @@ "hatchedPet": "Vyliahol si <%= potion %> <%= egg %>!", "displayNow": "Ukázať teraz", "displayLater": "Ukázať neskôr", - "petNotOwned": "You do not own this pet.", + "petNotOwned": "Nevlastníš toto zvieratko.", "earnedCompanion": "Vďaka tvojej produktivite si získal nového spoločníka. Nakŕm ho, aby vyrástol!", "feedPet": "Chceš nakŕmiť toto zvieratko: <%= name %> týmto: <%= article %><%= text %>?", "useSaddle": "Chceš osedlať toto zvieratko: <%= pet %>?", @@ -84,8 +85,8 @@ "petKeyBoth": "Vypusti oboje", "confirmPetKey": "Si si istý?", "petKeyNeverMind": "Ešte nie", - "petsReleased": "Pets released.", - "mountsAndPetsReleased": "Mounts and pets released", - "mountsReleased": "Mounts released", + "petsReleased": "Zvieratka vypustené.", + "mountsAndPetsReleased": "Tátoše a zvieratká vypustené", + "mountsReleased": "Tátoše vypustené", "gemsEach": "drahokamy za každý" } \ No newline at end of file diff --git a/common/locales/sk/questscontent.json b/common/locales/sk/questscontent.json index fe51f0b282..5a4c9c7a44 100644 --- a/common/locales/sk/questscontent.json +++ b/common/locales/sk/questscontent.json @@ -29,8 +29,8 @@ "questGhostStagDropDeerEgg": "Jeleň (vajce)", "questGhostStagUnlockText": "Odomkne predajné jelenie vajíčka na trhu", "questRatText": "Potkaní kráľ", - "questRatNotes": "Garbage! Massive piles of unchecked Dailies are lying all across Habitica. The problem has become so serious that hordes of rats are now seen everywhere. You notice @Pandah petting one of the beasts lovingly. She explains that rats are gentle creatures that feed on unchecked Dailies. The real problem is that the Dailies have fallen into the sewer, creating a dangerous pit that must be cleared. As you descend into the sewers, a massive rat, with blood red eyes and mangled yellow teeth, attacks you, defending its horde. Will you cower in fear or face the fabled Rat King?", - "questRatCompletion": "Your final strike saps the gargantuan rat's strength, his eyes fading to a dull grey. The beast splits into many tiny rats, which scurry off in fright. You notice @Pandah standing behind you, looking at the once mighty creature. She explains that the citizens of Habitica have been inspired by your courage and are quickly completing all their unchecked Dailies. She warns you that we must be vigilant, for should we let down our guard, the Rat King will return. As payment, @Pandah offers you several rat eggs. Noticing your uneasy expression, she smiles, \"They make wonderful pets.\"", + "questRatNotes": "Odpad! Masívne kopy neskontrolovaných denných úloh ležia po celej Habitice. Problém začal byť tak vážny, že hordy potkanov sú vidieť už úplne všade. Všimol si si, že @Pandah láskyplne hladká jedného z tvorov. Vysvetľuje, že potkany sú jemné stvorenia, ktoré sa živia neskontrolovanými dennými úlohami. Vážny problém je to, že denné úlohy padajú do kanálov, čím vytvorili nebezpečnú jamu, ktorá musí byť vyčistená. Ako zostupuješ do kanálov, obrovský potkan s krvavo červenými očami a skazenými žltými zubami na teba zaútočí obraňujúc svoju hordu. Ukryješ sa od strachu alebo sa postavíš legendárnemu Potkaniemu kráľovi?", + "questRatCompletion": "Tvoj finálny útok podlomil potkaniu silu, jeho oči chradnú do kalenej šedej. Tvor sa roztrhol na milión malých potkanov, ktorí mazali preč v panike. Všimneš si, že @Pandah stojí za tebou a pozerá sa na kedysi mocné stvorenie. Vysvetľuje, že občania Habitici sa inšpirovali tvojou odvahou a rýchlo plnia všetky svoje nesplnené denné úlohy. Varuje ťa, že musíme byť ostražitý, lebo keď opustíme našu stráž, Potkaní kráľ sa vráti. Ako odmenu, @Pandah ti ponúkne pár potkaních vajíčok. Všimnúc si tvoj neľahký výraz sa usmeje: \"Sú z nich úžasné zvieratká.\"", "questRatBoss": "Potkaní kráľ", "questRatDropRatEgg": "Potkan (vajce)", "questRatUnlockText": "Odomkne predajné potkanie vajíčka na trhu", diff --git a/common/locales/sk/rebirth.json b/common/locales/sk/rebirth.json index cc4dca6b6a..2030547460 100644 --- a/common/locales/sk/rebirth.json +++ b/common/locales/sk/rebirth.json @@ -25,5 +25,5 @@ "rebirthName": "Orb znovuzrodenia", "reborn": "Znovuzrodený, najvyšší dosiahnutý level: <%= reLevel %>", "confirmReborn": "Si si istý?", - "rebirthComplete": "You have been reborn!" + "rebirthComplete": "Bol si znovuzrodený!" } \ No newline at end of file diff --git a/common/locales/sk/settings.json b/common/locales/sk/settings.json index 8fc697f64b..2436f3bd65 100644 --- a/common/locales/sk/settings.json +++ b/common/locales/sk/settings.json @@ -15,11 +15,11 @@ "startCollapsedPop": "Keď je zvolená táto možnosť, keď začneš upravovať úlohu, zoznam štítkov bude skrytý.", "startAdvCollapsed": "Zrolované pokročilé možnosti v úlohách", "startAdvCollapsedPop": "S nastavením tejto možnosti budú pokročilé možnosti v momente, keď prvý krát otvoríš úlohu na úpravy, skryté.", - "dontShowAgain": "Don't show this again", - "suppressLevelUpModal": "Don't show popup when gaining a level", - "suppressHatchPetModal": "Don't show popup when hatching a pet", - "suppressRaisePetModal": "Don't show popup when raising a pet into a mount", - "suppressStreakModal": "Don't show popup when attaining a Streak achievement", + "dontShowAgain": "Už mi to nezobrazuj", + "suppressLevelUpModal": "Nezobrazuj popup, keď získam level", + "suppressHatchPetModal": "Nezobrazuj popup, keď vyliahnem zvieratko", + "suppressRaisePetModal": "Nezobrazuj popup, keď zo zvieratka vyrastie tátoš", + "suppressStreakModal": "Nezobrazuj popup, keď získam odznak za sériu", "showTour": "Zobraziť prehliadku", "restartTour": "Reštartovať úvodnú prehliadku prvej návštevy Habitica.", "showBailey": "Zobraziť Bailey", @@ -47,14 +47,15 @@ "customDayStart": "Vlastný začiatok dňa", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Rôzne", "showHeader": "Zobraziť záhlavie", "changePass": "Zmeniť heslo", "changeUsername": "Zmeniť používateľské meno", - "changeEmail": "Change Email Address", - "newEmail": "New Email Address", + "changeEmail": "Zmeniť Emailovú adresu", + "newEmail": "Nová Emailová adresa", "oldPass": "Staré heslo", "newPass": "Nové heslo", "confirmPass": "Zopakuj nové heslo", @@ -65,7 +66,7 @@ "deleteText": "Si si istý? Toto navždy zmaže tvoje konto a už sa nebude dať obnoviť! Ak budeš chcieť opäť používať Habitica, budeš sa musieť opäť registrovať s novým kontom. Odložené alebo použité drahokamy sa ti nepreplatia. Ak si si absolútne istý, napíš do textového poľa <%= deleteWord %>.", "API": "API", "APIv3": "API v3", - "APIv2": "API v2 - Deprecated", + "APIv2": "API v2 - Zastarané", "APIText": "Na použitie aplikácií tretích strán použi tento kľúč. API token považuj za niečo ako heslo, nezdieľaj ho verejne. Občas ťa môže niekto požiadať o User ID, ale nikomu nedávaj API Token, ani na Github.", "APIToken": "API Token (toto je heslo - prečítaj si varovanie vyššie!)", "thirdPartyApps": "Third Party Apps", @@ -107,7 +108,7 @@ "emailNotifications": "Email notifikácie", "wonChallenge": "You won a Challenge!", "newPM": "Received Private Message", - "sentGems": "Sent gems!", + "sentGems": "Poslať drahokamy!", "giftedGems": "Gifted Gems", "giftedGemsInfo": "<%= amount %> Gems - by <%= name %>", "giftedSubscription": "Gifted Subscription", @@ -139,20 +140,20 @@ "promoPlaceholder": "Enter Promotion Code", "displayInviteToPartyWhenPartyIs1": "Display Invite To Party button when party has 1 member.", "saveCustomDayStart": "Save Custom Day Start", - "registration": "Registration", + "registration": "Registrácia", "addLocalAuth": "Add local authentication:", "generateCodes": "Generate Codes", "generate": "Generate", "getCodes": "Get Codes", "webhooks": "Webhooks", - "enabled": "Enabled", + "enabled": "Povolené", "webhookURL": "Webhook URL", "invalidUrl": "invalid url", "invalidEnabled": "the \"enabled\" parameter should be a boolean", "regIdRequired": "RegId is required", "pushDeviceAdded": "Push device added successfully", "pushDeviceAlreadyAdded": "The user already has the push device", - "add": "Add", + "add": "Pridať", "buyGemsGoldCap": "Cap raised to <%= amount %>", "mysticHourglass": "<%= amount %> Mystic Hourglass", "mysticHourglassText": "Mystic Hourglasses allow purchasing a previous month's Mystery Item set.", @@ -164,7 +165,7 @@ "mysticHourglasses": "Mystic Hourglasses:", "paypal": "PayPal", "amazonPayments": "Amazon Payments", - "timezone": "Time Zone", + "timezone": "Časová zóna", "timezoneUTC": "Habitica uses the time zone set on your PC, which is: <%= utc %>", "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.

If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices." } \ No newline at end of file diff --git a/common/locales/sk/spells.json b/common/locales/sk/spells.json index 17d0667dd4..4071f7b165 100644 --- a/common/locales/sk/spells.json +++ b/common/locales/sk/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Hoď snehovou guľou po členovi svojej družiny! Čo zlé by sa mohlo stať? Trvá do konca jeho dňa.", "spellSpecialSaltText": "Soľ", "spellSpecialSaltNotes": "Niekto do teba hodil snehovú guľu. Ha ha, veľmi vtipné. Teraz zo mňa dostaň ten sneh!", - "spellSpecialSpookDustText": "Strašidelné iskričky", - "spellSpecialSpookDustNotes": "Premení tvojho priateľa na lietajúcu plachtu s očami!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Elixír nepriehľadnosti", "spellSpecialOpaquePotionNotes": "Zruší účinky strašidelných iskričiek.", "spellSpecialShinySeedText": "Lesklé semienko", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/sk/subscriber.json b/common/locales/sk/subscriber.json index 56cc700a71..2a82b5e58c 100644 --- a/common/locales/sk/subscriber.json +++ b/common/locales/sk/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/sk/tasks.json b/common/locales/sk/tasks.json index 50dae5642d..cf8c89b8c8 100644 --- a/common/locales/sk/tasks.json +++ b/common/locales/sk/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Vyčistiť", "hideTags": "Skryť", "showTags": "Zobraziť", + "toRequired": "You must supply a to value", "startDate": "Počiatočný dátum", "startDateHelpTitle": "Kedy by mala táto úloha začať?", "startDateHelp": "Nastav dátum, kedy bude táto úloha platná. Jej plnenie nebude povinné počas predchádzajúcich dní.", @@ -115,16 +116,16 @@ "rewardHelp4": "Neboj sa vlastných odmien! Pozri si zopár ich ukážok.", "clickForHelp": "Klikni pre pomoc", "taskIdRequired": "\"taskId\" must be a valid UUID.", - "taskNotFound": "Task not found.", + "taskNotFound": "Úloha nebola nájdená.", "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", - "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", + "cantDeleteChallengeTasks": "Úloha, ktorá je súčasťou výzvy, nemôže byť vymazaná.", "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", - "checklistItemNotFound": "No checklist item was found with given id.", - "itemIdRequired": "\"itemId\" must be a valid UUID.", + "checklistItemNotFound": "Žiadny zoznamový predmet nebol nájdený s daným id.", + "itemIdRequired": "\"itemId\" musí mať platné UUID.", "tagNotFound": "No tag item was found with given id.", - "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.", - "positionRequired": "\"position\" is required and must be a number.", - "cantMoveCompletedTodo": "Can't move a completed todo.", + "tagIdRequired": "\"tagId\" musí mať platné UUID zhodujúce sa so štítkom, ktorý patrí užívateľovi. ", + "positionRequired": "\"position\" je vyžadovaná a musí to byť číslo.", + "cantMoveCompletedTodo": "Hotový to-do sa nedá presunúť.", "directionUpDown": "\"direction\" is required and must be 'up' or 'down'", - "alreadyTagged": "The task is already tagged with given tag." + "alreadyTagged": "Úloha už má tento štítok." } \ No newline at end of file diff --git a/common/locales/sr/groups.json b/common/locales/sr/groups.json index cd7bc3a54b..682e2cc69f 100644 --- a/common/locales/sr/groups.json +++ b/common/locales/sr/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/sr/limited.json b/common/locales/sr/limited.json index 9f9d683377..af8afa5f74 100644 --- a/common/locales/sr/limited.json +++ b/common/locales/sr/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Iritantni prijatelji", "annoyingFriendsText": "Članovi družine gađali su Vas grudvama <%= snowballs %> puta.", "alarmingFriends": "Zastrašujući prijatelji", - "alarmingFriendsText": "Članovi družine uplašili su Vas <%= spookDust %> puta.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Prijatelji zemljoradnici", "agriculturalFriendsText": "Članovi družine pretvorili su Vas u cvet <%= seeds %> puta.", "aquaticFriends": "Aquatic Friends", diff --git a/common/locales/sr/pets.json b/common/locales/sr/pets.json index 5eae90de08..75974f58bf 100644 --- a/common/locales/sr/pets.json +++ b/common/locales/sr/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Eterični lav", "veteranWolf": "Vuk veteran", "veteranTiger": "Veteran Tiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Kerberovo kuče", "hydra": "Hidra", "mantisShrimp": "Ustonožac", diff --git a/common/locales/sr/settings.json b/common/locales/sr/settings.json index b888633f2b..45e583a89f 100644 --- a/common/locales/sr/settings.json +++ b/common/locales/sr/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Vreme početka dana", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Ostalo", diff --git a/common/locales/sr/spells.json b/common/locales/sr/spells.json index a9568458ba..f90094e76a 100644 --- a/common/locales/sr/spells.json +++ b/common/locales/sr/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Bacite grudvu na člana družine! Šta može poći naopako? Efekat traje do kraja dana.", "spellSpecialSaltText": "So", "spellSpecialSaltNotes": "Neko Vas je gađao grudvom. Ha ha, jako smešno. A sad skidaj ovaj sneg s mene.", - "spellSpecialSpookDustText": "Sablasne iskre", - "spellSpecialSpookDustNotes": "Pretvorite prijatelja u lebdeće ćebe s prorezima za oči!", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Neprozirni napitak", "spellSpecialOpaquePotionNotes": "Poništite efekat Sablasne iskre", "spellSpecialShinySeedText": "Svetlucava semenka", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/sr/subscriber.json b/common/locales/sr/subscriber.json index 67b1d7157c..dc5c6db6a5 100644 --- a/common/locales/sr/subscriber.json +++ b/common/locales/sr/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/sr/tasks.json b/common/locales/sr/tasks.json index 64a2a86638..03c3c51cac 100644 --- a/common/locales/sr/tasks.json +++ b/common/locales/sr/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Očistiti", "hideTags": "Sakriti", "showTags": "Prikazati", + "toRequired": "You must supply a to value", "startDate": "Start Date", "startDateHelpTitle": "When should this task start?", "startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.", diff --git a/common/locales/sv/backgrounds.json b/common/locales/sv/backgrounds.json index 4d438a227c..1ee282a699 100644 --- a/common/locales/sv/backgrounds.json +++ b/common/locales/sv/backgrounds.json @@ -140,32 +140,32 @@ "backgroundSnowmanArmyNotes": "Led en Snögubbearme.", "backgroundWinterNightText": "Vinternatt", "backgroundWinterNightNotes": "Se på stjärnorna en Vinternatt.", - "backgrounds022016": "UPPSÄTTNING 21: Utgiven februari 2016", + "backgrounds022016": "SET 21: Släpptes februari 2016", "backgroundBambooForestText": "Bambu Skog", "backgroundBambooForestNotes": "Promenera genom Bambu Skogen.", "backgroundCozyLibraryText": "Mysigt Bibliotek", "backgroundCozyLibraryNotes": "Läs i det Mysiga Biblioteket.", "backgroundGrandStaircaseText": "Storslagen Trappa", "backgroundGrandStaircaseNotes": "Kliv nedför den Storslagna Trappan.", - "backgrounds032016": "UPPSÄTTNING 22: Utgiven mars 2016", + "backgrounds032016": "SET 22: Släpptes mars 2016", "backgroundDeepMineText": "Djup Gruva", "backgroundDeepMineNotes": "Hitta dyrbara metaller i en Djup Gruva.", "backgroundRainforestText": "Regnskog", "backgroundRainforestNotes": "Bege in i en Regnskog.", "backgroundStoneCircleText": "Cirkel av Stenar", "backgroundStoneCircleNotes": "Kasta trollformler i en Cirkel av Stenar.", - "backgrounds042016": "SET 23: Släpptes April 2016", + "backgrounds042016": "SET 23: Släpptes april 2016", "backgroundArcheryRangeText": "Bågskyttebana", "backgroundArcheryRangeNotes": "Öva på bågskyttebanan", "backgroundGiantFlowersText": "Jätteblommor", "backgroundGiantFlowersNotes": "Roa dig ovan jätteblommor", "backgroundRainbowsEndText": "Regnbågens slut", "backgroundRainbowsEndNotes": "Upptäck guld vid regnbågens slut. ", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", - "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", - "backgroundGazeboText": "Gazebo", - "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgrounds052016": "SET 24: Släpptes maj 2016", + "backgroundBeehiveText": "Bikupa", + "backgroundBeehiveNotes": "Sorl och dans i en bikupa.", + "backgroundGazeboText": "Lusthus", + "backgroundGazeboNotes": "Slåss mot ett lusthus.", + "backgroundTreeRootsText": "Trärötter", + "backgroundTreeRootsNotes": "Utforska trärötterna." } \ No newline at end of file diff --git a/common/locales/sv/groups.json b/common/locales/sv/groups.json index 6f773cffad..3b41fe6501 100644 --- a/common/locales/sv/groups.json +++ b/common/locales/sv/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/sv/limited.json b/common/locales/sv/limited.json index cbd9f58104..689cf20e60 100644 --- a/common/locales/sv/limited.json +++ b/common/locales/sv/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Jobbiga Vänner", "annoyingFriendsText": "Fick <%= snowballs %> snöbollar kastad på sig av sällskapsmedlemmar.", "alarmingFriends": "Oroväckande Vänner", - "alarmingFriendsText": "Blev skrämd <%= spookDust %> gånger av sällskapsmedlemmar.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Jordbruksvänner", "agriculturalFriendsText": "Transformerades till en blomma <%= seeds %> gånger av sällskapsmedlemmar.", "aquaticFriends": "Akvatiska Vänner", diff --git a/common/locales/sv/pets.json b/common/locales/sv/pets.json index d9fdae92cf..fcb2998fd5 100644 --- a/common/locales/sv/pets.json +++ b/common/locales/sv/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Himmelskt Lejon", "veteranWolf": "Veteranvarg", "veteranTiger": "Veterantiger", + "veteranLion": "Veteran Lion", "cerberusPup": "Cerberusvalp", "hydra": "Hydra", "mantisShrimp": "Mantisräka", diff --git a/common/locales/sv/settings.json b/common/locales/sv/settings.json index 490888a6fa..9cbca16b1c 100644 --- a/common/locales/sv/settings.json +++ b/common/locales/sv/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Skräddarsydd Dagsstart", "changeCustomDayStart": "Ändra skräddarsydd dagsstarten?", "sureChangeCustomDayStart": "Är du säker på att du vill ändrar skräddarsydd dagsstarten?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Dina dagliga utmaningar kommer återställas den första gången du använder Habitica efter <%= time %>. Se till att ha avslutat dina dagliga utmaningar innan det händer!", "customDayStartInfo1": "Habitica återställer alltid dina dagliga utmaningar vid midnatt i din tidszon varje dag. Du kan ställa in den tiden här.", "misc": "Blandat", diff --git a/common/locales/sv/spells.json b/common/locales/sv/spells.json index 5c04a332d3..613792045b 100644 --- a/common/locales/sv/spells.json +++ b/common/locales/sv/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Kasta en snöboll på en sällskapsdeltagare! Vad kan gå fel? Varar fram till deltagarens nästa dag.", "spellSpecialSaltText": "Salt", "spellSpecialSaltNotes": "Någon kastade en snöboll på dig. Ha ha, väldigt roligt. Se nu till att få bort den här snön från mig!", - "spellSpecialSpookDustText": "Spöklik hud", - "spellSpecialSpookDustNotes": "Transformera en vän till en flytande filt med ögon.", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Ogenomskinlig Dryck", "spellSpecialOpaquePotionNotes": "Avbryt effekterna av spöklik hud.", "spellSpecialShinySeedText": "Glänsande Frö", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/sv/subscriber.json b/common/locales/sv/subscriber.json index d25fa10f12..5b6ad41dbc 100644 --- a/common/locales/sv/subscriber.json +++ b/common/locales/sv/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/sv/tasks.json b/common/locales/sv/tasks.json index 871cb91df3..c2e09bd715 100644 --- a/common/locales/sv/tasks.json +++ b/common/locales/sv/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Återställ", "hideTags": "Förminska", "showTags": "Visa", + "toRequired": "You must supply a to value", "startDate": "Startdatum", "startDateHelpTitle": "När Borde Detta Uppdraget Börja?", "startDateHelp": "Ställ in det datum för vilket den här uppgiften börjar. Kommer inte infalla på föreliggande dagar.", diff --git a/common/locales/uk/groups.json b/common/locales/uk/groups.json index 186220ff41..50cbb2dc83 100644 --- a/common/locales/uk/groups.json +++ b/common/locales/uk/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/uk/limited.json b/common/locales/uk/limited.json index 28d89508ee..408ba88a73 100644 --- a/common/locales/uk/limited.json +++ b/common/locales/uk/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Набридливі друзі", "annoyingFriendsText": "Ухопив сніжку <%= snowballs %> разів від спільників гурту.", "alarmingFriends": "Лякаючi друзi", - "alarmingFriendsText": "Був наляканий членами групи <%= spookDust %> разiв.", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "Друзі-Садівники", "agriculturalFriendsText": "Був <%= seeds %> разів перетворений на квітку товаришами по групі.", "aquaticFriends": "Aquatic Friends", diff --git a/common/locales/uk/pets.json b/common/locales/uk/pets.json index e8e87c56f0..954802eefb 100644 --- a/common/locales/uk/pets.json +++ b/common/locales/uk/pets.json @@ -12,6 +12,7 @@ "etherealLion": "Етерний лев", "veteranWolf": "Лютий вовк", "veteranTiger": "Лютий тигр", + "veteranLion": "Veteran Lion", "cerberusPup": "Церберятко", "hydra": "Гідра", "mantisShrimp": "Рак-богомол", diff --git a/common/locales/uk/settings.json b/common/locales/uk/settings.json index 3dd2a4bfab..479b909644 100644 --- a/common/locales/uk/settings.json +++ b/common/locales/uk/settings.json @@ -47,6 +47,7 @@ "customDayStart": "Обрати початок доби", "changeCustomDayStart": "Change Custom Day Start?", "sureChangeCustomDayStart": "Are you sure you want to change your custom day start?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!", "customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.", "misc": "Усяке", diff --git a/common/locales/uk/spells.json b/common/locales/uk/spells.json index 91d1ab0d2b..75ed2d220e 100644 --- a/common/locales/uk/spells.json +++ b/common/locales/uk/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Жбурніть сніжок в членів групи! Що може статися? Триває, до наступного ігрового дня.", "spellSpecialSaltText": "Сіль", "spellSpecialSaltNotes": "Хтось жбурнув у Вас сніжкою. Ха-ха, дуже смішно. Ану ж струсіть із мене цей сніг!", - "spellSpecialSpookDustText": "Моторошні Іскри", - "spellSpecialSpookDustNotes": "Перетворіть друга у літаючу ковдру з очима! ", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "Непрозоре Зілля", "spellSpecialOpaquePotionNotes": "Відмінити ефект Моторошних Іскор.", "spellSpecialShinySeedText": "Shiny Seed", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/uk/subscriber.json b/common/locales/uk/subscriber.json index 81db2d8ea0..b58a656c5a 100644 --- a/common/locales/uk/subscriber.json +++ b/common/locales/uk/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/uk/tasks.json b/common/locales/uk/tasks.json index 1affa65e5f..d1a49842e5 100644 --- a/common/locales/uk/tasks.json +++ b/common/locales/uk/tasks.json @@ -73,6 +73,7 @@ "clearTags": "Очистити", "hideTags": "Приховати", "showTags": "Показати", + "toRequired": "You must supply a to value", "startDate": "Дата початку", "startDateHelpTitle": "Коли має починатися це завдання?", "startDateHelp": "Вкажіть дату, коли це завдання набуде чинності. До цієї дати воно буде не обов'язковим.", diff --git a/common/locales/zh/challenge.json b/common/locales/zh/challenge.json index 19ed70a049..b26a01f029 100644 --- a/common/locales/zh/challenge.json +++ b/common/locales/zh/challenge.json @@ -64,19 +64,20 @@ "hurray": "万岁!", "noChallengeOwner": "没有主持人", "noChallengeOwnerPopover": "这项挑战没有主持人,因为创建该挑战的用户已删除帐号", - "challengeMemberNotFound": "User not found among challenge's members", - "onlyGroupLeaderChal": "Only the group leader can create challenges", - "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", - "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", - "challengeIdRequired": "\"challengeId\" must be a valid UUID.", - "winnerIdRequired": "\"winnerId\" must be a valid UUID.", - "challengeNotFound": "Challenge not found.", - "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", - "onlyLeaderUpdateChal": "Only the challenge leader can update it.", - "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", - "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", - "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", - "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "challengeMemberNotFound": "挑战成员中找不到用户", + "onlyGroupLeaderChal": "仅有队长能创建挑战", + "tavChalsMinPrize": "酒馆挑战的奖励至少1颗宝石。", + "cantAfford": "你不能支付这个奖励。请购买更多宝石或减少奖励数量。", + "challengeIdRequired": "\"challengeId\"必须是一个有效的UUID。", + "winnerIdRequired": "\"winnerId\"必须是一个有效的UUID。", + "challengeNotFound": "找不到挑战。", + "onlyLeaderDeleteChal": "仅有挑战发起者能删除它。", + "onlyLeaderUpdateChal": "仅有挑战发起者能更新它。", + "winnerNotFound": "获胜者ID \"<%= userId %>\"找不到或者不是挑战的一部分。", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", + "userTasksNoChallengeId": "当\"tasksOwner\"是\"user\" \"challengeId\",则不会通过。", + "onlyChalLeaderEditTasks": "属于一个挑战的任务仅能被发起者编辑。", + "userAlreadyInChallenge": "用户已经加入此挑战。", + "cantOnlyUnlinkChalTask": "仅有破碎的挑战任务能被拆开。", + "shortNameTooShort": "Tag Name must have at least 3 characters." } \ No newline at end of file diff --git a/common/locales/zh/contrib.json b/common/locales/zh/contrib.json index 0813083d5b..d690cc6405 100644 --- a/common/locales/zh/contrib.json +++ b/common/locales/zh/contrib.json @@ -35,12 +35,12 @@ "hallContributors": "贡献者殿堂", "hallPatrons": "赞助人殿堂", "rewardUser": "奖励玩家", - "UUID": "User ID", + "UUID": "用户ID", "loadUser": "载入玩家", - "noAdminAccess": "You don't have admin access.", - "pageMustBeNumber": "req.query.page must be a number", - "userNotFound": "User not found.", - "invalidUUID": "UUID must be valid", + "noAdminAccess": "你没有管理员权限。", + "pageMustBeNumber": "req.query.page必须是一个数字", + "userNotFound": "找不到用户", + "invalidUUID": "UUID 必须有效", "title": "头衔", "moreDetails": "更多详情(1-7)", "moreDetails2": "更多详情(8-9)", diff --git a/common/locales/zh/death.json b/common/locales/zh/death.json index 6444a4ff27..787da98b2f 100644 --- a/common/locales/zh/death.json +++ b/common/locales/zh/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "前一晚仍未完成的每日任务会使你收到伤害,所以请小心,初期别添加太多每日任务!", "lowHealthTips4": "如果每日任务在某一天仍未到期,你可以点击铅笔图标来取消它。", "goodLuck": "祝你好运!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "如果没有死亡就不能复活" } \ No newline at end of file diff --git a/common/locales/zh/front.json b/common/locales/zh/front.json index 3207383ba0..8f5f1794f8 100644 --- a/common/locales/zh/front.json +++ b/common/locales/zh/front.json @@ -28,7 +28,7 @@ "communityReddit": "Reddit", "companyAbout": "如何使用", "companyBlog": "博客", - "devBlog": "Developer Blog", + "devBlog": "开发者部落格", "companyDonate": "捐款", "companyExtensions": "插件", "companyPrivacy": "隐私", @@ -52,7 +52,7 @@ "featureSocialHeading": "多人游戏", "featuredIn": "特别提供", "featuresHeading": "我们还有这些特别的……", - "footerDevs": "Developers", + "footerDevs": "开发者", "footerCommunity": "社区", "footerCompany": "公司", "footerMobile": "手机版", diff --git a/common/locales/zh/groups.json b/common/locales/zh/groups.json index 87948f5d90..6e5e39f30c 100644 --- a/common/locales/zh/groups.json +++ b/common/locales/zh/groups.json @@ -174,16 +174,16 @@ "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", - "inviteMissingEmail": "Missing email address in invite.", - "partyMustbePrivate": "Parties must be private", - "userAlreadyInGroup": "User already in that group.", - "userAlreadyInvitedToGroup": "User already invited to that group.", - "userAlreadyPendingInvitation": "User already pending invitation.", - "userAlreadyInAParty": "User already in a party.", - "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", + "inviteMissingEmail": "邀请中缺失电子邮箱地址。", + "partyMustbePrivate": "队伍必须是私有的", + "userAlreadyInGroup": "用户已经在这个小组中。", + "userAlreadyInvitedToGroup": "用户已经被邀请进入这个小组。", + "userAlreadyPendingInvitation": "用户已经收到邀请等待接受。", + "userAlreadyInAParty": "用户已经在一个队伍中。", + "userWithIDNotFound": "这个ID \"<%= userId %>\"的用户没有找到。", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/zh/limited.json b/common/locales/zh/limited.json index baef2fe1e6..2dae63db3e 100644 --- a/common/locales/zh/limited.json +++ b/common/locales/zh/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "吵闹的朋友", "annoyingFriendsText": "被队员的雪球砸中<%= snowballs %>次。", "alarmingFriends": "惊人的朋友", - "alarmingFriendsText": "被队友吓到了 <%= spookDust %> 次", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "农民同志", "agriculturalFriendsText": "被队友变成花朵<%= seeds %>次。", "aquaticFriends": "水族朋友", @@ -73,5 +73,5 @@ "sneakySqueakerSet": "霹雳鼠(盗贼)", "fallEventAvailability": "有效期至十月31日", "winterEventAvailability": "有效期至十二月31日", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "5月31日前可用" } \ No newline at end of file diff --git a/common/locales/zh/maintenance.json b/common/locales/zh/maintenance.json index 9a32d0db2e..19ba27aaaa 100644 --- a/common/locales/zh/maintenance.json +++ b/common/locales/zh/maintenance.json @@ -3,32 +3,32 @@ "importantMaintenance": "我们正在进行重要维护,估计将持续到您时区中的<%= localDate %>。", "maintenance": "维护", "maintenanceMoreInfo": "想知道更多的维护信息?<%= linkStart %>查看我们的信息页面<%= linkEnd %>。", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", + "noDamageKeepStreaks": "您将不会受到伤害或失去连击数!", "thanksForPatience": "谢谢您的耐心!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "twitterMaintenanceUpdates": "对于最近的更新,查看我们的our Twitter,我们将在这里发布状态信息。", + "veteranPetAward": "最终,您将获得一只老兵宠物!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", + "maintenanceInfoTitle": "关于即将来临的Habitica维护信息", "maintenanceInfoWhat": "发生了什么?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", - "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", - "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", - "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", - "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", - "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", - "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", - "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", - "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", - "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", - "maintenanceInfoSeeTasks": "What if I need to see my task list?", - "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", - "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", - "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", - "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." + "maintenanceInfoWhatText": "在5月21日,Habitica将在1天中的大部分时间维护。你的账号将不会受到任何伤害在这个周末,虽然你不能及时登陆打卡你的每日任务!我们将会努力工作尽可能缩短维护时间,发布定期更新在我们的 Twitter账号。在维护结束,为感谢大家的耐心,你们所有人将收到一只稀有宠物!", + "maintenanceInfoWhy": "为什么这会发生?", + "maintenanceInfoWhyText": "再过去的几个月,我们已经在幕后彻底的改造了Habitica。特别地是,我们重写了API。它在表面看起来可能不会很不同,但它底层是一个完全不同的世界。这将允许我们的方式更灵活当我们想要去在以后构建特性,也导致了性能的提升!", + "maintenanceInfoTechDetails": "想要更多技术上的处理过程?访问 铁匠铺,我们的开发部落格。", + "maintenanceInfoMore": "更多信息", + "maintenanceInfoAccountChanges": "在这次改写完成后,我的账户将能看到什么样的改变?", + "maintenanceInfoAccountChangesText": "首先,不会有显著的变化除了为一些特性的性能上的提高,例如挑战。如果你注意到任何例外的变化,请通过邮件告知我们 admin@habitica.com,我们将会调查它们!", + "maintenanceInfoAddFeatures": "这将允许Habitica添加哪种特性?", + "maintenanceInfoAddFeaturesText": "完成这次重写将允许我们开始建立改进的聊天和工会,为家庭和组织的计划,和附加的产能特性例如每月的和记录昨天活动的能力!那些是他们自己所有涉及到的功能,所以会消耗时间建立它们,但是直到我们完成这次重写,在之前没有方式允许我们去开始这些。", + "maintenanceInfoHowLong": "维护持续多久?", + "maintenanceInfoHowLongText": "我们必须移动所有一百三十万Habitica用户的任务和数据 -- 这不是一个容易的任务!我们预期将发生在大约太平洋时间下午1点 (8pm UTC)到太平洋时间晚10点 (5am UTC)。请放心,我们会竭尽所能使维护更快!你能关注我们Twitter上的更新。", + "maintenanceInfoStatsAffected": "我的每日任务,连击数,增益魔法和探索任务将受到怎样的影响?", + "maintenanceInfoStatsAffectedText1": "你将 不会 在这个周末受到任何伤害或丢失任何连击数,但是另外的,你的天数会正常复位!你打卡的每日任务将会变成未打卡状态,增益魔法将重置,等等。如果你在一个采集任务中,你仍然会找到物品。如果你在BOSS战中,你仍然对BOSS造成伤害,但BOSS不能伤害你。(甚至怪物也需要打个小盹!)", + "maintenanceInfoStatsAffectedText2": "在思索了很多之后,我们团队得出在维护期间,由于很多用户将不能正常打卡每日任务,这是最公平的方式来处理。我们非常抱歉这带来的不便!", + "maintenanceInfoSeeTasks": "在当前状况下我是否需要去看我的任务列表?", + "maintenanceInfoSeeTasksText": "你是否知道你需要在周六检视你的任务列表来提醒自己要做什么,我们建议在维护开始前这样做,对你的任务截图以便于能用来做参考。", + "maintenanceInfoRarePet": "我将会得到哪种稀有宠物?", + "maintenanceInfoRarePetText": "为了感谢你们在维护期间耐心等待,每个人都将得到一只稀有老兵宠物。如果你之前从未得到一个老兵宠物,你将得到一直老兵狼。如果你已经有一只老兵狼,你将会得到一只老兵老虎。如果你两只都有,你将会得到一只之前从未见过的老兵宠物!在迁移完成后,会有几小时才显示你的宠物,但不要担心,每人都会得到一只。", + "maintenanceInfoWho": "谁工作于这个浩大的项目?", + "maintenanceInfoWhoText": "我们很高兴你提到了这个!它有我们厉害的贡献者团体带领,从Blade,TheHollidayInn,SabreCat,Victor Pudeyev,TheUnknown,和 Alys得到了很多帮助。", + "maintenanceInfoTesting": "新版本也会被我们厉害的开源志愿者不辞幸劳地测试。谢谢你们 -- 没有你们我们根本完成不了。" } diff --git a/common/locales/zh/npc.json b/common/locales/zh/npc.json index 9cd1b6a3ea..924df3e038 100644 --- a/common/locales/zh/npc.json +++ b/common/locales/zh/npc.json @@ -21,26 +21,26 @@ "ian": "岚", "ianText": "欢迎来到任务商店!这里你可以使用任务卷轴来同你的朋友一起与怪物战斗。不要选购一个在右边为您精心排列的任务卷轴吗?", "ianBrokenText": "欢迎来到任务商店……这里你可以使用任务卷轴来同你的朋友一起与怪物战斗……请一定要到右边栏来看看我们为您精心准备的任务卷轴……", - "missingKeyParam": "\"req.params.key\" is required.", - "itemNotFound": "Item \"<%= key %>\" not found.", - "cannotBuyItem": "You can't buy this item.", - "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.", - "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.", - "invalidPetName": "Invalid pet name supplied.", - "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.", - "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.", + "missingKeyParam": "需要\"req.params.key\" 。", + "itemNotFound": "找不到物品\"<%= key %>\" 。", + "cannotBuyItem": "你不能购买这个物品。", + "missingTypeKeyEquip": "\"key\"和\"type\"是必须的参数。", + "missingPetFoodFeed": "\"pet\"和\"food\"是必须的参数。", + "invalidPetName": "无效的宠物名提供。", + "missingEggHatchingPotionHatch": "\"egg\"和\"hatchingPotion\"是必须的参数。", + "invalidTypeEquip": "\"type\"必须是'equipped',,'pet', 'mount', 'costume'中的一个。", "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.", - "typeRequired": "Type is required", - "keyRequired": "Key is required", - "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]", - "contentKeyNotFound": "Key not found for Content <%= type %>", - "plusOneGem": "+1 Gem", - "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", - "userItemsKeyNotFound": "Key not found for user.items <%= type %>", - "pathRequired": "Path string is required", - "unlocked": "Items have been unlocked", - "alreadyUnlocked": "Full set already unlocked.", - "alreadyUnlockedPart": "Full set already partially unlocked.", + "typeRequired": "需要Type", + "keyRequired": "需要Key", + "notAccteptedType": "Type必须在 [eggs, hatchingPotions, food, quests, gear] 中", + "contentKeyNotFound": "内容<%= type %>找不到Key", + "plusOneGem": "+1 宝石", + "typeNotSellable": "Type不出售。必须是<%= acceptedTypes %>其中之一。", + "userItemsKeyNotFound": "user.items <%= type %>找不到Key ", + "pathRequired": "需要Path string", + "unlocked": "物品已经解锁", + "alreadyUnlocked": "全套已经解锁", + "alreadyUnlockedPart": "全套已经部分解锁", "USD": "(美元)", "newStuff": "新品", "cool": "稍后再跟我说", @@ -84,7 +84,7 @@ "tourPetsPage": "This is the Stable! After level 4, you can hatch pets using eggs and potions. When you hatch a pet in the Market, it will appear here! Click a pet's image to add it to your avatar. Feed them with the food you find after level 4, and they'll grow into powerful mounts.", "tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. (Pets, mounts, and food are available after level 4.) Click a mount to saddle up!", "tourEquipmentPage": "这里是你的装备库!你的战斗工具影响你的属性点。如果你想要在不影响属性点的情况下在你的角色形象上展示不同的装备,点击“使用服装”。", - "equipmentAlreadyOwned": "You already own that piece of equipment", + "equipmentAlreadyOwned": "你已经拥有那件装备", "tourOkay": "好的!", "tourAwesome": "太好了!", "tourSplendid": "太棒了!", diff --git a/common/locales/zh/pets.json b/common/locales/zh/pets.json index 729d4284f8..bac6485879 100644 --- a/common/locales/zh/pets.json +++ b/common/locales/zh/pets.json @@ -12,6 +12,7 @@ "etherealLion": "灵狮", "veteranWolf": "退伍军狼", "veteranTiger": "退伍老虎", + "veteranLion": "Veteran Lion", "cerberusPup": "地狱小狗", "hydra": "三头蛇", "mantisShrimp": "虾蛄", @@ -62,7 +63,7 @@ "hatchedPet": "你孵化了 <%= potion %> <%= egg %>!", "displayNow": "现在显示", "displayLater": "稍后显示", - "petNotOwned": "You do not own this pet.", + "petNotOwned": "你没有拥有这个宠物。", "earnedCompanion": "因为你的不懈努力,你又获得了一个新的伙伴,好好喂养它,让它成长!", "feedPet": "喂你的<%= name %>一个<%= article %><%= text %>?", "useSaddle": "把鞍用在<%= pet %>上?", @@ -84,8 +85,8 @@ "petKeyBoth": "全部释放", "confirmPetKey": "你确定吗?", "petKeyNeverMind": "还没有", - "petsReleased": "Pets released.", - "mountsAndPetsReleased": "Mounts and pets released", - "mountsReleased": "Mounts released", + "petsReleased": "宠物发布。", + "mountsAndPetsReleased": "坐骑和宠物发布", + "mountsReleased": "坐骑发布", "gemsEach": "每颗宝石" } \ No newline at end of file diff --git a/common/locales/zh/quests.json b/common/locales/zh/quests.json index ca40382217..55c254eb5b 100644 --- a/common/locales/zh/quests.json +++ b/common/locales/zh/quests.json @@ -79,16 +79,16 @@ "getMoreQuests": "获取更多探索任务", "unlockedAQuest": "你解锁了一个任务!", "leveledUpReceivedQuest": "你达到了 第 <%= level %> 级 ,并且收到了一个任务卷轴!", - "questInvitationDoesNotExist": "No quest invitation has been sent out yet.", - "questInviteNotFound": "No quest invitation found.", - "guildQuestsNotSupported": "Guilds cannot be invited on quests.", - "questNotFound": "Quest \"<%= key %>\" not found.", - "questNotOwned": "You don't own that quest scroll.", - "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.", - "questLevelTooHigh": "You must be level <%= level %> to begin this quest.", - "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.", - "questAlreadyAccepted": "You already accepted the quest invitation.", - "noActiveQuestToLeave": "No active quest to leave", + "questInvitationDoesNotExist": "还没有发出任何探索任务邀请。", + "questInviteNotFound": "找不到探索任务邀请。", + "guildQuestsNotSupported": "工会不能被邀请参加探索任务。", + "questNotFound": "找不到探索任务\"<%= key %>\"。", + "questNotOwned": "你还没拥有那个探索任务卷轴。", + "questNotGoldPurchasable": "探索任务\"<%= key %>\"不能用金币购买。", + "questLevelTooHigh": "你必须达到等级<%= level %>来开始这个探索任务。", + "questAlreadyUnderway": "你的队伍已经开始一个探索任务。当前探索任务结束后请再试一次。", + "questAlreadyAccepted": "你已经接收探索任务邀请。", + "noActiveQuestToLeave": "没有正进行的探索任务来退出", "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", "notPartOfQuest": "You are not part of the quest", "noActiveQuestToAbort": "There is no active quest to abort.", diff --git a/common/locales/zh/rebirth.json b/common/locales/zh/rebirth.json index 0ba9c8f644..5f853e107f 100644 --- a/common/locales/zh/rebirth.json +++ b/common/locales/zh/rebirth.json @@ -5,7 +5,7 @@ "rebirthStartOver": "复活后你的角色从1级开始。", "rebirthAdvList1": "生命值完全复原", "rebirthAdvList2": "你没有经验,金币,装备(免费物品例外如神秘物品)。", - "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.", + "rebirthAdvList3": "你的习惯,每日任务,和待办事项重置到黄色,并且连击数也重置,除了挑战任务。", "rebirthAdvList4": "你的初始职业是战士,但你以后可以解锁新的职业。", "rebirthInherit": "你的新角色继承了一些前身的东西:", "rebirthInList1": "任务,历史记录和设置保留了下来。", diff --git a/common/locales/zh/settings.json b/common/locales/zh/settings.json index 515de3d43c..8919e12068 100644 --- a/common/locales/zh/settings.json +++ b/common/locales/zh/settings.json @@ -47,6 +47,7 @@ "customDayStart": "自定义起始日期", "changeCustomDayStart": "变更自定义起始日期?", "sureChangeCustomDayStart": "你确定要变更自定义起始日期吗?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "从你第一次使用Habitica<%= time %>之后,你的每日任务将会重置。请确保你在这之前已经完成了所有每日任务!", "customDayStartInfo1": "在你所处地区时间每天凌晨12点,Habitica默认核对并重置你的每日任务。你可以在此变更任务重置时间。", "misc": "其他", @@ -65,7 +66,7 @@ "deleteText": "你确定吗?这会永久地删除你的帐号,并且永远也无法恢复!如果希望再次使用Habitica需要注册一个新的帐号。已有的金钱和花掉的宝石无法被退费。如果你非常确定,在下面的文本框中输入<%= deleteWord %>。", "API": "API / 应用程序接口", "APIv3": "API v3", - "APIv2": "API v2 - Deprecated", + "APIv2": "API v2 - 弃用", "APIText": "这个是用来复制到第三方应用的。但是,你的API令牌相当于密码,不要公开它。有时候别人会问你要用户ID,但是永远不要把你的API令牌分享给其他人,包括Github。", "APIToken": "API 令牌 (这相当于密码——看上面的警告!)", "thirdPartyApps": "第三方应用", @@ -77,7 +78,7 @@ "otherExtensions": "其他扩展", "otherDesc": "其他应用、扩展和工具请关注Habitica维基。", "resetDo": "去吧,重置我的账号吧!", - "resetComplete": "Reset complete!", + "resetComplete": "重置完成!", "fixValues": "修复数值", "fixValuesText1": "如果你遇到了一个bug或者错误地改变了你的角色 (不应该受到的伤害,获得了不属于你的金币等等),你可以手动的改正你的数值。是的,这使得用户有能力作弊:请正确地使用这个功能,否则你会破坏你自己的习惯养成系统!", "fixValuesText2": "注意你不可以在这里重置单个任务的连击数。如果希望那样做,编辑日常标签,进入高级选项,在那里你可以修复连击数。", diff --git a/common/locales/zh/spells.json b/common/locales/zh/spells.json index 8b6fd98298..d838e48d25 100644 --- a/common/locales/zh/spells.json +++ b/common/locales/zh/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "向队友丢了一个雪球!会发生什么事情呢?所产生的效果会持续到第二天早上.", "spellSpecialSaltText": "盐", "spellSpecialSaltNotes": "有人向你扔了雪球。哈哈,很好玩。现在把这些雪从我身上弄下来!", - "spellSpecialSpookDustText": "鬼火", - "spellSpecialSpookDustNotes": "把一个好友变成长眼睛的魔毯。", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "晦暗的药水", "spellSpecialOpaquePotionNotes": "取消鬼火的效果", "spellSpecialShinySeedText": "闪光种子", @@ -47,10 +47,10 @@ "spellSpecialSeafoamNotes": "把一位好友变成海洋生物", "spellSpecialSandText": "沙子", "spellSpecialSandNotes": "抵消海泡石的效果", - "spellNotFound": "Skill \"<%= spellId %>\" not found.", - "partyNotFound": "Party not found", - "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", - "spellNotOwned": "You don't own this skill.", - "spellLevelTooHigh": "You must be level <%= level %> to use this skill." + "spellNotFound": "找不到技能\"<%= spellId %>\"。", + "partyNotFound": "找不到队伍", + "targetIdUUID": "\"targetId\" 必须是有效ID。", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "spellNotOwned": "你没有拥有这个技能。", + "spellLevelTooHigh": "你必须达到等级<%= level %>来运用这个技能。" } \ No newline at end of file diff --git a/common/locales/zh/subscriber.json b/common/locales/zh/subscriber.json index 38d850b4a2..593fe5deec 100644 --- a/common/locales/zh/subscriber.json +++ b/common/locales/zh/subscriber.json @@ -4,8 +4,8 @@ "subDescription": "可以使用金币购买宝石,获得每月神秘装备,保留已完成项目的历史,双倍日常掉落率,支持开发者。点击获取更多信息。", "buyGemsGold": "用金币购买宝石", "buyGemsGoldText": "商人亚历山大可以按每个宝石 <%= gemCost %>金币的价格向你出售宝石,他每个月的初始进货量是 <%= gemLimit %> 个宝石,如果可以在接下来的时间里坚持捐助,每三个月宝石上限就会增加5,直到最大值1个月50颗宝石!", - "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP", - "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.", + "mustSubscribeToPurchaseGems": "必须捐赠来用GP购买宝石", + "reachedGoldToGemCap": "你已经达到了本月的 金币=>宝石 上限<%= convCap %>。我们用这个来阻止 滥用/买卖。这个上限将在每个月前三天重置。", "retainHistory": "保留额外的历史条目", "retainHistoryText": "使完成的待办事项和任务的历史纪录更长时间保留。", "doubleDrops": "日常掉率上限翻倍", @@ -31,7 +31,7 @@ "manageSub": "按这里管理捐助", "cancelSub": "取消捐助", "canceledSubscription": "取消捐助", - "cancelingSubscription": "Canceling the subscription", + "cancelingSubscription": "取消捐助", "adminSub": "管理员捐助", "morePlans": "更多计划
即将推出", "organizationSub": "私人组织", @@ -76,9 +76,9 @@ "timeTravelersPopover": "我们看到你有1个神秘的沙漏,所以我们愉快地决定穿梭回来找你!请选择你喜欢的宠物,坐骑或是神秘物品。你还可以看到一系列已不再开放的物品列表在 这里!如果这些都不能满足你,也许你会对我们富有时尚未来感的蒸汽朋克物品套装感兴趣?", "timeTravelersAlreadyOwned": "恭喜!你已经拥有了时光穿梭者目前能够提供的所有道具,感谢支持本站!", "mysticHourglassPopover": "神秘沙漏可以让你购买一些特别的限定道具,比如每月神秘道具组合,世界BOSS甚至过去已经发布完的奖励!", - "mysterySetNotFound": "Mystery set not found, or set already owned.", - "mysteryItemIsEmpty": "Mystery items are empty", - "mysteryItemOpened": "Mystery item opened.", + "mysterySetNotFound": "神秘套装找不到,或者已经拥有套装。", + "mysteryItemIsEmpty": "神秘物品是空的", + "mysteryItemOpened": "神秘物品打开。", "mysterySet201402": "有翼使者长袍套装", "mysterySet201403": "森林行者套装", "mysterySet201404": "薄暮蝴蝶套装", @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/zh/tasks.json b/common/locales/zh/tasks.json index 339b056b43..76ec6f5a7b 100644 --- a/common/locales/zh/tasks.json +++ b/common/locales/zh/tasks.json @@ -73,6 +73,7 @@ "clearTags": "清除", "hideTags": "隐藏", "showTags": "显示", + "toRequired": "You must supply a to value", "startDate": "开始日期", "startDateHelpTitle": "这个 任务 什么时候开始", "startDateHelp": "设置任务生效的日期。在这之前将不会到期。", diff --git a/common/locales/zh_TW/groups.json b/common/locales/zh_TW/groups.json index 48324086f3..693c83fb77 100644 --- a/common/locales/zh_TW/groups.json +++ b/common/locales/zh_TW/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "User already in a party.", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", - "uuidsMustBeAnArray": "UUIDs invites must be a an Array.", - "emailsMustBeAnArray": "Email invites must be a an Array.", + "uuidsMustBeAnArray": "User ID invites must be an array.", + "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" } \ No newline at end of file diff --git a/common/locales/zh_TW/limited.json b/common/locales/zh_TW/limited.json index 1698fbe8a6..520ee2a77c 100644 --- a/common/locales/zh_TW/limited.json +++ b/common/locales/zh_TW/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "吵鬧的朋友", "annoyingFriendsText": "被隊員的雪球砸中<%= snowballs %>次。", "alarmingFriends": "愛嚇人的好友", - "alarmingFriendsText": "被隊員嚇到 <%= spookDust %> 次。", + "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", "agriculturalFriends": "園藝之友", "agriculturalFriendsText": "隊伍成員變身成一朵花共計<%= seeds %>次", "aquaticFriends": "水族夥伴", diff --git a/common/locales/zh_TW/pets.json b/common/locales/zh_TW/pets.json index 702aafbdbd..13df27566b 100644 --- a/common/locales/zh_TW/pets.json +++ b/common/locales/zh_TW/pets.json @@ -12,6 +12,7 @@ "etherealLion": "靈獅", "veteranWolf": "退伍軍狼", "veteranTiger": "資深的老虎", + "veteranLion": "Veteran Lion", "cerberusPup": "三頭地獄幼犬", "hydra": "三頭蛇", "mantisShrimp": "瀨尿蝦", diff --git a/common/locales/zh_TW/settings.json b/common/locales/zh_TW/settings.json index e0a2d4364a..b5d5c99914 100644 --- a/common/locales/zh_TW/settings.json +++ b/common/locales/zh_TW/settings.json @@ -47,6 +47,7 @@ "customDayStart": "設定開始日期", "changeCustomDayStart": "要更改設定日期嗎?", "sureChangeCustomDayStart": "你確定你要更改設定日期嗎?", + "customDayStartHasChanged": "Your custom day start has changed.", "nextCron": "在<%= time %>以後,重新登入 Habitica,你的每日任務將會重新設定。確保<%= time %>前完成你的每日任務。", "customDayStartInfo1": "Habitica 預設每日午夜將會重新結算當日的每日任務於你所在的時區。你可以在這裡更改每日任務結算時間。", "misc": "其他", diff --git a/common/locales/zh_TW/spells.json b/common/locales/zh_TW/spells.json index 237511c8c0..d141c5e462 100644 --- a/common/locales/zh_TW/spells.json +++ b/common/locales/zh_TW/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "向隊友發射一個雪球,怎麼可能會有什麼問題呢?持續到隊友的下一天。", "spellSpecialSaltText": "鹽", "spellSpecialSaltNotes": "有人向你扔了雪球。哈哈,很好玩。現在把這些雪從我身上弄下來!", - "spellSpecialSpookDustText": "鬼火", - "spellSpecialSpookDustNotes": "把你的朋友變成長著眼睛的漂浮魔毯。", + "spellSpecialSpookySparklesText": "Spooky Sparkles", + "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", "spellSpecialOpaquePotionText": "晦暗藥水", "spellSpecialOpaquePotionNotes": "取消鬼火的效果", "spellSpecialShinySeedText": "閃亮種子", @@ -50,7 +50,7 @@ "spellNotFound": "Skill \"<%= spellId %>\" not found.", "partyNotFound": "Party not found", "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.", + "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "spellNotOwned": "You don't own this skill.", "spellLevelTooHigh": "You must be level <%= level %> to use this skill." } \ No newline at end of file diff --git a/common/locales/zh_TW/subscriber.json b/common/locales/zh_TW/subscriber.json index b30f434bb2..903822586b 100644 --- a/common/locales/zh_TW/subscriber.json +++ b/common/locales/zh_TW/subscriber.json @@ -131,7 +131,7 @@ "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", - "cardTypeNotAllowed": "Unkown card type.", + "cardTypeNotAllowed": "Unknown card type.", "invalidCoupon": "Invalid coupon code.", "couponUsed": "Coupon code already used.", "noSudoAccess": "You don't have sudo access.", diff --git a/common/locales/zh_TW/tasks.json b/common/locales/zh_TW/tasks.json index b8883b2a4b..5ff68283bc 100644 --- a/common/locales/zh_TW/tasks.json +++ b/common/locales/zh_TW/tasks.json @@ -73,6 +73,7 @@ "clearTags": "清除", "hideTags": "隱藏標籤", "showTags": "展開標籤", + "toRequired": "You must supply a to value", "startDate": "開始日", "startDateHelpTitle": "這個任務的開始日?", "startDateHelp": "設定這個認識開始有效的日期,在那之前它不會出現在到期清單。", From 30c2d8e25f28de45509174421f21de91e37c1ac3 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 17:18:55 +0200 Subject: [PATCH 008/152] payments: fix typo in paypal code --- website/server/controllers/top-level/payments/paypal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/top-level/payments/paypal.js b/website/server/controllers/top-level/payments/paypal.js index 4bcd2d0735..f60d5058dc 100644 --- a/website/server/controllers/top-level/payments/paypal.js +++ b/website/server/controllers/top-level/payments/paypal.js @@ -118,7 +118,7 @@ api.checkoutSuccess = { middlewares: [authWithSession], async handler (req, res) { let paymentId = req.query.paymentId; - let customerId = req.query.payerID; + let customerId = req.query.PayerID; let method = 'buyGems'; let data = { From 51254c26dce09de58537751cb8900e3e9287438f Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 18:59:54 +0200 Subject: [PATCH 009/152] fix cancelling stripe subscription --- website/server/controllers/top-level/payments/stripe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js index 2ac8c863f7..f7d490bb88 100644 --- a/website/server/controllers/top-level/payments/stripe.js +++ b/website/server/controllers/top-level/payments/stripe.js @@ -156,7 +156,7 @@ api.subscribeCancel = { let customer = await stripe.customers.retrieve(user.purchased.plan.customeerId); await stripe.customers.del(user.purchased.plan.customerId); - await payments.cancelSubscriptoin({ + await payments.cancelSubscription({ user, nextBill: customer.subscription.current_period_end * 1000, // timestamp in seconds paymentMethod: 'Stripe', From f1f593bcfd673e5cff30082f398d1345a0768178 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 19:59:14 +0200 Subject: [PATCH 010/152] fix unlocking items with numbers in paths --- common/script/ops/unlock.js | 13 +++++++++---- package.json | 1 + 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js index 5e0b118af0..5a53965443 100644 --- a/common/script/ops/unlock.js +++ b/common/script/ops/unlock.js @@ -5,6 +5,7 @@ import { NotAuthorized, BadRequest, } from '../libs/errors'; +import setWith from 'lodash.setwith'; // Not available in lodash 3 // If item is already purchased -> equip it // Otherwise unlock it @@ -60,10 +61,12 @@ module.exports = function unlock (user, req = {}, analytics) { if (isFullSet) { _.each(setPaths, function markItemsAsPurchased (pathPart) { if (path.indexOf('gear.') !== -1) { - _.set(user, pathPart, true); + // Using Object so path[1] won't create an array but an object {path: {1: value}} + setWith(user, pathPart, true); } - _.set(user, `purchased.${pathPart}`, true); + // Using Object so path[1] won't create an array but an object {path: {1: value}} + setWith(user, `purchased.${pathPart}`, true, Object); }); } else { if (alreadyOwns) { // eslint-disable-line no-lonely-if @@ -74,9 +77,11 @@ module.exports = function unlock (user, req = {}, analytics) { value = ''; } - _.set(user, `preferences.${key}`, value); + // Using Object so path[1] won't create an array but an object {path: {1: value}} + setWith(user, `preferences.${key}`, value); } else { - _.set(user, `purchased.${path}`, true); + // Using Object so path[1] won't create an array but an object {path: {1: value}} + setWith(user, `purchased.${path}`, true, Object); } } diff --git a/package.json b/package.json index fa12cb4dbb..b238a16977 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "jade": "~1.11.0", "js2xmlparser": "~1.0.0", "lodash": "^3.10.1", + "lodash.setwith": "^4.2.0", "loggly": "~1.0.8", "markdown-it": "^6.0.1", "merge-stream": "^1.0.0", From 20f4a49c0bbfa7bcb012b97f3f90e92173632ecb Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 20:02:57 +0200 Subject: [PATCH 011/152] fix missing 4th parameter to setWith --- common/script/ops/unlock.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js index 5a53965443..62f1420e2a 100644 --- a/common/script/ops/unlock.js +++ b/common/script/ops/unlock.js @@ -62,7 +62,7 @@ module.exports = function unlock (user, req = {}, analytics) { _.each(setPaths, function markItemsAsPurchased (pathPart) { if (path.indexOf('gear.') !== -1) { // Using Object so path[1] won't create an array but an object {path: {1: value}} - setWith(user, pathPart, true); + setWith(user, pathPart, true, Object); } // Using Object so path[1] won't create an array but an object {path: {1: value}} @@ -78,7 +78,7 @@ module.exports = function unlock (user, req = {}, analytics) { } // Using Object so path[1] won't create an array but an object {path: {1: value}} - setWith(user, `preferences.${key}`, value); + setWith(user, `preferences.${key}`, value, Object); } else { // Using Object so path[1] won't create an array but an object {path: {1: value}} setWith(user, `purchased.${path}`, true, Object); From 80323120b61cab3abad1ca3dcfea3358d92e027e Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 23 May 2016 21:32:43 +0100 Subject: [PATCH 012/152] Updated hall service and updated hall controller --- website/client/js/controllers/hallCtrl.js | 60 ++++++++++++---------- website/client/js/services/hallServices.js | 41 +++++++++++++++ website/client/manifest.json | 1 + website/views/options/social/hall.jade | 2 +- 4 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 website/client/js/services/hallServices.js diff --git a/website/client/js/controllers/hallCtrl.js b/website/client/js/controllers/hallCtrl.js index 3aad20a782..8d9d385546 100644 --- a/website/client/js/controllers/hallCtrl.js +++ b/website/client/js/controllers/hallCtrl.js @@ -1,47 +1,53 @@ "use strict"; -habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', - function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Hero = $resource(ApiUrl.get() + '/api/v3/hall/heroes/:uid', {uid:'@_id'}); +habitrpg.controller("HallHeroesCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', 'Hall', + function($scope, $rootScope, User, Notification, ApiUrl, Hall) { $scope.hero = undefined; - $scope.loadHero = function(uuid){ - Hero.query({uid:uuid}, function (heroData) { - $scope.hero = heroData.data; + $scope.currentHeroIndex = undefined; + $scope.heroes = []; + + Hall.getHeroes() + .then(function (response) { + $scope.heroes = response.data.data; }); + + $scope.loadHero = function(uuid, heroIndex) { + $scope.currentHeroIndex = heroIndex; + Hall.getHero(uuid) + .then(function (response) { + $scope.hero = response.data.data; + }); } + $scope.saveHero = function(hero) { $scope.hero.contributor.admin = ($scope.hero.contributor.level > 7) ? true : false; - hero.$save(function(){ - Notification.text("User updated"); - $scope.hero = undefined; - $scope._heroID = undefined; - Hero.query({}, function (heroesData) { - $scope.heroes = heroesData.data; + Hall.updateHero($scope.hero) + .then(function (response) { + Notification.text("User updated"); + $scope.hero = undefined; + $scope._heroID = undefined; + $scope.heroes[$scope.currentHeroIndex] = response.data.data; + $scope.currentHeroIndex = undefined; }); - }) } - Hero.query({}, function (heroesData) { - $scope.heroes = heroesData.data; - }); - $scope.populateContributorInput = function(id) { + $scope.populateContributorInput = function(id, index) { $scope._heroID = id; - window.scrollTo(0,200); - $scope.loadHero(id); + window.scrollTo(0, 200); + $scope.loadHero(id, index); }; }]); -habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', '$resource', - function($scope, $rootScope, User, Notification, ApiUrl, $resource) { - var Patron = $resource(ApiUrl.get() + '/api/v3/hall/patrons/:uid', {uid:'@_id'}); - +habitrpg.controller("HallPatronsCtrl", ['$scope', '$rootScope', 'User', 'Notification', 'ApiUrl', 'Hall', + function($scope, $rootScope, User, Notification, ApiUrl, Hall) { var page = 0; $scope.patrons = []; - $scope.loadMore = function(){ - Patron.query({page: page++}, function(patronsData){ - $scope.patrons = $scope.patrons.concat(patronsData.data); - }) + $scope.loadMore = function() { + Hall.getPatrons(page++) + .then(function (response) { + $scope.patrons = $scope.patrons.concat(response.data.data); + }); } $scope.loadMore(); diff --git a/website/client/js/services/hallServices.js b/website/client/js/services/hallServices.js new file mode 100644 index 0000000000..a3b7e49751 --- /dev/null +++ b/website/client/js/services/hallServices.js @@ -0,0 +1,41 @@ +'use strict'; + +angular.module('habitrpg') +.factory('Hall', [ '$rootScope', 'ApiUrl', '$http', + function($rootScope, ApiUrl, $http) { + var apiV3Prefix = '/api/v3'; + var Hall = {}; + + Hall.getHeroes = function () { + return $http({ + method: 'GET', + url: apiV3Prefix + '/hall/heroes', + }); + } + + Hall.getHero = function (uuid) { + return $http({ + method: 'GET', + url: apiV3Prefix + '/hall/heroes/' + uuid, + }); + } + + Hall.updateHero = function (heroDetails) { + return $http({ + method: 'PUT', + url: apiV3Prefix + '/hall/heroes/' + heroDetails._id, + data: heroDetails, + }); + } + + Hall.getPatrons = function (page) { + if (!page) page = 0; + + return $http({ + method: 'GET', + url: apiV3Prefix + '/hall/patrons?page=' + page, + }); + } + + return Hall; + }]); diff --git a/website/client/manifest.json b/website/client/manifest.json index 2c770893f1..fb59224a34 100644 --- a/website/client/manifest.json +++ b/website/client/manifest.json @@ -56,6 +56,7 @@ "js/services/socialServices.js", "js/services/statServices.js", "js/services/userServices.js", + "js/services/hallServices.js", "js/filters/money.js", "js/filters/roundLargeNumbers.js", diff --git a/website/views/options/social/hall.jade b/website/views/options/social/hall.jade index 381e5ec8fd..c5d0201666 100644 --- a/website/views/options/social/hall.jade +++ b/website/views/options/social/hall.jade @@ -88,7 +88,7 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html') span(ng-class='userAdminGlyphiconStyle(hero)') span(ng-if='!hero.contributor.admin') a.label.label-default(ng-class='userLevelStyle(hero)', ng-click='clickMember(hero._id, true)') {{hero.profile.name}} - td(ng-if='user.contributor.admin', ng-click='populateContributorInput(hero._id)').btn-link {{hero._id}} + td(ng-if='user.contributor.admin', ng-click='populateContributorInput(hero._id, $index)').btn-link {{hero._id}} td {{hero.contributor.level}} td {{hero.contributor.text}} td From d4287e1fd8d29028216d300a50718826b5b24b9f Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 23 May 2016 23:34:45 +0100 Subject: [PATCH 013/152] Added sync with user adds checklist item. Prevented task checklist remove request from being called on blank checklist --- website/client/js/controllers/tasksCtrl.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index fb44d19cb3..995b910122 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -193,24 +193,27 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N // Don't allow creation of an empty checklist item // TODO Provide UI feedback that this item is still blank } else if ($index == task.checklist.length - 1) { - Tasks.addChecklistItem(task._id, task.checklist[$index]); - task.checklist.push({completed:false,text:''}); - focusChecklist(task,task.checklist.length-1); + Tasks.addChecklistItem(task._id, task.checklist[$index]) + .then(function (response) { + task.checklist[$index] = response.data.data.checklist[$index]; + }); + task.checklist.push({completed:false, text:''}); + focusChecklist(task, task.checklist.length - 1); } else { $scope.saveTask(task, true); focusChecklist(task, $index + 1); } } - $scope.removeChecklistItem = function(task, $event, $index, force){ + $scope.removeChecklistItem = function(task, $event, $index, force) { // Remove item if clicked on trash icon if (force) { - Tasks.removeChecklistItem(task._id, task.checklist[$index].id); + if (task.checklist[$index].id) Tasks.removeChecklistItem(task._id, task.checklist[$index].id); task.checklist.splice($index, 1); } else if (!task.checklist[$index].text) { // User deleted all the text and is now wishing to delete the item // saveTask will prune the empty item - Tasks.removeChecklistItem(task._id, task.checklist[$index].id); + if (task.checklist[$index].id) Tasks.removeChecklistItem(task._id, task.checklist[$index].id); // Move focus if the list is still non-empty if ($index > 0) focusChecklist(task, $index-1); From 2ab76db27cbc2ea6c8e65a407a3fcc51fb015687 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 20:59:47 -0500 Subject: [PATCH 014/152] feat: Add make-admin debug route back in --- .../debug/POST-debug_make-admin.test.js | 2 +- website/server/controllers/api-v3/debug.js | 29 +++++++++---------- website/views/shared/footer.jade | 3 +- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/test/api/v3/integration/debug/POST-debug_make-admin.test.js b/test/api/v3/integration/debug/POST-debug_make-admin.test.js index 69628aa8bc..98818fb07e 100644 --- a/test/api/v3/integration/debug/POST-debug_make-admin.test.js +++ b/test/api/v3/integration/debug/POST-debug_make-admin.test.js @@ -3,7 +3,7 @@ import { generateUser, } from '../../../../helpers/api-v3-integration.helper'; -xdescribe('POST /debug/make-admin (pended for v3 prod testing)', () => { +describe('POST /debug/make-admin (pended for v3 prod testing)', () => { let user; before(async () => { diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 9949421513..9315f9bf4e 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -88,21 +88,20 @@ api.setCron = { * * @apiSuccess {Object} data An empty Object */ -// TODO: Re-enable after v3 prod testing is done -// api.makeAdmin = { -// method: 'POST', -// url: '/debug/make-admin', -// middlewares: [ensureDevelpmentMode, authWithHeaders()], -// async handler (req, res) { -// let user = res.locals.user; -// -// user.contributor.admin = true; -// -// await user.save(); -// -// res.respond(200, {}); -// }, -// }; +api.makeAdmin = { + method: 'POST', + url: '/debug/make-admin', + middlewares: [ensureDevelpmentMode, authWithHeaders()], + async handler (req, res) { + let user = res.locals.user; + + user.contributor.admin = true; + + await user.save(); + + res.respond(200, {}); + }, +}; /** * @api {post} /api/v3/debug/modify-inventory Manipulate user's inventory diff --git a/website/views/shared/footer.jade b/website/views/shared/footer.jade index 0164b16783..26e017696c 100644 --- a/website/views/shared/footer.jade +++ b/website/views/shared/footer.jade @@ -98,8 +98,7 @@ footer.footer(ng-controller='FooterCtrl') a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP +MP a.btn.btn-default(ng-click='addOneLevel()') +1 Level a.btn.btn-default(ng-click='addQuestProgress()' tooltip="+1000 to boss quests. 300 items to collection quests") Quest Progress Up - // TODO Re-enable after v3 prod testing - // a.btn.btn-default(ng-click='makeAdmin()') Make Admin + a.btn.btn-default(ng-click='makeAdmin()') Make Admin a.btn.btn-default(ng-click='openModifyInventoryModal()') Modify Inventory div(ng-init='deferredScripts()') From 770ffe93fce4bc65cb7b76cb7ddd1f525dd58ad7 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 21:13:35 -0500 Subject: [PATCH 015/152] fix: Prevent task cloning from cloneing challenge object fixes #7435 closes #7451 --- website/client/js/services/taskServices.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/js/services/taskServices.js b/website/client/js/services/taskServices.js index e7eeef1a93..7a99cd748e 100644 --- a/website/client/js/services/taskServices.js +++ b/website/client/js/services/taskServices.js @@ -1,6 +1,6 @@ 'use strict'; -var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt']; +var TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'date', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge']; angular.module('habitrpg') .factory('Tasks', ['$rootScope', 'Shared', '$http', From b1519eed148604e7e006a470201f2f3f4e61bdfb Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 21:47:42 -0500 Subject: [PATCH 016/152] fix: Display beastmaster count even if member route returned no items Because of this line: https://github.com/HabitRPG/habitrpg/blob/f1286762a813186cb4f1093dc33a52b117ab2827/website/server/controllers/api-v3/members.js#L53 We minimize any empty objects, which causes the statCalc methods to fail --- common/script/count.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/script/count.js b/common/script/count.js index 4572e29dd8..3b4c303b3b 100644 --- a/common/script/count.js +++ b/common/script/count.js @@ -8,7 +8,7 @@ import content from './content/index'; const DROP_ANIMALS = keys(content.pets); -function beastMasterProgress (pets) { +function beastMasterProgress (pets = {}) { let count = 0; each(DROP_ANIMALS, (animal) => { @@ -19,7 +19,7 @@ function beastMasterProgress (pets) { return count; } -function dropPetsCurrentlyOwned (pets) { +function dropPetsCurrentlyOwned (pets = {}) { let count = 0; each(DROP_ANIMALS, (animal) => { @@ -30,7 +30,7 @@ function dropPetsCurrentlyOwned (pets) { return count; } -function mountMasterProgress (mounts) { +function mountMasterProgress (mounts = {}) { let count = 0; each(DROP_ANIMALS, (animal) => { @@ -41,7 +41,7 @@ function mountMasterProgress (mounts) { return count; } -function remainingGearInSet (userGear, set) { +function remainingGearInSet (userGear = {}, set) { let gear = filter(content.gear.flat, (item) => { let setMatches = item.klass === set; let hasItem = userGear[item.key]; @@ -54,7 +54,7 @@ function remainingGearInSet (userGear, set) { return count; } -function questsOfCategory (userQuests, category) { +function questsOfCategory (userQuests = {}, category) { let quests = filter(content.quests, (quest) => { let categoryMatches = quest.category === category; let hasQuest = userQuests[quest.key]; From f9b759ae57278118453edf8264e12a5a2a89e4b9 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 22:06:06 -0500 Subject: [PATCH 017/152] fix: Impliment redirect for /static/api -> /apidoc --- website/server/controllers/top-level/pages.js | 9 +++ website/server/routes/pages.js | 58 ------------------- 2 files changed, 9 insertions(+), 58 deletions(-) delete mode 100644 website/server/routes/pages.js diff --git a/website/server/controllers/top-level/pages.js b/website/server/controllers/top-level/pages.js index 614b1edc0a..0af5bb5f9c 100644 --- a/website/server/controllers/top-level/pages.js +++ b/website/server/controllers/top-level/pages.js @@ -48,6 +48,15 @@ _.each(staticPages, (name) => { }; }); +api.redirectApi = { + method: 'GET', + url: '/static/api', + runCron: false, + async handler (req, res) { + res.redirect(301, '/apidoc'); + }, +}; + let shareables = ['level-up', 'hatch-pet', 'raise-pet', 'unlock-quest', 'won-challenge', 'achievement']; _.each(shareables, (name) => { diff --git a/website/server/routes/pages.js b/website/server/routes/pages.js deleted file mode 100644 index 17818285ed..0000000000 --- a/website/server/routes/pages.js +++ /dev/null @@ -1,58 +0,0 @@ -var nconf = require('nconf'); -var express = require('express'); -var router = express.Router(); -var _ = require('lodash'); -var locals = require('../middlewares/api-v2/locals'); -var i18n = require('../libs/api-v2/i18n'); -var md = require('markdown-it')({ - html: true, -}); - -const TOTAL_USER_COUNT = '1,100,000'; - -// -------- App -------- -router.get('/', i18n.getUserLanguage, locals, function(req, res) { - if (!req.headers['x-api-user'] && !req.headers['x-api-key'] && !(req.session && req.session.userId)) - return res.redirect('/static/front'); - - return res.render('index', { - title: 'Habitica | Your Life The Role Playing Game', - env: res.locals.habitrpg - }); -}); - -// -------- Static Pages -------- - -var pages = ['front', 'privacy', 'terms', 'api', 'features', 'videos', 'contact', 'plans', 'new-stuff', 'community-guidelines', 'old-news', 'press-kit', 'faq', 'overview', 'apps', 'clear-browser-data', 'merch', 'maintenance-info']; - -_.each(pages, function(name){ - router.get('/static/' + name, i18n.getUserLanguage, locals, function(req, res) { - res.render( 'static/' + name, { - env: res.locals.habitrpg, - md: md, - userCount: TOTAL_USER_COUNT - }); - }); -}); - -// -------- Social Media Sharing -------- - -var shareables = ['level-up','hatch-pet','raise-pet','unlock-quest','won-challenge','achievement']; - -_.each(shareables, function(name){ - router.get('/social/' + name, i18n.getUserLanguage, locals, function(req, res) { - res.render( 'social/' + name, { - env: res.locals.habitrpg, - md: md, - userCount: TOTAL_USER_COUNT - }); - }); -}); - -// --------- Redirects -------- - -router.get('/static/extensions', function(req, res) { - res.redirect('http://habitica.wikia.com/wiki/App_and_Extension_Integrations'); -}); - -module.exports = router; From aaf2ff5b703989591231d114eb194a9ef87d5831 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 22:12:19 -0500 Subject: [PATCH 018/152] fix: Correct api documentation for score route Fixes #7458 --- website/server/controllers/api-v3/tasks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 919c2fb541..092aed83ba 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -357,7 +357,7 @@ function _generateWebhookTaskData (task, direction, delta, stats, user) { } /** - * @api {put} /api/v3/tasks/:taskId/score/:direction Score a task + * @api {post} /api/v3/tasks/:taskId/score/:direction Score a task * @apiVersion 3.0.0 * @apiName ScoreTask * @apiGroup Task From 810355d2a56a787251e49de6eef4f18e6e604fb6 Mon Sep 17 00:00:00 2001 From: Alys Date: Tue, 24 May 2016 13:31:39 +1000 Subject: [PATCH 019/152] implement cron semi-safe mode - cron behaves as normal except boss causes no damage (#7462) --- config.json.example | 1 + website/server/libs/api-v3/cron.js | 17 ++++++++++------- website/server/models/group.js | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/config.json.example b/config.json.example index d70dcac18c..9d37d70db7 100644 --- a/config.json.example +++ b/config.json.example @@ -10,6 +10,7 @@ "TEST_DB_URI":"mongodb://localhost/habitrpg_test", "NODE_ENV":"development", "CRON_SAFE_MODE":"false", + "CRON_SEMI_SAFE_MODE":"false", "MAINTENANCE_MODE": "false", "SESSION_SECRET":"YOUR SECRET HERE", "ADMIN_EMAIL": "you@example.com", diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js index da076b54ce..9f74df610b 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/api-v3/cron.js @@ -5,6 +5,7 @@ import _ from 'lodash'; import nconf from 'nconf'; const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; +const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true'; const shouldDo = common.shouldDo; const scoreTask = common.ops.scoreTask; // const maxPMs = 200; @@ -175,13 +176,15 @@ export function cron (options = {}) { cron: true, }); - // Apply damage from a boss, less damage for Trivial priority (difficulty) - user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); - // NB: Medium and Hard priorities do not increase damage from boss. This was by accident - // initially, and when we realised, we could not fix it because users are used to - // their Medium and Hard Dailies doing an Easy amount of damage from boss. - // Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future - // setting between Trivial and Easy. + if (!CRON_SEMI_SAFE_MODE) { + // Apply damage from a boss, less damage for Trivial priority (difficulty) + user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1); + // NB: Medium and Hard priorities do not increase damage from boss. This was by accident + // initially, and when we realised, we could not fix it because users are used to + // their Medium and Hard Dailies doing an Easy amount of damage from boss. + // Easy is task.priority = 1. Anything < 1 will be Trivial (0.1) or any future + // setting between Trivial and Easy. + } } } } diff --git a/website/server/models/group.js b/website/server/models/group.js index 53305d94b9..fc1fba287b 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -517,7 +517,8 @@ schema.statics.bossQuest = async function bossQuest (user, progress) { group.quest.progress.hp -= progress.up; // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests! let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`; - let bossAttack = nconf.get('CRON_SAFE_MODE') === 'true' ? `${quest.boss.name('en')} did not attack the party because it was asleep while maintenance was happening.` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; + let bossAttack = nconf.get('CRON_SAFE_MODE') === 'true' || nconf.get('CRON_SEMI_SAFE_MODE') === 'true' ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; + // TODO Consider putting the safe mode boss attack message in an ENV var group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``); // If boss has Rage, increment Rage as well From 8faf73084a5380a7082d68926efd734dafa6c3c9 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 22:33:18 -0500 Subject: [PATCH 020/152] fix: correct sort task when task is at the top closes #7457 !0 === true, so we need to check for == null which matches undefined and null --- common/script/ops/sortTask.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js index ce002d8dc0..9a50abbe46 100644 --- a/common/script/ops/sortTask.js +++ b/common/script/ops/sortTask.js @@ -21,7 +21,7 @@ module.exports = function sortTask (user, req = {}) { if (index === -1) { throw new NotFound(i18n.t('messageTaskNotFound', req.language)); } - if (!to && !fromParam) { + if (to == null && fromParam == null) { throw new BadRequest('?to=__&from=__ are required'); } From 02d075e342c1f9e3467e4cccae03ca2c53aac263 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 22:41:37 -0500 Subject: [PATCH 021/152] lint: Ignore === for == null check in sort task --- common/script/ops/sortTask.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js index 9a50abbe46..07db8289cf 100644 --- a/common/script/ops/sortTask.js +++ b/common/script/ops/sortTask.js @@ -21,7 +21,7 @@ module.exports = function sortTask (user, req = {}) { if (index === -1) { throw new NotFound(i18n.t('messageTaskNotFound', req.language)); } - if (to == null && fromParam == null) { + if (to == null && fromParam == null) { // eslint-disable-line eqeqeq throw new BadRequest('?to=__&from=__ are required'); } From 64b389679735a1fa3c98737951ba5132462dc315 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 13:10:42 +0200 Subject: [PATCH 022/152] fix typo in migration script --- migrations/api_v3/groups.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/api_v3/groups.js b/migrations/api_v3/groups.js index dfd0616e07..c6582e6b68 100644 --- a/migrations/api_v3/groups.js +++ b/migrations/api_v3/groups.js @@ -109,7 +109,7 @@ function processGroups (afterId) { oldGroup.memberCount = oldGroup.members ? oldGroup.members.length : 0; oldGroup.challengeCount = oldGroup.challenges ? oldGroup.challenges.length : 0; - if (!oldGroup.balance <= 0) oldGroup.balance = 0; + if (oldGroup.balance <= 0) oldGroup.balance = 0; if (!oldGroup.name) oldGroup.name = 'group name'; if (!oldGroup.leaderOnly) oldGroup.leaderOnly = {}; if (!oldGroup.leaderOnly.challenges) oldGroup.leaderOnly.challenges = false; From 70d7a922f6c314bc2d7b55138757f7d34c0cdfb2 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 13:10:59 +0200 Subject: [PATCH 023/152] fix expression in angular template --- website/views/shared/tasks/edit/index.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/views/shared/tasks/edit/index.jade b/website/views/shared/tasks/edit/index.jade index d7bc217598..1ed0f4f3f8 100644 --- a/website/views/shared/tasks/edit/index.jade +++ b/website/views/shared/tasks/edit/index.jade @@ -3,7 +3,7 @@ div(ng-if='task._editing') // Broken Challenge .well(ng-if='task.challenge.broken') - div(ng-if='task.challenge.broken=="TASK_DELETED" || task.challenge.broken=="CHALLENGE_TASK_NOT_FOUND') + div(ng-if='task.challenge.broken=="TASK_DELETED" || task.challenge.broken=="CHALLENGE_TASK_NOT_FOUND"') p=env.t('brokenTask') p a(ng-click='unlink(task, "keep")')=env.t('keepIt') From ac77ceb75fdc5f9f88289d1e5a1efeb8a26028be Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 23 May 2016 23:30:37 -0500 Subject: [PATCH 024/152] fix: Correct change password on client * Add additional checks on server to prevent 500 * Add tests for param checks --- .../auth/PUT-user_update_password.test.js | 39 +++++++++++++++++++ website/server/controllers/api-v3/auth.js | 21 +++++++--- website/views/options/settings.jade | 4 +- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js index bcc1ac25d3..6821e18d3e 100644 --- a/test/api/v3/integration/user/auth/PUT-user_update_password.test.js +++ b/test/api/v3/integration/user/auth/PUT-user_update_password.test.js @@ -50,4 +50,43 @@ describe('PUT /user/auth/update-password', async () => { message: t('wrongPassword'), }); }); + + it('returns an error when password is missing', async () => { + let body = { + newPassword, + confirmPassword: newPassword, + }; + + await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns an error when newPassword is missing', async () => { + let body = { + password, + confirmPassword: newPassword, + }; + + await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + + it('returns an error when confirmPassword is missing', async () => { + let body = { + password, + newPassword, + }; + + await expect(user.put(ENDPOINT, body)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); }); diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js index f8eabf4c82..8e641f74e5 100644 --- a/website/server/controllers/api-v3/auth.js +++ b/website/server/controllers/api-v3/auth.js @@ -352,18 +352,27 @@ api.updatePassword = { if (!user.auth.local.hashed_password) throw new BadRequest(res.t('userHasNoLocalRegistration')); - let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); - if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); - req.checkBody({ password: { - notEmpty: {errorMessage: res.t('missingNewPassword')}, - }, - newPassword: { notEmpty: {errorMessage: res.t('missingPassword')}, }, + newPassword: { + notEmpty: {errorMessage: res.t('missingNewPassword')}, + }, + confirmPassword: { + notEmpty: {errorMessage: res.t('missingNewPassword')}, + }, }); + let validationErrors = req.validationErrors(); + + if (validationErrors) { + throw validationErrors; + } + + let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); + if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword')); + if (req.body.newPassword !== req.body.confirmPassword) throw new NotAuthorized(res.t('passwordConfirmationMatch')); user.auth.local.hashed_password = passwordUtils.encrypt(req.body.newPassword, user.auth.local.salt); // eslint-disable-line camelcase diff --git a/website/views/options/settings.jade b/website/views/options/settings.jade index 34c4dff0ca..754dd8dcda 100644 --- a/website/views/options/settings.jade +++ b/website/views/options/settings.jade @@ -183,11 +183,11 @@ script(type='text/ng-template', id='partials/options.settings.settings.html') h5=env.t('changePass') form(ng-submit='changeUser("password", passwordUpdates)', ng-show='user.auth.local', name='changePassword', novalidate) .form-group - input.form-control(type='password', placeholder=env.t('oldPass'), ng-model='passwordUpdates.oldPassword', required) + input.form-control(type='password', placeholder=env.t('oldPass'), ng-model='passwordUpdates.password', required) .form-group input.form-control(type='password', placeholder=env.t('newPass'), ng-model='passwordUpdates.newPassword', required) .form-group - input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='passwordUpdates.confirmNewPassword', required) + input.form-control(type='password', placeholder=env.t('confirmPass'), ng-model='passwordUpdates.confirmPassword', required) input.btn.btn-default(type='submit', ng-disabled='changePassword.$invalid', value=env.t('submit')) From 35544f14c599d2ba44b5d240231e17926a056555 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 24 May 2016 07:13:09 -0500 Subject: [PATCH 025/152] fix: Armoire sends back data from server rather than relying on client closes #5376 --- website/client/js/controllers/tasksCtrl.js | 5 +++++ website/client/js/services/userServices.js | 11 +++++++++++ .../views/shared/tasks/task_view/static_rewards.jade | 10 ++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index 995b910122..d09ebe1f24 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -259,6 +259,11 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N User.buy({params:{key:item.key}}); }; + $scope.buyArmoire = function () { + playRewardSound($scope.armoire); + User.buyArmoire(); + } + /* ------------------------ Hiding Tasks diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 70b8821c6d..edf07a4a86 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -355,6 +355,17 @@ angular.module('habitrpg') callOpsFunctionAndRequest('buy', 'buy', "POST", data.params.key, data); }, + buyArmoire: function () { + $http({ + method: "POST", + url: '/api/v3/user/buy-armoire', + }) + .then(function (response) { + Notification.text(response.data.message); + sync(); + }) + }, + buyQuest: function (data) { callOpsFunctionAndRequest('buyQuest', 'buy-quest', "POST", data.params.key, data); }, diff --git a/website/views/shared/tasks/task_view/static_rewards.jade b/website/views/shared/tasks/task_view/static_rewards.jade index 3ce7ab78b8..668c8de4ff 100644 --- a/website/views/shared/tasks/task_view/static_rewards.jade +++ b/website/views/shared/tasks/task_view/static_rewards.jade @@ -1,4 +1,6 @@ -mixin reward(item) +mixin reward(item, action) + - action = action || "buy(" + item + ")" + li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{::#{item}.notes()}}')&attributes(attributes) // right-hand side control buttons .task-meta-controls @@ -8,8 +10,8 @@ mixin reward(item) .task-controls.task-primary input.reward.visuallyhidden( type='checkbox', - ui-keypress='{13:"buy(#{item})"}') - a.money.btn-buy.item-btn(ng-class='::{highValue: #{item}.value >= 1000}', ng-click='::buy(#{item})') + ui-keypress='{13:"#{action}"}') + a.money.btn-buy.item-btn(ng-class='::{highValue: #{item}.value >= 1000}', ng-click='::#{action}') span.shop_gold span.reward-cost {{::#{item}.value}} // main content @@ -19,5 +21,5 @@ mixin reward(item) ul.items.rewards(ng-if='main && list.type=="reward"') +reward('item')(ng-repeat='item in itemStore') +reward('healthPotion') - +reward('armoire')(ng-if='user.flags.armoireEnabled', + +reward('armoire', 'buyArmoire()')(ng-if='user.flags.armoireEnabled', popover='{{armoire.notes(user, armoireCount(user.items.gear.owned))}}') From 9855f8c3858882d55812473a42b02c58e2c0e512 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 18:42:08 +0200 Subject: [PATCH 026/152] fix typo in stripe cancel --- website/server/controllers/top-level/payments/stripe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/top-level/payments/stripe.js b/website/server/controllers/top-level/payments/stripe.js index f7d490bb88..ea6d665613 100644 --- a/website/server/controllers/top-level/payments/stripe.js +++ b/website/server/controllers/top-level/payments/stripe.js @@ -154,7 +154,7 @@ api.subscribeCancel = { let user = res.locals.user; if (!user.purchased.plan.customerId) throw new NotAuthorized(res.t('missingSubscription')); - let customer = await stripe.customers.retrieve(user.purchased.plan.customeerId); + let customer = await stripe.customers.retrieve(user.purchased.plan.customerId); await stripe.customers.del(user.purchased.plan.customerId); await payments.cancelSubscription({ user, From 67e68ece29b8943230076129eab739fb977fe46a Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 21:31:29 +0200 Subject: [PATCH 027/152] fix gifting gems --- website/server/libs/api-v3/logger.js | 2 +- website/server/libs/api-v3/payments.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index ed1353e8ad..be450e8b5e 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -12,7 +12,7 @@ const logger = new winston.Logger(); if (IS_PROD) { if (ENABLE_CONSOLE_LOGS_IN_PROD) { logger.add(winston.transports.Console, { - colorize: true, + colorize: IS_PROD ? false : true, prettyPrint: true, }); } diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js index 5a9c888972..a6d5888893 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/api-v3/payments.js @@ -91,7 +91,7 @@ api.createSubscription = async function createSubscription (data) { data.user.purchased.txnCount++; if (data.gift) { - members.sendMessage(data.user, data.gift.member, data.gift); + data.user.sendMessage(data.user, data.gift.member, data.gift); let byUserName = getUserInfo(data.user, ['name']).name; @@ -164,7 +164,7 @@ api.buyGems = async function buyGems (data) { let byUsername = getUserInfo(data.user, ['name']).name; let gemAmount = data.gift.gems.amount || 20; - members.sendMessage(data.user, data.gift.member, data.gift); + data.user.sendMessage(data.user, data.gift.member, data.gift); if (data.gift.member.preferences.emailNotifications.giftedGems !== false) { txnEmail(data.gift.member, 'gifted-gems', [ {name: 'GIFTER', content: byUsername}, From 2ca52785e188f790127b64c147f37cb40ede3d4f Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 21:41:14 +0200 Subject: [PATCH 028/152] remove unused variable in payments code --- website/server/libs/api-v3/payments.js | 1 - 1 file changed, 1 deletion(-) diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js index a6d5888893..cacf52a685 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/api-v3/payments.js @@ -4,7 +4,6 @@ import { getUserInfo, sendTxn as txnEmail, } from './email'; -import members from '../../controllers/api-v3/members'; import moment from 'moment'; import nconf from 'nconf'; import pushNotify from './pushNotifications'; From c7d3539815ac2e33699d706ce20550ffdbe7eac1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 21:42:31 +0200 Subject: [PATCH 029/152] do not colorize or pretty print logs in production --- website/server/libs/api-v3/logger.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index be450e8b5e..9c1c05805c 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -12,8 +12,8 @@ const logger = new winston.Logger(); if (IS_PROD) { if (ENABLE_CONSOLE_LOGS_IN_PROD) { logger.add(winston.transports.Console, { - colorize: IS_PROD ? false : true, - prettyPrint: true, + colorize: false, + prettyPrint: false, }); } } else if (IS_TEST) { From 91e16ff1910240e7b740aeeeb1b11ef770211393 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 22:40:54 +0200 Subject: [PATCH 030/152] do not run cron when loading party to avoid race conditions --- website/server/controllers/api-v3/groups.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 07e9b5be54..7460757a51 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -117,6 +117,9 @@ api.getGroups = { api.getGroup = { method: 'GET', url: '/groups/:groupId', + // Disable cron when getting groups to avoid race conditions when the site is loaded + // and requests for party and user data are concurrent + runCron: false, middlewares: [authWithHeaders()], async handler (req, res) { let user = res.locals.user; From 048d55b9624975c9767601cb782213ef1692a7b2 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Tue, 24 May 2016 21:41:38 +0100 Subject: [PATCH 031/152] Removed sync call on load (#7485) --- website/client/js/services/userServices.js | 1 - 1 file changed, 1 deletion(-) diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 70b8821c6d..83edd614f6 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -112,7 +112,6 @@ angular.module('habitrpg') $rootScope.$emit('userSynced'); }); } - sync(); var save = function () { localStorage.setItem(STORAGE_USER_ID, JSON.stringify(user)); From 1e270e417cd6130b5abb3d45ec062279de6692d1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 24 May 2016 23:16:28 +0200 Subject: [PATCH 032/152] disable push notifications and fix buying gems --- website/server/libs/api-v3/payments.js | 6 +++--- website/server/libs/api-v3/pushNotifications.js | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js index cacf52a685..b896031afb 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/api-v3/payments.js @@ -6,7 +6,7 @@ import { } from './email'; import moment from 'moment'; import nconf from 'nconf'; -import pushNotify from './pushNotifications'; +import sendPushNotification from './pushNotifications'; import shared from '../../../../common' ; const IS_PROD = nconf.get('IS_PROD'); @@ -102,7 +102,7 @@ api.createSubscription = async function createSubscription (data) { } if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`); + sendPushNotification(data.gift.member, shared.i18n.t('giftedSubscription'), `${months} months - by ${byUserName}`); } } @@ -172,7 +172,7 @@ api.buyGems = async function buyGems (data) { } if (data.gift.member._id !== data.user._id) { // Only send push notifications if sending to a user other than yourself - pushNotify.sendNotify(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`); + sendPushNotification(data.gift.member, shared.i18n.t('giftedGems'), `${gemAmount} Gems - by ${byUsername}`); } await data.gift.member.save(); diff --git a/website/server/libs/api-v3/pushNotifications.js b/website/server/libs/api-v3/pushNotifications.js index 8354de04e4..ce5af61661 100644 --- a/website/server/libs/api-v3/pushNotifications.js +++ b/website/server/libs/api-v3/pushNotifications.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + import _ from 'lodash'; import nconf from 'nconf'; import pushNotify from 'push-notify'; @@ -26,9 +28,12 @@ if (gcm) { } module.exports = function sendNotification (user, title, message, timeToLive = 15) { - if (!user) return; + return; // TODO push notifications are not currently enabled - _.each(user.pushDevices, pushDevice => { + if (!user) return; + let pushDevices = user.pushDevices.toObject ? user.pushDevices.toObject() : user.pushDevices; + + _.each(pushDevices, pushDevice => { switch (pushDevice.type) { case 'android': if (gcm) { From a771c43989b04e18f5739a0d28800df139bf0441 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 12:35:58 +0200 Subject: [PATCH 033/152] feat(test): allow logs to show up when testing --- config.json.example | 1 + website/server/libs/api-v3/logger.js | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.json.example b/config.json.example index 9d37d70db7..01c549c803 100644 --- a/config.json.example +++ b/config.json.example @@ -9,6 +9,7 @@ "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", diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index 9c1c05805c..a5a46fec87 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -5,20 +5,19 @@ import _ from 'lodash'; const IS_PROD = nconf.get('IS_PROD'); const IS_TEST = nconf.get('IS_TEST'); -const ENABLE_CONSOLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') === 'true'; +const ENABLE_LOGS_IN_TEST = nconf.get('ENABLE_CONSOLE_LOGS_IN_TEST') === 'true'; +const ENABLE_LOGS_IN_PROD = nconf.get('ENABLE_CONSOLE_LOGS_IN_PROD') === 'true'; const logger = new winston.Logger(); if (IS_PROD) { - if (ENABLE_CONSOLE_LOGS_IN_PROD) { + if (ENABLE_LOGS_IN_PROD) { logger.add(winston.transports.Console, { colorize: false, prettyPrint: false, }); } -} else if (IS_TEST) { - // Do not log anything when testing -} else { +} else if (!IS_TEST || IS_TEST && ENABLE_LOGS_IN_TEST) { // Do not log anything when testing unless specified logger .add(winston.transports.Console, { colorize: true, From adce786ae2cc933d2fad2d36c9975a98f1c31e11 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 12:36:52 +0200 Subject: [PATCH 034/152] feat(members): add ability to return all members --- ...GET-challenges_challengeId_members.test.js | 38 ++++++++++++++++++- .../groups/GET-groups_groupId_members.test.js | 17 +++++++++ website/server/controllers/api-v3/members.js | 22 +++++++++-- 3 files changed, 72 insertions(+), 5 deletions(-) diff --git a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js index 5117dcd556..58b6583626 100644 --- a/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_challengeId_members.test.js @@ -69,7 +69,25 @@ describe('GET /challenges/:challengeId/members', () => { expect(res[0].profile).to.have.all.keys(['name']); }); - it('returns only first 30 members', async () => { + it('returns only first 30 members if req.query.includeAllMembers is not true', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + + let usersToGenerate = []; + for (let i = 0; i < 31; i++) { + usersToGenerate.push(generateUser({challenges: [challenge._id]})); + } + await Promise.all(usersToGenerate); + + let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=not-true`); + expect(res.length).to.equal(30); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + + it('returns only first 30 members if req.query.includeAllMembers is not defined', async () => { let group = await generateGroup(user, {type: 'party', name: generateUUID()}); let challenge = await generateChallenge(user, group); @@ -87,6 +105,24 @@ describe('GET /challenges/:challengeId/members', () => { }); }); + it('returns all members if req.query.includeAllMembers is true', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + let challenge = await generateChallenge(user, group); + + let usersToGenerate = []; + for (let i = 0; i < 31; i++) { + usersToGenerate.push(generateUser({challenges: [challenge._id]})); + } + await Promise.all(usersToGenerate); + + let res = await user.get(`/challenges/${challenge._id}/members?includeAllMembers=true`); + expect(res.length).to.equal(32); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + it('supports using req.query.lastId to get more members', async () => { let group = await generateGroup(user, {type: 'party', name: generateUUID()}); let challenge = await generateChallenge(user, group); diff --git a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js index 857f6fb863..5827de782d 100644 --- a/test/api/v3/integration/groups/GET-groups_groupId_members.test.js +++ b/test/api/v3/integration/groups/GET-groups_groupId_members.test.js @@ -74,6 +74,23 @@ describe('GET /groups/:groupId/members', () => { }); }); + it('returns only first 30 members even when ?includeAllMembers=true', async () => { + let group = await generateGroup(user, {type: 'party', name: generateUUID()}); + + let usersToGenerate = []; + for (let i = 0; i < 31; i++) { + usersToGenerate.push(generateUser({party: {_id: group._id}})); + } + await Promise.all(usersToGenerate); + + let res = await user.get('/groups/party/members?includeAllMembers=true'); + expect(res.length).to.equal(30); + res.forEach(member => { + expect(member).to.have.all.keys(['_id', 'id', 'profile']); + expect(member.profile).to.have.all.keys(['name']); + }); + }); + it('supports using req.query.lastId to get more members', async () => { let leader = await generateUser({balance: 4}); let group = await generateGroup(leader, {type: 'guild', privacy: 'public', name: generateUUID()}); diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 37bc3c5d27..8037c3b3d8 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -85,7 +85,13 @@ function _getMembersForItem (type) { // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge // for example if you've been booted from it, are the leader or a site admin - group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true}); + group = await Group.getGroup({ + user, + groupId: challenge.group, + fields: '_id type privacy', + optionalMembership: true, + }); + if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); } else { group = await Group.getGroup({user, groupId, fields: '_id type'}); @@ -117,10 +123,17 @@ function _getMembersForItem (type) { if (lastId) query._id = {$gt: lastId}; + let limit = 30; + + // Allow for all challenges members to be returned + if (type === 'challenge-members' && req.query.includeAllMembers === 'true') { + limit = 0; // no limit + } + let members = await User .find(query) .sort({_id: 1}) - .limit(30) + .limit(limit) .select(fields) .exec(); @@ -170,14 +183,15 @@ api.getInvitesForGroup = { /** * @api {get} /api/v3/challenges/:challengeId/members Get members for a challenge - * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. You can also use ?includeAllMembers=true but it's slow and discouraged. * @apiVersion 3.0.0 * @apiName GetMembersForChallenge * @apiGroup Member * * @apiParam {UUID} challengeId The challenge id * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results - * + * @apiParam {string} includeAllMembers Query parameter - If 'true' all challenge members are returned + * @apiSuccess {array} data An array of members, sorted by _id */ api.getMembersForChallenge = { From 042ac16c485ea66a3b02fc5649f194e4f2cbc1dc Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 12:41:24 +0200 Subject: [PATCH 035/152] client: fetch all challenges members --- website/client/js/services/memberServices.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/js/services/memberServices.js b/website/client/js/services/memberServices.js index be1eab4841..cec77de9f6 100644 --- a/website/client/js/services/memberServices.js +++ b/website/client/js/services/memberServices.js @@ -38,7 +38,7 @@ angular.module('habitrpg') function getChallengeMembers (challengeId) { return $http({ method: 'GET', - url: apiV3Prefix + '/challenges/' + challengeId + '/members', + url: apiV3Prefix + '/challenges/' + challengeId + '/members?includeAllMembers=true', }); } From d8051f3bf14088055c68e45eab0afa9f47205716 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 13:08:47 +0200 Subject: [PATCH 036/152] fix client side tests --- test/spec/services/memberServicesSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/services/memberServicesSpec.js b/test/spec/services/memberServicesSpec.js index 1344bf96e7..9c1b8cd638 100644 --- a/test/spec/services/memberServicesSpec.js +++ b/test/spec/services/memberServicesSpec.js @@ -47,7 +47,7 @@ describe('memberServices', function() { it('calls get challenge members', function() { var challengeId = 1; - var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members'; + var memberUrl = apiV3Prefix + '/challenges/' + challengeId + '/members?includeAllMembers=true'; $httpBackend.expectGET(memberUrl).respond({}); members.getChallengeMembers(challengeId); $httpBackend.flush(); From cb1d38575503acb07521545968b67e0f85e80f7e Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 14:10:31 +0200 Subject: [PATCH 037/152] fix tasks minimization --- website/server/models/task.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/website/server/models/task.js b/website/server/models/task.js index 536d95ec0c..3af644eee1 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -10,7 +10,10 @@ let Schema = mongoose.Schema; let discriminatorOptions = { discriminatorKey: 'type', // the key that distinguishes task types }; -let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), {_id: false}); +let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), { + _id: false, + minimize: false, +}); export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; @@ -52,7 +55,7 @@ export let TaskSchema = new Schema({ time: {type: Date, required: true}, }], }, _.defaults({ - minimize: true, // So empty objects are returned + minimize: false, // So empty objects are returned strict: true, }, discriminatorOptions)); From 506705d7380137492d9c266a71eee02e8493951f Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 14:47:51 +0200 Subject: [PATCH 038/152] mark includeAllMembers query parameter as beta --- website/server/controllers/api-v3/members.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 8037c3b3d8..afce322602 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -183,14 +183,18 @@ api.getInvitesForGroup = { /** * @api {get} /api/v3/challenges/:challengeId/members Get members for a challenge - * @apiDescription With a limit of 30 member per request. To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. You can also use ?includeAllMembers=true but it's slow and discouraged. + * @apiDescription With a limit of 30 member per request. + * To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * BETA You can also use ?includeAllMembers=true. This option is currently in BETA and may be removed in future. + * Its use is discouraged and its performaces are not optimized especially for large challenges. + * * @apiVersion 3.0.0 * @apiName GetMembersForChallenge * @apiGroup Member * * @apiParam {UUID} challengeId The challenge id * @apiParam {UUID} lastId Query parameter to specify the last member returned in a previous request to this route and get the next batch of results - * @apiParam {string} includeAllMembers Query parameter - If 'true' all challenge members are returned + * @apiParam {string} includeAllMembers BETA Query parameter - If 'true' all challenge members are returned * @apiSuccess {array} data An array of members, sorted by _id */ From f713bf53c15951c00ca9eb62de60d8103203f20d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 14:49:08 +0200 Subject: [PATCH 039/152] fix task.challenge tests and add checks when scoring challenge tasks --- .../challenges/POST-challenges_challengeId_leave.test.js | 2 +- test/api/v3/integration/tasks/PUT-tasks_id.test.js | 2 +- website/server/controllers/api-v3/tasks.js | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js index 9694b263a9..0502be654c 100644 --- a/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js +++ b/test/api/v3/integration/challenges/POST-challenges_challengeId_leave.test.js @@ -117,7 +117,7 @@ describe('POST /challenges/:challengeId/leave', () => { }); expect(testTask).to.not.be.undefined; - expect(testTask.challenge).to.be.undefined; + expect(testTask.challenge).to.eql({}); }); }); }); diff --git a/test/api/v3/integration/tasks/PUT-tasks_id.test.js b/test/api/v3/integration/tasks/PUT-tasks_id.test.js index 1325f8b392..566c1ebe5e 100644 --- a/test/api/v3/integration/tasks/PUT-tasks_id.test.js +++ b/test/api/v3/integration/tasks/PUT-tasks_id.test.js @@ -45,7 +45,7 @@ describe('PUT /tasks/:id', () => { expect(savedTask.history).to.eql(task.history); expect(savedTask.createdAt).to.equal(task.createdAt); expect(savedTask.updatedAt).to.be.greaterThan(task.updatedAt); - expect(savedTask.challenge).to.equal(task.challenge); + expect(savedTask.challenge).to.eql(task.challenge); expect(savedTask.completed).to.eql(task.completed); expect(savedTask.streak).to.equal(savedTask.streak); // it's an habit, dailies can change it expect(savedTask.dateCompleted).to.equal(task.dateCompleted); diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index 092aed83ba..9bf4ffe634 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -422,13 +422,15 @@ api.scoreTask = { sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user)); - if (task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') { + if (task.challenge && task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') { // Wrapping everything in a try/catch block because if an error occurs using `await` it MUST NOT bubble up because the request has already been handled try { let chalTask = await Tasks.Task.findOne({ _id: task.challenge.taskId, }).exec(); + if (!chalTask) return; + await chalTask.scoreChallengeTask(delta); } catch (e) { logger.error(e); From 7297a6fa762e0677e7a5985730e9262a8e050cc0 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Wed, 25 May 2016 13:52:17 +0100 Subject: [PATCH 040/152] Updated logic to not assign armoire incorrectly (#7476) * Updated logic to not assign armoire incorrectly * Updated test and added new test to ensure armorie is not enabled prematurely. * Fixed linting issue * Removed toObject from being called when called on the server --- common/script/fns/ultimateGear.js | 11 ++++++++++- test/common/fns/ultimateGear.js | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/common/script/fns/ultimateGear.js b/common/script/fns/ultimateGear.js index be5553201d..5bca8ff912 100644 --- a/common/script/fns/ultimateGear.js +++ b/common/script/fns/ultimateGear.js @@ -15,7 +15,16 @@ module.exports = function ultimateGear (user) { } }); - if (_.contains(user.achievements.ultimateGearSets, true) && user.flags.armoireEnabled !== true) { + let ultimateGearSetValues; + if (user.achievements.ultimateGearSets.toObject) { + ultimateGearSetValues = Object.values(user.achievements.ultimateGearSets.toObject()); + } else { + ultimateGearSetValues = Object.values(user.achievements.ultimateGearSets); + } + + let hasFullSet = _.includes(ultimateGearSetValues, true); + + if (hasFullSet && user.flags.armoireEnabled !== true) { user.flags.armoireEnabled = true; } diff --git a/test/common/fns/ultimateGear.js b/test/common/fns/ultimateGear.js index 8da991b565..242b57f3c1 100644 --- a/test/common/fns/ultimateGear.js +++ b/test/common/fns/ultimateGear.js @@ -8,6 +8,9 @@ describe('shared.fns.ultimateGear', () => { beforeEach(() => { user = generateUser(); + user.achievements.ultimateGearSets.toObject = function () { + return this; + }; }); it('sets armoirEnabled when partial achievement already achieved', () => { @@ -30,4 +33,25 @@ describe('shared.fns.ultimateGear', () => { ultimateGear(user); expect(user.flags.armoireEnabled).to.equal(true); }); + + it('does not set armoirEnabled when gear is not owned', () => { + let items = { + gear: { + owned: { + toObject: () => { + return { + armor_warrior_5: true, // eslint-disable-line camelcase + shield_warrior_5: true, // eslint-disable-line camelcase + head_warrior_5: true, // eslint-disable-line camelcase + weapon_warrior_6: false, // eslint-disable-line camelcase + }; + }, + }, + }, + }; + + user.items = items; + ultimateGear(user); + expect(user.flags.armoireEnabled).to.equal(false); + }); }); From 3f1343fdfbb8fdac7be36b97733ff850924b4590 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 14:58:38 +0200 Subject: [PATCH 041/152] fix trailing space --- website/server/controllers/api-v3/members.js | 2 +- website/server/models/challenge.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index afce322602..22a40afed2 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -184,7 +184,7 @@ api.getInvitesForGroup = { /** * @api {get} /api/v3/challenges/:challengeId/members Get members for a challenge * @apiDescription With a limit of 30 member per request. - * To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. + * To get all members run requests against this routes (updating the lastId query parameter) until you get less than 30 results. * BETA You can also use ?includeAllMembers=true. This option is currently in BETA and may be removed in future. * Its use is discouraged and its performaces are not optimized especially for large challenges. * diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js index 9901af0e79..4fddabac2c 100644 --- a/website/server/models/challenge.js +++ b/website/server/models/challenge.js @@ -231,7 +231,7 @@ schema.methods.removeTask = async function challengeRemoveTask (task) { }, {multi: true}).exec(); }; -// Unlink challenges tasks (and the challenge itself) from user +// Unlink challenges tasks (and the challenge itself) from user. TODO rename to 'leave' schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) { let challengeId = this._id; let findQuery = { From 1a2696554254bc6817a880e113c17f92ba9e9c65 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Mon, 23 May 2016 16:42:45 +0200 Subject: [PATCH 042/152] wip(): prevent cron from running twice --- website/server/middlewares/api-v3/cron.js | 116 +++++++++++++++++----- website/server/models/group.js | 7 +- website/server/models/user.js | 3 +- 3 files changed, 94 insertions(+), 32 deletions(-) diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index 780f400e8f..a512a7f397 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -6,6 +6,7 @@ import Bluebird from 'bluebird'; import { model as Group } from '../../models/group'; import { model as User } from '../../models/user'; import { cron } from '../../libs/api-v3/cron'; +import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; @@ -98,20 +99,43 @@ module.exports = function cronMiddleware (req, res, next) { if (daysMissed <= 0) return next(); - // Fetch active tasks (no completed todos) - Tasks.Task.find({ - userId: user._id, - $or: [ // Exclude completed todos - {type: 'todo', completed: false}, - {type: {$in: ['habit', 'daily', 'reward']}}, - ], + let quest; + let progress; + let tasks; + + // To avoid double cron we set _cronSignature on the user to a random string + // and check that it has remained the same before saving + user._cronSignature = uuid(); + + User.update({ + _id: user._id, + _cronSignature: 'not-running', + }, { + $set: { + _cronSignature: user._cronSignature, + }, }).exec() - .then(tasks => { + .then((updateResult) => { // Fetch active tasks (no completed todos) + // if the cron signature is set, throw an error and recover later + if (updateResult.nMatched === 0 || updateResult.nUpdated === 0) { + throw new Error('cron-already-running'); + } + + return Tasks.Task.find({ + userId: user._id, + $or: [ // Exclude completed todos + {type: 'todo', completed: false}, + {type: {$in: ['habit', 'daily', 'reward']}}, + ], + }).exec(); + }) + .then(tasksFetched => { + tasks = tasksFetched; let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; tasks.forEach(task => tasksByType[`${task.type}s`].push(task)); // Run cron - let progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); + progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); // Clear old completed todos - 30 days for free users, 90 for subscribers // Do not delete challenges completed todos TODO unless the task is broken? @@ -126,7 +150,7 @@ module.exports = function cronMiddleware (req, res, next) { }).exec(); let ranCron = user.isModified(); - let quest = common.content.quests[user.party.quest.key]; + quest = common.content.quests[user.party.quest.key]; if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired if (!ranCron) return next(); @@ -134,27 +158,65 @@ module.exports = function cronMiddleware (req, res, next) { // Group.tavernBoss(user, progress); // Save user and tasks - let toSave = [user.save()]; + // Uses mongoose's internals to get update command + let mongooseDelta = user.$__delta(); + if (mongooseDelta instanceof Error) { + throw mongooseDelta; + } + + let mongooseWhere = user.$__where(mongooseDelta[0]); + if (mongooseWhere instanceof Error) { + throw mongooseWhere; + } + mongooseWhere._cronSignature = user._cronSignature; // Only update the user if cron signature matches + + return User.update(mongooseWhere, mongooseDelta[1]); + }) + .then(updateResult => { + // if the cron signature is set, throw an error and recover later + if (updateResult.nMatched === 0 || updateResult.nUpdated === 0) { + throw new Error('cron-already-running'); + } + + let toSave = []; tasks.forEach(task => { if (task.isModified()) toSave.push(task.save()); }); - return Bluebird.all(toSave) - .then(saved => { - user = res.locals.user = saved[0]; - if (!quest) return; - // If user is on a quest, roll for boss & player, or handle collections - let questType = quest.boss ? 'boss' : 'collect'; - // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? - return Group[`${questType}Quest`](user, progress) - .then(() => User.findById(user._id).exec()) // fetch the updated user... - .then(updatedUser => { - res.locals.user = updatedUser; + return Bluebird.all(toSave); + }) + .then(() => { + if (!quest) return; + // If user is on a quest, roll for boss & player, or handle collections + let questType = quest.boss ? 'boss' : 'collect'; + // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? + return Group[`${questType}Quest`](user, progress); + }) + .then(() => { + User.findByIdAndUpdate(user._id, { + $set: {_cronSignature: 'not-running'}, + }, { + new: true, // return the updated document + }).exec(); + }) // fetch the updated user... + .then(updatedUser => { + user = res.locals.user = updatedUser; - return null; - }); - }) - .then(() => next()) - .catch(next); + return null; + }) + .then(() => next()) + .catch((err) => { + if (err.message === 'cron-already-running') { + // recovering after abort, wait 200ms and reload user + setTimeout(() => { + User.findById(user._id, (reloadErr, reloadedUser) => { + if (reloadErr) return next(reloadErr); + user = res.locals.user = reloadedUser; + return next(); + }); + }, 200); + } else { + return next(err); + } }); }; diff --git a/website/server/models/group.js b/website/server/models/group.js index fc1fba287b..b53138c1e7 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -281,7 +281,7 @@ schema.methods.sendChat = function sendChat (message, user) { this.chat.splice(200); // Kick off chat notifications in the background. - let lastSeenUpdate = {$set: {}, $inc: {_v: 1}}; + let lastSeenUpdate = {$set: {}}; lastSeenUpdate.$set[`newMessages.${this._id}`] = {name: this.name, value: true}; // do not send notifications for guilds with more than 5000 users and for the tavern @@ -431,7 +431,6 @@ schema.methods.finishQuest = function finishQuest (quest) { updates.$inc[`achievements.quests.${questK}`] = 1; updates.$inc['stats.gp'] = Number(quest.drop.gp); updates.$inc['stats.exp'] = Number(quest.drop.exp); - updates.$inc._v = 1; if (this._id === TAVERN_ID) { updates.$set['party.quest.completed'] = questK; // Just show the notif @@ -536,9 +535,9 @@ schema.statics.bossQuest = async function bossQuest (user, progress) { // Everyone takes damage await User.update({ - _id: {$in: _.keys(group.quest.members)}, + _id: {$in: _.keys(group.quest.members.toObject ? group.quest.members.toObject() : group.quest.members)}, }, { - $inc: {'stats.hp': down, _v: 1}, + $inc: {'stats.hp': down}, }, {multi: true}).exec(); // Apply changes the currently cronning user locally so we don't have to reload it to get the updated state // TODO how to mark not modified? https://github.com/Automattic/mongoose/pull/1167 diff --git a/website/server/models/user.js b/website/server/models/user.js index 2d0e561a66..8a457500de 100644 --- a/website/server/models/user.js +++ b/website/server/models/user.js @@ -345,6 +345,7 @@ export let schema = new Schema({ }, lastCron: {type: Date, default: Date.now}, + _cronSignature: String, // Private property to avoid double cron // {GROUP_ID: Boolean}, represents whether they have unseen chat messages newMessages: {type: Schema.Types.Mixed, default: () => { @@ -528,7 +529,7 @@ export let schema = new Schema({ schema.plugin(baseModel, { // noSet is not used as updating uses a whitelist and creating only accepts specific params (password, email, username, ...) noSet: [], - private: ['auth.local.hashed_password', 'auth.local.salt'], + private: ['auth.local.hashed_password', 'auth.local.salt', '_cronSignature'], toJSONTransform: function userToJSON (plainObj, originalDoc) { // plainObj.filters = {}; // TODO Not saved, remove? plainObj._tmp = originalDoc._tmp; // be sure to send down drop notifs From 31850830a01b3e2f53f791d202d117540725dd9b Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 16:55:57 +0200 Subject: [PATCH 043/152] refactor cron to avoid double cronning - work in progress --- website/server/controllers/api-v3/groups.js | 2 +- website/server/middlewares/api-v3/cron.js | 280 ++++++++++---------- website/server/models/group.js | 5 +- 3 files changed, 138 insertions(+), 149 deletions(-) diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 7460757a51..018193bec5 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -119,7 +119,7 @@ api.getGroup = { url: '/groups/:groupId', // Disable cron when getting groups to avoid race conditions when the site is loaded // and requests for party and user data are concurrent - runCron: false, + runCron: true, middlewares: [authWithHeaders()], async handler (req, res) { let user = res.locals.user; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index a512a7f397..de4692d461 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -10,132 +10,127 @@ import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; -module.exports = function cronMiddleware (req, res, next) { +module.exports = async function cronMiddleware (req, res, next) { let user = res.locals.user; - if (!user) return next(); // User might not be available when authentication is not mandatory let analytics = res.analytics; - let now = new Date(); + let _cronSignature = uuid(); - // If the user's timezone has changed (due to travel or daylight savings), - // cron can be triggered twice in one day, so we check for that and use - // both timezones to work out if cron should run. - // CDS = Custom Day Start time. - let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; - let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; - let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset')); - timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs; - // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults + try { + console.log('CHECKING RUN CRON', req.originalUrl, req.method, (new Date()).toISOString()); - if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { - // The user's browser has just told Habitica that the user's timezone has - // changed so store and use the new zone. - user.preferences.timezoneOffset = timezoneOffsetFromBrowser; - timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; - } + // If the user's timezone has changed (due to travel or daylight savings), + // cron can be triggered twice in one day, so we check for that and use + // both timezones to work out if cron should run. + // CDS = Custom Day Start time. + let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; + let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; + let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset')); + timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs; + // NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults - // How many days have we missed using the user's current timezone: - let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences)); - - if (timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs) { - // Since cron last ran, the user's timezone has changed. - // How many days have we missed using the old timezone: - let daysMissedNewZone = daysMissed; - let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ - now, - timezoneOffsetOverride: timezoneOffsetAtLastCron, - }, user.preferences)); - - if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { - // The timezone change was in the unsafe direction. - // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). - // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). - // Local time changed from, for example, 03:00 to 02:00. - - if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { - // Both old and new timezones indicate that we SHOULD run cron, so - // it is safe to do so immediately. - daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); - // use minimum value to be nice to user - } else if (daysMissedOldZone > 0) { - // The old timezone says that cron should run; the new timezone does not. - // This should be impossible for this direction of timezone change, but - // just in case I'm wrong... - // TODO - // console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens - } else if (daysMissedNewZone > 0) { - // The old timezone says that cron should NOT run -- i.e., cron has - // already run today, from the old timezone's point of view. - // The new timezone says that cron SHOULD run, but this is almost - // certainly incorrect. - // This happens when cron occurred at a time soon after the CDS. When - // you reinterpret that time in the new timezone, it looks like it - // was before the CDS, because local time has stepped backwards. - // To fix this, rewrite the cron time to a time that the new - // timezone interprets as being in today. - - daysMissed = 0; // prevent cron running now - let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; - // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 - - user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); - // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. - // From now on we can ignore the old timezone: - user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; - } else { - // Both old and new timezones indicate that cron should - // NOT run. - daysMissed = 0; // prevent cron running now - } - } else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { - daysMissed = daysMissedNewZone; - // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. - // There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; - // if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). + if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) { + // The user's browser has just told Habitica that the user's timezone has + // changed so store and use the new zone. + user.preferences.timezoneOffset = timezoneOffsetFromBrowser; + timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser; } - } - if (daysMissed <= 0) return next(); + // How many days have we missed using the user's current timezone: + let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences)); - let quest; - let progress; - let tasks; + if (timezoneOffsetAtLastCron !== timezoneOffsetFromUserPrefs) { + // Since cron last ran, the user's timezone has changed. + // How many days have we missed using the old timezone: + let daysMissedNewZone = daysMissed; + let daysMissedOldZone = daysSince(user.lastCron, _.defaults({ + now, + timezoneOffsetOverride: timezoneOffsetAtLastCron, + }, user.preferences)); - // To avoid double cron we set _cronSignature on the user to a random string - // and check that it has remained the same before saving - user._cronSignature = uuid(); + if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) { + // The timezone change was in the unsafe direction. + // E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0). + // or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300). + // Local time changed from, for example, 03:00 to 02:00. + + if (daysMissedOldZone > 0 && daysMissedNewZone > 0) { + // Both old and new timezones indicate that we SHOULD run cron, so + // it is safe to do so immediately. + daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone); + // use minimum value to be nice to user + } else if (daysMissedOldZone > 0) { + // The old timezone says that cron should run; the new timezone does not. + // This should be impossible for this direction of timezone change, but + // just in case I'm wrong... + // TODO + // console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens + } else if (daysMissedNewZone > 0) { + // The old timezone says that cron should NOT run -- i.e., cron has + // already run today, from the old timezone's point of view. + // The new timezone says that cron SHOULD run, but this is almost + // certainly incorrect. + // This happens when cron occurred at a time soon after the CDS. When + // you reinterpret that time in the new timezone, it looks like it + // was before the CDS, because local time has stepped backwards. + // To fix this, rewrite the cron time to a time that the new + // timezone interprets as being in today. + + daysMissed = 0; // prevent cron running now + let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs; + // e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60 + + user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes'); + // NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran. + // From now on we can ignore the old timezone: + user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; + } else { + // Both old and new timezones indicate that cron should + // NOT run. + daysMissed = 0; // prevent cron running now + } + } else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) { + daysMissed = daysMissedNewZone; + // TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. + // There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; + // if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared). + } + } + + if (daysMissed <= 0) return next(); + console.log('RUNNING CRON FOR REAL', req.originalUrl, req.method, (new Date()).toISOString()); + + // To avoid double cron we first set _cronSignature to now and then check that it's not changed while processing + let userUpdateResult = await User.update({ + _id: user._id, + _cronSignature: 'not-running', // Check that in the meantime another cron has not started + }, { + $set: { + _cronSignature, + }, + }).exec(); + console.log('FIRST USER UPDATE?', userUpdateResult, req.originalUrl, req.method, (new Date()).toISOString()); - User.update({ - _id: user._id, - _cronSignature: 'not-running', - }, { - $set: { - _cronSignature: user._cronSignature, - }, - }).exec() - .then((updateResult) => { // Fetch active tasks (no completed todos) // if the cron signature is set, throw an error and recover later - if (updateResult.nMatched === 0 || updateResult.nUpdated === 0) { + if (userUpdateResult.nMatched === 0 || userUpdateResult.nModified === 0) { throw new Error('cron-already-running'); } - return Tasks.Task.find({ + let tasks = await Tasks.Task.find({ userId: user._id, $or: [ // Exclude completed todos {type: 'todo', completed: false}, {type: {$in: ['habit', 'daily', 'reward']}}, ], }).exec(); - }) - .then(tasksFetched => { - tasks = tasksFetched; + let tasksByType = {habits: [], dailys: [], todos: [], rewards: []}; tasks.forEach(task => tasksByType[`${task.type}s`].push(task)); // Run cron - progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); + let progress = cron({user, tasksByType, now, daysMissed, analytics, timezoneOffsetFromUserPrefs}); // Clear old completed todos - 30 days for free users, 90 for subscribers // Do not delete challenges completed todos TODO unless the task is broken? @@ -149,74 +144,65 @@ module.exports = function cronMiddleware (req, res, next) { 'challenge.id': {$exists: false}, }).exec(); - let ranCron = user.isModified(); - quest = common.content.quests[user.party.quest.key]; + let quest = common.content.quests[user.party.quest.key]; - if (ranCron) res.locals.wasModified = true; // TODO remove after v2 is retired - if (!ranCron) return next(); + res.locals.wasModified = true; // TODO remove after v2 is retired // Group.tavernBoss(user, progress); + let reallyModifiedPaths = {}; // Mongoose stores both path and path.nested in user.modifiedPaths() - // Save user and tasks - // Uses mongoose's internals to get update command - let mongooseDelta = user.$__delta(); - if (mongooseDelta instanceof Error) { - throw mongooseDelta; - } + user.modifiedPaths().forEach(path => { + let rootPath = path.split('.')[0]; + if (Object.keys(reallyModifiedPaths).indexOf(rootPath) === -1) { + let dataForPath = user[rootPath].toObject ? user[rootPath].toObject() : user[rootPath]; + reallyModifiedPaths[rootPath] = dataForPath; + } + }); + reallyModifiedPaths._cronSignature = 'not-running'; // Finish running cron - let mongooseWhere = user.$__where(mongooseDelta[0]); - if (mongooseWhere instanceof Error) { - throw mongooseWhere; - } - mongooseWhere._cronSignature = user._cronSignature; // Only update the user if cron signature matches + // Save user (only if another cron has done it already!) + let secondUserUpdateResult = await User.update({ + _id: user._id, + _cronSignature, + }, { + $set: reallyModifiedPaths, + }).exec(); + console.log('SECOND USER UPDATE?', secondUserUpdateResult, req.originalUrl, req.method, (new Date()).toISOString()); - return User.update(mongooseWhere, mongooseDelta[1]); - }) - .then(updateResult => { - // if the cron signature is set, throw an error and recover later - if (updateResult.nMatched === 0 || updateResult.nUpdated === 0) { + // if cron already run, throw and recover later + if (secondUserUpdateResult.nMatched === 0 || secondUserUpdateResult.nModified === 0) { throw new Error('cron-already-running'); } - let toSave = []; + let tasksToSave = []; tasks.forEach(task => { - if (task.isModified()) toSave.push(task.save()); + if (task.isModified()) tasksToSave.push(task.save()); }); + await Bluebird.all(tasksToSave); - return Bluebird.all(toSave); - }) - .then(() => { if (!quest) return; + // If user is on a quest, roll for boss & player, or handle collections let questType = quest.boss ? 'boss' : 'collect'; // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? - return Group[`${questType}Quest`](user, progress); - }) - .then(() => { - User.findByIdAndUpdate(user._id, { - $set: {_cronSignature: 'not-running'}, - }, { - new: true, // return the updated document - }).exec(); - }) // fetch the updated user... - .then(updatedUser => { - user = res.locals.user = updatedUser; + await Group[`${questType}Quest`](user, progress); - return null; - }) - .then(() => next()) - .catch((err) => { + res.locals.user = await User.findById(user._id).exec(); + + return next(); + } catch (err) { if (err.message === 'cron-already-running') { + console.log('RECOVERING FROM CRON', req.originalUrl, req.method, (new Date()).toISOString()); // recovering after abort, wait 200ms and reload user - setTimeout(() => { - User.findById(user._id, (reloadErr, reloadedUser) => { - if (reloadErr) return next(reloadErr); - user = res.locals.user = reloadedUser; - return next(); - }); - }, 200); + Bluebird.delay(200).then(() => { + return User.findById(user._id).exec(); + }).then((reloadedUser) => { + res.locals.user = reloadedUser; + console.log('RECOVERED FROM CRON', req.originalUrl, req.method, (new Date()).toISOString()); + return next(); + }).catch(secondError => next(secondError)); } else { return next(err); } - }); + } }; diff --git a/website/server/models/group.js b/website/server/models/group.js index b53138c1e7..4bba8df960 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -25,6 +25,9 @@ const Schema = mongoose.Schema; export const INVITES_LIMIT = 100; export const TAVERN_ID = shared.TAVERN_ID; +const CRON_SAFE_MODE = nconf.get('CRON_SAFE_MODE') === 'true'; +const CRON_SEMI_SAFE_MODE = nconf.get('CRON_SEMI_SAFE_MODE') === 'true'; + // NOTE once Firebase is enabled any change to groups' members in MongoDB will have to be run through the API // changes made directly to the db will cause Firebase to get out of sync export let schema = new Schema({ @@ -516,7 +519,7 @@ schema.statics.bossQuest = async function bossQuest (user, progress) { group.quest.progress.hp -= progress.up; // TODO Create a party preferred language option so emits like this can be localized. Suggestion: Always display the English version too. Or, if English is not displayed to the players, at least include it in a new field in the chat object that's visible in the database - essential for admins when troubleshooting quests! let playerAttack = `${user.profile.name} attacks ${quest.boss.name('en')} for ${progress.up.toFixed(1)} damage.`; - let bossAttack = nconf.get('CRON_SAFE_MODE') === 'true' || nconf.get('CRON_SEMI_SAFE_MODE') === 'true' ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; + let bossAttack = CRON_SAFE_MODE || CRON_SEMI_SAFE_MODE ? `${quest.boss.name('en')} does not attack, because it respects the fact that there are some bugs\` \`post-maintenance and it doesn't want to hurt anyone unfairly. It will continue its rampage soon!` : `${quest.boss.name('en')} attacks party for ${Math.abs(down).toFixed(1)} damage.`; // TODO Consider putting the safe mode boss attack message in an ENV var group.sendChat(`\`${playerAttack}\` \`${bossAttack}\``); From bca7d9fad343c5137cfe7aece92a3defc70883e0 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 25 May 2016 11:07:45 -0500 Subject: [PATCH 044/152] chore: Correct version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b238a16977..e758937b61 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.0.0", + "version": "3.2.0", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 0bb7f8d931a2e52ddb2b69bbe4ae4001cee31f0b Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 25 May 2016 11:08:04 -0500 Subject: [PATCH 045/152] chore: Move apidoc config to separate file --- apidoc.json | 5 +++++ package.json | 6 ------ 2 files changed, 5 insertions(+), 6 deletions(-) create mode 100644 apidoc.json diff --git a/apidoc.json b/apidoc.json new file mode 100644 index 0000000000..094a59e80f --- /dev/null +++ b/apidoc.json @@ -0,0 +1,5 @@ +{ + "name": "Habitica V3 API Documentation", + "title": "Habitica", + "url": "https://habitica.com" +} diff --git a/package.json b/package.json index e758937b61..d8ec90e3f5 100644 --- a/package.json +++ b/package.json @@ -160,11 +160,5 @@ "vinyl-source-stream": "^1.0.0", "vinyl-transform": "^1.0.0", "xml2js": "^0.4.16" - }, - "apidoc": { - "name": "habitica", - "title": "Habitica", - "version": "3.0.0", - "url": "https://habitica.com" } } From 7539e2a93cdf15a135a307e58558c68a6dcb4b4f Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 25 May 2016 11:11:39 -0500 Subject: [PATCH 046/152] chore: Remove versioning from apidoc --- apidoc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apidoc.json b/apidoc.json index 094a59e80f..91b4830440 100644 --- a/apidoc.json +++ b/apidoc.json @@ -1,5 +1,8 @@ { "name": "Habitica V3 API Documentation", "title": "Habitica", - "url": "https://habitica.com" + "url": "https://habitica.com", + "template": { + "withCompare": false + } } From 193c46db38230b502b1c0edab4005f827eaeb13f Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 25 May 2016 11:21:43 -0500 Subject: [PATCH 047/152] fix(achievement): broaden helpedHabit definition --- common/locales/en/contrib.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/locales/en/contrib.json b/common/locales/en/contrib.json index 9ce0473f3c..f05e2f10e7 100644 --- a/common/locales/en/contrib.json +++ b/common/locales/en/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visit the Hall of Heroes (contributors and backers)", "conLearn": "Learn more about contributor rewards", "conLearnHow": "Learn how to contribute to Habitica", - "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.", - "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Current Survey", "surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", From ed815d494764dbd5375d3ec317683fafff725633 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 19:58:18 +0200 Subject: [PATCH 048/152] refactor recovering from cron --- website/server/libs/api-v3/cron.js | 2 - website/server/middlewares/api-v3/cron.js | 136 +++++++++++++--------- website/server/models/user.js | 2 +- 3 files changed, 83 insertions(+), 57 deletions(-) diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js index 9f74df610b..c59403d545 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/api-v3/cron.js @@ -83,8 +83,6 @@ function performSleepTasks (user, tasksByType, now) { export function cron (options = {}) { let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options; - user.auth.timestamps.loggedin = now; - user.lastCron = now; user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs; // User is only allowed a certain number of drops a day. This resets the count. if (user.items.lastDrop.count > 0) user.items.lastDrop.count = 0; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index de4692d461..ff9338b416 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -10,6 +10,33 @@ import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; +async function recoverCron (status, req, res, next) { + try { + let user = res.locals.user; + + await Bluebird.delay(300); + let reloadedUser = await User.findOne({_id: user._id}).exec(); + + if (!reloadedUser) { + throw new Error(`User ${user._id} not found while recovering.`); + } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { + console.log((new Date()).toISOString(), 'RECOVERED FROM CRON - STILL RUNNING', req.originalUrl, req.method); + status.times++; + if (status.times < 4) { + await recoverCron(status, req, res, next); + } else { + throw new Error(`Impossible to recover from cron for user ${user._id}.`); + } + } else { + console.log((new Date()).toISOString(), 'RECOVERED FROM CRON', req.originalUrl, req.method); + res.locals.user = reloadedUser; + return next(); + } + } catch (err) { + return next(err); + } +} + module.exports = async function cronMiddleware (req, res, next) { let user = res.locals.user; if (!user) return next(); // User might not be available when authentication is not mandatory @@ -19,13 +46,13 @@ module.exports = async function cronMiddleware (req, res, next) { let _cronSignature = uuid(); try { - console.log('CHECKING RUN CRON', req.originalUrl, req.method, (new Date()).toISOString()); + console.log((new Date()).toISOString(), 'CHECKING RUN CRON', req.originalUrl, req.method); // If the user's timezone has changed (due to travel or daylight savings), // cron can be triggered twice in one day, so we check for that and use // both timezones to work out if cron should run. // CDS = Custom Day Start time. - let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0; + let timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset; let timezoneOffsetAtLastCron = _.isFinite(user.preferences.timezoneOffsetAtLastCron) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs; let timezoneOffsetFromBrowser = Number(req.header('x-user-timezoneoffset')); timezoneOffsetFromBrowser = _.isFinite(timezoneOffsetFromBrowser) ? timezoneOffsetFromBrowser : timezoneOffsetFromUserPrefs; @@ -99,23 +126,28 @@ module.exports = async function cronMiddleware (req, res, next) { } } - if (daysMissed <= 0) return next(); - console.log('RUNNING CRON FOR REAL', req.originalUrl, req.method, (new Date()).toISOString()); + if (daysMissed <= 0) { + if (user.isModified()) await user.save(); + return next(); + } + + console.log((new Date()).toISOString(), 'RUNNING CRON FOR REAL', req.originalUrl, req.method); // To avoid double cron we first set _cronSignature to now and then check that it's not changed while processing let userUpdateResult = await User.update({ _id: user._id, - _cronSignature: 'not-running', // Check that in the meantime another cron has not started + _cronSignature: 'NOT_RUNNING', // Check that in the meantime another cron has not started }, { $set: { _cronSignature, }, }).exec(); - console.log('FIRST USER UPDATE?', userUpdateResult, req.originalUrl, req.method, (new Date()).toISOString()); + console.log((new Date()).toISOString(), 'FIRST USER UPDATE?', userUpdateResult, req.originalUrl, req.method); - // if the cron signature is set, throw an error and recover later + // If the cron signature is already set, cron is running in another request + // throw an error and recover later, if (userUpdateResult.nMatched === 0 || userUpdateResult.nModified === 0) { - throw new Error('cron-already-running'); + throw new Error('CRON_ALREADY_RUNNING'); } let tasks = await Tasks.Task.find({ @@ -144,65 +176,61 @@ module.exports = async function cronMiddleware (req, res, next) { 'challenge.id': {$exists: false}, }).exec(); - let quest = common.content.quests[user.party.quest.key]; - res.locals.wasModified = true; // TODO remove after v2 is retired // Group.tavernBoss(user, progress); - let reallyModifiedPaths = {}; // Mongoose stores both path and path.nested in user.modifiedPaths() - user.modifiedPaths().forEach(path => { - let rootPath = path.split('.')[0]; - if (Object.keys(reallyModifiedPaths).indexOf(rootPath) === -1) { - let dataForPath = user[rootPath].toObject ? user[rootPath].toObject() : user[rootPath]; - reallyModifiedPaths[rootPath] = dataForPath; - } + // Save user and tasks + let toSave = [user.save()]; + tasks.forEach(task => { + if (task.isModified()) toSave.push(task.save()); }); - reallyModifiedPaths._cronSignature = 'not-running'; // Finish running cron + await Bluebird.all(toSave); - // Save user (only if another cron has done it already!) - let secondUserUpdateResult = await User.update({ - _id: user._id, - _cronSignature, - }, { - $set: reallyModifiedPaths, - }).exec(); - console.log('SECOND USER UPDATE?', secondUserUpdateResult, req.originalUrl, req.method, (new Date()).toISOString()); + let quest = common.content.quests[user.party.quest.key]; - // if cron already run, throw and recover later - if (secondUserUpdateResult.nMatched === 0 || secondUserUpdateResult.nModified === 0) { - throw new Error('cron-already-running'); + if (quest) { + // If user is on a quest, roll for boss & player, or handle collections + let questType = quest.boss ? 'boss' : 'collect'; + await Group[`${questType}Quest`](user, progress); } - let tasksToSave = []; - tasks.forEach(task => { - if (task.isModified()) tasksToSave.push(task.save()); - }); - await Bluebird.all(tasksToSave); - - if (!quest) return; - - // If user is on a quest, roll for boss & player, or handle collections - let questType = quest.boss ? 'boss' : 'collect'; - // TODO this saves user, runs db updates, loads user. Is there a better way to handle this? - await Group[`${questType}Quest`](user, progress); - - res.locals.user = await User.findById(user._id).exec(); + // Set _cronSignature, lastCron and auth.timestamps.loggedin to signal end of cron + await User.update({ + _id: user._id, + }, { + $set: { + _cronSignature: 'NOT_RUNNING', + lastCron: now, + 'auth.timestamps.loggedin': now, + }, + }).exec(); + // Reload user + res.locals.user = await User.findOne({_id: user._id}).exec(); return next(); } catch (err) { - if (err.message === 'cron-already-running') { - console.log('RECOVERING FROM CRON', req.originalUrl, req.method, (new Date()).toISOString()); - // recovering after abort, wait 200ms and reload user - Bluebird.delay(200).then(() => { - return User.findById(user._id).exec(); - }).then((reloadedUser) => { - res.locals.user = reloadedUser; - console.log('RECOVERED FROM CRON', req.originalUrl, req.method, (new Date()).toISOString()); - return next(); - }).catch(secondError => next(secondError)); + // If cron was aborted for a race condition try to recover from it + if (err.message === 'CRON_ALREADY_RUNNING') { + console.log((new Date()).toISOString(), 'RECOVERING FROM CRON', req.originalUrl, req.method); + // Recovering after abort, wait 300ms and reload user + // do it for max 4 times then reset _cronSignature so that it doesn't prevent cron from running + // at the next request + let recoveryStatus = { + times: 0, + }; + + recoverCron(recoveryStatus, req, res, next); } else { - return next(err); + // For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running + // at the next request + await User.update({ + _id: user._id, + }, { + _cronSignature: 'NOT_RUNNING', + }).exec() + .then(() => next(err)) + .catch(secondError => next(secondError)); } } }; diff --git a/website/server/models/user.js b/website/server/models/user.js index 8a457500de..ac68ac2846 100644 --- a/website/server/models/user.js +++ b/website/server/models/user.js @@ -345,7 +345,7 @@ export let schema = new Schema({ }, lastCron: {type: Date, default: Date.now}, - _cronSignature: String, // Private property to avoid double cron + _cronSignature: {type: String, default: 'NOT_RUNNING'}, // Private property used to avoid double cron // {GROUP_ID: Boolean}, represents whether they have unseen chat messages newMessages: {type: Schema.Types.Mixed, default: () => { From f9762f4f8166415a147aaaf8c452c178cb1e93bd Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 21:01:12 +0200 Subject: [PATCH 049/152] fix cron tests --- test/api/v3/unit/libs/cron.test.js | 9 -- .../api/v3/unit/middlewares/cronMiddleware.js | 105 ++++++++++-------- website/server/middlewares/api-v3/cron.js | 80 ++++++------- 3 files changed, 103 insertions(+), 91 deletions(-) diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index a7e732442c..6f16f9973f 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -34,15 +34,6 @@ describe('cron', () => { }; }); - it('updates user.auth.timestamps.loggedin and lastCron', () => { - let now = new Date(); - - cron({user, tasksByType, daysMissed, analytics, now}); - - expect(user.auth.timestamps.loggedin).to.equal(now); - expect(user.lastCron).to.equal(now); - }); - it('updates user.preferences.timezoneOffsetAtLastCron', () => { let timezoneOffsetFromUserPrefs = 1; diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js index f4e040a11c..cba4cf83c1 100644 --- a/test/api/v3/unit/middlewares/cronMiddleware.js +++ b/test/api/v3/unit/middlewares/cronMiddleware.js @@ -1,7 +1,6 @@ import { generateRes, generateReq, - generateNext, generateTodo, generateDaily, } from '../../../../helpers/api-unit.helper'; @@ -14,13 +13,12 @@ import analyticsService from '../../../../../website/server/libs/api-v3/analytic import { v4 as generateUUID } from 'uuid'; describe('cron middleware', () => { - let res, req, next; + let res, req; let user; - beforeEach(() => { + beforeEach((done) => { res = generateRes(); req = generateReq(); - next = generateNext(); user = new User({ auth: { local: { @@ -33,24 +31,27 @@ describe('cron middleware', () => { }, }); - user._statsComputed = { - mp: 10, - maxMP: 100, - }; + user.save() + .then(savedUser => { + savedUser._statsComputed = { + mp: 10, + maxMP: 100, + }; - res.locals.user = user; - res.analytics = analyticsService; + res.locals.user = savedUser; + res.analytics = analyticsService; + done(); + }) + .catch(done); }); - it('calls next when user is not attached', () => { + it('calls next when user is not attached', (done) => { res.locals.user = null; - cronMiddleware(req, res, next); - expect(next).to.be.calledOnce; + cronMiddleware(req, res, (err) => done(err)); }); - it('calls next when days have not been missed', () => { - cronMiddleware(req, res, next); - expect(next).to.be.calledOnce; + it('calls next when days have not been missed', (done) => { + cronMiddleware(req, res, (err) => done(err)); }); it('should clear todos older than 30 days for free users', async (done) => { @@ -59,35 +60,37 @@ describe('cron middleware', () => { task.dateCompleted = moment(new Date()).subtract({days: 31}); task.completed = true; await task.save(); + await user.save(); - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; + cronMiddleware(req, res, (err) => { + Tasks.Task.findOne({_id: task}, function (secondErr, taskFound) { + expect(secondErr).to.not.exist; expect(taskFound).to.not.exist; - done(); + done(err); }); }); }); - it('should not clear todos older than 30 days for subscribed users', (done) => { + it('should not clear todos older than 30 days for subscribed users', async (done) => { user.purchased.plan.customerId = 'subscribedId'; user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); user.lastCron = moment(new Date()).subtract({days: 2}); let task = generateTodo(user); task.dateCompleted = moment(new Date()).subtract({days: 31}); task.completed = true; - task.save(); + await task.save(); + await user.save(); - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; + cronMiddleware(req, res, (err) => { + Tasks.Task.findOne({_id: task}, function (secondErr, taskFound) { + expect(secondErr).to.not.exist; expect(taskFound).to.exist; - done(); + done(err); }); }); }); - it('should clear todos older than 90 days for subscribed users', (done) => { + it('should clear todos older than 90 days for subscribed users', async (done) => { user.purchased.plan.customerId = 'subscribedId'; user.purchased.plan.dateUpdated = moment('012013', 'MMYYYY'); user.lastCron = moment(new Date()).subtract({days: 2}); @@ -95,46 +98,60 @@ describe('cron middleware', () => { let task = generateTodo(user); task.dateCompleted = moment(new Date()).subtract({days: 91}); task.completed = true; - task.save(); + await task.save(); + await user.save(); - cronMiddleware(req, res, () => { - Tasks.Task.findOne({_id: task}, function (err, taskFound) { - expect(err).to.not.exist; + cronMiddleware(req, res, (err) => { + Tasks.Task.findOne({_id: task}, function (secondErr, taskFound) { + expect(secondErr).to.not.exist; expect(taskFound).to.not.exist; - done(); + done(err); }); }); }); - it('should call next is user was not modified after cron', (done) => { + it('should call next if user was not modified after cron', async (done) => { let hpBefore = user.stats.hp; user.lastCron = moment(new Date()).subtract({days: 2}); + await user.save(); - user.save().then(function () { - cronMiddleware(req, res, function () { - expect(hpBefore).to.equal(user.stats.hp); - done(); - }); + cronMiddleware(req, res, (err) => { + expect(hpBefore).to.equal(user.stats.hp); + done(err); }); }); - it('does damage for missing dailies', (done) => { + it('updates user.auth.timestamps.loggedin and lastCron', async (done) => { + user.lastCron = moment(new Date()).subtract({days: 2}); + let now = new Date(); + await user.save(); + + cronMiddleware(req, res, (err) => { + expect(moment(now).isSame(user.lastCron, 'day')); + expect(moment(now).isSame(user.auth.timestamps.loggedin, 'day')); + done(err); + }); + }); + + it('does damage for missing dailies', async (done) => { let hpBefore = user.stats.hp; user.lastCron = moment(new Date()).subtract({days: 2}); let daily = generateDaily(user); daily.startDate = moment(new Date()).subtract({days: 2}); - daily.save(); + await daily.save(); + await user.save(); - cronMiddleware(req, res, () => { + cronMiddleware(req, res, (err) => { expect(user.stats.hp).to.be.lessThan(hpBefore); - done(); + done(err); }); }); - it('updates tasks', (done) => { + it('updates tasks', async (done) => { user.lastCron = moment(new Date()).subtract({days: 2}); let todo = generateTodo(user); let todoValueBefore = todo.value; + await user.save(); cronMiddleware(req, res, () => { Tasks.Task.findOne({_id: todo._id}, function (err, todoFound) { @@ -150,7 +167,7 @@ describe('cron middleware', () => { user.lastCron = moment(new Date()).subtract({days: 2}); let daily = generateDaily(user); daily.startDate = moment(new Date()).subtract({days: 2}); - daily.save(); + await daily.save(); let questKey = 'dilatory'; user.party.quest.key = questKey; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index ff9338b416..75589e5af3 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -10,44 +10,36 @@ import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; -async function recoverCron (status, req, res, next) { - try { - let user = res.locals.user; +async function recoverCron (status, req, res) { + let user = res.locals.user; - await Bluebird.delay(300); - let reloadedUser = await User.findOne({_id: user._id}).exec(); + await Bluebird.delay(300); + let reloadedUser = await User.findOne({_id: user._id}).exec(); - if (!reloadedUser) { - throw new Error(`User ${user._id} not found while recovering.`); - } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { - console.log((new Date()).toISOString(), 'RECOVERED FROM CRON - STILL RUNNING', req.originalUrl, req.method); - status.times++; - if (status.times < 4) { - await recoverCron(status, req, res, next); - } else { - throw new Error(`Impossible to recover from cron for user ${user._id}.`); - } + if (!reloadedUser) { + throw new Error(`User ${user._id} not found while recovering.`); + } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { + status.times++; + + if (status.times < 4) { + await recoverCron(status, req, res); } else { - console.log((new Date()).toISOString(), 'RECOVERED FROM CRON', req.originalUrl, req.method); - res.locals.user = reloadedUser; - return next(); + throw new Error(`Impossible to recover from cron for user ${user._id}.`); } - } catch (err) { - return next(err); + } else { + res.locals.user = reloadedUser; + return null; } } -module.exports = async function cronMiddleware (req, res, next) { +async function cronAsync (req, res) { let user = res.locals.user; - if (!user) return next(); // User might not be available when authentication is not mandatory + if (!user) return null; // User might not be available when authentication is not mandatory let analytics = res.analytics; let now = new Date(); - let _cronSignature = uuid(); try { - console.log((new Date()).toISOString(), 'CHECKING RUN CRON', req.originalUrl, req.method); - // If the user's timezone has changed (due to travel or daylight savings), // cron can be triggered twice in one day, so we check for that and use // both timezones to work out if cron should run. @@ -128,10 +120,10 @@ module.exports = async function cronMiddleware (req, res, next) { if (daysMissed <= 0) { if (user.isModified()) await user.save(); - return next(); + return null; } - console.log((new Date()).toISOString(), 'RUNNING CRON FOR REAL', req.originalUrl, req.method); + let _cronSignature = uuid(); // To avoid double cron we first set _cronSignature to now and then check that it's not changed while processing let userUpdateResult = await User.update({ @@ -142,7 +134,6 @@ module.exports = async function cronMiddleware (req, res, next) { _cronSignature, }, }).exec(); - console.log((new Date()).toISOString(), 'FIRST USER UPDATE?', userUpdateResult, req.originalUrl, req.method); // If the cron signature is already set, cron is running in another request // throw an error and recover later, @@ -208,11 +199,10 @@ module.exports = async function cronMiddleware (req, res, next) { // Reload user res.locals.user = await User.findOne({_id: user._id}).exec(); - return next(); + return null; } catch (err) { // If cron was aborted for a race condition try to recover from it if (err.message === 'CRON_ALREADY_RUNNING') { - console.log((new Date()).toISOString(), 'RECOVERING FROM CRON', req.originalUrl, req.method); // Recovering after abort, wait 300ms and reload user // do it for max 4 times then reset _cronSignature so that it doesn't prevent cron from running // at the next request @@ -220,17 +210,31 @@ module.exports = async function cronMiddleware (req, res, next) { times: 0, }; - recoverCron(recoveryStatus, req, res, next); + recoverCron(recoveryStatus, req, res); } else { // For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running // at the next request - await User.update({ - _id: user._id, - }, { - _cronSignature: 'NOT_RUNNING', - }).exec() - .then(() => next(err)) - .catch(secondError => next(secondError)); + try { + await User.update({ + _id: user._id, + }, { + _cronSignature: 'NOT_RUNNING', + }).exec(); + + throw err; // re-throw original error + } catch (secondErr) { + throw secondErr; + } } } +} + +module.exports = function cronMiddleware (req, res, next) { + cronAsync(req, res) + .then(() => { + next(); + }) + .catch(err => { + next(err); + }); }; From 9b529fff5244f60718688f9392c736172ae9fb57 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 21:53:44 +0200 Subject: [PATCH 050/152] cleanup quests code --- website/server/models/group.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/website/server/models/group.js b/website/server/models/group.js index 4bba8df960..46a5a4f449 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -500,11 +500,11 @@ schema.statics.collectQuest = async function collectQuest (user, progress) { // Still needs completing if (_.find(shared.content.quests[group.quest.key].collect, (v, k) => { return group.quest.progress.collect[k] < v.count; - })) return group.save(); + })) return await group.save(); await group.finishQuest(quest); group.sendChat('`All items found! Party has received their rewards.`'); - return group.save(); + return await group.save(); }; schema.statics.bossQuest = async function bossQuest (user, progress) { @@ -538,7 +538,7 @@ schema.statics.bossQuest = async function bossQuest (user, progress) { // Everyone takes damage await User.update({ - _id: {$in: _.keys(group.quest.members.toObject ? group.quest.members.toObject() : group.quest.members)}, + _id: {$in: _.keys(group.quest.members)}, }, { $inc: {'stats.hp': down}, }, {multi: true}).exec(); @@ -554,10 +554,9 @@ schema.statics.bossQuest = async function bossQuest (user, progress) { // Participants: Grant rewards & achievements, finish quest await group.finishQuest(shared.content.quests[group.quest.key]); - return group.save(); } - return group.save(); + return await group.save(); }; // to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` From 275d29e3f4220d872623d44f9c46efaed01f20cf Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 23:14:34 +0200 Subject: [PATCH 051/152] run cron when getting group --- test/api/v3/integration/user/GET-user_anonymized.test.js | 2 +- website/server/controllers/api-v3/groups.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js index bbcaaf6249..4bc673001a 100644 --- a/test/api/v3/integration/user/GET-user_anonymized.test.js +++ b/test/api/v3/integration/user/GET-user_anonymized.test.js @@ -7,7 +7,7 @@ import { import common from '../../../../../common'; import { v4 as generateUUID } from 'uuid'; -describe('GET /user/anonymized', () => { +describe.only('GET /user/anonymized', () => { let user; let endpoint = '/user/anonymized'; diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 018193bec5..07e9b5be54 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -117,9 +117,6 @@ api.getGroups = { api.getGroup = { method: 'GET', url: '/groups/:groupId', - // Disable cron when getting groups to avoid race conditions when the site is loaded - // and requests for party and user data are concurrent - runCron: true, middlewares: [authWithHeaders()], async handler (req, res) { let user = res.locals.user; From 8ca3144563b3f9f02447139a72325e57f634f893 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 25 May 2016 16:17:24 -0500 Subject: [PATCH 052/152] fix(crit): notify from server response (#7498) Fixes #3166. --- website/client/js/controllers/notificationCtrl.js | 8 -------- website/client/js/services/userServices.js | 5 +++++ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/website/client/js/controllers/notificationCtrl.js b/website/client/js/controllers/notificationCtrl.js index 16a2c3a991..39dc1e4790 100644 --- a/website/client/js/controllers/notificationCtrl.js +++ b/website/client/js/controllers/notificationCtrl.js @@ -82,14 +82,6 @@ habitrpg.controller('NotificationCtrl', } }); - $rootScope.$watch('user._tmp.crit', function(after, before){ - if (after == before || !after) return; - var amount = User.user._tmp.crit * 100 - 100; - // reset the crit counter - User.user._tmp.crit = undefined; - Notification.crit(amount); - }); - $rootScope.$watch('user._tmp.drop', function(after, before){ // won't work when getting the same item twice? if (_.isEqual(after, before) || !after) return; diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 255ac85d14..8b99c2c3b3 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -224,8 +224,13 @@ angular.module('habitrpg') Tasks.scoreTask(data.params.task._id, data.params.direction).then(function (res) { var tmp = res.data.data._tmp || {}; // used to notify drops, critical hits and other bonuses + var crit = tmp.crit; var drop = tmp.drop; + if (crit) { + var critBonus = crit * 100 - 100; + Notification.crit(critBonus); + } if (drop) user._tmp.drop = drop; }); }, From 8292903444e5b893d1f09400195db531e87611b1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 25 May 2016 23:33:29 +0200 Subject: [PATCH 053/152] fix user anonymized tests --- test/api/v3/integration/user/GET-user_anonymized.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/api/v3/integration/user/GET-user_anonymized.test.js b/test/api/v3/integration/user/GET-user_anonymized.test.js index 4bc673001a..136174a75e 100644 --- a/test/api/v3/integration/user/GET-user_anonymized.test.js +++ b/test/api/v3/integration/user/GET-user_anonymized.test.js @@ -7,13 +7,13 @@ import { import common from '../../../../../common'; import { v4 as generateUUID } from 'uuid'; -describe.only('GET /user/anonymized', () => { +describe('GET /user/anonymized', () => { let user; let endpoint = '/user/anonymized'; before(async () => { user = await generateUser(); - await user.update({ newMessages: ['some', 'new', 'messages'], profile: 'profile', 'purchased.plan': 'purchased plan', + await user.update({ newMessages: ['some', 'new', 'messages'], 'profile.name': 'profile', 'purchased.plan': 'purchased plan', contributor: 'contributor', invitations: 'invitations', 'items.special.nyeReceived': 'some', 'items.special.valentineReceived': 'some', webhooks: 'some', 'achievements.challenges': 'some', 'inbox.messages': [{ text: 'some text' }], From 7075cf671df5db25658d1a6413605d99995a4a65 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 25 May 2016 20:18:51 -0500 Subject: [PATCH 054/152] fix: Omit chat when sending group update call Closes #7200 --- website/client/js/services/groupServices.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/js/services/groupServices.js b/website/client/js/services/groupServices.js index 916efe50b7..1411e5e2cb 100644 --- a/website/client/js/services/groupServices.js +++ b/website/client/js/services/groupServices.js @@ -45,7 +45,7 @@ angular.module('habitrpg') //@TODO: Check for what has changed? //Remove populated fields - var groupDetailsToSend = _.omit(groupDetails, ['challenges', 'members', 'invites']); + var groupDetailsToSend = _.omit(groupDetails, ['chat', 'challenges', 'members', 'invites']); if (groupDetailsToSend.leader && groupDetailsToSend.leader._id) groupDetailsToSend.leader = groupDetailsToSend.leader._id; return $http({ From 328a183738f4a4f7d1adcf427ea5ebf7b2a90bd5 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 25 May 2016 21:34:49 -0500 Subject: [PATCH 055/152] Mystery Items May 2016 (#7501) * feat(mystery): May 2016 subscriber set * chore(news): Bailey --- .../sprites/spritesmith-largeSprites-0.css | 60 +- .../sprites/spritesmith-largeSprites-0.png | Bin 295886 -> 296773 bytes common/dist/sprites/spritesmith-main-10.css | 1040 ++++---- common/dist/sprites/spritesmith-main-10.png | Bin 149086 -> 150236 bytes common/dist/sprites/spritesmith-main-11.css | 900 +++---- common/dist/sprites/spritesmith-main-11.png | Bin 156933 -> 154718 bytes common/dist/sprites/spritesmith-main-12.css | 2126 +++++++++-------- common/dist/sprites/spritesmith-main-12.png | Bin 126575 -> 133905 bytes common/dist/sprites/spritesmith-main-4.css | 994 ++++---- common/dist/sprites/spritesmith-main-4.png | Bin 137649 -> 139405 bytes common/dist/sprites/spritesmith-main-5.css | 776 +++--- common/dist/sprites/spritesmith-main-5.png | Bin 261236 -> 261763 bytes common/dist/sprites/spritesmith-main-6.css | 522 ++-- common/dist/sprites/spritesmith-main-6.png | Bin 297048 -> 301758 bytes common/dist/sprites/spritesmith-main-7.css | 556 ++--- common/dist/sprites/spritesmith-main-7.png | Bin 148847 -> 151921 bytes common/dist/sprites/spritesmith-main-8.css | 502 ++-- common/dist/sprites/spritesmith-main-8.png | Bin 151450 -> 149583 bytes common/dist/sprites/spritesmith-main-9.css | 516 ++-- common/dist/sprites/spritesmith-main-9.png | Bin 146691 -> 145649 bytes .../broad_armor_mystery_201605.png | Bin 0 -> 3336 bytes .../mystery_201605/head_mystery_201605.png | Bin 0 -> 3174 bytes .../shop_armor_mystery_201605.png | Bin 0 -> 3214 bytes .../shop_head_mystery_201605.png | Bin 0 -> 3058 bytes .../slim_armor_mystery_201605.png | Bin 0 -> 3327 bytes .../promo/promo_mystery_201605.png | Bin 0 -> 3712 bytes common/locales/en/gear.json | 4 + common/locales/en/subscriber.json | 1 + common/script/content/gear/sets/mystery.js | 12 + common/script/content/mystery-sets.js | 4 + migrations/mystery_items.js | 2 +- website/views/shared/new-stuff.jade | 46 +- 32 files changed, 4068 insertions(+), 3993 deletions(-) create mode 100644 common/img/sprites/spritesmith/gear/events/mystery_201605/broad_armor_mystery_201605.png create mode 100644 common/img/sprites/spritesmith/gear/events/mystery_201605/head_mystery_201605.png create mode 100644 common/img/sprites/spritesmith/gear/events/mystery_201605/shop_armor_mystery_201605.png create mode 100644 common/img/sprites/spritesmith/gear/events/mystery_201605/shop_head_mystery_201605.png create mode 100644 common/img/sprites/spritesmith/gear/events/mystery_201605/slim_armor_mystery_201605.png create mode 100644 common/img/sprites/spritesmith_large/promo/promo_mystery_201605.png diff --git a/common/dist/sprites/spritesmith-largeSprites-0.css b/common/dist/sprites/spritesmith-largeSprites-0.css index b6fea67655..a45d6a8a3f 100644 --- a/common/dist/sprites/spritesmith-largeSprites-0.css +++ b/common/dist/sprites/spritesmith-largeSprites-0.css @@ -1,12 +1,12 @@ .2014_Fall_HealerPROMO2 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1005px -809px; + background-position: -808px -706px; width: 90px; height: 90px; } .2014_Fall_Mage_PROMO9 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -954px -660px; + background-position: -734px -317px; width: 120px; height: 90px; } @@ -18,13 +18,13 @@ } .2014_Fall_Warrior_PROMO { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1033px -1157px; + background-position: -942px -1157px; width: 90px; height: 90px; } .promo_android { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -332px; + background-position: -1096px -508px; width: 175px; height: 175px; } @@ -42,19 +42,19 @@ } .promo_backgrounds_armoire_201604 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -593px 0px; + background-position: -452px 0px; width: 140px; height: 441px; } .promo_backgrounds_armoire_201605 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -452px 0px; + background-position: -593px 0px; width: 140px; height: 441px; } .promo_backtoschool { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -553px; + background-position: -1343px -402px; width: 150px; height: 150px; } @@ -114,7 +114,7 @@ } .promo_enchanted_armoire_201509 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -808px -706px; + background-position: -861px -525px; width: 90px; height: 90px; } @@ -126,7 +126,7 @@ } .promo_enchanted_armoire_201601 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -760px -1157px; + background-position: -578px -1157px; width: 90px; height: 90px; } @@ -138,7 +138,7 @@ } .promo_habitica { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -508px; + background-position: -1096px -332px; width: 175px; height: 175px; } @@ -162,7 +162,7 @@ } .promo_mystery_201405 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -52px -1248px; + background-position: -1033px -1157px; width: 90px; height: 90px; } @@ -186,7 +186,7 @@ } .promo_mystery_201409 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -578px -1157px; + background-position: -1005px -809px; width: 90px; height: 90px; } @@ -198,7 +198,7 @@ } .promo_mystery_201411 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -942px -1157px; + background-position: -760px -1157px; width: 90px; height: 90px; } @@ -216,7 +216,7 @@ } .promo_mystery_201502 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -143px -1248px; + background-position: -52px -1248px; width: 90px; height: 90px; } @@ -234,7 +234,7 @@ } .promo_mystery_201505 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -861px -525px; + background-position: -325px -1248px; width: 90px; height: 90px; } @@ -252,7 +252,7 @@ } .promo_mystery_201508 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1494px -553px; + background-position: -855px -317px; width: 93px; height: 90px; } @@ -264,7 +264,7 @@ } .promo_mystery_201510 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -855px -317px; + background-position: -1494px -402px; width: 93px; height: 90px; } @@ -282,7 +282,7 @@ } .promo_mystery_201601 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -734px -317px; + background-position: -954px -660px; width: 120px; height: 90px; } @@ -300,10 +300,16 @@ } .promo_mystery_201604 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1494px -402px; + background-position: -1494px -553px; width: 93px; height: 90px; } +.promo_mystery_201605 { + background-image: url(spritesmith-largeSprites-0.png); + background-position: -143px -1248px; + width: 90px; + height: 90px; +} .promo_mystery_3014 { background-image: url(spritesmith-largeSprites-0.png); background-position: -1343px -1091px; @@ -336,19 +342,19 @@ } .promo_peppermint_flame { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -704px; + background-position: -1343px -852px; width: 140px; height: 147px; } .promo_pet_skins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -852px; + background-position: -1343px -704px; width: 140px; height: 147px; } .customize-option.promo_pet_skins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1368px -867px; + background-position: -1368px -719px; width: 60px; height: 60px; } @@ -378,13 +384,13 @@ } .promo_springclasses2014 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -289px -1157px; + background-position: 0px -1157px; width: 288px; height: 90px; } .promo_springclasses2015 { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -1157px; + background-position: -289px -1157px; width: 288px; height: 90px; } @@ -456,13 +462,13 @@ } .scene_phone_peek { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -402px; + background-position: -1343px -553px; width: 150px; height: 150px; } .welcome_basic_avatars { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px 0px; + background-position: -1096px -166px; width: 246px; height: 165px; } @@ -474,7 +480,7 @@ } .welcome_sample_tasks { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -166px; + background-position: -1096px 0px; width: 246px; height: 165px; } diff --git a/common/dist/sprites/spritesmith-largeSprites-0.png b/common/dist/sprites/spritesmith-largeSprites-0.png index 2924e79650842fc29f337ce802014813cb5f7b06..7790f21bc2c69c21534e0686d58a64d5a3717b4a 100644 GIT binary patch literal 296773 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 %^ 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

$smGLDyC`p6ebGn9AUn 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

+cjN$z7yChJVL6yZq_hyAAx*Cd&Sbs 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%0D94-XmE28+h`i+Ps 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 diff --git a/common/dist/sprites/spritesmith-main-4.css b/common/dist/sprites/spritesmith-main-4.css index a4c604a723..9ceaad1257 100644 --- a/common/dist/sprites/spritesmith-main-4.css +++ b/common/dist/sprites/spritesmith-main-4.css @@ -1,1464 +1,1464 @@ .broad_armor_wizard_2 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -819px; + background-position: -1245px -1092px; width: 90px; height: 90px; } .broad_armor_wizard_3 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -1183px; + background-position: -910px -1410px; width: 90px; height: 90px; } .broad_armor_wizard_4 { background-image: url(spritesmith-main-4.png); - background-position: -91px -1137px; + background-position: -1427px -1092px; width: 90px; height: 90px; } .broad_armor_wizard_5 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -819px; + background-position: -1245px -910px; width: 90px; height: 90px; } .shop_armor_healer_1 { background-image: url(spritesmith-main-4.png); - background-position: -1025px -1624px; + background-position: -1650px -1353px; width: 40px; height: 40px; } .shop_armor_healer_2 { background-image: url(spritesmith-main-4.png); - background-position: -984px -1624px; + background-position: -1650px -1312px; width: 40px; height: 40px; } .shop_armor_healer_3 { background-image: url(spritesmith-main-4.png); - background-position: -943px -1624px; + background-position: -1650px -1271px; width: 40px; height: 40px; } .shop_armor_healer_4 { background-image: url(spritesmith-main-4.png); - background-position: -902px -1624px; + background-position: -1312px -1592px; width: 40px; height: 40px; } .shop_armor_healer_5 { background-image: url(spritesmith-main-4.png); - background-position: -861px -1624px; + background-position: -1271px -1592px; width: 40px; height: 40px; } .shop_armor_rogue_1 { background-image: url(spritesmith-main-4.png); - background-position: -820px -1624px; + background-position: -1230px -1592px; width: 40px; height: 40px; } .shop_armor_rogue_2 { background-image: url(spritesmith-main-4.png); - background-position: -861px -1583px; + background-position: -1189px -1592px; width: 40px; height: 40px; } .shop_armor_rogue_3 { background-image: url(spritesmith-main-4.png); - background-position: -820px -1583px; + background-position: -1148px -1592px; width: 40px; height: 40px; } .shop_armor_rogue_4 { background-image: url(spritesmith-main-4.png); - background-position: -779px -1583px; + background-position: -1107px -1592px; width: 40px; height: 40px; } .shop_armor_rogue_5 { background-image: url(spritesmith-main-4.png); - background-position: -738px -1583px; + background-position: -1066px -1592px; width: 40px; height: 40px; } .shop_armor_special_0 { background-image: url(spritesmith-main-4.png); - background-position: -697px -1583px; + background-position: -1025px -1592px; width: 40px; height: 40px; } .shop_armor_special_1 { background-image: url(spritesmith-main-4.png); - background-position: -656px -1583px; + background-position: -984px -1592px; width: 40px; height: 40px; } .shop_armor_special_2 { background-image: url(spritesmith-main-4.png); - background-position: -615px -1583px; + background-position: -943px -1592px; width: 40px; height: 40px; } .shop_armor_special_finnedOceanicArmor { background-image: url(spritesmith-main-4.png); - background-position: -574px -1583px; + background-position: -902px -1592px; width: 40px; height: 40px; } .shop_armor_warrior_1 { background-image: url(spritesmith-main-4.png); - background-position: -533px -1583px; + background-position: -861px -1592px; width: 40px; height: 40px; } .shop_armor_warrior_2 { background-image: url(spritesmith-main-4.png); - background-position: -492px -1583px; + background-position: -820px -1592px; width: 40px; height: 40px; } .shop_armor_warrior_3 { background-image: url(spritesmith-main-4.png); - background-position: -451px -1583px; + background-position: -779px -1592px; width: 40px; height: 40px; } .shop_armor_warrior_4 { background-image: url(spritesmith-main-4.png); - background-position: -410px -1583px; + background-position: -738px -1592px; width: 40px; height: 40px; } .shop_armor_warrior_5 { background-image: url(spritesmith-main-4.png); - background-position: -369px -1583px; + background-position: -697px -1592px; width: 40px; height: 40px; } .shop_armor_wizard_1 { background-image: url(spritesmith-main-4.png); - background-position: -328px -1583px; + background-position: -656px -1592px; width: 40px; height: 40px; } .shop_armor_wizard_2 { background-image: url(spritesmith-main-4.png); - background-position: -287px -1583px; + background-position: -615px -1592px; width: 40px; height: 40px; } .shop_armor_wizard_3 { background-image: url(spritesmith-main-4.png); - background-position: -246px -1583px; + background-position: -574px -1592px; width: 40px; height: 40px; } .shop_armor_wizard_4 { background-image: url(spritesmith-main-4.png); - background-position: -205px -1583px; + background-position: -533px -1592px; width: 40px; height: 40px; } .shop_armor_wizard_5 { background-image: url(spritesmith-main-4.png); - background-position: -164px -1583px; + background-position: -492px -1592px; width: 40px; height: 40px; } .slim_armor_healer_1 { background-image: url(spritesmith-main-4.png); - background-position: -1001px -1319px; + background-position: -1427px -1183px; width: 90px; height: 90px; } .slim_armor_healer_2 { background-image: url(spritesmith-main-4.png); - background-position: 0px -1410px; + background-position: -1518px -819px; width: 90px; height: 90px; } .slim_armor_healer_3 { background-image: url(spritesmith-main-4.png); - background-position: -91px -1410px; + background-position: -1518px -1092px; width: 90px; height: 90px; } .slim_armor_healer_4 { background-image: url(spritesmith-main-4.png); - background-position: -1274px -1410px; + background-position: -878px -273px; width: 90px; height: 90px; } .slim_armor_healer_5 { background-image: url(spritesmith-main-4.png); - background-position: -1582px 0px; + background-position: -878px -364px; width: 90px; height: 90px; } .slim_armor_rogue_1 { background-image: url(spritesmith-main-4.png); - background-position: -942px -546px; + background-position: -878px -637px; width: 90px; height: 90px; } .slim_armor_rogue_2 { background-image: url(spritesmith-main-4.png); - background-position: -942px -728px; + background-position: -684px -530px; width: 90px; height: 90px; } .slim_armor_rogue_3 { background-image: url(spritesmith-main-4.png); - background-position: -851px -291px; + background-position: 0px -864px; width: 90px; height: 90px; } .slim_armor_rogue_4 { background-image: url(spritesmith-main-4.png); - background-position: -851px -382px; + background-position: -273px -864px; width: 90px; height: 90px; } .slim_armor_rogue_5 { background-image: url(spritesmith-main-4.png); - background-position: -851px -473px; + background-position: -364px -864px; width: 90px; height: 90px; } .slim_armor_special_2 { background-image: url(spritesmith-main-4.png); - background-position: -851px -564px; + background-position: -455px -864px; width: 90px; height: 90px; } .slim_armor_special_finnedOceanicArmor { background-image: url(spritesmith-main-4.png); - background-position: -91px -864px; + background-position: -546px -864px; width: 90px; height: 90px; } .slim_armor_warrior_1 { background-image: url(spritesmith-main-4.png); - background-position: -182px -864px; + background-position: -972px -364px; width: 90px; height: 90px; } .slim_armor_warrior_2 { background-image: url(spritesmith-main-4.png); - background-position: -273px -864px; + background-position: -819px -955px; width: 90px; height: 90px; } .slim_armor_warrior_3 { background-image: url(spritesmith-main-4.png); - background-position: -364px -864px; + background-position: -910px -955px; width: 90px; height: 90px; } .slim_armor_warrior_4 { background-image: url(spritesmith-main-4.png); - background-position: -1036px -91px; + background-position: -1063px -273px; width: 90px; height: 90px; } .slim_armor_warrior_5 { background-image: url(spritesmith-main-4.png); - background-position: -273px -955px; + background-position: -1063px -364px; width: 90px; height: 90px; } .slim_armor_wizard_1 { background-image: url(spritesmith-main-4.png); - background-position: -364px -955px; + background-position: -1063px -546px; width: 90px; height: 90px; } .slim_armor_wizard_2 { background-image: url(spritesmith-main-4.png); - background-position: -728px -955px; + background-position: -1063px -637px; width: 90px; height: 90px; } .slim_armor_wizard_3 { background-image: url(spritesmith-main-4.png); - background-position: -819px -955px; + background-position: 0px -1046px; width: 90px; height: 90px; } .slim_armor_wizard_4 { background-image: url(spritesmith-main-4.png); - background-position: -1001px -955px; + background-position: -91px -1046px; width: 90px; height: 90px; } .slim_armor_wizard_5 { background-image: url(spritesmith-main-4.png); - background-position: -1127px 0px; + background-position: -1154px -1001px; width: 90px; height: 90px; } .broad_armor_special_birthday { background-image: url(spritesmith-main-4.png); - background-position: -1127px -364px; + background-position: -455px -1137px; width: 90px; height: 90px; } .broad_armor_special_birthday2015 { background-image: url(spritesmith-main-4.png); - background-position: -1127px -455px; + background-position: -546px -1137px; width: 90px; height: 90px; } .broad_armor_special_birthday2016 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -819px; + background-position: -819px -1137px; width: 90px; height: 90px; } .shop_armor_special_birthday { background-image: url(spritesmith-main-4.png); - background-position: -123px -1583px; + background-position: -451px -1592px; width: 40px; height: 40px; } .shop_armor_special_birthday2015 { background-image: url(spritesmith-main-4.png); - background-position: -82px -1583px; + background-position: -410px -1592px; width: 40px; height: 40px; } .shop_armor_special_birthday2016 { background-image: url(spritesmith-main-4.png); - background-position: -41px -1583px; + background-position: -369px -1592px; width: 40px; height: 40px; } .slim_armor_special_birthday { background-image: url(spritesmith-main-4.png); - background-position: -1309px -273px; + background-position: -273px -1228px; width: 90px; height: 90px; } .slim_armor_special_birthday2015 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -364px; + background-position: -1336px -1001px; width: 90px; height: 90px; } .slim_armor_special_birthday2016 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -637px; + background-position: -728px -1319px; width: 90px; height: 90px; } .broad_armor_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: 0px -773px; + background-position: -658px -773px; width: 93px; height: 90px; } .broad_armor_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -442px -379px; + background-position: -224px -379px; width: 105px; height: 90px; } .broad_armor_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1400px -1092px; + background-position: -1183px -1319px; width: 90px; height: 90px; } .broad_armor_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1400px -1183px; + background-position: -1274px -1319px; width: 90px; height: 90px; } .broad_armor_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -182px -1319px; + background-position: -1427px -182px; width: 90px; height: 90px; } .broad_armor_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: -242px -91px; + background-position: -242px 0px; width: 120px; height: 90px; } .broad_armor_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -182px -470px; + background-position: -330px -379px; width: 105px; height: 90px; } .broad_armor_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -637px -1319px; + background-position: -1427px -910px; width: 90px; height: 90px; } .head_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -942px 0px; + background-position: -564px -773px; width: 93px; height: 90px; } .head_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -288px -470px; + background-position: -436px -379px; width: 105px; height: 90px; } .head_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1491px -91px; + background-position: -1001px -1410px; width: 90px; height: 90px; } .head_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1491px -182px; + background-position: -1092px -1410px; width: 90px; height: 90px; } .head_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -1491px -637px; + background-position: -1518px -728px; width: 90px; height: 90px; } .head_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: -242px 0px; + background-position: 0px -91px; width: 120px; height: 90px; } .head_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -394px -470px; + background-position: -578px 0px; width: 105px; height: 90px; } .head_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1183px -1410px; + background-position: -878px -182px; width: 90px; height: 90px; } .shield_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -846px -773px; + background-position: -376px -773px; width: 93px; height: 90px; } .shield_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: 0px -576px; + background-position: -578px -182px; width: 105px; height: 90px; } .shield_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -942px -364px; + background-position: -878px -455px; width: 90px; height: 90px; } .shield_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -942px -455px; + background-position: -878px -546px; width: 90px; height: 90px; } .shield_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -106px -576px; + background-position: -578px -273px; width: 105px; height: 90px; } .shield_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -942px -637px; + background-position: -878px -728px; width: 90px; height: 90px; } .shop_armor_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: 0px -1583px; + background-position: -328px -1592px; width: 40px; height: 40px; } .shop_armor_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1599px -1542px; + background-position: -287px -1592px; width: 40px; height: 40px; } .shop_armor_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1558px -1542px; + background-position: -246px -1592px; width: 40px; height: 40px; } .shop_armor_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1517px -1542px; + background-position: -205px -1592px; width: 40px; height: 40px; } .shop_armor_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -1476px -1542px; + background-position: -164px -1592px; width: 40px; height: 40px; } .shop_armor_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: -1435px -1542px; + background-position: -123px -1592px; width: 40px; height: 40px; } .shop_armor_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -1394px -1542px; + background-position: -82px -1592px; width: 40px; height: 40px; } .shop_armor_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1353px -1542px; + background-position: -41px -1592px; width: 40px; height: 40px; } .shop_head_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1312px -1542px; + background-position: 0px -1592px; width: 40px; height: 40px; } .shop_head_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1271px -1542px; + background-position: -1609px -1517px; width: 40px; height: 40px; } .shop_head_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1230px -1542px; + background-position: -1609px -1476px; width: 40px; height: 40px; } .shop_head_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1189px -1542px; + background-position: -1609px -1435px; width: 40px; height: 40px; } .shop_head_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -1148px -1542px; + background-position: -1609px -1394px; width: 40px; height: 40px; } .shop_head_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: -1107px -1542px; + background-position: -1609px -1353px; width: 40px; height: 40px; } .shop_head_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -1066px -1542px; + background-position: -1609px -1312px; width: 40px; height: 40px; } .shop_head_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1025px -1542px; + background-position: -1609px -1271px; width: 40px; height: 40px; } .shop_shield_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -984px -1542px; + background-position: -1609px -1230px; width: 40px; height: 40px; } .shop_shield_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -943px -1542px; + background-position: -1609px -1189px; width: 40px; height: 40px; } .shop_shield_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -902px -1542px; + background-position: -1609px -1148px; width: 40px; height: 40px; } .shop_shield_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -861px -1542px; + background-position: -1609px -1107px; width: 40px; height: 40px; } .shop_shield_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -820px -1542px; + background-position: -1609px -1066px; width: 40px; height: 40px; } .shop_shield_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -779px -1542px; + background-position: -1609px -1025px; width: 40px; height: 40px; } .shop_weapon_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -738px -1542px; + background-position: -1609px -984px; width: 40px; height: 40px; } .shop_weapon_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -697px -1542px; + background-position: -1609px -943px; width: 40px; height: 40px; } .shop_weapon_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -656px -1542px; + background-position: -1609px -902px; width: 40px; height: 40px; } .shop_weapon_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -615px -1542px; + background-position: -1609px -861px; width: 40px; height: 40px; } .shop_weapon_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -574px -1542px; + background-position: -1609px -820px; width: 40px; height: 40px; } .shop_weapon_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: -533px -1542px; + background-position: -1609px -779px; width: 40px; height: 40px; } .shop_weapon_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -492px -1542px; + background-position: -1609px -738px; width: 40px; height: 40px; } .shop_weapon_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -451px -1542px; + background-position: -1609px -697px; width: 40px; height: 40px; } .slim_armor_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -942px -273px; + background-position: -775px -564px; width: 93px; height: 90px; } .slim_armor_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -318px -576px; + background-position: -546px -576px; width: 105px; height: 90px; } .slim_armor_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -455px -955px; + background-position: -1063px 0px; width: 90px; height: 90px; } .slim_armor_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -546px -955px; + background-position: -1063px -91px; width: 90px; height: 90px; } .slim_armor_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -637px -955px; + background-position: -1063px -182px; width: 90px; height: 90px; } .slim_armor_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: 0px 0px; + background-position: -242px -91px; width: 120px; height: 90px; } .slim_armor_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -424px -576px; + background-position: 0px -682px; width: 105px; height: 90px; } .slim_armor_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -910px -955px; + background-position: -1063px -455px; width: 90px; height: 90px; } .weapon_special_fall2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -94px -773px; + background-position: -282px -773px; width: 93px; height: 90px; } .weapon_special_fall2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -336px -379px; + background-position: -106px -682px; width: 105px; height: 90px; } .weapon_special_fall2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1127px -91px; + background-position: -1063px -728px; width: 90px; height: 90px; } .weapon_special_fall2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1127px -182px; + background-position: -1063px -819px; width: 90px; height: 90px; } .weapon_special_fallHealer { background-image: url(spritesmith-main-4.png); - background-position: -1127px -273px; + background-position: -1063px -910px; width: 90px; height: 90px; } .weapon_special_fallMage { background-image: url(spritesmith-main-4.png); - background-position: 0px -91px; + background-position: -121px 0px; width: 120px; height: 90px; } .weapon_special_fallRogue { background-image: url(spritesmith-main-4.png); - background-position: -530px -576px; + background-position: -212px -682px; width: 105px; height: 90px; } .weapon_special_fallWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1127px -546px; + background-position: -182px -1046px; width: 90px; height: 90px; } .broad_armor_special_gaymerx { background-image: url(spritesmith-main-4.png); - background-position: -1127px -637px; + background-position: -273px -1046px; width: 90px; height: 90px; } .head_special_gaymerx { background-image: url(spritesmith-main-4.png); - background-position: -1127px -728px; + background-position: -364px -1046px; width: 90px; height: 90px; } .shop_armor_special_gaymerx { background-image: url(spritesmith-main-4.png); - background-position: -410px -1542px; + background-position: -1609px -656px; width: 40px; height: 40px; } .shop_head_special_gaymerx { background-image: url(spritesmith-main-4.png); - background-position: -369px -1542px; + background-position: -1609px -615px; width: 40px; height: 40px; } .slim_armor_special_gaymerx { background-image: url(spritesmith-main-4.png); - background-position: 0px -1046px; + background-position: -637px -1046px; width: 90px; height: 90px; } .back_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -91px -1046px; + background-position: -728px -1046px; width: 90px; height: 90px; } .broad_armor_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -182px -1046px; + background-position: -819px -1046px; width: 90px; height: 90px; } .head_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -273px -1046px; + background-position: -910px -1046px; width: 90px; height: 90px; } .shop_armor_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -328px -1542px; + background-position: -1609px -574px; width: 40px; height: 40px; } .shop_back_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -287px -1542px; + background-position: -1609px -533px; width: 40px; height: 40px; } .shop_head_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -246px -1542px; + background-position: -1125px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201402 { background-image: url(spritesmith-main-4.png); - background-position: -637px -1046px; + background-position: -1154px -182px; width: 90px; height: 90px; } .broad_armor_mystery_201403 { background-image: url(spritesmith-main-4.png); - background-position: -728px -1046px; + background-position: -1154px -273px; width: 90px; height: 90px; } .headAccessory_mystery_201403 { background-image: url(spritesmith-main-4.png); - background-position: -819px -1046px; + background-position: -1154px -364px; width: 90px; height: 90px; } .shop_armor_mystery_201403 { background-image: url(spritesmith-main-4.png); - background-position: -205px -1542px; + background-position: -1084px -1501px; width: 40px; height: 40px; } .shop_headAccessory_mystery_201403 { background-image: url(spritesmith-main-4.png); - background-position: -164px -1542px; + background-position: -1043px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201403 { background-image: url(spritesmith-main-4.png); - background-position: -1092px -1046px; + background-position: -1154px -637px; width: 90px; height: 90px; } .back_mystery_201404 { background-image: url(spritesmith-main-4.png); - background-position: -1218px 0px; + background-position: -1154px -728px; width: 90px; height: 90px; } .headAccessory_mystery_201404 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -91px; + background-position: -1154px -819px; width: 90px; height: 90px; } .shop_back_mystery_201404 { background-image: url(spritesmith-main-4.png); - background-position: -1036px -910px; + background-position: -1002px -1501px; width: 40px; height: 40px; } .shop_headAccessory_mystery_201404 { background-image: url(spritesmith-main-4.png); - background-position: -1168px -1001px; + background-position: -961px -1501px; width: 40px; height: 40px; } .broad_armor_mystery_201405 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -364px; + background-position: 0px -1137px; width: 90px; height: 90px; } .head_mystery_201405 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -455px; + background-position: -91px -1137px; width: 90px; height: 90px; } .shop_armor_mystery_201405 { background-image: url(spritesmith-main-4.png); - background-position: -1127px -1001px; + background-position: -920px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201405 { background-image: url(spritesmith-main-4.png); - background-position: -1259px -1092px; + background-position: -879px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201405 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -728px; + background-position: -364px -1137px; width: 90px; height: 90px; } .broad_armor_mystery_201406 { background-image: url(spritesmith-main-4.png); - background-position: -851px -97px; + background-position: -775px -285px; width: 90px; height: 96px; } .head_mystery_201406 { background-image: url(spritesmith-main-4.png); - background-position: -851px 0px; + background-position: -775px -91px; width: 90px; height: 96px; } .shop_armor_mystery_201406 { background-image: url(spritesmith-main-4.png); - background-position: -1218px -1092px; + background-position: -838px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201406 { background-image: url(spritesmith-main-4.png); - background-position: -1350px -1183px; + background-position: -797px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201406 { background-image: url(spritesmith-main-4.png); - background-position: -851px -194px; + background-position: -775px -188px; width: 90px; height: 96px; } .broad_armor_mystery_201407 { background-image: url(spritesmith-main-4.png); - background-position: -182px -1137px; + background-position: -910px -1137px; width: 90px; height: 90px; } .head_mystery_201407 { background-image: url(spritesmith-main-4.png); - background-position: -273px -1137px; + background-position: -1001px -1137px; width: 90px; height: 90px; } .shop_armor_mystery_201407 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -1183px; + background-position: -756px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201407 { background-image: url(spritesmith-main-4.png); - background-position: -1441px -1274px; + background-position: -715px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201407 { background-image: url(spritesmith-main-4.png); - background-position: -546px -1137px; + background-position: -1245px -91px; width: 90px; height: 90px; } .broad_armor_mystery_201408 { background-image: url(spritesmith-main-4.png); - background-position: -637px -1137px; + background-position: -1245px -182px; width: 90px; height: 90px; } .head_mystery_201408 { background-image: url(spritesmith-main-4.png); - background-position: -728px -1137px; + background-position: -1245px -273px; width: 90px; height: 90px; } .shop_armor_mystery_201408 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -1274px; + background-position: -674px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201408 { background-image: url(spritesmith-main-4.png); - background-position: -1532px -1365px; + background-position: -633px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201408 { background-image: url(spritesmith-main-4.png); - background-position: -1001px -1137px; + background-position: -1245px -546px; width: 90px; height: 90px; } .broad_armor_mystery_201409 { background-image: url(spritesmith-main-4.png); - background-position: -1092px -1137px; + background-position: -1245px -637px; width: 90px; height: 90px; } .headAccessory_mystery_201409 { background-image: url(spritesmith-main-4.png); - background-position: -1183px -1137px; + background-position: -1245px -728px; width: 90px; height: 90px; } .shop_armor_mystery_201409 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -1365px; + background-position: -592px -1501px; width: 40px; height: 40px; } .shop_headAccessory_mystery_201409 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1450px; + background-position: -551px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201409 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -182px; + background-position: -1245px -1001px; width: 90px; height: 90px; } .back_mystery_201410 { background-image: url(spritesmith-main-4.png); - background-position: -564px -773px; + background-position: -775px -382px; width: 93px; height: 90px; } .broad_armor_mystery_201410 { background-image: url(spritesmith-main-4.png); - background-position: -752px -773px; + background-position: -775px -655px; width: 93px; height: 90px; } .shop_armor_mystery_201410 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1450px; + background-position: -510px -1501px; width: 40px; height: 40px; } .shop_back_mystery_201410 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1409px; + background-position: -469px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201410 { background-image: url(spritesmith-main-4.png); - background-position: -697px -667px; + background-position: -775px -473px; width: 93px; height: 90px; } .head_mystery_201411 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -728px; + background-position: -364px -1228px; width: 90px; height: 90px; } .shop_head_mystery_201411 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1409px; + background-position: -428px -1501px; width: 40px; height: 40px; } .shop_weapon_mystery_201411 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1368px; + background-position: -387px -1501px; width: 40px; height: 40px; } .weapon_mystery_201411 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -1001px; + background-position: -637px -1228px; width: 90px; height: 90px; } .broad_armor_mystery_201412 { background-image: url(spritesmith-main-4.png); - background-position: -1309px -1092px; + background-position: -728px -1228px; width: 90px; height: 90px; } .head_mystery_201412 { background-image: url(spritesmith-main-4.png); - background-position: 0px -1228px; + background-position: -819px -1228px; width: 90px; height: 90px; } .shop_armor_mystery_201412 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1368px; + background-position: -346px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201412 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1327px; + background-position: -305px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201412 { background-image: url(spritesmith-main-4.png); - background-position: -273px -1228px; + background-position: -1092px -1228px; width: 90px; height: 90px; } .broad_armor_mystery_201501 { background-image: url(spritesmith-main-4.png); - background-position: -364px -1228px; + background-position: -1183px -1228px; width: 90px; height: 90px; } .head_mystery_201501 { background-image: url(spritesmith-main-4.png); - background-position: -455px -1228px; + background-position: -1336px 0px; width: 90px; height: 90px; } .shop_armor_mystery_201501 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1327px; + background-position: -264px -1501px; width: 40px; height: 40px; } .shop_head_mystery_201501 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1286px; + background-position: -223px -1501px; width: 40px; height: 40px; } .slim_armor_mystery_201501 { background-image: url(spritesmith-main-4.png); - background-position: -728px -1228px; + background-position: -1336px -273px; width: 90px; height: 90px; } .headAccessory_mystery_201502 { background-image: url(spritesmith-main-4.png); - background-position: -819px -1228px; + background-position: -1336px -364px; width: 90px; height: 90px; } .shop_headAccessory_mystery_201502 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1286px; + background-position: -182px -1501px; width: 40px; height: 40px; } .shop_weapon_mystery_201502 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1245px; + background-position: -1456px -1451px; width: 40px; height: 40px; } .weapon_mystery_201502 { background-image: url(spritesmith-main-4.png); - background-position: -1092px -1228px; + background-position: -1336px -637px; width: 90px; height: 90px; } .broad_armor_mystery_201503 { background-image: url(spritesmith-main-4.png); - background-position: -1183px -1228px; + background-position: -1336px -728px; width: 90px; height: 90px; } .eyewear_mystery_201503 { background-image: url(spritesmith-main-4.png); - background-position: -1274px -1228px; + background-position: -1336px -819px; width: 90px; height: 90px; } .shop_armor_mystery_201503 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1245px; + background-position: -1456px -1410px; width: 40px; height: 40px; } .shop_eyewear_mystery_201503 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1204px; + background-position: -1365px -1360px; width: 40px; height: 40px; } .slim_armor_mystery_201503 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -182px; + background-position: -1336px -1092px; width: 90px; height: 90px; } .back_mystery_201504 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -273px; + background-position: -1336px -1183px; width: 90px; height: 90px; } .broad_armor_mystery_201504 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -364px; + background-position: 0px -1319px; width: 90px; height: 90px; } .shop_armor_mystery_201504 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1204px; + background-position: -1365px -1319px; width: 40px; height: 40px; } .shop_back_mystery_201504 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1163px; + background-position: -1274px -1269px; width: 40px; height: 40px; } .slim_armor_mystery_201504 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -637px; + background-position: -273px -1319px; width: 90px; height: 90px; } .head_mystery_201505 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -728px; + background-position: -364px -1319px; width: 90px; height: 90px; } .shop_head_mystery_201505 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1163px; + background-position: -1274px -1228px; width: 40px; height: 40px; } .shop_weapon_mystery_201505 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1122px; + background-position: -1183px -1178px; width: 40px; height: 40px; } .weapon_mystery_201505 { background-image: url(spritesmith-main-4.png); - background-position: -1400px -1001px; + background-position: -637px -1319px; width: 90px; height: 90px; } .broad_armor_mystery_201506 { background-image: url(spritesmith-main-4.png); - background-position: -91px -667px; + background-position: -684px -424px; width: 90px; height: 105px; } .eyewear_mystery_201506 { background-image: url(spritesmith-main-4.png); - background-position: 0px -667px; + background-position: -684px -318px; width: 90px; height: 105px; } .shop_armor_mystery_201506 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1122px; + background-position: -1183px -1137px; width: 40px; height: 40px; } .shop_eyewear_mystery_201506 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1081px; + background-position: -1092px -1087px; width: 40px; height: 40px; } .slim_armor_mystery_201506 { background-image: url(spritesmith-main-4.png); - background-position: -760px -424px; + background-position: -684px -106px; width: 90px; height: 105px; } .back_mystery_201507 { background-image: url(spritesmith-main-4.png); - background-position: -760px -318px; + background-position: -455px -576px; width: 90px; height: 105px; } .eyewear_mystery_201507 { background-image: url(spritesmith-main-4.png); - background-position: -760px -106px; + background-position: -364px -576px; width: 90px; height: 105px; } .shop_back_mystery_201507 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1081px; + background-position: -1092px -1046px; width: 40px; height: 40px; } .shop_eyewear_mystery_201507 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -1040px; + background-position: -1001px -996px; width: 40px; height: 40px; } .broad_armor_mystery_201508 { background-image: url(spritesmith-main-4.png); - background-position: -282px -773px; + background-position: 0px -773px; width: 93px; height: 90px; } .head_mystery_201508 { background-image: url(spritesmith-main-4.png); - background-position: -376px -773px; + background-position: -94px -773px; width: 93px; height: 90px; } .shop_armor_mystery_201508 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -1040px; + background-position: -1001px -955px; width: 40px; height: 40px; } .shop_head_mystery_201508 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -999px; + background-position: -910px -905px; width: 40px; height: 40px; } .slim_armor_mystery_201508 { background-image: url(spritesmith-main-4.png); - background-position: -470px -773px; + background-position: -188px -773px; width: 93px; height: 90px; } .broad_armor_mystery_201509 { background-image: url(spritesmith-main-4.png); - background-position: -1092px -1319px; + background-position: -1427px -637px; width: 90px; height: 90px; } .head_mystery_201509 { background-image: url(spritesmith-main-4.png); - background-position: -1183px -1319px; + background-position: -1427px -728px; width: 90px; height: 90px; } .shop_armor_mystery_201509 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -999px; + background-position: -910px -864px; width: 40px; height: 40px; } .shop_head_mystery_201509 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -958px; + background-position: -730px -723px; width: 40px; height: 40px; } .slim_armor_mystery_201509 { background-image: url(spritesmith-main-4.png); - background-position: -1491px 0px; + background-position: -1427px -1001px; width: 90px; height: 90px; } .back_mystery_201510 { background-image: url(spritesmith-main-4.png); - background-position: -212px -576px; + background-position: -578px -91px; width: 105px; height: 90px; } .headAccessory_mystery_201510 { background-image: url(spritesmith-main-4.png); - background-position: -658px -773px; + background-position: -470px -773px; width: 93px; height: 90px; } .shop_back_mystery_201510 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -958px; + background-position: -730px -682px; width: 40px; height: 40px; } .shop_headAccessory_mystery_201510 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -917px; + background-position: -309px -223px; width: 40px; height: 40px; } .broad_armor_mystery_201511 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -455px; + background-position: -91px -1410px; width: 90px; height: 90px; } .head_mystery_201511 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -546px; + background-position: -182px -1410px; width: 90px; height: 90px; } .shop_armor_mystery_201511 { background-image: url(spritesmith-main-4.png); - background-position: -1625px -546px; + background-position: -1561px -1456px; width: 42px; height: 42px; } .shop_head_mystery_201511 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -546px; + background-position: -1518px -1456px; width: 42px; height: 42px; } .slim_armor_mystery_201511 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -819px; + background-position: -455px -1410px; width: 90px; height: 90px; } .broad_armor_mystery_201512 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -910px; + background-position: -546px -1410px; width: 90px; height: 90px; } .head_mystery_201512 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -1001px; + background-position: -637px -1410px; width: 90px; height: 90px; } .shop_armor_mystery_201512 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -876px; + background-position: -363px -212px; width: 40px; height: 40px; } .shop_head_mystery_201512 { background-image: url(spritesmith-main-4.png); - background-position: -1066px -1624px; + background-position: -725px -621px; width: 40px; height: 40px; } .slim_armor_mystery_201512 { background-image: url(spritesmith-main-4.png); - background-position: -1491px -1274px; + background-position: -91px -1501px; width: 90px; height: 90px; } .head_mystery_201601 { background-image: url(spritesmith-main-4.png); - background-position: -121px 0px; + background-position: 0px 0px; width: 120px; height: 90px; } @@ -1470,283 +1470,313 @@ } .shop_head_mystery_201601 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -835px; + background-position: -972px -910px; width: 40px; height: 40px; } .shop_shield_mystery_201601 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -835px; + background-position: -1104px -1001px; width: 40px; height: 40px; } .back_mystery_201602 { background-image: url(spritesmith-main-4.png); - background-position: -364px -1410px; + background-position: -1365px -1410px; width: 90px; height: 90px; } .head_mystery_201602 { background-image: url(spritesmith-main-4.png); - background-position: -455px -1410px; + background-position: -1518px 0px; width: 90px; height: 90px; } .shop_back_mystery_201602 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -794px; + background-position: -1063px -1001px; width: 40px; height: 40px; } .shop_head_mystery_201602 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -794px; + background-position: -1195px -1092px; width: 40px; height: 40px; } .broad_armor_mystery_201603 { background-image: url(spritesmith-main-4.png); - background-position: -728px -1410px; + background-position: -1518px -273px; width: 90px; height: 90px; } .head_mystery_201603 { background-image: url(spritesmith-main-4.png); - background-position: -819px -1410px; + background-position: -1518px -364px; width: 90px; height: 90px; } .shop_armor_mystery_201603 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -671px; + background-position: -1154px -1092px; width: 40px; height: 40px; } .shop_head_mystery_201603 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -671px; + background-position: -1286px -1183px; width: 40px; height: 40px; } .slim_armor_mystery_201603 { background-image: url(spritesmith-main-4.png); - background-position: -1092px -1410px; + background-position: -1518px -637px; width: 90px; height: 90px; } .broad_armor_mystery_201604 { background-image: url(spritesmith-main-4.png); - background-position: -942px -91px; + background-position: -752px -773px; width: 93px; height: 90px; } .head_mystery_201604 { background-image: url(spritesmith-main-4.png); - background-position: -942px -182px; + background-position: -878px 0px; width: 93px; height: 90px; } .shop_armor_mystery_201604 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -630px; + background-position: -1427px -1365px; width: 40px; height: 40px; } .shop_head_mystery_201604 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -630px; + background-position: -309px -182px; width: 40px; height: 40px; } .slim_armor_mystery_201604 { background-image: url(spritesmith-main-4.png); - background-position: -188px -773px; + background-position: -878px -91px; width: 93px; height: 90px; } +.broad_armor_mystery_201605 { + background-image: url(spritesmith-main-4.png); + background-position: -1518px -1183px; + width: 90px; + height: 90px; +} +.head_mystery_201605 { + background-image: url(spritesmith-main-4.png); + background-position: -1518px -1274px; + width: 90px; + height: 90px; +} +.shop_armor_mystery_201605 { + background-image: url(spritesmith-main-4.png); + background-position: -1609px -123px; + width: 40px; + height: 40px; +} +.shop_head_mystery_201605 { + background-image: url(spritesmith-main-4.png); + background-position: -404px -212px; + width: 40px; + height: 40px; +} +.slim_armor_mystery_201605 { + background-image: url(spritesmith-main-4.png); + background-position: 0px -1501px; + width: 90px; + height: 90px; +} .broad_armor_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -91px; + background-position: -1518px -1365px; width: 90px; height: 90px; } .eyewear_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -182px; + background-position: -1518px -1001px; width: 90px; height: 90px; } .head_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -273px; + background-position: -1518px -910px; width: 90px; height: 90px; } .shop_armor_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -589px; + background-position: -1468px -1365px; width: 40px; height: 40px; } .shop_eyewear_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -917px; + background-position: -1336px -1274px; width: 40px; height: 40px; } .shop_head_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -876px; + background-position: -1377px -1274px; width: 40px; height: 40px; } .shop_weapon_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1558px -1501px; + background-position: -1245px -1183px; width: 40px; height: 40px; } .slim_armor_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -455px; + background-position: -1518px -546px; width: 90px; height: 90px; } .weapon_mystery_301404 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -364px; + background-position: -1518px -455px; width: 90px; height: 90px; } .eyewear_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1456px -1410px; + background-position: -1518px -182px; width: 90px; height: 90px; } .headAccessory_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1001px -1410px; + background-position: -1274px -1410px; width: 90px; height: 90px; } .head_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1365px -1410px; + background-position: -1518px -91px; width: 90px; height: 90px; } .shield_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -910px -1410px; + background-position: -1183px -1410px; width: 90px; height: 90px; } .shop_eyewear_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -712px; + background-position: -1013px -910px; width: 40px; height: 40px; } .shop_headAccessory_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1582px -753px; + background-position: -919px -819px; width: 40px; height: 40px; } .shop_head_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -712px; + background-position: -878px -819px; width: 40px; height: 40px; } .shop_shield_mystery_301405 { background-image: url(spritesmith-main-4.png); - background-position: -1623px -753px; + background-position: -684px -621px; width: 40px; height: 40px; } .broad_armor_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -637px -1410px; + background-position: -819px -1410px; width: 90px; height: 90px; } .broad_armor_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -546px -1410px; + background-position: -728px -1410px; width: 90px; height: 90px; } .broad_armor_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -273px -1410px; + background-position: -364px -1410px; width: 90px; height: 90px; } .broad_armor_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -182px -1410px; + background-position: -273px -1410px; width: 90px; height: 90px; } .broad_armor_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -1491px -1092px; + background-position: 0px -1410px; width: 90px; height: 90px; } .broad_armor_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1491px -728px; + background-position: -1427px -1274px; width: 90px; height: 90px; } .broad_armor_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -594px -667px; + background-position: -524px -682px; width: 102px; height: 90px; } .broad_armor_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1491px -364px; + background-position: -1427px -819px; width: 90px; height: 90px; } .broad_armor_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -1491px -273px; + background-position: -1427px -455px; width: 90px; height: 90px; } .broad_armor_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -1365px -1319px; + background-position: -1427px -364px; width: 90px; height: 90px; } .broad_armor_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1274px -1319px; + background-position: -1427px -91px; width: 90px; height: 90px; } .broad_armor_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -910px -1319px; + background-position: -1427px 0px; width: 90px; height: 90px; } .headAccessory_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -910px -1228px; + background-position: -1001px -1228px; width: 90px; height: 90px; } .headAccessory_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -637px -1228px; + background-position: -910px -1228px; width: 90px; height: 90px; } @@ -1758,535 +1788,535 @@ } .headAccessory_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -182px -1228px; + background-position: -455px -1228px; width: 90px; height: 90px; } .headAccessory_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -91px -1228px; + background-position: -182px -1228px; width: 90px; height: 90px; } .headAccessory_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1309px -910px; + background-position: -91px -1228px; width: 90px; height: 90px; } .headAccessory_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -285px -667px; + background-position: -421px -682px; width: 102px; height: 90px; } .headAccessory_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1309px -546px; + background-position: -1245px -819px; width: 90px; height: 90px; } .headAccessory_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -1309px -455px; + background-position: -1245px -455px; width: 90px; height: 90px; } .headAccessory_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -1309px -91px; + background-position: -1245px -364px; width: 90px; height: 90px; } .headAccessory_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1309px 0px; + background-position: -1245px 0px; width: 90px; height: 90px; } .headAccessory_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -910px -1137px; + background-position: -1092px -1137px; width: 90px; height: 90px; } .head_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -819px -1319px; + background-position: -1001px -1319px; width: 90px; height: 90px; } .head_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -546px -1319px; + background-position: -910px -1319px; width: 90px; height: 90px; } .head_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -455px -1319px; + background-position: -546px -1319px; width: 90px; height: 90px; } .head_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -91px -1319px; + background-position: -455px -1319px; width: 90px; height: 90px; } .head_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: 0px -1319px; + background-position: -182px -1319px; width: 90px; height: 90px; } .head_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1400px -910px; + background-position: -91px -1319px; width: 90px; height: 90px; } .head_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -388px -667px; + background-position: -775px 0px; width: 102px; height: 90px; } .head_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1400px -546px; + background-position: -1336px -910px; width: 90px; height: 90px; } .head_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -1400px -455px; + background-position: -1336px -546px; width: 90px; height: 90px; } .head_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -1400px -91px; + background-position: -1336px -455px; width: 90px; height: 90px; } .head_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1400px 0px; + background-position: -1336px -182px; width: 90px; height: 90px; } .head_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1001px -1228px; + background-position: -1336px -91px; width: 90px; height: 90px; } .shield_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -819px -1137px; + background-position: -728px -1137px; width: 90px; height: 90px; } .shield_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -455px -1137px; + background-position: -637px -1137px; width: 90px; height: 90px; } .shield_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -364px -1137px; + background-position: -273px -1137px; width: 90px; height: 90px; } .shield_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: 0px -1137px; + background-position: -182px -1137px; width: 90px; height: 90px; } .shield_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -182px -667px; + background-position: -627px -682px; width: 102px; height: 90px; } .shield_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1218px -637px; + background-position: -1154px -910px; width: 90px; height: 90px; } .shield_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -1218px -546px; + background-position: -1154px -546px; width: 90px; height: 90px; } .shield_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1218px -273px; + background-position: -1154px -455px; width: 90px; height: 90px; } .shield_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1218px -182px; + background-position: -1154px -91px; width: 90px; height: 90px; } .shop_armor_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1077px -910px; + background-position: -1166px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -942px -819px; + background-position: -1207px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -983px -819px; + background-position: -1248px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -578px -530px; + background-position: -1289px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -619px -530px; + background-position: -1330px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -363px -212px; + background-position: -1371px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -404px -212px; + background-position: -1412px -1501px; width: 40px; height: 40px; } .shop_armor_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -309px -182px; + background-position: -1453px -1501px; width: 40px; height: 40px; } .shop_armor_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -309px -223px; + background-position: -1494px -1501px; width: 40px; height: 40px; } .shop_armor_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -500px -470px; + background-position: -1535px -1501px; width: 40px; height: 40px; } .shop_armor_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -500px -511px; + background-position: -182px -1542px; width: 40px; height: 40px; } .shop_armor_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -791px -667px; + background-position: -223px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -451px -1501px; + background-position: -756px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -492px -1501px; + background-position: -797px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -533px -1501px; + background-position: -838px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -574px -1501px; + background-position: -879px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -615px -1501px; + background-position: -920px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -656px -1501px; + background-position: -961px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -697px -1501px; + background-position: -1002px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -738px -1501px; + background-position: -1043px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -779px -1501px; + background-position: -1084px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -820px -1501px; + background-position: -1125px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -861px -1501px; + background-position: -1166px -1542px; width: 40px; height: 40px; } .shop_headAccessory_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -902px -1501px; + background-position: -1207px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -791px -708px; + background-position: -264px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: 0px -1501px; + background-position: -305px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -41px -1501px; + background-position: -346px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -82px -1501px; + background-position: -387px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -123px -1501px; + background-position: -428px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -164px -1501px; + background-position: -469px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -205px -1501px; + background-position: -510px -1542px; width: 40px; height: 40px; } .shop_head_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -246px -1501px; + background-position: -551px -1542px; width: 40px; height: 40px; } .shop_head_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -287px -1501px; + background-position: -592px -1542px; width: 40px; height: 40px; } .shop_head_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -328px -1501px; + background-position: -633px -1542px; width: 40px; height: 40px; } .shop_head_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -369px -1501px; + background-position: -674px -1542px; width: 40px; height: 40px; } .shop_head_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -410px -1501px; + background-position: -715px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -943px -1501px; + background-position: -1248px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -984px -1501px; + background-position: -1289px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1025px -1501px; + background-position: -1330px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -1066px -1501px; + background-position: -1371px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1107px -1501px; + background-position: -1412px -1542px; width: 40px; height: 40px; } .shop_shield_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1148px -1501px; + background-position: -1453px -1542px; width: 40px; height: 40px; } .shop_shield_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -1189px -1501px; + background-position: -1494px -1542px; width: 40px; height: 40px; } .shop_shield_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1230px -1501px; + background-position: -1535px -1542px; width: 40px; height: 40px; } .shop_shield_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1271px -1501px; + background-position: -1609px 0px; width: 40px; height: 40px; } .shop_weapon_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1312px -1501px; + background-position: -1609px -41px; width: 40px; height: 40px; } .shop_weapon_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1353px -1501px; + background-position: -1609px -82px; width: 40px; height: 40px; } .shop_weapon_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1394px -1501px; + background-position: -1650px -1394px; width: 40px; height: 40px; } .shop_weapon_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1435px -1501px; + background-position: -1609px -164px; width: 40px; height: 40px; } .shop_weapon_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -1476px -1501px; + background-position: -1609px -205px; width: 40px; height: 40px; } .shop_weapon_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1517px -1501px; + background-position: -1609px -246px; width: 40px; height: 40px; } .shop_weapon_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1582px -589px; + background-position: -1609px -287px; width: 40px; height: 40px; } .shop_weapon_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1599px -1501px; + background-position: -1609px -328px; width: 40px; height: 40px; } .shop_weapon_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: 0px -1542px; + background-position: -1609px -369px; width: 40px; height: 40px; } .shop_weapon_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -41px -1542px; + background-position: -1609px -410px; width: 40px; height: 40px; } .shop_weapon_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -82px -1542px; + background-position: -1609px -451px; width: 40px; height: 40px; } .shop_weapon_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -123px -1542px; + background-position: -1609px -492px; width: 40px; height: 40px; } .slim_armor_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1001px -1046px; + background-position: -1154px 0px; width: 90px; height: 90px; } .slim_armor_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -910px -1046px; + background-position: -1001px -1046px; width: 90px; height: 90px; } @@ -2304,169 +2334,169 @@ } .slim_armor_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -364px -1046px; + background-position: -728px -955px; width: 90px; height: 90px; } .slim_armor_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1127px -910px; + background-position: -637px -955px; width: 90px; height: 90px; } .slim_armor_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1127px -819px; + background-position: -546px -955px; width: 90px; height: 90px; } .slim_armor_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -182px -955px; + background-position: -455px -955px; width: 90px; height: 90px; } .slim_armor_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -91px -955px; + background-position: -364px -955px; width: 90px; height: 90px; } .slim_armor_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: 0px -955px; + background-position: -273px -955px; width: 90px; height: 90px; } .slim_armor_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -1036px -819px; + background-position: -182px -955px; width: 90px; height: 90px; } .slim_armor_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1036px -728px; + background-position: -91px -955px; width: 90px; height: 90px; } .weapon_special_spring2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1036px -637px; + background-position: 0px -955px; width: 90px; height: 90px; } .weapon_special_spring2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1036px -546px; + background-position: -972px -819px; width: 90px; height: 90px; } .weapon_special_spring2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1036px -455px; + background-position: -972px -728px; width: 90px; height: 90px; } .weapon_special_spring2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1036px -364px; + background-position: -972px -637px; width: 90px; height: 90px; } .weapon_special_spring2016Healer { background-image: url(spritesmith-main-4.png); - background-position: -1036px -273px; + background-position: -972px -546px; width: 90px; height: 90px; } .weapon_special_spring2016Mage { background-image: url(spritesmith-main-4.png); - background-position: -1036px -182px; + background-position: -972px -455px; width: 90px; height: 90px; } .weapon_special_spring2016Rogue { background-image: url(spritesmith-main-4.png); - background-position: -491px -667px; + background-position: -318px -682px; width: 102px; height: 90px; } .weapon_special_spring2016Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1036px 0px; + background-position: -972px -273px; width: 90px; height: 90px; } .weapon_special_springHealer { background-image: url(spritesmith-main-4.png); - background-position: -910px -864px; + background-position: -972px -182px; width: 90px; height: 90px; } .weapon_special_springMage { background-image: url(spritesmith-main-4.png); - background-position: -819px -864px; + background-position: -972px -91px; width: 90px; height: 90px; } .weapon_special_springRogue { background-image: url(spritesmith-main-4.png); - background-position: -728px -864px; + background-position: -972px 0px; width: 90px; height: 90px; } .weapon_special_springWarrior { background-image: url(spritesmith-main-4.png); - background-position: -637px -864px; + background-position: -819px -864px; width: 90px; height: 90px; } .body_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -546px -864px; + background-position: -728px -864px; width: 90px; height: 90px; } .body_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -455px -864px; + background-position: -637px -864px; width: 90px; height: 90px; } .body_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -103px -182px; + background-position: -363px -106px; width: 102px; height: 105px; } .body_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -669px -530px; + background-position: -182px -576px; width: 90px; height: 105px; } .body_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -669px -424px; + background-position: 0px -470px; width: 90px; height: 105px; } .body_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -669px -318px; + background-position: 0px -576px; width: 90px; height: 105px; } .broad_armor_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: 0px -864px; + background-position: -182px -864px; width: 90px; height: 90px; } .broad_armor_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -851px -655px; + background-position: -91px -864px; width: 90px; height: 90px; } @@ -2478,121 +2508,121 @@ } .broad_armor_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -669px -106px; + background-position: -578px -364px; width: 90px; height: 105px; } .broad_armor_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -669px 0px; + background-position: -91px -470px; width: 90px; height: 105px; } .broad_armor_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -578px -424px; + background-position: -684px -212px; width: 90px; height: 105px; } .broad_armor_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -224px -288px; + background-position: -112px -288px; width: 111px; height: 90px; } .broad_armor_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -466px -273px; + background-position: -224px -288px; width: 111px; height: 90px; } .eyewear_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -224px -379px; + background-position: -112px -379px; width: 111px; height: 90px; } .eyewear_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -112px -379px; + background-position: 0px -379px; width: 111px; height: 90px; } .head_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -728px -1319px; + background-position: -1092px -1319px; width: 90px; height: 90px; } .head_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -364px -1319px; + background-position: -819px -1319px; width: 90px; height: 90px; } .head_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -363px -106px; + background-position: -103px -182px; width: 102px; height: 105px; } .head_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: 0px -470px; + background-position: -364px -470px; width: 90px; height: 105px; } .head_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -91px -470px; + background-position: -273px -470px; width: 90px; height: 105px; } .head_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -578px -106px; + background-position: -182px -470px; width: 90px; height: 105px; } .head_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -466px 0px; + background-position: -466px -182px; width: 111px; height: 90px; } .head_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -336px -288px; + background-position: -466px -91px; width: 111px; height: 90px; } .Healer_Summer { background-image: url(spritesmith-main-4.png); - background-position: -578px -318px; + background-position: -91px -576px; width: 90px; height: 105px; } .Mage_Summer { background-image: url(spritesmith-main-4.png); - background-position: -760px 0px; + background-position: -578px -470px; width: 90px; height: 105px; } .SummerRogue14 { background-image: url(spritesmith-main-4.png); - background-position: 0px -288px; + background-position: -466px 0px; width: 111px; height: 90px; } .SummerWarrior14 { background-image: url(spritesmith-main-4.png); - background-position: 0px -379px; + background-position: -336px -288px; width: 111px; height: 90px; } .shield_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1218px -1001px; + background-position: -1427px -546px; width: 90px; height: 90px; } @@ -2604,265 +2634,265 @@ } .shield_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -760px -212px; + background-position: -455px -470px; width: 90px; height: 105px; } .shield_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -760px -530px; + background-position: -684px 0px; width: 90px; height: 105px; } .shield_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -466px -182px; + background-position: -466px -273px; width: 111px; height: 90px; } .shield_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -466px -91px; + background-position: 0px -288px; width: 111px; height: 90px; } .shop_armor_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -902px -1583px; + background-position: -1353px -1592px; width: 40px; height: 40px; } .shop_armor_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -943px -1583px; + background-position: -1394px -1592px; width: 40px; height: 40px; } .shop_armor_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -984px -1583px; + background-position: -1435px -1592px; width: 40px; height: 40px; } .shop_armor_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1025px -1583px; + background-position: -1476px -1592px; width: 40px; height: 40px; } .shop_armor_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -1066px -1583px; + background-position: -1517px -1592px; width: 40px; height: 40px; } .shop_armor_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -1107px -1583px; + background-position: -1558px -1592px; width: 40px; height: 40px; } .shop_armor_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -1148px -1583px; + background-position: -1599px -1592px; width: 40px; height: 40px; } .shop_armor_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1189px -1583px; + background-position: -1650px 0px; width: 40px; height: 40px; } .shop_body_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1230px -1583px; + background-position: -1650px -41px; width: 40px; height: 40px; } .shop_body_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1271px -1583px; + background-position: -1650px -82px; width: 40px; height: 40px; } .shop_body_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -1312px -1583px; + background-position: -1650px -123px; width: 40px; height: 40px; } .shop_body_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -1353px -1583px; + background-position: -1650px -164px; width: 40px; height: 40px; } .shop_body_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -1394px -1583px; + background-position: -1650px -205px; width: 40px; height: 40px; } .shop_body_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -1435px -1583px; + background-position: -1650px -246px; width: 40px; height: 40px; } .shop_eyewear_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -1476px -1583px; + background-position: -1650px -287px; width: 40px; height: 40px; } .shop_eyewear_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -1517px -1583px; + background-position: -1650px -328px; width: 40px; height: 40px; } .shop_head_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1558px -1583px; + background-position: -1650px -369px; width: 40px; height: 40px; } .shop_head_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -1599px -1583px; + background-position: -1650px -410px; width: 40px; height: 40px; } .shop_head_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: 0px -1624px; + background-position: -1650px -451px; width: 40px; height: 40px; } .shop_head_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -41px -1624px; + background-position: -1650px -492px; width: 40px; height: 40px; } .shop_head_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -82px -1624px; + background-position: -1650px -533px; width: 40px; height: 40px; } .shop_head_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -123px -1624px; + background-position: -1650px -574px; width: 40px; height: 40px; } .shop_head_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -164px -1624px; + background-position: -1650px -615px; width: 40px; height: 40px; } .shop_head_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -205px -1624px; + background-position: -1650px -656px; width: 40px; height: 40px; } .shop_shield_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -246px -1624px; + background-position: -1650px -697px; width: 40px; height: 40px; } .shop_shield_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -287px -1624px; + background-position: -1650px -738px; width: 40px; height: 40px; } .shop_shield_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -328px -1624px; + background-position: -1650px -779px; width: 40px; height: 40px; } .shop_shield_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -369px -1624px; + background-position: -1650px -820px; width: 40px; height: 40px; } .shop_shield_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -410px -1624px; + background-position: -1650px -861px; width: 40px; height: 40px; } .shop_shield_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -451px -1624px; + background-position: -1650px -902px; width: 40px; height: 40px; } .shop_weapon_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -492px -1624px; + background-position: -1650px -943px; width: 40px; height: 40px; } .shop_weapon_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -533px -1624px; + background-position: -1650px -984px; width: 40px; height: 40px; } .shop_weapon_special_summer2015Rogue { background-image: url(spritesmith-main-4.png); - background-position: -574px -1624px; + background-position: -1650px -1025px; width: 40px; height: 40px; } .shop_weapon_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -615px -1624px; + background-position: -1650px -1066px; width: 40px; height: 40px; } .shop_weapon_special_summerHealer { background-image: url(spritesmith-main-4.png); - background-position: -656px -1624px; + background-position: -1650px -1107px; width: 40px; height: 40px; } .shop_weapon_special_summerMage { background-image: url(spritesmith-main-4.png); - background-position: -697px -1624px; + background-position: -1650px -1148px; width: 40px; height: 40px; } .shop_weapon_special_summerRogue { background-image: url(spritesmith-main-4.png); - background-position: -738px -1624px; + background-position: -1650px -1189px; width: 40px; height: 40px; } .shop_weapon_special_summerWarrior { background-image: url(spritesmith-main-4.png); - background-position: -779px -1624px; + background-position: -1650px -1230px; width: 40px; height: 40px; } .slim_armor_special_summer2015Healer { background-image: url(spritesmith-main-4.png); - background-position: -1218px -910px; + background-position: -1427px -273px; width: 90px; height: 90px; } .slim_armor_special_summer2015Mage { background-image: url(spritesmith-main-4.png); - background-position: -273px -1319px; + background-position: 0px -1228px; width: 90px; height: 90px; } @@ -2874,25 +2904,7 @@ } .slim_armor_special_summer2015Warrior { background-image: url(spritesmith-main-4.png); - background-position: -578px -212px; + background-position: -273px -576px; width: 90px; height: 105px; } -.slim_armor_special_summerHealer { - background-image: url(spritesmith-main-4.png); - background-position: -578px 0px; - width: 90px; - height: 105px; -} -.slim_armor_special_summerMage { - background-image: url(spritesmith-main-4.png); - background-position: -669px -212px; - width: 90px; - height: 105px; -} -.slim_armor_special_summerRogue { - background-image: url(spritesmith-main-4.png); - background-position: -112px -288px; - width: 111px; - height: 90px; -} diff --git a/common/dist/sprites/spritesmith-main-4.png b/common/dist/sprites/spritesmith-main-4.png index 7fe138e983df1c6adef23a7b0b3fdc6bca6511c8..a9cc8fdf09cfc9ff8e9457a8cd254d0b8084c61e 100644 GIT binary patch literal 139405 zcma&O3p~`>|3AK4Hfy(%OE(pz+`3R1GQ?Ya^UVq>{Qt}ME8s64lvuHEmTk2%!XL7_(($2tRjbqSfI(g+=la02 zqQ@?8T~uEGPAC?ayz24F?$79b-^&`C>6dS@zBHPm>@Ag3*!q3!eD(&$U=9 zw@E0}+}qImIAoV=T>XuEU2gKj-xtw$eM>)NW^P$bYcAmR-0slkX?tf6CuTP~a3B9X z$8OE)o#`12A|~aTj$CudEB^h>S!-9Nkd$k?Gu(KUouv8@|iR~G&;mn9Jh-518rQqo|x%(Ui`wgAZ48+T&&u;w(!|!9Lq{Iehl#;InH8bE4=K{r3Fp z_X9+iUN+k#czO187XK&o(%C(3bsaNXdxXQ}2_mj5)vkBklW7&fQ&i$!1;P94PPX4F zg!hjJ{O%mgNUt$Fb8l6vY0^(PF#@~}YsX$cgno@pfmdPahm!oO_lL>PA4TuPmR~9S zUtShQa)Foi+y8m_UWDM?{tsJ&wqk3C?Tz@EnN*4H9+k2=BK_RA;@>j-gSm~G!WXO& zK8h!e#}@Lx$#B33cl`8F|EAR&>n-vcD{`&W?;OKqy0Xw*@18`$W=!J$oXD!KGXAnf zv15-ciTw~JIoN&2Ov-2&W4NdBWR;_e1vyP3N-t0*dnAN67}@F5^l0uwn(vc@_Jx1G zWbqUJCHfTOx-TucJRWXOBAl_2Ww4ZfMB9pCVQ*RLO1csM5A1|M%4$tGu_h~Xays|m zg%7HV)a;Q`+UxC9I5y@r;s35PH8VQSGSya}s(u?CZHc`oOO=W-3H`zz#uJ5}jl_4) zcRjT!)>See?}(Xc#!j!{r2F45OvT{K$G>~Nm1_K^%-b#RbWC1b(4;R&d1UxO-|WhP zLZ3ZuYY2Rc?~n6y@fK5*D>GezuXL?CbLL64+h9Sfo?r{wMoqLG7NV0uH78{%q(M`g zgmm!j&ZB;2{5N%aU*1^;kmww)BhQ~^eodkYk8?2w?Rwa;hOoGKvU4!nJbc3bylrlD zm-{`!MHhPB+NO&eh3G|G{;;q1!;^QW+~avUk(P;PSp62Gh33lcpJ(ro%W64gmzrbk zu7FD3wJ4pucT`)}yxVN0_ze7=TIj`1KfcS?+e~2%0pGGiW@{k-r30N^eY$J#C2dli z;=ebWKQxtJbN}oXzo9*kM@^6A7f1G~HguG~y)csI>-|c9@)ptG(f)C+b*M6#VPD+S zlPONTeIony=jiSLI7C=D93nCbJVjHCumbi58iRg_5nf)fksv%+M+nioV@*!CTy57h z__QyoC)Aafaxt4%9tzTW_xEx&!NbEs^5_pBK8NzL+qNRTpU&W7~XO@ zofFf@|4mlGXLaYFsn%jMWD@63J>M&`qY{o<_2(CD(uK3UGBvg?qpMEj-j#+fUUtuw z{$?2&FMMyGpU1jTu^CSIrRL>ihUS@h5#G$sqC|hvo+9_oxUE!byh)yBkcy|@sJgjw z5}#Ue%tDi+=UZ`q-*Y-gN9(cz{i9D)QI#=ns`699r;6V532IG`-tT+tyVm6e26f zZ`)pPj&*i@GDo!6DW-3o>pZG+K^9U_um2MI^u2XAW!99|VX`U1a=k&z8Aneu+4_t&M@@OL8K!#_Ny$Tj+GPp4;}972 zHlX$;o~W7h+q($~@19+i2NdE#+}|}xe)rOJR7YE^cr2(@Lc;uqXY+ZVUyDj;a3p7Z zs$~KXibZYg$-!h_N9wou@^a&M=COv$gPn(74<@`Xp|+hGr}=Z&1$n%sq*i0kd>o#*?zUm3c&X$Jd2Fp-h#Gb8@bY(_ z+9!^mIk?{JuJGqA?^4-L=9@i77>1kWnzMuk+b?P)P)te-6X@#oWJY3w>qG|u7Wt_4 zJB zk}#u6INLONhx^+O`p1n1%CMSQhfk^v!Or0^3gIbvM@d61R_L}~CP0(15a zlRgaMC{U)3c0FPh*a<;V? zC%1d>(3nErNy=7gN{y@6OO2?pRcToZ6bZbBQf#MQ@exbQ+6&n-kum2x8y_Uvbrin8 zg(utXj3$0caHkMKz*Z5n-wb~|ns< zI;wrnKd(!Z2(rolFqX5ibn=;LPfXZ&<|u#f{>teaGL`$HDa477dL`SbQgd9!x-dpE zLx?W&@{B;b!l1S&cpO^t3d;mq3$EkqLwMYx>!_X2y)SUfaE!?L@4mrM3^u-M+EsN` zy!v>46$7ulhtxxGlQ36)HiDOEJ>gE&IBSyETci?nV4o2uRzf#=irX${+-{T2b09D? z5>7P~b_f4f`#gXgifX-bvWqZ31a;9DLUSjHZfyD6Ij-mSm+bKV=1 zu4FA@tf9HG0q0Sp?wvk%dx)#Cz^6h6V`{9a^WMPmSnT&1hW;b!>=+++2T_WN`2xPu z^EpexlihyW_m+2Db69Od#h79%XBJMReQ!E^v4vm^j1;qnA$;t%-OQPnBiSrk~9lCBeR}=d4 z4pJvs9#RpAcF%;}HBw_Y9pq2@=#$+J8vOjg?oQa4TK$ts&$^5vn(bij+dS)Nuq93` z#ZYd4Hap={0S68qcLZ$6$6eC6blwZjiIfzIrTV^-O_D4Eyg-z_UEVH57?@J8$_xC| zQoQ4cm|5EQ8M7OYe6BQxv%ph+{H7_r#zPrJt;H<`gC~YGEMFeBGa+!ubY{dVc+Bj~ z;#n?+y_sK7YL8rnz#R69oHtXpzsRA$y3LfZ|iTzrF`MPKGURfynPF%kqhUhPacEp-(s%VhB9a`TsDhUc0dx058KJJb_-N~07C zyK0(IARg~f9B-+trM-_(ipP}-Q`DTgiKUn3^(qfO{Mc~~-*psiwV6|6nrC7+@lZw{ zDQmlI*r*O5; zsc`#5{f?%*E{^EbL z-?Ozv^cZf+;Zs8COw!C)PMTq+=W{uve|HTrv-=+=`tQHtntbfZ=~WdIegVnz5mnrY zt2w%?OsQScm(R)2MU;z>S}Y0%GH}d^B?$`zi9~~$J`$^P5$QzoqboCRs}0%GDFbGG z@9!Mhc=>(g4RH-4>%-k=G^1)wBt@g6F1(GY8&R!y8+>>`u372j{KQKw70)@BOgWUP zihH|BL`k4*OMer)`M&*j)02gCmXPl+2d~s+^9zb4CzPqAW=^d&-&xT;-UMZS+^leh zp}1DO?q~*CSj?<2-IA#IY)0z!`P9>KgWjXnBUPoJ4oY~BIYI=GUU@Wia$2h=5f1;B zkx{1jfDZS{!a#fP%^835H4%rk&y5Bgh1877Qg~p`%Bd5fn}^lj98-(%u@^}<#vKpl zw8s12YgF)9I6^xR&RDxkrqkAG!iF|nu<)lF&k9`fEJ>hZOCkPW3J127jzap97TW!~ z80NrJFiQ<^rSYgVg@QU-kdrRjqw8JgdXY@yy*lj4!D*YrVP$KzY_bf(7fv4=}Xs{Cs-^p$+vyNEafWQQXxY2Oohy$e0+`=UFguUT}x z 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 literal 137649 zcma%jc|6r=`~S?OvLpv5N|I%=JPFB3BIa;xA�#Lh9JbNcL?yIy%S}Q+7G2G>IZg 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

D}Zf(o~PcS*w{q;kI0>Our9!Van zFWZ=Y@SC0Svqa>{%R2G})A-EyQReUhoVEpf=*!D*ab`J#R(sGhBdP7#XDb8DG$#_D zk&WW_%nQEKb`X_1c$DqZCwN%m3Ruf^LO39_yO~R@l=KFzLIi*8)_A&GQGg1hJkV9= z&?g=?UN0~zJ{nGGQ|>0In}N|sF!^Sc1IF~q=!A9z=N7bQ+MF|i0dhU<&3n$oQU>YR zEcy`)cjh?E+}-V(OZ=n#khyaZ~(>h_^H+*Kmw3hr=(?*t4UrZ^tE`0aYVQ5Kee# z0d!Z%!f4Js7!QENI)a^pkXAFz(FmOnGM0>Q(;X0G?Wl6 zUq?QLpi_ zNkB!euV@0gXjgSGWWTh%b*-4O6h&R4znp}Je#Y=(}wy5(Hx3W+UOa$ZJCoD-(q~?|JO|XF9ZHY8^5e7 zJBDaZ2SfMLwSqfw<(~U*gkT|5%EZ(Wo#PJlN5`mU>J_Sk`8`EX1*70@Mo4g4GRpoS zHe;k_pe1YDb@zdJ?rEWw%<)vW{&-;tzQ8rEUsV#iYRh!fo$+b~$+w6v(}SB``#YPh z(`p= zma<@{f|?!*p;P?Ppj8xZX%|K~&|^ro#}Ekbh}h;0jZ7t94s3^ESiMB{IA%GhD&bzw zfQ0Wh>L$RKz-WNZjjdkv#y`O0f7lR=x__?!UjA_tv>yZwI&y<~9=tK-R?q#RxYpha zrX!YNl!d!vh%8R#W=-U6FoOlEy#Ja4`fb_oh2e5ZZR*CZSp| zRGlRvzMbtQMo$F9^o-y*pSl7hB$#_y+Vb8`3>hH7dH)VP!AafD^`8yp33oZIYh#Qi zYP1wAx*lB1#vx;dniH$u5|IISIiG#Yj18q3mFgosmVj&ajuW}pvt4;>XlZc|whgp+ zw6O{zSlF2dCy=A|dM9rto6qc%xLdl!GV?7<@-fBh#ZX(U@bhZ$>E=1b(IUDTm8Eapnuyf%4E(PHftA%fKUQapovz7njrugOD;G?R8_Yq7s(Ku4MBJj=7AHY-v z!13dtM6P--h=vc2hVjJ=y8#(%pX7e3l4C~Q_TbB`tp@(-bLZ@$<64Kqt*V(Z6SS)hM&Q*0#Z#=hdX;%021(d3s` zwq=iMbJ@LT-4i8_mKdgJ4d~%Jibu?z1rrdus37Ke zFk~d0<|gd0sJzf_06Qf4Gg`MBfK?O57MlT}nvVy{bOI{?UQx+B7{=2S5OjNzym~?Es_2k5&EM_`PgCKm;V26tiBG84%EZ;6j00!0dGV zL!tZ+8CeT~=R^M^6fQaxU)XwWskX!?mwle$usOeXl)LftN+N5_RtA|-IVn}pUcEf4 z)VnhC>BqeFK5_0^MTLoHbHsfLlGB7;P;H^S!65Bz#+0|?EFlb9&%A&hsa0kNMJ1rZH(Eo)@n4y^fy`j$18?pyqt6bnELRK`KGU5 z7eN7i+=SH!av0fD`+HwpVyC60)(*cu-b5q?bzxI9?pB)59JFuTI-6KL+U>0%lYa(8 z$Muj%n-{G>jf_Ojn><0r%(FyfP&YUfCGK!*$>|YS~aPNFE+5~V^;;$am?}H zVPJcJ#JwH>jU7z={+cJge7zOxy1GFmzQZS7E+rDQfUKx23>1iTuxRd_t&>B)66h{~ zVLp8s=-zNv6+9`pDsgzRU) zi!fFV)!x`>%Pjkv(;07|ECc;3hdg$z13*6t_vJlgITer}!$a2(Srq7KZGF^nQYpz{ zZnTBE{Erm+H-r9}TfxM0!LFqojD2giczE%tVAx#Av9q94ed{9Z9-!;t=ta8c6dc@| zAl>)$qY^-Ko+7_|LVlVz8y!uqo#!9|clVc=%G~{yIwlo{0Sm+^a>DUf zxEc2#U;My_5O&1XW<=DEF1dpAz$D(w*4Jc(gtG|-<$QK(7co&I0)W`kX*)I2!5iCY7s>1)u;TQTpV@`thAG_LW2glh&lTt9I`R zW zpzq2814^Jtu;>$u8Wb=Cx}H|`2Z{Z6JD)kzKciLASYetRnd}kUTUk_p@%GH*lU@7ZbG_zX$t!&qOr2I%r=}6bq~{e0z)6YoQ}pg}QM%{!)TBr4R>^Fo zKOOU*Id#wCo4@95>#g~sA#yZ&Cugxv$K3#47Hn~m5)&i;Y*8-{g zXWxLK6_&FPk#v+Rubkl@RUGmZmz{S26{j6Cah%=Mri5JhHY;TIjt6J6@o5Ud-z>b! z5RZq=Ola;mgn4+C0<5Y5<}q)mgpZ$RK6Q1oA7$Wn4OfmfnZdsmZK?yZf#d8*c~E+Y zD7b*Bq1u#i`(c6=S1)A-ndtC6{u8tHle3K-^pT4 zF@@;-+g$P8hrgCTjQr2V{%wfU_7f#yEmPQ9>Cke^eu0GZJ#y%s=dFh{;?wt%L8nV_ zXMPph#C{Wwkaj`)_z?a5`0}io3m@s5Llf3{e*me{Hp`t4iZO8jAzQh?ih%!&|~}2{m0zQ`s}~N zY6oOR^G5ZO5S%0PPeO*XrJc)VmA?*%<3}z^l8F#QW1Ys$aFeYqU{oqll;6jt*~sjC zIp8~e2nFu+uqt92+#rHoyPVdmkD3eKYy_s?Ayic1oQY@nE(o1p4|ch;%>xPJgbrRE!NC}1V9{!g=0TJ<^j<00mGJ8j%%_x(>{ z{lNqOuFDsn=I_oSt^{+&%fJq8Os5T;0QZ84XMF>*4|ad&b=X`uulNW=9x!3IgJP%) zEO_wIIh(69Ck~$EXi9@SjSvdr5Pd{IJJ^z+xQ^I>%bJP{e}& z&~dZqj}~C8l)#@*yIRXhFkrM}XF_^$3R)^GjJ{T1{z#!lcKF(KivVoga3faITLECa4NI@__){olmI7F&}&iSb} zshEOc(Q#lr1t^HFq^`D-$=i$C7SqW_8 zXuB<#R2(8G8`Y_QA1GF={$GT>cRbbo|37{;s7NX+du8MpCF8P5%1p@Na8MkCkd^Er z4U%IV$5uELhm#|Fh2+>#$j;8*Gi&x*TXKfbOAWkh1`oM+~QpvGCqD?y@m zqudaXkkx=5ZV>0ZMco6p$xOIbjT|Jw0n9~+GQpLe0(7c~gGxV8;CUT(b?(45>l+cR z1%<^4t(JUz{%Xyxu~=DPB|zWHK|$`SyVl5<5{vc0ZoAOom#5n1lqqq7selcpHfxRf z+kaTxuA{JbzJN{h-v$`G%L55>B*mUI=ffXkF)gb|U&dty+;XQCC1_a0O?J+45t}=~ zR}Zfz2DmkX0wCyAk@i`!y$U|_0hurGyS3kH)o5)kX_$&-n~&|B8o$$66?@qnP!(cM z?X@O;+LV^G{xhrfKW9dEz?4NgN$(%j`|cs#*}UaaK?Xo90OGtmIvObMM0KgQ z5A+dE2@2^0*lO}5Hb}1Ac2y{xTdWS$1j>oF&p$lehyx9vtC6CkuF?YsbTYEZ?t1P@WJ9Mw=N*at#i0ocb;U0$q?D5x&eM>WU`e~GML zafl`zY%FZr|c%GUBq;r`n+l>Y7y)YSLofrmR%>lSoyTrG6 z);#Q7*vdA3EZ?RSziNH$KQz^UiUAKT{w&?zj^2wo3iHedjZd%%3vhh@?icpB)-j!P zrVex=2i^whE;)m8@8bzGxw&mc9rUIkYh7A@?OVMoU}kLJ`EU)iWE2^mAqRQI#WM0g ziaGyB>i;1KI#3YKY@nF5C}cLE$5>YMF<25%t+k3KuKoC|m731}w*kl(w%CbsW`=}a zl9`&y!D$HFsB&)YCp#{zoYKI!&UcB@?tBS460V{@T#@|@kImXq=B*JHX; zR)%D!EPJUC`7I4?&r{jm4`qY0$2I1uc!1&n6>ZB0^ho_x0L%K!A7TXUzzR*RcewB{ z*TcuI7IC~3(Jh)z67F@0@x$bH+yo?J2_@Y*+X;sY&ShrF-As7ltrP3Onef zX}&O)E&NV#lU~{;)wv7k)1pSk+|?W;Uyt=2*cU_@pHEIO;~pvNzZKAbzC-08M+0sF zd8_#xmB9!jLC4UzCZHQTlo&uxppgKKHYlvuWWqm!d^2c-2u*huzLoDQj|_Q>x&S(Z z^%@E4eVdZZ0COHx8QJji|62|J`He_LWLUBKB5qC)D-Y;DdyQWAgA4Ecm8J?t9gg2+C-~@0ieGkU;X_)w0!eX$MCW!YE*l1)H9cRD~py8OB` zkwF1Y%J!nSR~Af~kHKH~aLC!W9tQoFzVP%M4TZ#M>LQoHrN{S_vb^``XR}<+;DPx9 z*hvmZL)h;?;(u#n5R`H7)P}>%qakT}_tg%C+}{$56<-@Xim*J37#;Sjq|V2jE^LI4 zIY_ZwHxnzDeT`WTtu?nPZOhwT%Fzv^pgT_!dlmp%8K+!6?i^E9Xl545GTnOj!FPhM zt+>hwpg9IqrzoFkFdJy0xETNL0gVXk_Yc8F0|e1aCj*R%U7M&t`_y%jzm*$H)jLZC zsjlC7T^B1vj;OhQ1h6w6+wB~{D+|q1fi$@E+Rh7^kXz6f8}ami_@5v{^zV$gI>%J6-R9A8*PnTL z+CiC*WPU&0{viM(_w<8J5(PG61=(Z7t|S ziV|Ug*c-`dOuCOJrA`d@hjoY~aZRicJ?b8wAS7SC0_9cI0!3C(GrBu}@dEm0a8kkY z5B+LRq4Q>s@PNJc5-IIoI6|o<{CAWH($eScT=4l0c|cVhL;iR5Gip%py+e!wGCp0X zwzG@Oqs!@DeaKa~JK;y>F26dK8r8WXhg)BW2s-i)?LtTF=6#a)Enl#gO%66YzGv0{ zpw_@pk7Jo~wsMY8kDvXBIFNCFTtt51nj zk?lyg735gBFWe&1v11|B_m2AsLJ&1yZtb;HoN36YKR+<8c^lDyrL017ujM7ssFxWJ z@U|qXqkjxKiLC1Pek(%n5{lQ}`H?XP1^Hjn15Fyg|8?zY?=u=Zs>JysxBdYU6m!XH zclw*uGh@M(IZrEkl-ok>rrUT<;$Mz6ojWhY)UV`Rl|2+(@Q~G`>P5KxZp$%+^L_4i z5S#Arergrj@thOlaIsPG69leKv8ubJq;8T(8;%I!Z*6v<_+wh287%}@+#WPm09ru6 z4s*NXQUoX{eZ+V_1Er&I9f@Ykda(~qmDS$LLJp09pSekT`P9zhx^d;Q_*8&jNF1A3 zN!nV+XWBp3M!HL%j9fyR$3fi(tbc*>(B7r|9T*i}(qKl2J9fNT`|dzpamo5;q(@~# z(ktrXRgVil+oss{jQZw(yr|jTyi-GWuMaJ>09}~vsn0}kBoQ}PN0f%^RAzyB?1F-G^ z{dCKgyaQRGrtgmB@>#SpR-5x%T%f{G&)N*D7?Rq1CsqlL;CnN%R;z&z5^c+i;=4VCW$+iFk(3_EhI{BU=z(6r-pd~KQ zAmv&}c)rrfF02n66sEb~DG8NLY_F^dH_Cgn7hc+Aznf68l60_&paVX?{H_PRZ#bT>*3$^_*j?)=lM@b8zk}8} z_fd_<6g6&Ims?u?ICT*;QPNYvJd-r9Mc*P}@`0B(G%h4_S`al3m}d+@IiPU924ZJ1 zrfUm2rkYWKI!u-Do56Fel2%R}nQc-tUixM4zba>m6c+il3T>NFrDeluyhXQ5Uay2u zlaHJBjQ0#>Fw|-Z?A<=^<63+D5V@zoUjO0_CmqG_O#s)jI}5hiZ!EJkojE5`JX_A- zuhw92Y&WKeqi|z2Js@8$G1^b5c4xDoFx%0@$}+G7WRULA>k|80idgv292&#AU)NS6 z99W!`dY9!v&%k#edz@nX=>IO45MP^-XBU5O;ca_cTAOLaG&Q9HKMWgLUrb*6mM0P0Z)`e~bF+W4%C64oR0gM%pGu=Qn`$!3li-KL zw)|R*ON%Cm?%Vg*-c19&Ca!*PCtJ>1x6Z8c)mb1ajE;Z z#=@2D57XnXCp_HAo+WkL6P<({dUm(sHudeDOa^RU>6_<+hxj$ofCO2O46H08s!9(b<#?dcabkz@fOT-fQ;3upkEBFm2KjZ6 zD949+Wnhp5ZQb)Vmr0%AIc_A%oB-Nk)DoB_>^jHWf*m|uU22b+p5hfWVbnCuk&ey- zs$#jcMk%7ho!n1$xTC2F@Q3yc6_@9-Y~~j(0XSROKDu|G(flA|b}%78c#`Bo{&kuI z{F0>AXn%8AT1&@`yB+0gX{+v3CqHk0;9B=2wp5Ow?ELQx?3@-Wo1gs=?Xq)L%x(bk z_gc4nrH7>axp+Ulirr2R$yG`tbhgQGu-C1-V0&113{-9hd!;Ut(C#Lp^Ax}M(ap1R zcbUqnG41|$g+r%ih*jN6V%vocC$n!UL1{CRAJJ{Q(B^|93l51B+dC`3`3hbE(_Y+2 zeD^DxRdiOu{pBt>iG;p<_xVqJ7XE%O6K^eQpS&RmXY{@}(eooDEF$1*cQ3P;6wdmh zXBGBGiHb&m2c@4t`|}t||2KN$WbAZnb2du>J&Eh%{#LA>TKB*M|!saqY!p zLAlxs5s!Jkj+qB4Woeqa`1bP)f@F&&X02kUx=Xhlr2Uwgjk)r(-rj--s*a2h*tJVm zQmf-0^nF_n_X?1o^EUE_(NGwW4`2;hHDP%Y=W_4c-0!YgBM)gQlQUee#&h&b_|0tG zc3vnqUQ5c~K>wJ1JVyr9pUhpi>LaZVGIMFixOX>2a`T~o-E#^hsk>g>{Np{^+e5HH zMaYIW#kD(-leZVTj({LgYrmJ2^lz~3rH+xeDd8qRVCVH0T$)0hPAPX>62i^N484TR zZjR0CmCz@2kM+NvD@h{L-WhMWZ9bTBmeAe(ou?td%XH&n;Yyt!Icu>34ZLS&;`ZRs zD2BtXdQ7NNBu)bT!C`6acJEeAWeJTHc>rHC#Z%2#*50)$jqvYjH&$2-k>$R}tXL@bAcXnu{FDnl%ma zzl)RiNX*Dv?v7y4aFq3V@oi%|;5;-uPE5cLq-iLVL1FG=_zdu&#;FlHpzi}kp6Oi; z*}Kh<7-aiath`ERpVY;3;tp2x&DSjgqF6V`(5{T9W!H%7KWaqEg(v2?`N*k#{uceY zG;6Jt$-F_Pn+Tb#F-_@mO8PJ%JWB?0M$<(zgh}s8>kr`gkS#F?xoyC<@AN&r z-L{yOo8NI%&(GAj?PcCnfVKQNeX!wlN!66bErB6k=P5z*c(F!!V4GB9-*4Z^?p#`N zu>&(qLjEV9aJn2dyL)!3&_#OLcS>a2l$lubB&Ko%Hh|uOH*M~u|A>doQ5%`=-mdfW z$)m%$ZY!KSv&HV^ewbIqZN<+!yi`r|<$~5wCb7cFPwf_xr?n80tfX_AR@_#s5Pw|f z^i`+pDvFBm?^nMS4wd^C`aeq6fzi>rtEea)wYqKiOlo)XsW7!gQjAx6#@d`m;sCea zt%2-vz00@yLi@(E(di9GaE(zIh(VEZM_MSqre$**Ma+8T;Ud3DLB{2elu2xTP9KLW zFRhFukfIM%56kj!l6vv_m5wSh%C^r8_+%F9HApYBXy=onG0y}$(Q*bDu8e6{LCvx= z!8cBV>cNhDKF?w}62=(# zu26vHV@->=uL;x6&ORvb0xJ`|0kp~8s!7P~LQ0~i{;H$b3zmn|mZ3)nnLo+3 zQQw3ZT&N+j6yJ9wz-h*cZ{j)&dUIZka4FK0A$0bh7I=CGM*jX~dKdf25I1;cE-r#C zU@oEnjYCQ`K<`~m!_bMk6YEDxvd_+ytFP1+PjnfKG&z=Q9Paq+u7{FQhGx)1v(+eJ z1+ua*CN|gdX%E%V>;CgpBvGp8H1&CJQGKzv07c1FLxQj`U;1eQ5G=C>g_4Nu6D}m0 zx?UL_T1gv~ldw7OWmxwJ^HDk)Q(`hecLg2F^M2f58E+2qIKZ;?zN}flfvw1&{rgAS zS7BDE@>(J4dNN5l{xWho{?37=(nce^?9sG|;Hdg_5B7$yE(+>aTO~<^mo*LLyM`Y* zlCt1DX8PGtdCa!E&nqqMI(aq`WoG4z$Jj;ISMd}v7st67z2}0R7rtDVzt^2S{@&`@ z%H-K6aU>3IlKf}upKxh{yOfDTeWR8X z60Wr{pzyZ}88;6h-0t={J&N~OIafri=^Uv7K)sZJVvWrk%9g7I4b|>&p0Eq+xi_?) zsF#)4j&5`h#8|EXEVa18_wiTO14!OytC49!o^s6KIcCEQ&yj%#X|}6^U+z?dmT^ou z%88r+y)61Rxvou22virgAX( z6)m$Z(O*Ajh$M$+I3PC&(qp|-mS8XoB6B7bX*PEsu~G+hqLI$lF)jNmNx$C)ZR3Uy>3R z?)u}sw z(XK_gCG4^GjmRD|U%hg}9Cgz59&12lN^Tlf7L*fjWS7lcnvh#}D7s6KtAk(Ouqm2t zwjRZ-$)lwj1WNmxFbbygM2{rG>ztN`QEqm>imp3l!c@*iNOmU!Wv3Pj(xYUa9g79# z!<)lyaXh%X@O2cW>BObSf?tAifn$EVY~tzVugjA zl9M?BbJz0F$dI^q+)uuP%rCLz1pe&YCr_8b0(|Wu))DmX+g~pF@`dzXQ9c2o>#aspYWric&#H|YnX3mbGBZ;&M7uub-8|$sE$m!Z%{24K3oy1 zYhZY8WeV`;n=rkz0^KLwKYhHzg4lj9faHLJ!{)xj<~?btrm|L3Ur?@z`N9BZuQ1Uq z4Q^Jo^jO_g?&ZG36E!@@yO;OHnrA)buBWl-y}e%V(o$s6EGOBw8}2%NSvS8w2X$}B z&wiBJE0JsS!^DLnN%6E-p!#lkg0M1ZIV_L&g_fhwcEU-&dl}0WF8~1mF(ea|IRYDR-U}S!M4*%98mV+V)jADVx-LwVetkT$ZbT zsBqoHpkjK!I`(ZO<}kvN3SYeKWLUK9?TfHtRk!j;t-ebQ%^&SS8e zEY64J&QOBkT(%)k=fG|$OaGwlg!6Wv=))}pc zWE>EV7*|+_v-wP!bZrtS5uIht;R7?CjaEohz0)p}pvkcT5$(q4q)(aOWO6>Y5EVfR zD$cn*LAxQwr95{5?{)6Im& zsmjcr-iY3iGbbzBnLtM{&W~Iz^6JJSsKKx;FH$EDa5%^RU`vwKr&(Lv03(3;}JdzX>~W>wh0Y`^z2y9A1m0# z3x5Nfq-D1FsJr{4WCyO1Xnc!nCL0xSkwzzSK~` zQlRJCtV4|r>dHGcBe+`%jf9zr(WPZra|?xxMbsi}bh63!22@|4%+G+$SMWyzyD#Ul z{IV;nvZ*Cov%B36(=YO>&(z(^^Jzf%t~?7NewAm%biH99u+r>syFj4z+m59XM>8vJ z!heRW{4ib$ZwZ^kppGN;J=at%6U(v$OJhxi?Dt6U%-UP76d9e{@`?%S zpT&oY%dbJ;nc*8vd9&Q(J$kS1o9-3nRdfI*R56N-oA~rfN6zTdC5EY~(eaQ2ieBti zrW`I7rJD5ApeYKv&8xmPH5YY9Tl2;$E_S2tO3~aXZ1Lv^Ow_6VWXz>apRmcv4@kG0 zC6H97rK1Q|?%gsMyROwoY>S)9o2n5VXaeYoLvi0IzGal;{BGK1@urP;+wmfwA$W{I zxvqoP0@Z}eCQaMiAOm84eeR6idN8hzRWH2(ctNCCaqlwK(Wt~qfKPNBMXuP5q1vDd|oVH^dIz1X#x}Z3RxXmrdZ#JMa^|!Bcv#zOo^^< zTz^WbcPGfHKDg(M-zsmd!+XuWYfByQa^Cr_J(9sEkLDZ84p&RkSKE9BtxtN2Y7oao zHqfx^tFFZ*Ayb+k1Yke8l&#iqF-mS8DVpi(jLMskop0hu@!yIri5)7rE)e{s$2NUc zuF7z;XcFFX81y3LL$UL6}7Gb?Buxr-93L+P8mCIuSnk^XxP%$d3guNWUz!*n2ZRz8#dMPvHxTN^O5s}=Sy%kUbBebU> zg752GKd#`o@=P$qfZkT+Q1-b?0z#U^v`E0qoPz!4oEV8o2F3M^PMI9vf%zY^RG~8$ z=hIfL;Uez6ySiubyf@+?SphiXL~qpeN`Y|O&)2yl?j`vs<({=D#$fdJUpcxS_LW>x zd9}x-cppx`7(W?Sk>2L?_5=HN4$ImX0jb$aRNeIDv?~wTbvXG>#`2Gwxs)t3uDo4l zl~LMI*hoI9egx(yWF&y|$uo5D+fisM{`%lbrANQ>u1d{XEVSFbya!OtOM_qn|?5VnMfPidcYX2`Jf%6|97i zd1vDNYXyx2=rTL&yYr?hS~dWs0#6;5GBK^^G78_IHM;n{RJ`&sw1IHU6bWVBY#>T1 zAiWJbEON&hWEup*%L>o#`aIZ4cOHIXklGT>m#UTiSP>f5&uz11v%2-{&i-swr{x`C zjxo_2wBPDi*SIN66Us;Zo)tMv*-yP}=WZCy*(e&ilGV+jks*I7ceRiF4uJInA)FX9 z8gBEk7Noj7L~yKzxBrZ|NUy1XW1t&?d1@5j`QrpqMTeHb6Y%wr5Y4d7i+)dnrLP=# zyMV)bLtHxb*-2J8)3Z0_>Ik6FZxP4@GUDYrky*FM}8> z$Ww-Ly>Qpn#~O>+Wk_ssFlS^&7(Qr{7#oWXII+zIN-xy9b!X%;Ip+MUOjcEj>bve9?e0nxATPBVn_>G7ZXlGDWyrH609Bu>q*eprV?&HVzp$icv;c6E)K(kSN5;lY zQNh<0gzZaCg+J&cS7=T9`cbIIm2I8!|93fip(6B*GIUtkQx&H~U7wlcC&Zl?FTl52 zsN+yfv`|N7dHcxLhXx$_7tfw?V~>=zBW(qoQ4OE^ZF7 zSZk)Q=g=KNzzh*HDVs2p^(p(w#@L{s4^AFfU(jC${CO~eLrJJZ zvQV{8_5&)YuQK#> zSGqoXS4EShb1M}!-==vVaf{ix%0rSswcEcQ7NFnWUKe^)(;I&KaLSm78Rt|0&Ol$E zXTTD*e!MD=S>k-edhXNAtD%sDs}-3=af=Q_ilX~p0`POKXw`IIceUMG)5yo#nkUcL z5iaYUW#{3q-LZSm;@*$U$&#U3*NSUCQm|K8*-t8fpaVD*@Q9iug?^Fn!%ITn+V-z8`*gjiMGC8>v&3=LXxxqn%+_}*AOugxYg5OWpESXd5KHx$9jv!E=sp~IGm4I+xEZbK}UT-bp`)0%1 zo2s2(pwy6%E5cuvmshFGUrPFZ^&XdKAdE?M}AlVr6Fg=F@JuJ!e+-pvjZy>47ONnwRnT z*|a#KfGh{1!a_K# zEVPurF=_X~8!?$uw3r-rJmUvZARylPhZ7n_ax$KB*!kajd%m3Q=)Y|dFR->i2dn7x z8iKeYlEKs_x=j(xLIHY;3|@3a$mb*_y`U;HiTu)*#7eQ~V@lan$e3&QRpvv!!^8V) z2}TLt_H+5WEFAVXrbPT?CX6OLR{9o4B?sz~=jts@-116yK{$J_v(kA3lX8Wyu}LP$ z6XB2jG>|tTded`YBO2~DaI|Q#-#T1sqRM!LaCx)tPP%EITT6iFG>hyw^cbtV2cUcA zBd`aE-Bj}~qdG`}fESRtS&EJ9hFdSXJ$N{)p_W?yh~&ts=m-X#{$)j+p<1VAVhHb) zI>=ys^RH(!Wy~H<0YjOA=-Uv0v5y33S47d2P@{5j#X`hO{y6VdIRwwnR)-`r=M>31 z8OUj=r)tqey`hmvs~#;-pL%s973iqiL$~0$?JsB5M%%{K9SeTkRjEO(ygFfb>LDhy z;wG3SQ%FjTfK-_1LMbV~dDWvo)+H5BTae; z9-+v?UNRw3Tl~E?>25kvl*a*Cmp+L^_1QWDck;tYUDm6s_xhD@$a=lU!g6n9WM*PK zmGcBm@$ef(KgJOBA{Ws0_2O+4L9P1s#qf%qjLoEXY@)t+P2!fFhZigmesRQCs(AR( zr;@xIf9Llgffu!N{L4`6ui@+<)(GFuru^`B$7)s%68e z&RgtjIOdU|ozUAxg55o%jf-=Q8?Iyw2+TxM5}B{?t5 zQ)`#FrphigUsoeANet>2ZOx`*s_6=v9YG0V%3hjU76A2T>?C|D-fZ5S02~p)26maB zJ!^jV?*qV(EAK}EgW8+S2pG!oZ4H;$!bI50;FGK>!@|@8Km4X+m7sn3^>gUj!{GFr zo2E3C^e4HG4j(4rL6gBs%ypieJ}4caR@86na@D#ruBTMrnAl7Kxo7n>E%a?K^kX4o zcea~8lRVtv>@*LFCfXXg)n01DV+`kY6I647L*BLQ=7A(nUoIl10}B%!@e{SOj9nX@ zv4o5?8(7u4Eev(VE$FRbaL)f>v zaN4dM>={nsGbnGi%bEa0ofk@Cg6+C01e&ABu2>RC(yF_xzET?W9$=GeRzIygecIg_U)X+;`=XDzc zzoO1FWj*AZdDyUO|1G>pr;-9Sn@svlQ2^reW|D?wJGsYHW{v2=un4A>YOSGleEE;= zz{7~gr=AqOns7^dB=8mJ1X8dIG=pFem}3R5%ienH=@4!jrCu2|7kn@_!HF+q6W8x5 zQNdpL8W@BoaLg2+y0KieaghL3YI`Zk5hmo{XyZP+ve;j1#_JG08bOyC&VK3Pf)I5B z2>I(wa{&%^HW1*{*!Bql826`1FMw&%lqp5V7nieuSJ)m0>J>$gu$cu!SoEcmE#8;p z!X)~^D)@f#fb^6#$;Xi@zs>`zn&;P}K{JuBJtg0^65fNTw_8Fq`iS_pe^6<;z9yZ# zmRUJaS6ORzq1Kg&>w1D)8OxVh(XP2@4Ja@JonI>l6nvMZw%HXjdbC0%i);pmsjlBT zdcm)BaDG&KR_^}nq7Qjv)MfVV8^sC*>dkBhoaqYsoHIC2)o#~6SRKJ`LcnfrNZVEE zrya_q<$%;*@rZP)+7^+U`F4?*e;>z5`7Ie=W679^^kME@M2yg7vQ2Q%*v zmjnJoM;;1W%eUkD3AgqAkthkL5O#0sD#L+V&v@pQo$^Rqar;(Cm;)8{EGL@!AqR2x z-3wf4f~Pe!gFQ;DEAm?>xH3FmtgXgI32q4=q}Q(5crSIf-5;svpsSm{WNV33Cw`r$ zAP&EGChCdOIp3XoHWGc~cOXgH(fffHJNG^y6}d=H_?u&%L%HH#5y+V#c6ci9!nE>*b}&Ga*jyw}^PS2KB5uh&w}xh5|Pd$l#5*OooJBDyom zxVSt5bDppGtR9g&8x`QCz}vJbn{z>1{p%ab)WPRL9HVu+J9%IGoE3ZIoj>NF-HunO z=$?S*f&mtElsIy4zB|i1<=gmWc~2HOK!h(`I~Qyu_>%q6y=D@0xA8cpCHM zc4H+xcMF@xHBII_{6#sBYf4Wgf5^FAKN)Okk9%xn`L6Jz3S15+miqHASQluL+ePj4 z7cE=oa%5gp$iAo;MFlIvm1sw0WrnX+Y(PJ2#R}_LHKPMTZ2Zk5>AXC}&0@GuiMOfF zh06-NMz0j+$fdT2&ZrRt%yQIk{PYZ9@$D8D9jRm(?O4FoD|k{zq{x_w_%W7BA;hKZ zHP4p%k53~XI{XHz0}EPsWzjNw?Yjl_I|y-ITJJu}=Hs4z2?j=q9$dTMHQ>>ccqZ01 zWnuQh1y;j%8HNR>Rw7wQKI=v_sp5PYcTVUlgGdCr-;AVsqP_~VM1e7ZJ6&GUEIsz` zOSf9lqMIcY6A#<%+lnoA*~_`7*i$fdmE&p=V{+0*2f5yT0xYH8X=sgTli3)yYoQiz zB39t^2o**!zdKZq9n9~}xoEVGL_xlppNaj(fENXXI#8QDLCmFowN{Mzktn9{K$!kjA4zUoMw>X$QHxf2(icU{&4u@%g?75k6)ER9p)Fm3z zh?Xdv=AIM&X+X=Y6n=wc_35y>uyOnCRe-+Izi9%|C3V!m@8XHfesrw5fu zf%h&Hg!<`dk3b_udVOY7SeHxsK$;d~*?WFqBDq$c3ZmD|4L~M>2VukV<)JvXE9{Q< z1Xc5!7pn-{6x+nzLrWzUadIEFmM_G>GZ52D-$eo?YA6FW$ ztH>@l9?LRBO-HOeKx#t_93?6;GbIS6w>Nt^=Ca@kn&t!U-#TAZ#O1kSK6u{7OiP$! zrsYeyREwrBnOk^j!9`NWo`cc=tN;D5-|$FM1^6)DnF8cAK8LcKS&N2%WdLvWK=J%G zK!@?S#Ai1UTzyOlo2e`=M7_4T)dlC7=nzKN+M|pv0~&X(^(`eYGcRoq7*EJp`LkSf z9+t`JRwz}MmMyxrTH>Q}_Je16q3N|oT1E-wgG-#s zcL}D%w7Rhq5>ucAI=u|Uh+r7^TxMk7FY^YphRYwbP|O1bacpo!db$vd(?|m7s>ouzpCn znoVdEW?VlFqyp6`VK&1cx%ArAjY&@7B^fM_;~eP3fq_cnM92LR3Y%|pCSAG10nT5D zp#&QPInEhjUri==&!3y|ZgT}qI}cjtfy-6wk;~#S@$>WeOOw{Z;ee@FHlOysrdNds z!xT<4?~-u>MbSNH)XAg^&PV@=O-KPeICj)Cq)O<1r_cXvCEZ~}2tWEMH%OL-K~kHP zGecIv-rk9e|En%aecE=`y)#ixm zl{Jy>l}*t2rdwi_J73GQkwaM_E&h^Hw+2;X=5~B|(a3uth-`N#5L!ONM(MlI?p&Kyy73q#%%@g98Be=rNemSs$#C!p#fZ z)HEX zROgm9hMTIdPS}qgb^slvP%|L@fEtkFIAA*FUM|vv?!9`*A3h>UBaU7|Y+Jep ze(<=DVp;l;A*GamTz6@~n2eEiq@SaLdhs%F7ZVMwWy^Taxuaw^bx__V>LYQeHj+}~ zdC)W#Y$TI67bdZh!-N*Qk5c&=hS}pnkYIRjapwc*=9+W?>=>ENvOeo%Go5aL0694DrSS>e1#OKKF3G=48i;mi20Nx8OR3X zIR%rZr^wqZ^IjmjuWDhX`!?{CUOt7G%81qaJ4MJJ)M~Y7l!G76ZlJJe5guRf5G5{O%49wbV zW-H(&p2EfrV8dzHO)vPE!g(m|o?){BWZ6anIeHY9-fcDdW2vKp8n-24PAL_tlI}SD zwS$m&?}`9~NRjdD&c7p05b&?n9NFb>-;DNtDmYJ3n$g9rVT~i-jgaX0JMT69N8lc( zj@+Dzk20w;{OD%iQt|ZNb%jjnu#9sY8tA;xKm%#$E?2X0@*A}zKH#;AWp5-pVot%{ z$wMW}c@}?ea+%?JqmSP9DdX~HXa{=Yr@OAbKlZ9`xN3lL6q+jOg7MLq%jQu8FO92F z*zWHPkV~H<*j4e?9nWL-WCyIiri%X`p&X?JSjX@Tw>$V3|6xi=#)%vek|^Zd0uLKk z*xq5i^}-v*De9*t>f(Rz%qNz)jCcAk?wvv07GISu@Wrc;iZ#T%Kq|_RR?*I zxjO2o-vDQhh<-701S701gt^ z$pAn@{n?K*zwb37&ct^H9)g8BZ2Evn-S_`j?^=~3M}=3~ND3qFaazpo_U;pUW`ryFL=iB&6Di};r2-o?xwzY+s3~xJe;u_sKQ=8N_IXv7P7**Wj zk~hEq2sfgpzwxjw6iX*!^72EZgJdIz}5;XL5)h=e$p}^U_L?WRCwFh z>Vg--`e8PkmNNs^M$u@8do0&T6y7LXlg-JeSBp)y>&c7O5^RZgV+bV!&<0~lNdneU z!gqVafNML=9(lV0Xi!zz&ly_mm)CPSw8@AU2^k#D3#luj&S|Ps#{?`v+3@VWDdczb z|K46m4}dneXOt^YFFlwNsz%TN5|e$&Chfw$*4r~!?`%EoRDeqrnt`NYHxaTJQ$3py z+h@dS&kjdz6;_txZq+4SH&A^)uy4MOA$S&pIgF1Hslja{g3A3>rZ7@8Mxr?oz6%sM zaFUgy$ZiVXwv>}FP$&^xuMIUS7KtJU<0J*X6$vl*!!Qp`jVsWAJiZcw9FY!jN;HWU`+PnaxxJK%D@KnTi*L69F<&P<5e_W?pN|>5=EDeIv;1R{@ zCXNGlV2(&HpxSg`o=|JS`9R)mA8972TAFwpZQ5N0B=}P~q#3l}n3s2$eggBl%{U); zAw-9%5G!xAOsMy}+U>YDzyKeTwZ^}oBR(?vXmn?#AqcS0n$ig%V67ZKbfIQg;CwN- zw#g-6@5Z<0tX_cr$Y`w=vR9C##M=_tQo$^AJf~ggnNj>WVG)3H&d&e#b`QY>*GZnv zxSQIQs8{{Rz;Wyy&B4oWHv)Q!Gjoty&yg^>P$kW{crA~`O#!TH`!s&2Dl?&b?f7ty znZ_Khc0rEZPLm^8B$za~T!Y`Z zi~8GilqHOJSx%tQr|gtQoRak{bJ}>0R@*i9bT+;5QRWuH-Jm-J4J?ny?EGGAAIsnVYEdZW`&pumjO7q(|N z;*r$CMJ*+oF~}fhaxU2Z#}n6|0PPQ$uzGdggN6SYckW*aC@T{65pz`pyX|1c=`{ci zJC(zw1dRo%2WmuEoY4l^Nv0IR==!4|(@YA_W2T0#3UOpQkS^=N2L8vFCLk?KT?E3Y zlsd>gxCt$CoKvG5QxYKu16ykR6@G)L9$|+Rg&0y7`)U~q;qM{r92ynE9FVnfcixZn zV4?~4EUg$I`AZX%p6I>5>PwO;FlJ%e`LPJ=ltCzF?#_AeX6`4AX!9kj}snlECy`*V;R&;EN9682tE8}2U6v7UU7;HY!oOgCh#Lkk4On92mw1o&W{!@F|Z9&RmwumQ$w-)n6h6N z4Y*>zlQF~-upY852mv(5?46n5%YBe1sYp~6i2cSi@C2jHw?z1!6UTGvOMO@Jecr^k zc#0o#evnifEj zazN|8X4x~u&JPz<2z#CvphDfaOM=+~de0=l2PjrmcvU%sjuOw3gXJi{4mja9i`B~{ z9z&lADneJ%c6ati%F|zi&0FsQvGEbH}=J#9#b3Ho~d4uZ4<$p7Q!|__6~jC(y~R+1I09TY!tC6a$pvhqdUvi&z5s$JvD* zP)3AC$7m6P4xftoi{>Uo#%1a(@NQ!sVLeNuUMfS;t3v1LDD$qpVqyGzSh)6Q`8AK= z^TH1SGCRosmheS712NgjpB4`$Qc#(5DU_z*CsU9s!yGclJOl|@O1^^>x_ftEZ((~l zWU#QV$JkfeuBv0D*(MPaQW#@V@jE29^`D#c$Gad3{EoMmM;BXlL)=jq!$>uUF&W9F zeK|HRvwcffYHFt`CsZ&UI+-uZ23YN%l)^*oFu?1&$7NIqM!VVJKZ?tg#{dN?GaXXpbe=H#o8=)=hl>6;TR_RkgD%MwW zDZu|=G9`)ePN8B1NJTg$zXnFMgJt6il(uvT3aJ1spyBBG0Nf)*xo zu$frq2kJ1GKD={|3hr6*3J~P0>hk;KIFo_-z<1@K?71MVdOQXK_~^!D z3WT@uZ<`dS3fZr8XjyVwsYit6BHVU6B~-r}L(cY8oW%dCGVWjF$G>v)f9%`uJWjm? zqKrA!ZfMG~U034XQ!z$)J?L5-2|z(S0<(T4BmJ^J`%=AR$GX=*k;yrZU@~PAP@g0n zOABrtN3(7X{k5;Iejobpbjv6+f@gl>`imJ!ZS>Vx5V}FlmiD`J0UY@$=_^Rh2S;Fx z8Bv{@QMc-4H)T`+(W*84CIPU{f5YEzyduOvSX9XpZ80@ggbog(99tk|+VIc8nryVAEceTXg`bHAdCL{%I!6gyZjBXzM$(w$u;S}c!hPI!mHuRR zyf@7!76KwM;nd^TLws*a)k5UbC=FGVE>hy3ipz+;bToSh_nC8@aM3?T2x7pe2_mn? zYHnYFDlGPs6C3Ux7#;ePr01pqX!qvMPMa*9&u%Xd)Z@X+Kbs377!@0xwZVIbV12z> z$-6d5+;#QB2s-s4_@}+PxM(vJaq_$+4f7AE14?~r`Ua_Fqg&wAh#sV?v>zi2=?kNiry5k>`@@v+ZrJD z74Jk_`oYbj14HFk58)`N@uICu^;MV3D%igIV?AF(rytTW@WX2Ft)QGIBu6RDm{NxP zMdYlg-ka;3w<5H?9z0Bz^Ys? z_wvgsn{6T**g)<|%vMEB=m)A5#Mw(gfZ!yh#uRJIp;?wv`+03+zH(zb z3|k4nDo{bRQ`L~|B~%(a36~K%aKK<9*g+~aTY=_>suEQ2c!Kr6xBH)WC_92J%SaT5 ziLPp%&E_sSBQ4@Ck~siQhtL2t|M`MK(N8+R6g#5YtJV2vHqkX(XXF&=gTDx_lMa&v zCfE;S|B#bkpOHS2`kWc)QgAY*V4R}R@X6lefZGd?80}gc%2F8(56U|?<8S1&;t8T> zqCYCo0P1aK09PF?(H6pm)pHX>hfhE~OI8fc6m!`!^{9U=P6lG$@MiZzbrb~$el+BC z((nG&NrO*b7Cnp)V$OTz;O%`5bk2Lh--4uH*K8PuVR7Yfh3UGxk`PerOwXOcbY|~O z9}<-&;9JC0FwjfGTFbJqGab=P{;sfX{Nf=zmgss&-xFcE&1#}>qD`IvVwh1@yDrYJQ+TcPXEh>|B0(ik6r4uh{2yeH}S z<9h<}Z<_EwbpPH3Pc{@ggbzB27wFuQet9h`(4s3`t1=gZuM&Pb2&M)o}x%S(yRL6IGMApbQ0w z7?FgPC;#oXt#K&=_iOwx}b?XT%o$36hpi2ekj%55WHkf+k#FE|)Isj1F2{ z9G5^VWw)O}dKwYtpa8dR1{ngI3(jg98YuX*{(C8F)u&5xGK?l#^u7iO=Rg;h$h~|B z)8IupKi)=RcI?@1$~*2a6>9=*1X~$!_dgKT;?!YD^LdEJrpSRz027*HyI#iE+bhM~ z+|BujxaY$Hu@iVMcZ1Z&?zc znBos_6yisB{>;PrHH=+a+Ezs$>zdc9*H8&Tv!f-?yH%8MExQ1lLJ!kK^(W$g#B+Nmj&+0Z%0aj15pE*@Q$V*YM4hMUvDAuxoqzEgP!SO^}hufh%Y2f zOf7q^mrK#xi^D9up1qK8axXvXZ{PA2o$WAm$dK1VX`h}Bh={1n0HKJNg z!<)t#6RNi3nfgw~yUK$t%H6EMKx7)?XMusq>U+0FmDAmiNu-%#MCw8eBA7`Hr(S*> zX2P@###$D0z@8{@k&WhT3luOTaWAq=^kn8Ou^E-Fc49s1#iKQc&KP-<1Em3}UFieO zTK{&LH^fk5B^CkHNXtK!Td^jhSO)EIbEdat&#c&)eaiB*62rX0ip3xFJJ ziTy#D)%HEXRky+ySc#Zx8%OL<o#YWWGyudPCUf&2tU){ zl-CjDoZCFIqv$Tc5P=6Vp(=Y!VZ(~rG>O>Ru(XHmoYRj*w_90L_AsVl7e`IUDgRpr z;F+k;PSp{UKx$!LAvDc+gA&|dFwqR3sjtcFX*y~!$nh8*w2;BuA$sH{&$Vco5|84} z3M4;^kM6S|)8UL;$1?yHKs{eHxby0jHmsg(Br5QWlZbPcmNbA2p%w}utjwC$GpL12 zbG8$wvH)4Qtb3gF%#p8?@sFVZW?jZ51|If>voE$1+;p)?IK>zhWUrbFMD1YR!^A!e z1T9l|g}>KGES_FFoIt&O8Fh~4BX%CSb#VG%#(xF3R&y_Q0UOYAM<%3Ex z_d$!_(RKDl?=O1C2MHJQQ}?D(diG%cJ|}?W}xmP%`|2&6$Q@!ft0=m zzMD!DGmK`+CsfZV#q3q@8!Dla?ydD0*d8mdC*D=$*`sVmyq=)1kGt4>4)Q*&leSq? zcNTEN(B2!ymOsW#+9H$bn@Gm)R`oXT%9SMI8nrY3g!5gJRq zP$i%CT-@{Iaxqq0GpCE?@J<~zqIRp!e3N_8ZL=! zT3vW@>~(&7bk3QJ1KY*NF#rb9-*>ORdTJ{~SNGJDW7LQnu;Oh4z`>mB8@OPF21JNU1rvuI$`VE8h_KAXu-u5+&Uh?C*C zVoj>qZv%36{jE`RsGf9NmX@9>Gg9oloF639n;E^@r3$`e4q?GV09t*;Gb&v)CT6t0 zbF|lZ0BgUj^*9ULBzE27N-5jP7}|YoFM&tyaRYF>3Iwelbu*p1A=iT%_oit=DNE>t zbBF{*mPh^S)g@8W_L}ov>7sb2^8+eoM!8lzFeubE`?_tWW1M<2bg`$^HRziuM#3`D zupCX4>KQRpygjw}c3Usbtq#;_>N{(G>R)3Y)QkD8FOck#@^YPHXFPcw$<-<&QFRZ8 zc%R2ZqkVm&lW`ol%E_XqUbN|nrKvl_;#3uBesSqP+>?aeB`^=KyAMG;T!fX zM?YA6FCXn)*tcK+`hQ|oMvS2G%<=TUQR+XmPg4*e=jB1vGd4OQ0V&jb#kpf1W@r5( zZa((6$Wz~D+D@!`LuXS^H(4_O1LY*FXUmdO(%$xC#-c(1n)%&u(NZ8O3K$uIG?5EU zKS_#Ando=VW?6mdnf2u%0@@95U+chbMjZCtHyR8K#0M_zjk0c}$?tsmT%EJ+E2IRl z9xtGIpGoC(uiM!;QF!4UC(jW3Rp*Yp%f9!mHJ0kdrNc8YY_^f=(C}>S0R$OsA>H+X`{%p zS|)l?3;UqPY0bGwHq(*Ds8IvMwn~`(WZHCavFCrTeZ$`ZMU55C<1*oRkf%F8m z!g41#y!C;NT`yu6a*PxhNYA$C>_5VBC5-SQfHmc4MANoCj3(HV-Sb8z8NzHHDej5Q z^Z`a&Qg+;B(vO}4h4hzd&PV*(Spf4w(MH6iH*@#)s&5_1n##EDcvrP#5;%Lve!G>3tJR-3f=_6~ z_K!^bd*(Icl_PJzkA6FE_Mg7AX@4!IoX$fou%VAjrN+?zO)Q`%8DrErs@sW$@y!R- z6H($Uw78}a4J}vP@+c8+v=`~Fd%Z8t#w59!j&Rl_A*U84y~C941))pynewc<0>)_J zKExLIJ>rcHiZYx4s#}}Ki2_1U;N+vIi8}B~_j{`;ipHj|!v8D}R4A26F7Ql_$FQF? zMc?OQ3wJe9_CXhyhVr#3i7`W}W0odpc#$V*LQ?-E zJUaWI zrX;F`EroHsOgSyD5W^r0tqs2CKx&ij`0G(5^A~Ha*oa0P8)1dg<~I;i?uY-3)TD&P z$n!x{kWfTZGx>Fl=mE#OMj z?arGkX_H^{TVdJ=Sik}aFQQS6FF}2xU3Hz|?hC5j)khcYA2sr3eC+sNu{nR*PDpiv z-yvoBFypMzHEHXHof*A>V;@DkW<*poB$iKWZC%G-f#+J`U5?-hH1KRmgbSJtVd>R! zwx29A+sT1W8=2aei}dnA*YV-<3++V6IKq6GB7F3- zyPMf3+rsm!QGxU>AqL`{c+Al>srr&LC8?7wKM%aV5Z8bQrH@ak9`#4JC7k21iGJU@ z=du@{1G_1yK_aD#M(5C6miWo;Ex_q_EdiUOipn_?dEaYUqh>&+LIHN~bREho063+R z7P7L6!BV&Qu;S3wW~qn=m?sB(+X-3tj@wK`m8nmM)x%-N&3lpO@>-JSTLX^&wgkT^ zVP9;rOT2(zTZA8F*!!C-wt4WyN&uS19Dm6fhh1_P%@-3WCFIVsvJT?!)L%EW5=px~^}3!MJaExsexz+9YdEepZALFo5iacUw923l#478*m?b zQf?13y8B~OEJ8A>UwFgH5cHJ~=cMgPPpx%gdgO>;;nhqDn8OKMtAIa(2p;I2@8xo! z=w;@y*2xn`W4|6{M8XCr31}pGqu3UR%g7ag3p9QNyQ$kWNxus&)|BZe6DdOEJiTZp z8|AVz(F-bT_4=w&Q9d~cg{YYgTx0KjGs-_t4Z4_3$mcm^ryd|;r>SgUjFFf-|4QChcjq;Rwy zjh@j+cf$$tAwFguC}02Hvaa^E@IahLhqxe)+Q_<2FXU{=`*Tgj#vdf?J9LfkDaqAY z38;G~yYCU>=7KAmIMu4@??ORPm{UvJAq}}usGge3%2^17+_!aV7RM%tZ|TJeJw#W@ zg27}!VHgJD#2SgNR^8C-YG0O?zxmX$80jU-lshsAh?tjz04e`?Etntt5J8_U!B>9S zvQ`HyzpeYncI?h$@0mV_?pE`kCI37TRsj*BN7jBp)aK2Xiq8E%PccU_Vlf{NF=+=qW{yvul^E~bqtwU^i_w$rq3Sj4eLWBMz0}qvQ2n8cf(qXA@hayGPv9Uc8^rAI#_q zvVeS*^3o!fh&zS0pS{n3)4Hzpa_HxYQLmivDGSf&M+k#&6z^`g<6If zC+s_|2~^wkoOom~+)^y2f*y-hiNrPJzi?{FH6tt-yIS>iz2FyRONC|LOeV8Gx&n?) zsvP7zc~wz)O{)LzQ)%%-2mWT)Us{SVC#W9hPW2E58QOLnZVQF7)g4Hp?p(#EYT=Tu z3q?fp2QingDqpvYvWFdS^w1r&VpIq}4MtWQoK|7YUw`>KcSs2CKsrirgWeLZ>Od9O z9dgTlMr;@CCgo4dhlse*VE&SygaYOZKBcTCkO>#P{Uh;0y{+jZhcfZB9eP|>)R`%)Y3Oy`-5i-N_ z>-K-|9TK1Ef?bZ9=ZA(HmTEW&6XX4S*yl&bXQ{B5O=-*V^Vrb+Zr?rPa(7|Xxg6o@ z@+y|wCHjD{yDo(nPqBRPtY&+iU+ZP`D+2z@mj%ICsmcELx71ikU)={gcYLqlBr2qb zydOuVyf56dP^?~#=*nPK;;@A=)ezz}b@>`|jLoR%8Ff`iKW5r!(6*+N{y&HP_sOih zV%$nJdUZlRf$#j>;>#QFyYzc;5AM)owW{9F_jWZ_!wyTRI8%V4I_$ORxAW=d-h>Zz zGbiH2dz@IZTNZ72nX#pJU@dlgAd~v6`Ptn`n5V~LN+00}`-pdj}IJ z32Onh%Xygs_fosJ#99QU>FJ7Srqw>E)?5M_!pvks4!QSJ$9$q+qE zJih;Xc0(17aL74Vw=h1KLn4tfJ?g(#PS4}amu3{ll|KE29Ct|wA(W|63vx|eho>aT zhe-|XNX}w2Hs3778lcuI1%PuY&2dc}nVG7C(ug~+(f}f4(*KX%`hO`CD4lfNiMwl|=styRKt69Nuz*GhB5JL|cwm?fHPW zEDK`dh4IyUaRpRCOf`M-dL^kTk(cq^4Z~`ehnvO*B{HAxCmvLilHZ;|{j8Gpt#Gs` zUfOAFxGZ|(!x*wNT;kcV(aUpD(Av=+J*yAI=R664J>*zk>7Ji>ZFBwYHwm5`cj`TC zNG%m*oI}6{MsdJas$@5tz8(j~T`0_%J+akuozV=bEoY`!vbnXtTvM6j^2joBH34_^ zDy)N7=YB67tRjZu$$^p2^`6x-Dh#d z2Mzh<66BIR{zl#?$RC-GfP)M>u1x_z=h^oF% zV3qP;<8k$Ot!k%2`c&(J_{9keX>sdz7)d0g76!xMob1FmLq!%onk5;XNt~*0s=u^b zi9+Hir6%^?uN=1RntlLDmiB1!@Qb-xCl4xBMJW2$4M?%CvUhNhwl;ay$)sAc{xQUa zdvTTzO(}*!HRW3m;;zG*I~Z8()I)#kNr=Az&*S~P1r_uqfFUN50|8wdYGQZs&+>G+ z0KpUr+|tak5GI_ZAcuaRV*`E3RWv#$f~?HS1VJ+=nKNf%)Xp&1ma9$Pfa4R6rgNgm zx}6BE3waXNDNm3Lc3ITdOG6fuuc1avdpMk)^xL!l&G*ZYhXUr3z`!n#;mG6X#f{=> z?;PXY6*+fka{BkXe$h3$86RyOPi^c?aF?wfL-xy{c9{D>7Yjh}7xSv#BWat7qhS+sIRjnErvrw%Cdj z%oG}|b$EN(Jm1NC(5=b&Eh|*VB8px|`unrG{Pr-iQ2@5RdOU{;3x(QGZHE$!-kypY-n)DfNIC&(Jd?B$t_LMTl-GFo zT3C&cU%9*b$-`0$5@1+nH2#vEq+3fKMSj|D^k2-VJgcz zp&)uM?R-3YW7r*A&7d{`&*ohva#RDfSe~G(ZF`tvjlRmk94`m%>TcM7iPj6@Q#Co) z9$;`M?qr6%hE}F>a!f*62Q7ntR;Yk@Bc#F~LYI_(1#c1tN^>8?sD+{1?pcJFIEgUY zymfnSssam_&U=YL{RU#OiWfT63{p;SC1h)=g=Hw7O0+0Eqi6K0n?|#+KKe&;_m<|w zR;3&01%0TUI9fwrvHvRQEr4;Ev2wizla&V!@;d%%qK7XPTM&17! zdArP@ub_YJSC5+-N!8W$0?CXr&As;_}8*L2#)Sue9ppD>l0>v1neC? zB~VSDyy!UKZ$>G(?s)(G{E5DoDS4c8N0!^>kRoH|mG8%#hy8me6=K7Lulf)IV3O^B zgz&eHqsIDg1V6<3ctjTMYl!$R8@;RLgus#G=P6GmOo)j-!0|aV+ar2 zXB26YIV+@ek1mauMVw?LXyc<1Ik!A}wXsHNhnVTv+YTE$EP!{lG|AJ~xA*KBk}bG_ z4%q-$A0Jj@$D2-X0~v#m$w0mj^!MfC4+&1WH;yGEGl};4&OQX7ipPDznORzEF0AhY zsbPT#MXG`Fo+0N-xOADc*=rvL0F)&T9>~qDu+c_K9GqHH0)UQD`4hnoeFEp9w+MQ8 zv0N#jeuoDd?EAbdeL$uEt+k%R27+;(wf%Z)=)rpV;eG|Eq#`d;0cur?*X_l3GJ*N) z-eTHV(^CKm9mG7CEtkn7nHMgbPR*5vuJ&3|+#BW1d|YlhEH zS_DZZzo|27bL)wFVQqihar;x0?iss0?i-TLNgSoe#=44}%LJK-f}Ma6G!>M}%Rm-* zU1HxgfioO1lzQQ=xuO`SW?;i!GX2vD=PxHvj5gg{h!4GWw<4vi+sgU@3JR4^96iTn z3aiy}s@|~Mz<~H`P}g;c#}Nw5^^w(!RoW0=pT*8B7@iFh6o;H!W<#s=v)*10nfZf8 z6v5yotKA;}JcpdBfw%%r`6z|{DZ#@%k?a~$0@>1k6!o&IS>-~y)w-oMDkj1o&3wB4 z%XLpKCIUl3ju*hf*{`@jHufALHN2;4O1{|jVt3+_J6G%i#Pi;w;#+`*3M z&`mhGwx674pfhNQQaAL(i=RQlu|roVEE!r2JzANFFQ^HYrucX*l2XV<^H zr`tJjx-aeFz+5)VdJBPICKI`F^=1hOs+lzJ03yZVI}3?A8`$F$2Rn4+2;c%}r&8&v z1DAGq9iFAcjmeClmzHTJe`OI$!-1TB=t#h<3Xyl`yJ)VdLRIRK_=zhD%*yuJL4(k732XqD7? zVh$Cei{%fI9*`6<`7P*h}>=i%D|BHA`wexshiRi{Worao8+Z zE9|SBminQ>>JKVm+EM5n0-AS6OPv^Y({@o+8?_Q?ISNl9B93elkV9-e;+6IN& zi0==gu-xtd1z*Sg2>C}fBy{l6pEUkGv9+3+eLZfLCN>l*FH*aSrF?CpLVXs7gk)sa zQO!bO<+=fVQv3SULAlxTS0{`yd~>5z_Ii!64=C3-6CIdh3r4C3goHm*At{U;l6o3L7B1C|;aPDA7M;*)nu6#-4Qd-E^u1 z&tr5uV0z)#%?Kv?f|sE)nLHVwm(C}tq5Zy^uo9Q6PxihSx`-|^7+*Z|I;yf!NbN~X ziT+u6#TZB)!=hZ`ev_O}qmLx+^ag>t-aCjBboE4kL?R;?CGHaD9!iv(gpUCkb|&Lx20?Yi{dE>&BXOq{0`? z;!X^t8a^D>+oJe@+PAE{52D<5Q8&v&S&E69xhjM|F<4=rDnH=mNfS>{L5?5Un+YLz!e|8 z0kkjbzF6)@LG{O4C_Q{ic>hA*y5>9X$I{LH(9E)h`(5?99s;Vp$JB0gh&#p2#5%J9 z@ZE`EBK~4+<^ZrL2S;CX>U83MYi+Jrd!x|m>5r=#8tTLORx3=`9PfWr4kla?^WerB z_CDHdPkpuoIX%INCk^X$I3Dx>;s{ZrAQnGKdFU{DTY3{^ynnfmU6c#L6_hx%%Dqj= zJ6vb=mHIhD+R1CWlmi}*-7zko=PaABY8)YvSFth{{(8^aKFeHGZ`96c_C;@(yS0yx zZ?gzv$z}bk+Hs<(DKM;}ff|9!rKgtocAQN_an@!j2mu@e0dEPwG4vRY!3+FgCZ|8T zcaS-W?%_3`p2Mt?zm9}MS`F8X)~JC zFb^qWn4bXdPbDK3Q;!NcJ2zQqSHG^Y<^(Aa8t?&4(nm6Nvgeg!n+^~3O;h}lEw3o( z9=-C7MiWCg7$y}e&u0n~QkD+@EXnnA%e8_3A^Bg&0jU;_z}^yFn2vZ~eK64)a0EEH zvP8rTjRI&!m@Fl3nY{b_tNr*$3N3`OtMZn|V)P_xF_=+O5dYC{q9$Z}p7^B^#d;b=e*0Qbp(h7Rpn zCCtr|e_W5sqRn@vSAN{aFa15YGWX zyp$zfquWe5phJ1?mdwqa&7i^-+=Av>qGsi@{Hn(4>!WS3k_L7aUb76b=s06UWjRT9 zxNc`ujf6$WnhIn@@b@B-ly%srUAuE_rT*rSe_oMkiTlO3&wUd&E7Y(~T%P43Hi*bL z4Nem3y6X%{4yTICY9_CJP>dOHGTBJl`n<`oHTj6%BiA1b^}_6?MGk$#kGdeg#c9&n z-~6rN9HjX8Sn&JBQ-7w9+GblZGzf@2(`wsp>Rms)a|Rk%A7o}qfGNWu3c@q zfLQhUO1-mF6{NZ~)u4jg0D#U1Fs87(^)U3uLy0rf%d1dJi(pn9jX1XbNPKzM-R)FC zk#p=<3`M>~E(0=>PCrY|MZX2kyqhuN@XdK_fu z?72Vq@-W>+2S-%0n^C^*69W$z+v?$l>$^VXG34qMI~?n@5!8m`vBZ6=RtK$wxURL* zyVXf!*Pn~xC&zXr3hx`tiH$heeX}}}yP(Y?;-tA@q0>-0OB>g%j7Z&uPsMK@RT=-R zNRqmL^u=|7zXIz2IHqK3CA3i>h=)8BhQhG}?11zob@q~@>IW|D&VRPSVE2`|*{@<2 zK7^;uys_#wpSRkjXpiT|*QA*0JTBYA_z4*g`^vJ|B;+Iqj6^x_z-7x~903iE18(0O zHh$UVVQA$(-TcM{Ffy`IddVK193rC;_sL1l>YJb&VB&5O1hI8U;-(7bf$oKXi>TdS zoXyBeL{q(Q%?{pXGSjt<(>st@%B0dKX`Hkh@p*dv*B&6m{k2TVuCOt2@T&w*24ZgF z^h`Wumg~8DJB>%0&%t~g#FNUIu$?gG49$*>=ES8W^-bD2*upBL=htoc?dd)*8z5&u%DTN(rs3z zjEBKb!-j_6=5DmO*i4l&YIA-5-fL*Xsk+)1+ud-hhLb^zG$zL|(9;6-@ErZCm({<) z0Hu_7Y_$vV>_%=PI^#n0wtj@4$I;M!@Ajin_Nl#2eWa_26$$(=&Xe}k zX~4^^j&C?~+`|-0O%?zo0O0j<77quV##^U)d zhQ%MNywhB>KFz!NXsrj-FrSq{?xn7Z<_{2Yp;@ zEw^@!XEEv=cq&o4P`qQ33q?9Kc|cAO=fqLa+eeF=JXA-&xYTGtmyb)egnx#%(_@cu z=iJ@ z6;7TlP6*=Y9uUBd?tHLnm?xtZ}*QIlnHj>%j)> zk>>{~wIlw7A9+8s6+ZlUYHBOV2qp8a4Phy5KQzy`uq;2@?>kv})HN?-d?L?C84;g2 z$FLS{Y26Iu*eDQW@%9Q~1D2MR80=m(@U>2yB;~6|Bz-zen=$y*uqjd2f{y5UWgyZO zhlhLSrbY?jE$e4K>XfQc!dQ!>Xs}ZinD)ZWJwM!((_5yexw1@R1}op2y|SdrI8?om zSxx$6*hG?|y1*bn7HM?Ydgb;c5tsGE*<{C8*$r4uDq_XYcN1)K3%9qaXp+}oxlQJw z7RN3TNprCR5q8By1KiZBDAxEQ&1-8OWBCBLY6J&%5k+<@g{{Y!KUYN!NiJab2JL!q z>uLyrZ!{1&#|>D9`nN5OU%By4KPzDCbS~`@gF%e`^H&jylvzp}o2%1s8}X~#VhQ=t z&3_q0nZ%0FcRbJ-G>M6wbu55GJU1_$uQg*D03X+YYQued9QPsmoNj&nY$ zi9^cQAlEl~W5y4b?H_D|dG`@L!Yz}Iag@sntrE5J*Dc({TVC;spOW`1@%T$={Cx-l zpig++v}-y#ZfhxV93yq=@8h*qqGbA`g-!||KDF03c%w6ek{H@Byqb>`K^-NFks?Ke zJW1wPyQ!B;$#hV;DA^(tPZh8FN&p=K3Xl4W!~C zD6hAe)DQX)n5657htB7g+_`;HIEb}T5XX!r@mzRkhPuLC#%oo5QVi`T0jgu&n4{_{ zxj?#suXzadw*vIcapxxMQtr70ugBJ@h$Y|uml(vrh;u5X zPfD)-c%k$!Mq6e>N|Fy68^X=4-Eyc6Lvq+^ayx5X#*g9!6*;NKh@9{TF&q7Hslru! zBp$E!DHEY%VlB=*clMTRw@6w~1PKTPA@oANDB*?(&Yr*>D!qJj|0jXVmpK~w*r@ak zi?^GJyz8-{3}S}!lD8+laQyt`9B!SWRQ57svd@-$B;-~2(~gd+QcABCy|K~e?)#8% z*C3bjC8DgE$`^gFJ%zvLvg_ciHmXzCDm;h})wsnpGPtqSVYA7>%|zE5yM?mG6ADk%9+)e<;eQUSjv7%phcT)f-={H=Fx-o;YQ+a~RuuPQ_mM?zd0Y}(E$2u$_ z95EVS^`|@H?;k7K^|4UyztrOp7@(m5)o|R3Vs4M)8X-omiEZ6yF zh2!zv=*BIrq)t&fN0p^xgc9!6gO$92HVK!mI8(t~)pP8KjODwXe!JaX)tnqm*D@cF zg5|#JT$z@JSO{#3<>@9d#TFi|7xNxG8Ig|1OXI6Pk!Gd+hD%uvT3HR`GJkJ&`dfn_ zrp;FWSWJ3OHZJm`O7KQvMF3v#VNAeDm2;}<*Zb|{rW)%a6p_Ej`xtb(R=Q$CpM^V7 z^QY-Gnsc47OmY|bc1r5kcY^l(e`{JG9zy;<4g|uG47+f*Bf|?7>ZqQV1z2#}%wMEO z848g3iX5i6OhhxKv`vFtw1+)xrm*YOHa>{iiCLJqY>ajr31kyc3)Cx3d>8$I4oh6f zVUG$%AKt8BxBivTAIc%mrwI%*<+@Ha1kFmoa46Of>rwjp&NOyz@KTBqsxqKUTe|lB zQ9@hMb%RY>;L@DrC$G0D3zc2)GT*g&s~R`Z@U#4wmQ=(+MFuMcj2B|d0yG5mZ(p?8 zB=$}y_s+yj#spWHIJh)Q$_z%wsEKy;rZ*+G{G!h4cl|Mu@&AKZfBw3L_YZbu2eUTv z1dntN$};MUpH0tPJbu)zs+!yBx!v2Tt{m6(M!9fCk~A1j#tilH%&Dyc75=Hmsdl1;I*))Tt=O^O#Qpi#?m>XzBMf`sB zPTTc=^UJ^PMdb>|-NeJK|Na!ul|;+~w&i$J;gLeJASdPjv;Rv&R2#j`8Dduz!Z{>@eOAQu0{FB6+pO63M_Fw!X zT%KmpR`UFW`_R@Na{u->nzs3hG3!;$!}Nb3?Aw9nh0`RLeF_36mj!J6K{Wl$vvB z_o@0T3h5m)u3~QK*}GIsKGLoi#tY|csx$(lnuCQdQw;L ziZQIgL?w}kVZQgMcgj$4Oq;3TdVu)h!Zk5(A7y4 z!^^mYrnoa31_?n~W#;5ucUF)XX53?0yptl{oDk1}CheaJO8 z_YaG(!Ijqhb?s@ciXUOo4=t1`{mwh>=ztWn^9;{mPSG`2uiirCGiD1Yr5Ls|Kc^Wn zK3;cPY*&td#oYz!BC71*I9-BVs?*dvF1hmU#9*q%0T-j2Umw0;J4SM#vboL80%1-6 z>(rhEH;wuoOEV|rh_h2lF@xiC3S`Zn8x@v0Lza~YKggHcl9Xaty}>?*PeIs6a{d%3 z0ZwF7Wke;#Kwl;4O+uDKRelR&f=Uu0kw2}v{vaJ!mDV-Ok&i+^#NX$=YM7RfAK2pm z!5P2aNr4%!C(N1^NLRL{`DkXV#SB&3PL5n%@P`e8WtEw@qz-nVW77`2aJeY9Vk*pd ziO9F|{3{#?`WjK(bw_-x=yGt^q`AVPJpsmltg$C8`&;O?6Md`#YIbmboq-nKwy6&( zS8kyVM^)qZo=;MsoO>GXRN{41_(a%bVG$wZy;tlCgTu+K9v&|P_KRlP2@=>7^Ye21 z3#$V%268E{rL@5yc@93MwxlBj<3OFrRZ%?cDtEY5)o68n7cH8bDw%f;-k1aG^tf16 z$8w}RI?Mm-nOBA5_seC;{G&YJaR1Rd48l0<;ukx&=0_>>Lq|YK{UEyxyC@u^Xx?3K z>BqTo-PpL)=CYcT@yr;Nk^Uq2?o3WsjdBiR#VbWg$y$GwRi0l}C)|a2;bUT~8B-aO zeFC@^1}wGG@ysm{l}#O6R=C0706^G4jko+zK=usKk&m2Y!0yrH_D4&vQ2>bYtM&Fp zbMJriN|nPNTl&Ugg3AYO=tIVkq=+VNHtM&Nb{fiD>3brl!k|N(A`b2MHJDO}*;n-z zLl@~2jZX3RcdhV@pZscBZ?*HLefy{iu;ed|R|z2yvku91=tq_TdL(M%#=)AlMj4ew zIh!oBRbO?(-qlC?rG_9jSy4|bMt;Wq*Xz~y**mZ>zM12_)Ftt`{{i#yqAjMpOSAP} zQ*q--m_!S@r@ZWk+qeQO%GVRQz-{==TJ;dnpOmk! zgnu&c^yDr>u|BQfdKz2lVyY28b0=i2lKEE!mEPF58gWM8yMw)w+b}NPS#q23c7(u1g=OJs)}ZYr-+Lmp{BM`(p8x)*os3%W5RA3Dcso z7KS~i59xEIG1 zi*rBq7cNH$Wmq2xQz3O$AO-~Vapx)4o7}#;%!6O~W($x{9-RWPjz(UkUHPV-O25t< z8^i8z-+IGOn{N~H;AxRL(L@4uW%n5;DYYo|Xqk09&kOyY$zrkQ;^Oy)TehqyzZIr7TTx=t0Rlno6|%H7|D`PP(YIbnv6@mQF(^S$ zD~*}lOq8|nY}`eXYB0HhmJIb3i5#~*)AGW^-o0WWT5$)kUZuoui2(!ovsQ&$;_Et zk&@3N`))n29nDlT)lZjdiSjNrKRIcloxc#E%$*VFI&U;~Nri&ic7R(SprqU>p|o&I z+!ivnt+dn7wI+ha-i5xs;MW6JBS1hewm8fOzuTzK%T8cW{$UOJf2; z0IrfZ66!m&wJlT#ayiRE!7|4ad>F7IJ#ABW?6v3EmLc7nZ8}I8ze11|sVI4{ulC(> zLgb{VH65d&&zBqpEuh#RMn_D~F8EI5le}<})vnhNf+=x?2fnI2aP=m)_d$Kyic4t< zpL3J-3%a&NB^G4HReHH>{BU$vX+`8*=Q_I<9O}yL)Xv~SeI&}ktS7D`23KGZbSl{f z_vU-+p-X^KnMK)Qe;GY4&odqR`;d!-4U}{?-S4XEE4V(WFQ>wHH<+uNOkcEij8v7JJ zJ)xcv(%|SaHSW3`U9|>WzZ3}_*W29YI9r3phgv-C7yUoAkxd+%yF}X^c3wl+W@G7- zD+F!??X3QVA&vTnnK2ln@hKwC1Kd&484spzJ~yErj~+|(?UWI-WGIc73QpFx=lL{5%cRPK*QEqBI`BIj-aN;?Ij5NV zrERUguwhB+V-3Ue#`f!E*z=Mf@yMng?_>FSrCP#|jd=V5kv)9yX^`~UGp}z4oOQe4 z!^f>~yNhyd@PI2zM!s^`#+r@K$+$NPd-08flVYes!n5R}616KG+tETV8EtGzw+7FV|sJP53XMkQeBvyptt)62D#3ksJ78} zLVlcCA%x$~^wADzg(NaXSn5o*<(XP)3{VQ5+8!DeuAyWeh2I%4ZuG26NL zDbN7qHgejL;0tYo&A1K!0>V~4PNip0}t+(W4(Z~0t2U1OjADE01I zMTa=Ipg`(3N$}c+PQFqW15ULCI@t#J=|0?S&S^R`cV&|NfQ_`*T2bZtHur263v0>- zG+F-kU~)QXx2@9GG9Ej5%^+jpU72|^+a7XsHAd;WnCs{Y5z~0^o^%19tu;R8I(Nf} zB$W4Q|6M879T#@n;teW-dNC|^&OF~JLqu57+ELV z4;DvNa$)bsvgbUvaQgALotp3Ia8c409^9D^x9I0*(eC(8<+#u1M)@lqOOwK}yg~DG zjvF$&HO2PW7~IAN>u5ixke;n`sHx2?ETLKdnR5G`v1UW)H>q)5zk|2xPKGIlS+{iv z4)4pq^oeC=JH2_RDEaJF<8a+ZnyoU^LyTgc`G{%;`Ig=a$dbuzykKWjx#LU0x>%oR z_Ay>Rz7vW%Zf?m5lqWkVLUplN7sv}cq&y}pW03pW+H~2w(PJs{=ULYg2p4gI|8;A*kfVUW@g*3you+z zrg1cM+xfwpVGZ&f+xXLoOwU=QscmLDLjUO2WwQj>^p10gZbhsMz7j=bFiw~-@lTLa zBt9OwRMe)q)XoXZi5 zl?&{7xAwZ{TI!wJF0=ho!?|xUb(ofiy5}l@w`;^O#57p zD=%h0oa6^W_Lciz7{XfKa>*rch^@0J*WK}g-}vCo1MGR`j;FTGbktpAtm8dyTHZDP zb2e!Da3=0;sGZ!AS<8NtU8yG;9nJ>3W4T`An}lrM*aUHxrHw1ZJk1+%@>%7-UrHAU z<0>2ClsBAQ{5m{9ODayP;R#`fVP42DKBXrz?&%LVqWCD^u^O=!m)KXz=9ovN!&DpB zU0lGvhc27ynM{v!T0lL@-R5y;LK!m(F#Vw38RUyJs=! zyRO&IE!|#n*dSB&povZxRlzM+d4}mk%;LT?Z`6eyd1|%164NMdwn`?-s4=yBn(t2p z3$MD+%YwP09O4vB*!-4VC3_zJlskwSxi?l@B>irm%f;b4yN=74a#*{WZ3}*JfM_Dr zdfC?PM2O$jV%r-59;yjKw5|eLFW}Z9tk^$Q%tWYwh|LG*je6apGM|-%nWPZ=k)t{wakIc&WE@(;G zI#ieyaXW3jtjt>$AJP<8+Obu>{%A;3jpRKedOP&cEZ31F-qy$19*Kc+j=1kKufL`L z{K=a6;|h~QjRD>%)tO`xZn^2uLE7X19VZzh%Cc5y}q$4vB4r!KKkqT?^za;pATM& z=d&+Km(CBi+qCL|9UMCS_#sDXDlOxlyLc^=u!)R&S?DfV4l5cR87jRJudRIJJnfyv ze%#x9&NvumN|c(sbWo$e@i`C^bsrqssa<&+@~8Zi^6S!lT8j+a3t#dDvE+1qSa?+t zCv#*6P0{9OyspV$$!!eX}aa38{MBy-(8(_%LVoMeD1kNdQ2*BWz&9sX4y2X zQ6WmUjH2467W$y5keNLvcSrT=gCXb5Ob-eAZGypIlCf?=Bq6`;+=6d%PhiSx5_qt= zKQh9H@|?UvfZ>UyD!HbE)ub74%jdoY_jFCQUcSW@df<$bBjF%rB^k5=4V4d`eoQE2 zvpM~+h{%A$?B^f4z~-K<(HXSAeZjq*C!_01qG3j*tsgg0Ui~QSz;$Kc{Or@LvadZC z?#rMtedM%tetkyy>xH-anHd`r2Wd@iU!d?0bgyr}Z*=a*V@xgiuzz%oWd4s-UZQH+ zJqd?P>(oDNlO`47oY$$;QXjFReP1IPpN`He7&JUfasDTkMl1s-I`50~HrJZ`((`@c zIayZVo~{`D<>V#AF|0VbC3~p}_eRHr`FvD;D_fSSVGsF}teIQFd@71^QM=Im+`UTs zY@TCoQ{)8u8Byg;ZMeew6Gy+?eCRr^y9X6Uwb(|}mWa9W-qH5lU1rDcRC;xyl@a9% zMb65Yg`^!P^jr?4O1egTsH{}>`gZU6!tSSXL7j(^jO3^`Sz@Y~-4<{Ng z`8^)$ZA6*ers@t}40-qq9y&O2`tc)H(jHpOSsJxX_ukhyQg%BUKbSpFn)A`y;9kP8 zC5)!UGRM!8_w;k|cji=?pFe+Y@9$@HadAHy}NU+t@M|MO*!XIoJw9adUPN)PxJ`g2LeZat$!%? z=pAS14WR4(sDYA8Q^YJjFd(F*Le6v9IlhUP9z?R|7dti$u=cbvVx>+>YFpHhHn<9n zG$giK*e-PvRRC+wE+q~{P42xE_VMdUX31X#7uUAj?J?*2Tdy1M&d{-C&=txfu$y(i z+Iy5#k@5I^7iD^?tSOD{$37p!Vn$Z%hMjv(WnAznRIhxR?dN1BYde zq>;1FWwDdXo*;6zE1o;^UWnUjhQglv2-iOvZWg-d)vRXB+VlUh^_5{!HQd_L-O?#Y z2tx~qq;yG14c$nWbT>+O2?&S?3^laWAl*uLch}JIZJ&7G^PTJb0|x%E*IseQS}G9G zZ-)HT1p9{gcsL9RPkitOzQ@)jeA>%^e|DDpIM4crg5T6}{fC0*`wG0ODAvEuyne*j zl!N3*M<*UqMw(0~7E5TKt^R=A>8c7M(v6ZcPM+6!udi<~?Igopd{G=X-EwlPTl*;U+_Wq|`eTa3~Ey#pWjPoQ5CU+QfVMH5EiO!jbj%wE9vXnuQU>iC+{bZx6Hnn+t z;>y?9zA%Br!9?slpF-}y)tDQVC99px&qO-n>RkOvv0f)G?UPD5MR=KRJx zge_0COMFUp6uhr^wxAFXuC~AAODU1&8dw;dN?!YN8mff5ncO68b@*w()O#qp#kQb9^H!_l^Q@;%lf4)UP}6zh1#n*^b0gx=FgC@0pY zgUDpalW(2HG}(-6gC+jmqMP*cFe+RtbiPy%jwso2S+qptKN$`MhP3=kThhJ%TpKe*%){!t#>kJ3ovzeM^Z(9Tsk%? zC`v;Dn%?GZ*l-Qh72keyXuFl(eE*7hvHO?7U;p2CXwBp<`XLbcAnC~4*=U{8Sj1Mn zC%F}Yzx>2u<;tP^N8-ylS~$?0=vPq!`LPVjg1}AlCzP=tcGs$#Fs^csS(PGR#rl{c z2-GU4QV>^(YMc_(%z|HZ{c>RYE}^A~^iO2|JJbV;A~L|S2X23&-nS{na~nzHQx_Bu zsHgs*DuA2~NKp{~;m)p^O7qct5CAKD+&xMKkucP7zch-B@lz(OW@%!JYBH_ml<@I- zM6Fx05$d{@AQ58Hngi)o*a_PkroD1#x#XQ+GCz%K?YP0DQ@w6=9jYO6$_1)rv{t$$ zziDR#PJ_)X0x}IsVoBiqDn~P`;7s@_Z#FKuSkjza`n*j0IydIx0224Y6zy0lXV2Ej zi4Hzdj0Lx|PF7NgTQPaBLZ7&UH~*-sYr^M@VgrMe#DwBENfBFeI8pQSkUTbi4usz) z7?JWic_JlhTgBpq-H6(|1t=9Dz~b2^5j87nBAs~gU8dv3fdboEfj4m~CU|G3xIe9; zws&xN)WNJlRC1nHo}_hN0? zC#tzO3<^X6&h=t~C23b$IGIRBQcdcGqUUvYqhvyYfN3Lc?r0-*Q_i27(?X zK|?K)iVR|0Se=>rG&w{y_IL8AjCpE3T}vfWMwj?*p7(wQTE7Z{Y_eO}p-uZwKNz!6 zAgu~}h_y1>yeKly_^j>*buUYCPtcgSb+#D*pTBZix7&xJu}k+7p6$*9bjh`_;0S=h z{xfP&*u=Yyh+K**Nk{~1=I7^Uc0Ffe${|cjO?6jQx_kgshcXUgHYz}wWxfN{T|M49 z_i;A#N*SOXRIo^JB0+QFkO1O4s6Bpt(!bLp{H>+gj{I|N#5b}-@f(7q6qb#HHo-m3 zuk{by&=F(@;|qU=_)BZ^ed&I7yC#bHjUWy7^3>Fm-JtwUYFb+xD%hyI$2z{2Lx8k6 zL$FtQXq?N|!21UYP8AfIkeK=4sBKYPPo_b;Mnp(JP8l&x{85+Q)RZq;;qAb$R7GX^ z>Yr?GIU81PU@Y_ExZnMC-<>rbeOOE<8%fV05Nw=ks0%b9Gbem~@WWS&t!Ip~@)xKO z^_z2ykCXU&zsWM_f-*+aDSpAi$p{pxFkFAOifURmvw}`075Qk zA@0z4{Yiot%BqZ%Vl@~K-#{>Kr&)eop^(1G+H)#Qr??|l{T(Hv5QnAsZ?R7^{odUR zc2Fq0v&`dt<9%KERL(;-w*$bwP{V!Um&9P6@gJwucp@@3EPczpayTsH=6%OD3YtLj z8}6l_qhYuoP#usPVyk+)x?bQd_1^n7)?B3S=a&Y^A6*B789>sXc;d$;IhG1}|BI-e zgo0AAql}RL?JPIg z*YV>nMkgyF#w|loCp#xRd7kQ^1dcIP5w1mvktL6?u57swL3^z(GZPY-|irnC0d z-m0=!Y75GY_Rl70O?*1ECk<6nV3{3;+I%FR%pjzG?@yV@Yo01tz8-9T ztOF0H3l&Z~KC(`QQ_f#m_6Aw)-&i`Ah3ou}3Gv@5B_0SOf8T3k*tba;l(2#dRuO?e z?F1-_oa3#(q^bZ&3KO3^O52i*a~$y+VD<8b%(Bl?vhR&Ta;0IK|lk3K0=On@!4vtM8}+PL0M__1~-_Lb6So zc^mwptj1x|(zZ?(8CDrbTeRp)8xQEX7`&2`jcnE9Y*kQK*BU)cnJ#8}VSkk1HNq;5 z8Hc6m6dfvpIIn?@6ze4k?gC9HZ9ZrbDnwOvZsw*OU9Dd7@Yw~ zpt%W*M-kX4J8ZQD7EIm0#=jgO!FD3EZ_-So86BLD%Wj>LO<}pASwmk0L1`DFtn!T` zhcZR*Ehk7Vx3U;#yWCl(e%E&4`;wm!FTMYRYsFRKKa<0MP@{j|+_NSp56~aI{|_A~ zK=N9!*<=^_r@hVX`%$K-Lfc)V0$f)7Q_c|{nBIA#vS=mxeJAje25@tGNlP%LI_n(1 zCw9YZDdsmpg^rm(ZQNsPWgHU6^4|_<_1`pFYK^l*WeRJ>8+Eh3e{*z$I$VbcIDCGI z^C;*=QBLzn=+JyA7~Qg_mOjs+#iRQXO;m3)sxgXW&FsLdK~krTwc<1 z6jO?k(bP|wf6~hQJzq6Q+uX}LHnc6^OE{SIRdKvUaSU;bX_&us>V^#)U#i|yrXs8N zZKd`TZF=SF(7|f`3}Sq;5=Jh$Fp$SVKgjLghL$z5Y-mt?y?+8-K~AE#o9#AguJxmNn2Sh`rKq- zB=y_Kc`VQp?$ti3A&%I`o~b97J;gm-@p_2n#B5@0_nT>TW@cef$mk_$Qy_|hVXSC4 zb3q>CX(ag=v@}z7@ozy*IIp=NrU_a14BgC9Jfr^!uexNPmS@7VIP8j8zWQAP0{L)d zU?A>0R@gVv2autc>I{3^D}CPP-{DIPkNL9h!FqNK9c<19GP}-wIYuX*e?%jI(){t^ z=rKG!G&Hou^zQCXw@XS&>cb}t9GnCEq~v4`kmY=XGydB9-kXZK08H*j&w zVb%Wj#JPA!l?uyNYY?8T1tXmeE3w;R%`1^G=DYs|IpIQCHGE5uPCw~Y=G7oIS{(G2 zZ;QJ~v)#o$nHmwb42OVW^KW5RjkP-Q0fc?BL2zPN_-T34<87RWL>Hv>utSAOr3Z(o z$aJ6WXtlp7w*&4W5|XU0GCBRgE14QW>Cm!9g9GKDyrlORb$820ZeZ<3at{%28_3aKTe8jrdba z4^;KaH~8A~0_2a*M^Ozs0(c$k{t1L0jQv=#rI^ATTm~OrSaM=@o_FP?@6;pa$`t+- zc#WtS5)!W-Uo_FL?#P{H{E?^s=wWQFf z7=@0{s^6+@Nq2TxpG~;u{r*kie;pS3{saSC>-Di4uBw!;9L zg2Mo$DV59^%Ek03qt=ItHy)==5DFHpd>RkglpX^{zG)5hnCL|B!6DnRQ)dSuUytJY zCw9`XH?AU(@6_U>+C{4V6jyb{Krcx_@S~Pu00^uXUK~FNMq~t5ianPPw{fGjX^iAw ze+uh|zL^`8yLhk9KofHSGjoO^t+1!#&&6&r=*6p=bXV9%%(mlsY~FRup0t^Q*Y9ZCk zpJVSSP<^aB&oIyM@=Txj#&OoDRdl=MiV z!kja7o>CyUMLIzS@vRD;J z0+^aM<8ws1Tmw9u{4e@2YY{s-2%ZukJpNKGbc}O#)}G9Yf$Zz!RM))LE!Co@=aw&x zf?IgLY>ifeVdv5*ondq&ZxC4a@)%%xjLCVjs%%8M#=D(0+@6G>8N#;F7CYUDTKEQU zjy@h?|47|@dl3& zz@RZGhO+~5P2kI5ZJ9c7U7KmvTU(lO28kqefD%vodr|;QWA4Jev43e5fQJm26~g}p z1^j{_az@(h>^si7`(YqOu~h%R$f`Tb5D+9?9)@nBHbcv6SRFP=Ax8W zze8hBZNB8=X@ zUi&`7J?hO-mtxh+*Ce@8Wk0RBZ7bpE{d-F3B%PLZ(F41>brZdF;UqsDmyV*b zD*C*^!rIjw*;}dSAR4d-eP^##!psT0xlN$w@wE zioGje&w?b_KN%X6#u7)m67ECOWwDmqBYoKxl-vA3(5-ZX{CJe|NSVw$pt(M8glDIg z_<;;vE4JlJLH|iA+MsG|{mu+*^#C9ujIU#r2Nx@-k|F{N``2>#mp$#7TLs6$3(+Vl zKj2kL_37nldh*_pF@=w>l&8Qd)&hIz163{%O?HV5ZUdL3KdjEYU;cQ^n~tv+fo2*M zK{!5VT1VGbgK@n9YZqN4{CBedkGSOXM_rGW8l$*K4d``S!;7~g4ApVl&ZvfPV(5>h zMa~y5id6>_85QZ6SXuj7F)%QSy6*O^>fa1r*Hv@IyUp2)g{5ofG!CEu68t`EBtfwDobwFquM z?tg;NR>e%*MV5I(>K#kt?OVESvKY?_?Vryv8KgC|tuE2m?%=DX@k_9Npxc+#z`(t(DOJ*&x0>xgI=^D4xugW}|a z6r2A(97m~oC?4)E$|Z((MPEUXYcXLH&v#j>{G7IIiq3YR{mAJ`X$fC(XsBWYm{f-@ zX&$mr!BdNHDg~9?t^_mY;{yRB+jNW8mM_doIRzsY&qehQD;B;TEpQ@LzVhbtHPtfp z@rnb|jPw(Ee z=gr!qPka|EJ=17V570_V%8DDW!9M7(_zbZ#|5|9~=MI4KFTAcP%>78x0!K`fD!E+v zEI}Au*pE(1XE@wO)|)&MGyZtI#*Ga>CtQ(P+eCx<3#?SH;4k!jwj7bNwOPCNH%!}C{ zL|}+RCRjj_HuOLdFpKHAgbEMxO0t&z9HY<_w{Kf4eLabxDLP~+ks}OATxMYOMLn9x%lqJDKXM$>OSNMs z=di5P^Tvh->v|?(r+k!KWjEt34=d;UmbMN)yyoD6BOhu3<;;+*#qK@nJ=tiY!c+TJdbkt?>zV3CRET%(NQ%j8Hwk3R zc`VC)(@ertS}|y5vnNi%@w#3lJJK<7Y}TFayqh-$cTPZ063%ZECsIueQtt=vMbHJlcFc)=CR_tXl^3cDl-#0s_>#C$>G|+oR>9q*dEl4{i z*w054(8Z`+{Y9t$hrPMkc>7K1nx}CqT9>pGX@>EjR~T8Jr-+!;tA6>D*V1a75kj>~ zP$Ola)z7^X9bu;)OyjQ}B1*hA%iKk3rEzeRSn?{-;lp3gY~Q|!*F&)@vy+jJA&0Ww zIE6)Ff^E86MTyduE>ZoRmIfL&7t<=&=G^!1f6z!Tjb~OEs}l^2jbVdn`j1GBf{U?8 zt>gI?I1zKDm`vqA(33=T(KGsl0P>B-Mhg8xODh}CLeY}#8V(U+glBm=n_CrY$)B`R z*`T1d(l3P%3m%PaxO%$<-h8Q_z`F-%6RqR^6FuwEcI)P(bnJYjSoy0jdv(cL0GPdl zo8cwlJ46X%7uv!%sh@Nlwros`<7>~}m!X1vlpM<@mfC(9QxU-N@{tr#~Y32 zznR2jbd;v)a#ge9dIqId=|seneNquY^_$Lim;`3ao7|S&nWR>fJe6HAT}ai`qwHpb zF~1laXwYAvE2}7TAOJCP?k+!+U&!YHad`Xp#geV*~c>)KY`huXM%(lLa`J zI@;qc$vQsI<;?H@u&Jc)Jr&iU&_chrn3mEUNqHDREU<6}QgTSkv^DrhE=Y0ahs4EB zNA>Q$qms*nkwlhxb)Zt``Ieoyi+j##dQ5GGwN6?}Y&4FS4#hg&(l0s?7Varl1N2m&a{}z%8OBfr40dt z<+i*w?|#4vSr(y2UzmvH(}exNG_FmIq;`v(nF(&lhD8zZdFp^44&N{Pob4vBkvnD0 zd@_cUgv4s=Vod zqKm|}iAcvyhttXc(&K-lhvb>!h3|*979(%(n`hj%Q{^HrnCR%fNGd5QVRycwfc|Jx z7WTZvD?43xtf=dcnKH0T$&J0v(e%HMVjIpnJZO@fa*Z>xO)2ufWhlMd8gZz_RFOCf z75lTu0nffS5wPPzDZo6HiCtj6za$U+i5@@N%_;#((ybN(k;R+Df$c@!+!aux>hetQA zjJ?n;k!4qswmp3b2=H>#pO?v`pa7GwrY(;;-=I3e0H-i=6jEwd#lDN%9(mdL-2Dr+ z-~EL4Ii~{J7A>izbsE4g+4{+ZyHiTSSt|!?^v%DLz?j{`>O`+g)-Bz(t3LoN>$Q5FF%Oj({+tgUY#k{7qSo2xf8yxU1JF_d>>&m)uY_MZEZ3k8Y zpC>&OX>BbiLJJX(8G(o}sE9u6*moPlvs4>nv&K4}xTXUe+%BYBb%A z^v(DMlazyX&w!d687*#ETU7JNc06%q3l@~baaU>`f!9)maN)bjWM?F1C)<5t+UG7n zIE3?uo%ySvpo2q17Fyq9@8Z|q;6clMdn8p3xVJOlPTJ95Qqe&&##{bVM%xK*V#okX zv0Hhx9Ux6XHzyBb(pyp1PXpS{U(A3Z%G%t=+|vTyjMWq3`I)Sp?H{!Eza!K?Aiu>G z%1SAee?6tf#Ety8U}tGn;b`nCXR7{K!=K-;(6CTgZA4J;DTQ50NzVhXxSELSEPC=~m1t@vcUvy-BA$1vEFJ&cSTpLv zJM_WOUOJ;HZ`>oF_uMjT*wMc1Sa;^u*SI=y+Vej70Eabq4RG<&VZZZ3ta{}DB$hIm z#Kz?WIe6Cop(az)OHd~(HTZnq4mmvHC+Bl?#d~0cn%cFKf3_!l28t|PwBZus#oH;~ zgcq7`zTZqS^jpLeBb(rqVIZHebp|)xx$>(%)J>Sc-3Wlgr~qVt#4R1QJ_$c{P47rH~>Q^ zbgAcaXUA`=6HlsRr)0r4O@DtDnyV>oSQ84&hcZa8U#wc?0!i`W$sXohd>K_hDQlob zQ&407lISd2Gl~CG$4=h}=6uRy$qEVIfNl(Z{(GTj4>@aB{3|;&{O>h5b82ys1~Hzz zr>_jLL_Y;O2wI(-d5j%b2NW(_y~8}Nh=I-ZFFjrmS3;@eSdj=7gDNruktQr*tPW2w z_mXo{k|%-O(fwdjjtZ_<&WRm3^g+>{zs*Mln6at$^=nB0*MG;O3#`yP=^8V8u5`#? zOaT`uJSe<+2|`sgOI#)*D0-8MsjxT7W8%UG?pe^>89~__8%;<^DxLVToAbfKiyJ)C zD5p3w{^)?f4&Ma{kg8ZTjkDFw^9?Et^-s{*f>#wlZ*_2%dZjF~(^%$w2$UE&e(#zT z$8Q{(jP98?7TYR6nFv=OI>e@=YD7}r(1mM6z+ zDnel<10t@@LAUK3;EwTRI=5}6azi9CQ!!Ovo{C&BjA@|eL!X`v5|h> zdxRvqfHcVR@-lDH^f-tZ=C&x^U~xloceU1X|M`XrFKSrQ{7QK-)2mP zs_U6KXLK`wnvgwWmN)Im^X@lzzt;veW=n$DZ4B7pseu3@gR|{bOaf00_Wv!9jvgoL zttC1-$`hZNxd>k^T}=HoA>F2Ftfhqut#;-6-rqMgMe{Dj0_4P)uk~$|Uyu=JFX_N+s1d@+hg5Qkc~>ENC#xBIB?-AzgWt}ctJZEf*#Mjn+S`3AM_5QLOL zWvw`!L^$Es$B6vFIF-&C6ZLKRW^!j#6+4pdK8D)hz0pyLG?{YHZy!HVmtBPd)1ja<#)*5Ns6-uY@{rQ*{1{PNJ zr!pt__&b#q-yIyNq2G-dWRyDdhvfK~ih*s4fKkgy^Uh+cz;x#E@`M(!2;slUmhr_nU|}5 z$L>n#D2A%-HhMyc^>ytYq{lYv7||-h;b4EES-xKNDg~5s(x1w!9_?IHo>^lbOT}1v zihP1l`j^sP#I@_a#&4~q&eI#AuhpMnve47$Wrh6Fi~Vh&`CF$Cq;tGlsjq;wHxk(K z@mFViStn7XEzzH29c!ukr{z

;L5=04Rp8mZA*9LMd{#sKT(c>+|aFY-A1Mv} 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 P3gzzN>0JF?;I;n(WmgE3 literal 261236 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 BC%z4OL%+<<%; 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

gd{uBQCe{D4MxBzuzAStWK-$$SIfZ`JkL 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

3zJ_zTQ-Rci5JM07#@%c8qjqcb?IQ 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 O9}82PA4>l2dFS5{H|(7N diff --git a/common/dist/sprites/spritesmith-main-6.css b/common/dist/sprites/spritesmith-main-6.css index 498fc4512c..dc8f7ddb1e 100644 --- a/common/dist/sprites/spritesmith-main-6.css +++ b/common/dist/sprites/spritesmith-main-6.css @@ -4,9 +4,15 @@ width: 221px; height: 39px; } +.quest_goldenknight1 { + background-image: url(spritesmith-main-6.png); + background-position: -724px -1176px; + width: 221px; + height: 39px; +} .quest_goldenknight2 { background-image: url(spritesmith-main-6.png); - background-position: 0px -1070px; + background-position: -251px -1070px; width: 250px; height: 150px; } @@ -114,7 +120,7 @@ } .quest_sheep { background-image: url(spritesmith-main-6.png); - background-position: -440px -672px; + background-position: -220px 0px; width: 219px; height: 219px; } @@ -132,13 +138,13 @@ } .quest_snake { background-image: url(spritesmith-main-6.png); - background-position: -1100px -401px; + background-position: -657px -892px; width: 216px; height: 177px; } .quest_spider { background-image: url(spritesmith-main-6.png); - background-position: -251px -1070px; + background-position: 0px -1070px; width: 250px; height: 150px; } @@ -150,7 +156,7 @@ } .quest_stressbeast_bailey { background-image: url(spritesmith-main-6.png); - background-position: -220px 0px; + background-position: -440px -672px; width: 219px; height: 219px; } @@ -180,7 +186,7 @@ } .quest_trex_undead { background-image: url(spritesmith-main-6.png); - background-position: -657px -892px; + background-position: -1100px -579px; width: 216px; height: 177px; } @@ -204,7 +210,7 @@ } .quest_vice3 { background-image: url(spritesmith-main-6.png); - background-position: -1100px -579px; + background-position: -1100px -401px; width: 216px; height: 177px; } @@ -216,1429 +222,1429 @@ } .quest_dilatoryDistress1_blueFins { background-image: url(spritesmith-main-6.png); - background-position: -1640px -264px; + background-position: -1244px -1070px; width: 51px; height: 48px; } .quest_dilatoryDistress1_fireCoral { background-image: url(spritesmith-main-6.png); - background-position: -1640px -313px; + background-position: -1244px -1119px; width: 48px; height: 51px; } .quest_egg_plainEgg { background-image: url(spritesmith-main-6.png); - background-position: -1689px -313px; + background-position: -1484px -1221px; width: 48px; height: 51px; } .quest_goldenknight1_testimony { background-image: url(spritesmith-main-6.png); - background-position: -1640px -365px; + background-position: -1484px -1273px; width: 48px; height: 51px; } .quest_moonstone1_moonstone { background-image: url(spritesmith-main-6.png); - background-position: -856px -1176px; + background-position: -1640px -1560px; width: 30px; height: 30px; } .quest_vice2_lightCrystal { background-image: url(spritesmith-main-6.png); - background-position: -873px -1643px; + background-position: -123px -1643px; width: 40px; height: 40px; } .inventory_quest_scroll_atom1 { background-image: url(spritesmith-main-6.png); - background-position: -1640px -417px; + background-position: -1484px -1379px; width: 48px; height: 51px; } .inventory_quest_scroll_atom1_locked { background-image: url(spritesmith-main-6.png); - background-position: -1689px -365px; + background-position: -1484px -1327px; width: 48px; height: 51px; } .inventory_quest_scroll_atom2 { background-image: url(spritesmith-main-6.png); - background-position: -1640px -469px; + background-position: -1427px -1433px; width: 48px; height: 51px; } .inventory_quest_scroll_atom2_locked { background-image: url(spritesmith-main-6.png); - background-position: -1689px -417px; + background-position: -1378px -1433px; width: 48px; height: 51px; } .inventory_quest_scroll_atom3 { background-image: url(spritesmith-main-6.png); - background-position: -1640px -521px; + background-position: -1525px -1433px; width: 48px; height: 51px; } .inventory_quest_scroll_atom3_locked { background-image: url(spritesmith-main-6.png); - background-position: -1689px -469px; + background-position: -1476px -1433px; width: 48px; height: 51px; } .inventory_quest_scroll_basilist { background-image: url(spritesmith-main-6.png); - background-position: -1689px -521px; + background-position: -1574px -1433px; width: 48px; height: 51px; } .inventory_quest_scroll_bunny { background-image: url(spritesmith-main-6.png); - background-position: -1640px -573px; + background-position: -1378px -1485px; width: 48px; height: 51px; } .inventory_quest_scroll_cheetah { background-image: url(spritesmith-main-6.png); - background-position: -1689px -573px; + background-position: -1427px -1485px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatoryDistress1 { background-image: url(spritesmith-main-6.png); - background-position: -1640px -885px; + background-position: -1525px -1485px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatoryDistress2 { background-image: url(spritesmith-main-6.png); - background-position: -1484px -1379px; + background-position: 0px -1539px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatoryDistress2_locked { background-image: url(spritesmith-main-6.png); - background-position: -1484px -1327px; + background-position: -1574px -1485px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatoryDistress3 { background-image: url(spritesmith-main-6.png); - background-position: -1590px -1485px; + background-position: -392px -1539px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatoryDistress3_locked { background-image: url(spritesmith-main-6.png); - background-position: -1590px -1433px; + background-position: -49px -1539px; width: 48px; height: 51px; } .inventory_quest_scroll_dilatory_derby { background-image: url(spritesmith-main-6.png); - background-position: -1640px -625px; + background-position: -1476px -1485px; width: 48px; height: 51px; } .inventory_quest_scroll_egg { background-image: url(spritesmith-main-6.png); - background-position: 0px -1539px; + background-position: -1640px -416px; width: 48px; height: 51px; } .inventory_quest_scroll_evilsanta { background-image: url(spritesmith-main-6.png); - background-position: -49px -1539px; + background-position: -1640px -468px; width: 48px; height: 51px; } .inventory_quest_scroll_evilsanta2 { background-image: url(spritesmith-main-6.png); - background-position: -98px -1539px; + background-position: -1640px -520px; width: 48px; height: 51px; } .inventory_quest_scroll_falcon { background-image: url(spritesmith-main-6.png); - background-position: -147px -1539px; + background-position: -1640px -572px; width: 48px; height: 51px; } .inventory_quest_scroll_frog { background-image: url(spritesmith-main-6.png); - background-position: -196px -1539px; + background-position: -1640px -624px; width: 48px; height: 51px; } .inventory_quest_scroll_ghost_stag { background-image: url(spritesmith-main-6.png); - background-position: -245px -1539px; + background-position: -1640px -676px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight1 { background-image: url(spritesmith-main-6.png); - background-position: -1323px -1539px; + background-position: -1640px -780px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight1_locked { background-image: url(spritesmith-main-6.png); - background-position: -1127px -1539px; + background-position: -1640px -728px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight2 { background-image: url(spritesmith-main-6.png); - background-position: -1421px -1539px; + background-position: -1640px -884px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight2_locked { background-image: url(spritesmith-main-6.png); - background-position: -1372px -1539px; + background-position: -1640px -832px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight3 { background-image: url(spritesmith-main-6.png); - background-position: -1519px -1539px; + background-position: -441px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_goldenknight3_locked { background-image: url(spritesmith-main-6.png); - background-position: -1470px -1539px; + background-position: -49px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_gryphon { background-image: url(spritesmith-main-6.png); - background-position: -1568px -1539px; + background-position: -490px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_harpy { background-image: url(spritesmith-main-6.png); - background-position: -1617px -1539px; + background-position: -539px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_hedgehog { background-image: url(spritesmith-main-6.png); - background-position: -1666px -1539px; + background-position: -588px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_horse { background-image: url(spritesmith-main-6.png); - background-position: 0px -1591px; + background-position: -637px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_kraken { background-image: url(spritesmith-main-6.png); - background-position: -1519px -1591px; + background-position: -686px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_monkey { background-image: url(spritesmith-main-6.png); - background-position: -1470px -1591px; + background-position: -1689px -520px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone1 { background-image: url(spritesmith-main-6.png); - background-position: -1372px -1591px; + background-position: -1689px -416px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone1_locked { background-image: url(spritesmith-main-6.png); - background-position: -1421px -1591px; + background-position: -1689px -468px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone2 { background-image: url(spritesmith-main-6.png); - background-position: -1274px -1591px; + background-position: -1689px -312px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone2_locked { background-image: url(spritesmith-main-6.png); - background-position: -1323px -1591px; + background-position: -1689px -364px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone3 { background-image: url(spritesmith-main-6.png); - background-position: -1176px -1591px; + background-position: -1689px -208px; width: 48px; height: 51px; } .inventory_quest_scroll_moonstone3_locked { background-image: url(spritesmith-main-6.png); - background-position: -1225px -1591px; + background-position: -1689px -260px; width: 48px; height: 51px; } .inventory_quest_scroll_octopus { background-image: url(spritesmith-main-6.png); - background-position: -1127px -1591px; + background-position: -1689px -156px; width: 48px; height: 51px; } .inventory_quest_scroll_owl { background-image: url(spritesmith-main-6.png); - background-position: -1078px -1591px; + background-position: -1689px -104px; width: 48px; height: 51px; } .inventory_quest_scroll_penguin { background-image: url(spritesmith-main-6.png); - background-position: -1029px -1591px; + background-position: -1689px -52px; width: 48px; height: 51px; } .inventory_quest_scroll_rat { background-image: url(spritesmith-main-6.png); - background-position: -980px -1591px; + background-position: -1689px 0px; width: 48px; height: 51px; } .inventory_quest_scroll_rock { background-image: url(spritesmith-main-6.png); - background-position: -931px -1591px; + background-position: -1617px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_rooster { background-image: url(spritesmith-main-6.png); - background-position: -882px -1591px; + background-position: -1568px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_sabretooth { background-image: url(spritesmith-main-6.png); - background-position: -833px -1591px; + background-position: -1519px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_sheep { background-image: url(spritesmith-main-6.png); - background-position: -784px -1591px; + background-position: -1470px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_slime { background-image: url(spritesmith-main-6.png); - background-position: -735px -1591px; + background-position: -1421px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_snail { background-image: url(spritesmith-main-6.png); - background-position: -686px -1591px; + background-position: -1372px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_snake { background-image: url(spritesmith-main-6.png); - background-position: -637px -1591px; + background-position: -1323px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_spider { background-image: url(spritesmith-main-6.png); - background-position: -588px -1591px; + background-position: -1274px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_treeling { background-image: url(spritesmith-main-6.png); - background-position: -539px -1591px; + background-position: -1225px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_trex { background-image: url(spritesmith-main-6.png); - background-position: -441px -1591px; + background-position: -1127px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_trex_undead { background-image: url(spritesmith-main-6.png); - background-position: -490px -1591px; + background-position: -1176px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_unicorn { background-image: url(spritesmith-main-6.png); - background-position: -392px -1591px; + background-position: -1078px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice1 { background-image: url(spritesmith-main-6.png); - background-position: -294px -1591px; + background-position: -980px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice1_locked { background-image: url(spritesmith-main-6.png); - background-position: -343px -1591px; + background-position: -1029px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice2 { background-image: url(spritesmith-main-6.png); - background-position: -196px -1591px; + background-position: -882px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice2_locked { background-image: url(spritesmith-main-6.png); - background-position: -245px -1591px; + background-position: -931px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice3 { background-image: url(spritesmith-main-6.png); - background-position: -98px -1591px; + background-position: -784px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_vice3_locked { background-image: url(spritesmith-main-6.png); - background-position: -147px -1591px; + background-position: -833px -1591px; width: 48px; height: 51px; } .inventory_quest_scroll_whale { background-image: url(spritesmith-main-6.png); - background-position: -49px -1591px; + background-position: -735px -1591px; width: 48px; height: 51px; } .shop_copper { background-image: url(spritesmith-main-6.png); - background-position: -1706px -1509px; + background-position: -1166px -863px; width: 32px; height: 22px; } .shop_eyes { background-image: url(spritesmith-main-6.png); - background-position: -422px -1643px; + background-position: -1689px -1023px; width: 40px; height: 40px; } .shop_gold { background-image: url(spritesmith-main-6.png); - background-position: -1673px -1509px; + background-position: -1133px -863px; width: 32px; height: 22px; } .shop_opaquePotion { background-image: url(spritesmith-main-6.png); - background-position: -340px -1643px; + background-position: -1689px -1228px; width: 40px; height: 40px; } .shop_potion { background-image: url(spritesmith-main-6.png); - background-position: -299px -1643px; + background-position: -1689px -1064px; width: 40px; height: 40px; } .shop_reroll { background-image: url(spritesmith-main-6.png); - background-position: -217px -1643px; + background-position: -1689px -1105px; width: 40px; height: 40px; } .shop_seafoam { background-image: url(spritesmith-main-6.png); - background-position: -823px -1176px; + background-position: -1012px -1176px; width: 32px; height: 32px; } .shop_shinySeed { background-image: url(spritesmith-main-6.png); - background-position: -724px -1176px; + background-position: -1045px -1176px; width: 32px; height: 32px; } .shop_silver { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1509px; + background-position: -1100px -863px; width: 32px; height: 22px; } .shop_snowball { background-image: url(spritesmith-main-6.png); - background-position: -757px -1176px; + background-position: -979px -1176px; width: 32px; height: 32px; } .shop_spookySparkles { background-image: url(spritesmith-main-6.png); - background-position: -790px -1176px; + background-position: -946px -1176px; width: 32px; height: 32px; } .shop_backStab { background-image: url(spritesmith-main-6.png); - background-position: -258px -1643px; + background-position: -164px -1643px; width: 40px; height: 40px; } .shop_brightness { background-image: url(spritesmith-main-6.png); - background-position: -381px -1643px; + background-position: -205px -1643px; width: 40px; height: 40px; } .shop_defensiveStance { background-image: url(spritesmith-main-6.png); - background-position: -586px -1643px; + background-position: -82px -1643px; width: 40px; height: 40px; } .shop_earth { background-image: url(spritesmith-main-6.png); - background-position: -914px -1643px; + background-position: -41px -1643px; width: 40px; height: 40px; } .shop_fireball { background-image: url(spritesmith-main-6.png); - background-position: -996px -1643px; + background-position: 0px -1643px; width: 40px; height: 40px; } .shop_frost { background-image: url(spritesmith-main-6.png); - background-position: -955px -1643px; + background-position: -1689px -1597px; width: 40px; height: 40px; } .shop_heal { background-image: url(spritesmith-main-6.png); - background-position: -832px -1643px; + background-position: -1689px -1556px; width: 40px; height: 40px; } .shop_healAll { background-image: url(spritesmith-main-6.png); - background-position: -791px -1643px; + background-position: -1689px -1515px; width: 40px; height: 40px; } .shop_intimidate { background-image: url(spritesmith-main-6.png); - background-position: -750px -1643px; + background-position: -1689px -1474px; width: 40px; height: 40px; } .shop_mpheal { background-image: url(spritesmith-main-6.png); - background-position: -709px -1643px; + background-position: -1689px -1433px; width: 40px; height: 40px; } .shop_pickPocket { background-image: url(spritesmith-main-6.png); - background-position: -668px -1643px; + background-position: -1689px -1392px; width: 40px; height: 40px; } .shop_protectAura { background-image: url(spritesmith-main-6.png); - background-position: -627px -1643px; + background-position: -1689px -1351px; width: 40px; height: 40px; } .shop_smash { background-image: url(spritesmith-main-6.png); - background-position: -176px -1643px; + background-position: -1689px -1310px; width: 40px; height: 40px; } .shop_stealth { background-image: url(spritesmith-main-6.png); - background-position: -545px -1643px; + background-position: -1689px -1269px; width: 40px; height: 40px; } .shop_toolsOfTrade { background-image: url(spritesmith-main-6.png); - background-position: -504px -1643px; + background-position: -1689px -1146px; width: 40px; height: 40px; } .shop_valorousPresence { background-image: url(spritesmith-main-6.png); - background-position: -463px -1643px; + background-position: -1689px -1187px; width: 40px; height: 40px; } .Pet_Egg_BearCub { background-image: url(spritesmith-main-6.png); - background-position: -1689px -625px; + background-position: -98px -1539px; width: 48px; height: 51px; } .Pet_Egg_Bunny { background-image: url(spritesmith-main-6.png); - background-position: -1640px -677px; + background-position: -147px -1539px; width: 48px; height: 51px; } .Pet_Egg_Cactus { background-image: url(spritesmith-main-6.png); - background-position: -1689px -677px; + background-position: -196px -1539px; width: 48px; height: 51px; } .Pet_Egg_Cheetah { background-image: url(spritesmith-main-6.png); - background-position: -1640px -729px; + background-position: -245px -1539px; width: 48px; height: 51px; } .Pet_Egg_Cuttlefish { background-image: url(spritesmith-main-6.png); - background-position: -1689px -729px; + background-position: -294px -1539px; width: 48px; height: 51px; } .Pet_Egg_Deer { background-image: url(spritesmith-main-6.png); - background-position: -1640px -781px; + background-position: -343px -1539px; width: 48px; height: 51px; } .Pet_Egg_Dragon { background-image: url(spritesmith-main-6.png); - background-position: -1689px -781px; + background-position: -1583px -1378px; width: 48px; height: 51px; } .Pet_Egg_Egg { background-image: url(spritesmith-main-6.png); - background-position: -1640px -833px; + background-position: -441px -1539px; width: 48px; height: 51px; } .Pet_Egg_Falcon { background-image: url(spritesmith-main-6.png); - background-position: -1689px -833px; + background-position: -490px -1539px; width: 48px; height: 51px; } .Pet_Egg_FlyingPig { background-image: url(spritesmith-main-6.png); - background-position: -1689px -212px; + background-position: -539px -1539px; width: 48px; height: 51px; } .Pet_Egg_Fox { background-image: url(spritesmith-main-6.png); - background-position: -1689px -885px; + background-position: -588px -1539px; width: 48px; height: 51px; } .Pet_Egg_Frog { background-image: url(spritesmith-main-6.png); - background-position: -1640px -937px; + background-position: -637px -1539px; width: 48px; height: 51px; } .Pet_Egg_Gryphon { background-image: url(spritesmith-main-6.png); - background-position: -1689px -937px; + background-position: -686px -1539px; width: 48px; height: 51px; } .Pet_Egg_Hedgehog { background-image: url(spritesmith-main-6.png); - background-position: -1640px -989px; + background-position: -735px -1539px; width: 48px; height: 51px; } .Pet_Egg_Horse { background-image: url(spritesmith-main-6.png); - background-position: -1689px -989px; + background-position: -784px -1539px; width: 48px; height: 51px; } .Pet_Egg_LionCub { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1041px; + background-position: -833px -1539px; width: 48px; height: 51px; } .Pet_Egg_Monkey { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1041px; + background-position: -882px -1539px; width: 48px; height: 51px; } .Pet_Egg_Octopus { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1093px; + background-position: -931px -1539px; width: 48px; height: 51px; } .Pet_Egg_Owl { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1093px; + background-position: -980px -1539px; width: 48px; height: 51px; } .Pet_Egg_PandaCub { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1145px; + background-position: -1029px -1539px; width: 48px; height: 51px; } .Pet_Egg_Parrot { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1145px; + background-position: -1078px -1539px; width: 48px; height: 51px; } .Pet_Egg_Penguin { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1197px; + background-position: -1127px -1539px; width: 48px; height: 51px; } .Pet_Egg_PolarBear { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1197px; + background-position: -1176px -1539px; width: 48px; height: 51px; } .Pet_Egg_Rat { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1249px; + background-position: -1225px -1539px; width: 48px; height: 51px; } .Pet_Egg_Rock { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1249px; + background-position: -1274px -1539px; width: 48px; height: 51px; } .Pet_Egg_Rooster { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1301px; + background-position: -1323px -1539px; width: 48px; height: 51px; } .Pet_Egg_Sabretooth { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1301px; + background-position: -1372px -1539px; width: 48px; height: 51px; } .Pet_Egg_Seahorse { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1353px; + background-position: -1421px -1539px; width: 48px; height: 51px; } .Pet_Egg_Sheep { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1353px; + background-position: -1470px -1539px; width: 48px; height: 51px; } .Pet_Egg_Slime { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1405px; + background-position: -1519px -1539px; width: 48px; height: 51px; } .Pet_Egg_Snail { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1405px; + background-position: -1568px -1539px; width: 48px; height: 51px; } .Pet_Egg_Snake { background-image: url(spritesmith-main-6.png); - background-position: -1640px -1457px; + background-position: -1640px 0px; width: 48px; height: 51px; } .Pet_Egg_Spider { background-image: url(spritesmith-main-6.png); - background-position: -1689px -1457px; + background-position: -1640px -52px; width: 48px; height: 51px; } .Pet_Egg_TRex { background-image: url(spritesmith-main-6.png); - background-position: -1244px -1070px; + background-position: -1640px -208px; width: 48px; height: 51px; } .Pet_Egg_TigerCub { background-image: url(spritesmith-main-6.png); - background-position: -1534px -1378px; + background-position: -1640px -104px; width: 48px; height: 51px; } .Pet_Egg_Treeling { background-image: url(spritesmith-main-6.png); - background-position: -1583px -1378px; + background-position: -1640px -156px; width: 48px; height: 51px; } .Pet_Egg_Unicorn { background-image: url(spritesmith-main-6.png); - background-position: -1244px -1122px; + background-position: -1640px -260px; width: 48px; height: 51px; } .Pet_Egg_Whale { background-image: url(spritesmith-main-6.png); - background-position: -1484px -1221px; + background-position: -1640px -312px; width: 48px; height: 51px; } .Pet_Egg_Wolf { background-image: url(spritesmith-main-6.png); - background-position: -1484px -1273px; + background-position: -1640px -364px; width: 48px; height: 51px; } .Pet_Food_Cake_Base { background-image: url(spritesmith-main-6.png); - background-position: -88px -1643px; + background-position: -1689px -891px; width: 43px; height: 43px; } .Pet_Food_Cake_CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: 0px -1689px; + background-position: -1689px -935px; width: 42px; height: 44px; } .Pet_Food_Cake_CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: 0px -1643px; + background-position: -1689px -710px; width: 43px; height: 45px; } .Pet_Food_Cake_Desert { background-image: url(spritesmith-main-6.png); - background-position: -1656px -1591px; + background-position: -1689px -846px; width: 43px; height: 44px; } .Pet_Food_Cake_Golden { background-image: url(spritesmith-main-6.png); - background-position: -132px -1643px; + background-position: -1689px -980px; width: 43px; height: 42px; } .Pet_Food_Cake_Red { background-image: url(spritesmith-main-6.png); - background-position: -1700px -1591px; + background-position: -1689px -756px; width: 43px; height: 44px; } .Pet_Food_Cake_Shade { background-image: url(spritesmith-main-6.png); - background-position: -44px -1643px; + background-position: -1689px -801px; width: 43px; height: 44px; } .Pet_Food_Cake_Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -1568px -1591px; + background-position: -1689px -617px; width: 42px; height: 47px; } .Pet_Food_Cake_White { background-image: url(spritesmith-main-6.png); - background-position: -1611px -1591px; + background-position: -1689px -665px; width: 44px; height: 44px; } .Pet_Food_Cake_Zombie { background-image: url(spritesmith-main-6.png); - background-position: -1692px -264px; + background-position: -1689px -572px; width: 45px; height: 44px; } .Pet_Food_Candy_Base { background-image: url(spritesmith-main-6.png); - background-position: -294px -1539px; + background-position: -1640px -936px; width: 48px; height: 51px; } .Pet_Food_Candy_CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -343px -1539px; + background-position: -1640px -988px; width: 48px; height: 51px; } .Pet_Food_Candy_CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -392px -1539px; + background-position: -1640px -1040px; width: 48px; height: 51px; } .Pet_Food_Candy_Desert { background-image: url(spritesmith-main-6.png); - background-position: -441px -1539px; + background-position: -1640px -1092px; width: 48px; height: 51px; } .Pet_Food_Candy_Golden { background-image: url(spritesmith-main-6.png); - background-position: -490px -1539px; + background-position: -1640px -1144px; width: 48px; height: 51px; } .Pet_Food_Candy_Red { background-image: url(spritesmith-main-6.png); - background-position: -539px -1539px; + background-position: -1640px -1196px; width: 48px; height: 51px; } .Pet_Food_Candy_Shade { background-image: url(spritesmith-main-6.png); - background-position: -588px -1539px; + background-position: -1640px -1248px; width: 48px; height: 51px; } .Pet_Food_Candy_Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -637px -1539px; + background-position: -1640px -1300px; width: 48px; height: 51px; } .Pet_Food_Candy_White { background-image: url(spritesmith-main-6.png); - background-position: -686px -1539px; + background-position: -1640px -1352px; width: 48px; height: 51px; } .Pet_Food_Candy_Zombie { background-image: url(spritesmith-main-6.png); - background-position: -735px -1539px; + background-position: -1640px -1404px; width: 48px; height: 51px; } .Pet_Food_Chocolate { background-image: url(spritesmith-main-6.png); - background-position: -784px -1539px; + background-position: -1640px -1456px; width: 48px; height: 51px; } .Pet_Food_CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -833px -1539px; + background-position: -1640px -1508px; width: 48px; height: 51px; } .Pet_Food_CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -882px -1539px; + background-position: 0px -1591px; width: 48px; height: 51px; } .Pet_Food_Fish { background-image: url(spritesmith-main-6.png); - background-position: -931px -1539px; + background-position: -1534px -1378px; width: 48px; height: 51px; } .Pet_Food_Honey { background-image: url(spritesmith-main-6.png); - background-position: -980px -1539px; + background-position: -98px -1591px; width: 48px; height: 51px; } .Pet_Food_Meat { background-image: url(spritesmith-main-6.png); - background-position: -1029px -1539px; + background-position: -147px -1591px; width: 48px; height: 51px; } .Pet_Food_Milk { background-image: url(spritesmith-main-6.png); - background-position: -1078px -1539px; + background-position: -196px -1591px; width: 48px; height: 51px; } .Pet_Food_Potatoe { background-image: url(spritesmith-main-6.png); - background-position: -1640px -212px; + background-position: -245px -1591px; width: 48px; height: 51px; } .Pet_Food_RottenMeat { background-image: url(spritesmith-main-6.png); - background-position: -1176px -1539px; + background-position: -294px -1591px; width: 48px; height: 51px; } .Pet_Food_Saddle { background-image: url(spritesmith-main-6.png); - background-position: -1225px -1539px; + background-position: -343px -1591px; width: 48px; height: 51px; } .Pet_Food_Strawberry { background-image: url(spritesmith-main-6.png); - background-position: -1274px -1539px; + background-position: -392px -1591px; width: 48px; height: 51px; } .Mount_Body_BearCub-Base { background-image: url(spritesmith-main-6.png); - background-position: -1640px 0px; + background-position: -1166px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -1484px -1433px; + background-position: -1060px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -1378px -1433px; + background-position: -954px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Desert { background-image: url(spritesmith-main-6.png); - background-position: -1272px -1433px; + background-position: -848px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Floral { background-image: url(spritesmith-main-6.png); - background-position: -1166px -1433px; + background-position: -742px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Golden { background-image: url(spritesmith-main-6.png); - background-position: -1060px -1433px; + background-position: -636px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Peppermint { background-image: url(spritesmith-main-6.png); - background-position: -954px -1433px; + background-position: -530px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Polar { background-image: url(spritesmith-main-6.png); - background-position: -848px -1433px; + background-position: -424px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Red { background-image: url(spritesmith-main-6.png); - background-position: -742px -1433px; + background-position: -318px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Shade { background-image: url(spritesmith-main-6.png); - background-position: -636px -1433px; + background-position: -212px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -530px -1433px; + background-position: -106px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-Spooky { background-image: url(spritesmith-main-6.png); - background-position: -424px -1433px; + background-position: 0px -1433px; width: 105px; height: 105px; } .Mount_Body_BearCub-White { background-image: url(spritesmith-main-6.png); - background-position: -318px -1433px; + background-position: -1534px -1272px; width: 105px; height: 105px; } .Mount_Body_BearCub-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -212px -1433px; + background-position: -1534px -1166px; width: 105px; height: 105px; } .Mount_Body_Bunny-Base { background-image: url(spritesmith-main-6.png); - background-position: -106px -1433px; + background-position: -1534px -1060px; width: 105px; height: 105px; } .Mount_Body_Bunny-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: 0px -1433px; + background-position: -1534px -954px; width: 105px; height: 105px; } .Mount_Body_Bunny-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -1534px -1272px; + background-position: -1534px -848px; width: 105px; height: 105px; } .Mount_Body_Bunny-Desert { background-image: url(spritesmith-main-6.png); - background-position: -1534px -1166px; + background-position: -1534px -742px; width: 105px; height: 105px; } .Mount_Body_Bunny-Golden { background-image: url(spritesmith-main-6.png); - background-position: -1534px -1060px; + background-position: -1534px -636px; width: 105px; height: 105px; } .Mount_Body_Bunny-Red { background-image: url(spritesmith-main-6.png); - background-position: -1534px -954px; + background-position: -1534px -530px; width: 105px; height: 105px; } .Mount_Body_Bunny-Shade { background-image: url(spritesmith-main-6.png); - background-position: -1534px -848px; + background-position: -1534px -424px; width: 105px; height: 105px; } .Mount_Body_Bunny-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -1534px -742px; + background-position: -1534px -318px; width: 105px; height: 105px; } .Mount_Body_Bunny-White { background-image: url(spritesmith-main-6.png); - background-position: -1534px -636px; + background-position: -1534px -212px; width: 105px; height: 105px; } .Mount_Body_Bunny-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -1534px -530px; + background-position: -1534px -106px; width: 105px; height: 105px; } .Mount_Body_Cactus-Base { background-image: url(spritesmith-main-6.png); - background-position: -1534px -424px; + background-position: -1534px 0px; width: 105px; height: 105px; } .Mount_Body_Cactus-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -1534px -318px; + background-position: -1378px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -1534px -212px; + background-position: -1272px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Desert { background-image: url(spritesmith-main-6.png); - background-position: -1534px -106px; + background-position: -1166px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Floral { background-image: url(spritesmith-main-6.png); - background-position: -1534px 0px; + background-position: -1060px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Golden { background-image: url(spritesmith-main-6.png); - background-position: -1378px -1327px; + background-position: -954px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Peppermint { background-image: url(spritesmith-main-6.png); - background-position: -1272px -1327px; + background-position: -848px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Red { background-image: url(spritesmith-main-6.png); - background-position: -1166px -1327px; + background-position: -1320px -1112px; width: 105px; height: 105px; } .Mount_Body_Cactus-Shade { background-image: url(spritesmith-main-6.png); - background-position: -1060px -1327px; + background-position: -636px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -954px -1327px; + background-position: -530px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Spooky { background-image: url(spritesmith-main-6.png); - background-position: -848px -1327px; + background-position: -424px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-White { background-image: url(spritesmith-main-6.png); - background-position: -742px -1327px; + background-position: -318px -1327px; width: 105px; height: 105px; } .Mount_Body_Cactus-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -636px -1327px; + background-position: -212px -1327px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Base { background-image: url(spritesmith-main-6.png); - background-position: -530px -1327px; + background-position: -106px -1327px; width: 105px; height: 105px; } .Mount_Body_Cheetah-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -424px -1327px; + background-position: 0px -1327px; width: 105px; height: 105px; } .Mount_Body_Cheetah-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -318px -1327px; + background-position: -1378px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Desert { background-image: url(spritesmith-main-6.png); - background-position: -212px -1327px; + background-position: -1272px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Golden { background-image: url(spritesmith-main-6.png); - background-position: -106px -1327px; + background-position: -1166px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Red { background-image: url(spritesmith-main-6.png); - background-position: 0px -1327px; + background-position: -1060px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Shade { background-image: url(spritesmith-main-6.png); - background-position: -1378px -1221px; + background-position: -954px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -1272px -1221px; + background-position: -848px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-White { background-image: url(spritesmith-main-6.png); - background-position: -1166px -1221px; + background-position: -742px -1221px; width: 105px; height: 105px; } .Mount_Body_Cheetah-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -1060px -1221px; + background-position: -636px -1221px; width: 105px; height: 105px; } .Mount_Body_Cuttlefish-Base { background-image: url(spritesmith-main-6.png); - background-position: -1320px -997px; + background-position: -1320px -537px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -1426px -882px; + background-position: -1426px -537px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -1320px -882px; + background-position: -1320px -652px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Desert { background-image: url(spritesmith-main-6.png); - background-position: -1426px -767px; + background-position: -1426px -652px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Golden { background-image: url(spritesmith-main-6.png); - background-position: -1320px -537px; + background-position: -1320px -767px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Red { background-image: url(spritesmith-main-6.png); - background-position: -1426px -652px; + background-position: -1426px -767px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Shade { background-image: url(spritesmith-main-6.png); - background-position: -1426px -997px; + background-position: -1320px -882px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -1426px -537px; + background-position: -1426px -882px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-White { background-image: url(spritesmith-main-6.png); - background-position: -1320px -767px; + background-position: -1320px -997px; width: 105px; height: 114px; } .Mount_Body_Cuttlefish-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -1320px -652px; + background-position: -1426px -997px; width: 105px; height: 114px; } .Mount_Body_Deer-Base { background-image: url(spritesmith-main-6.png); - background-position: -1138px -1070px; + background-position: -714px -1070px; width: 105px; height: 105px; } .Mount_Body_Deer-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -1032px -1070px; + background-position: -608px -1070px; width: 105px; height: 105px; } .Mount_Body_Deer-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -926px -1070px; + background-position: -502px -1070px; width: 105px; height: 105px; } .Mount_Body_Deer-Desert { background-image: url(spritesmith-main-6.png); - background-position: -820px -1070px; + background-position: -1206px -757px; width: 105px; height: 105px; } .Mount_Body_Deer-Golden { background-image: url(spritesmith-main-6.png); - background-position: -714px -1070px; + background-position: -1100px -757px; width: 105px; height: 105px; } .Mount_Body_Deer-Red { background-image: url(spritesmith-main-6.png); - background-position: -608px -1070px; + background-position: -742px -1327px; width: 105px; height: 105px; } .Mount_Body_Deer-Shade { background-image: url(spritesmith-main-6.png); - background-position: -502px -1070px; + background-position: -1426px -1112px; width: 105px; height: 105px; } .Mount_Body_Deer-Skeleton { background-image: url(spritesmith-main-6.png); - background-position: -1206px -757px; + background-position: -820px -1070px; width: 105px; height: 105px; } .Mount_Body_Deer-White { background-image: url(spritesmith-main-6.png); - background-position: -1100px -757px; + background-position: -926px -1070px; width: 105px; height: 105px; } .Mount_Body_Deer-Zombie { background-image: url(spritesmith-main-6.png); - background-position: -1426px -1112px; + background-position: -1032px -1070px; width: 105px; height: 105px; } .Mount_Body_Dragon-Base { background-image: url(spritesmith-main-6.png); - background-position: -1320px -1112px; + background-position: -1138px -1070px; width: 105px; height: 105px; } .Mount_Body_Dragon-CottonCandyBlue { background-image: url(spritesmith-main-6.png); - background-position: -1640px -106px; + background-position: 0px -1221px; width: 105px; height: 105px; } .Mount_Body_Dragon-CottonCandyPink { background-image: url(spritesmith-main-6.png); - background-position: -954px -1221px; + background-position: -106px -1221px; width: 105px; height: 105px; } .Mount_Body_Dragon-Desert { background-image: url(spritesmith-main-6.png); - background-position: -848px -1221px; + background-position: -212px -1221px; width: 105px; height: 105px; } .Mount_Body_Dragon-Floral { background-image: url(spritesmith-main-6.png); - background-position: -742px -1221px; + background-position: -318px -1221px; width: 105px; height: 105px; } .Mount_Body_Dragon-Golden { background-image: url(spritesmith-main-6.png); - background-position: -636px -1221px; + background-position: -424px -1221px; width: 105px; height: 105px; } @@ -1650,31 +1656,7 @@ } .Mount_Body_Dragon-Red { background-image: url(spritesmith-main-6.png); - background-position: -424px -1221px; - width: 105px; - height: 105px; -} -.Mount_Body_Dragon-Shade { - background-image: url(spritesmith-main-6.png); - background-position: -318px -1221px; - width: 105px; - height: 105px; -} -.Mount_Body_Dragon-Skeleton { - background-image: url(spritesmith-main-6.png); - background-position: -212px -1221px; - width: 105px; - height: 105px; -} -.Mount_Body_Dragon-Spooky { - background-image: url(spritesmith-main-6.png); - background-position: -106px -1221px; - width: 105px; - height: 105px; -} -.Mount_Body_Dragon-White { - background-image: url(spritesmith-main-6.png); - background-position: 0px -1221px; + background-position: -1272px -1433px; width: 105px; height: 105px; } diff --git a/common/dist/sprites/spritesmith-main-6.png b/common/dist/sprites/spritesmith-main-6.png index e23e25596e5155b8b17474b17567813c6dbb1c9f..3efd305bb073947dda964c3f9d43fa7a6331c33f 100644 GIT binary patch literal 301758 zcmbTecU)8H8Z|0X6cJI7B1jKG5hQf!(t`w$QM&XPYUt97f}$WTiImWrh|&fKK|1IF zMnV^mu2Q9l^bYrB=A83==bV{)&)h%s2MKJl_g?RM*R!7W?8w`CTJ&ew&zv}Mf?gZ0 z{>zCIG$im3cbW#gQc8Tcbb>17iMG1RUBBVQu~UARH>wYV4<@z*9>&x{+Uq}hw$#Jl zB{Qm)>C&}GzkT%RGLQA;XIE(^uaA`12yaDrxRr}k8{fD&cV3m}32y40RKjg^)AE8BamPv}@1GLX@wf|u;}gYBXljTc7))8^Y{-FdMWNZa`|c0m{w zh+VWOwA%gaf_5w0#64tmqg(XKR@90|)OXoDpXEyh9ovoKkw|s%hS$w(R><=tHh!$f z*LQ=sXt9Rs4{V_mY?I5(lauO@Nma0->?OjXMEaDpJ)Wxsg+YZ2rwaTzwnp`#)-qmwjczcU$Qm~KAq~+- zoRgCmm1pAzw^H0Q^WxszHOxCx^4zi6?u-_M|Bn;EM8h?I|C$XG{l~ekbZE40?;XGR z`&sZ|J^pd7|DQj)%1*lcV%K{l_v>XsRDajG*b5|EIy=j+Oq&vZKdqP1t2O%1WjjewR0zQ|A>cwDpVr&+q_;nH>G>}G z>k~f%m;ZW@B8hE+U>kjbbo}k1r5GO`9Hv!l$(dz`URGV4@6yC49`NhO@A6&x{XjoA zz|D2LF5WsJb9}T$^+}j)vZHOD+S<>$rY4m5U-{9^EoRwM5&ODgy>N%CUfAV0yFRt57X^(SvdriOl z)zR9i^&MlYEYYKV!SxNR29?l*y@~FLqZQxvuFylKgGuhAXTuXmhg$^)OKzcRp$7|A zxtmFi`_ugYM-d|~R_#E?{;#Kw_gIc)OITgy1OOJPhtCVZDqZ$gj z?z85O=U66fYAT8cMh4{kxRkTbbp>sb$7&TM*8KemtL4pC`k9)7`WfnidTBS?;<)36 z%k0{6eAed-ytjYac+Yk7dVgu6xNB({ySY5!cChEubL4AkA^b(7OkJD_))$YkHF>@= zp>ef6omM`BNR##-sZ_bV$34YIIR0Zqfv|YsZ-6f9U*RNm0f7~gN z3Gx~(w3`7mU@K=Fz4O#52y?;lmtXcC_HE}E$1g>T;Y@9ze$q4VX69leB0u=J;ztil zgd9-e_75)fZ-=GAeWcgLwQZBp&^_K{kFN`)nnyMk9P}DTD?>7H^P@$y-J7L1pH%vx zaW;2$YT@Ihv|q~L&CU@B0K6G3x7@*y1Db&8q*9IDmcqGd2LAfpcA0LD<`H?r)(VF1 zr4#ta5Z6-y_j^FYT(EndGRF z^iT38rS5}>xl3<%5tLfe3yuK!V^aRN+ompV z8$(R>UT6vE;|I&2N6-bC4UW;&Y~jSXItyYUBG0_w;7gGi2F09RW3k2n4W zDou#p??)$dL;7wO>ZVG=M=W*Jgz3qIse_M`Vvl9611`+>YROlwAI8m}xz|5X{mB?c zCjvFGmDh1u)gU>H?sRnL(L_4re7FX~jrC+{E_F}kZ9apy&QoHwpUJ&l zPjr&NE9Pe>rJ_%x?ma_RGT2S*Uwa4%STf!A`(!zRXO=?u5R)9Y1aP18zv83gygD$8 z308FxmfK(Md+#2w_C#*!<>HhBDbhoZCVF(Z52>^j0@reVyY)l&Bp|bi2fKYoHyigR z+vXY%_ZZ-m6>f*C_IGK?^E}ZcStR@MfA8J6FSui2M&@iYG+mOg1v!|Mlc#j@qD$5^ zWH8l*f3W?Qj?Mxl)<5hT@quf~)6Ou>{*zb{tRoxhq@&c$@`BlKG$Xbo3xgi7v;id zP>IR3Sb?|6=-&@mNo&V`y2I?J!pDzePgrIXG=)CLu+02W_hNC?T#2wBULjk)rS7n* zqgRw)_Md92{$vmKM?2T1@0~Ov-RM~<6}v9CD5%AY$8})@4|>L)-!w@dIu6^UTqEmJ zY*=)xC+U88v65VD5GZjsGSz!TZ|oy$a56^n^xq$NHIb+a5l!T4Y!xjM=zMkz`5Z~F z{)kU*vky1dE#KDjs47-{$^BvBe~ag zk<)!%!iR@l!RN-Cv#1FkS`=feBt+to7}N2m^*o2Cjc(!au5O0m&`1j!Z;e{tWxawD zi&`IE+=dx1o7ZrKy}pHpc$bGpP7<0i>iXqiSvfi=uYWMQnGASMflIa!AusKJ-4&HO z^e%a6BTgXsn1ZD@8YHE%&oyhs-569^M+@(r6#lFsqqA|Iwm`_5SptdIhVCYRE$z>0a58X=+bcn#Gk`Yf-ab z-kuU6k35+X9gjtI1akR5xV}M4eskF12$QZh(D63TMS5G|j6PgbD5P?v$8{G^IEJ;} z>pmM(|EsL$6MLHTH(q<(%0m_R)L-Sqq!~(=XrE#4gI#Fh$XKD?s(ylWwMM9kE6>Ca zaWOOlvL#Q*A{ko|Gx5~1oNA`U5&X(o?VBO8h2%hp{OS+dyN3fdgyjv-ySu|7gxP+0 zV0h?^H;*5OV0G6W<-iN|`(F#^HmvHOj2|oo7W5^&rVy+~)*C+?$oTv$B9=3@VRxpG z>Xq`*bk_%#*18lm6di!tP`b=LM)w-;#px>t`3OnB_1ltPQdA0-6Ao@?wqMt*kJvgE zA-qhZy-kRBhG+`&*$|7{jTxNX1I6Y{X+)QyFd}*+VoKWcU~_4tCzew_hv+ri8Sn%t zbpp$7ABJQX#(%++7R6+&QJ!QXZ01zNj@WxZ5VjjPp3KB;d@Py^oD8I1xKwhoMJQgF z3Wd?sXsN(3_^ME9Ro*PFk)k8qD*WIEc|UmFNTvLZ3GrAFIPGpcH!S-@Z~TvlEQ;mD zQzElVGPp5en{UbC_Vya(62Gh8XUL#C;s4O}e`uOxt@$q= zJC?S8>{I;M{-2jGFP@CLYE?D2>C9Rfa=?cXJ{MmE_Nyz+)~IA7SJ&&}rf#WUJ$~1^ z@rZ97(L)P8m?aB^Qok087shoABAv(30-*df#D}8^Sz~e2^0XYr*7d@l1Y)=czuu#% zsNluZ3X@ucUODEi@yFC>$zmm1p;j-VM^()Du+~0WKkWt03An+p6K1}u0?n*v^!A>f z(|lf=mmR5VmR)N`(5Rx?n)dd^AtkdN&v;679bYwxC!ieE zFOYzq{_RTA+{G%5@G`eU=4UApNY9YZQ_{vcxvww5Q^kA?=Xvtb{2n?e&&FZ20G8yX zJZPUV#D{-erpQYr-xq7VnS~ZzRyDVZA}mdhIcyc~bsx%{%i&+XtR)z_2|n+@_1rs6 z+TmiwL-OUfTR&fzD|BnnbH`dkZOF)Tc#&vFuMd)Jsow4PDFkWlMo?DIW2ZtVghobu zIIt1Fobpw(IQ3GKolU!wvLamob<&y$A9(F%zaB1@l-r;vXHw}g*-G^aT>tEjR2lwA zG_Oga8r;Z^XXQ+*OEZ}&O)?1UE+9c60rn6qDcI?@{pmm>_zZJIKg;j**#FK%6Nq1l zCpU|}IkTjfnnmyL_1j~wuw>}?pj=InnnMLm1zNv^KPYxEo5yF~p*$!-v&{KgXrOBd zrwL2L3YUKuOvjq(pMvK2D^Q#MAJqKoJre}?E3WUJ=S;hV;Lt)7D_YVSz9;UBU-zRl z^_Cdfq>7#2_Vm>ls+J!PN;3@R>zw|W@^tMaso{xTKac5yRnsQal~_%pJmK{rWBd`#H`p?!c-&SCz7_G zVcyC*y1_(3pyGzY@WyPY+Imu%wR&e>qIt-_s*32nJ$kP-vP;h8tiPj6iO6Zp%mpEi%ugDu?2} zT-0I19iwgSObub=hj4~%e~0neXT$s4mYh*4f?~&uPKLrwNVShJto}th($_iUMNM`4ynd21&O1T*|GSCcpe{bM>oFf{YS!fF)cm38{SI@mn`&2+6jQ*{!YKRBqH+u_@KlNXotDw^C|5s8 z`j~(`bGDD~Or%Amw!5MbGdKQA^E-wZ+2C2S%a~jmkh(;fSy_6REKDU2Z691{6 zS^qzW%b8bq4>obcVqoI=7{p!%oQK`GKIWma8KM_rYwTrJ#pm- zE5Dr(@8X1n;`g`C$5twV`u#t#^+)~ppFsP)G`K*@Nuz8qGEfmcMItB<#*?elD?&$r zsEfLeA(&vG_GI5{s%Q!Q~QoJY?Gim`LoHRyq}HPwwLcU%_7)4Ik~ z*9Cu?+8mZ9u8|O(I<9KRaIKKa8|={Z-|!9le$XN`%|@xm*b0GaOZMK@RZSYTv`tR@ zO~l`v=`8NFAb+%f9P^HQ*NPTEMq_C)}1m*)2Y!Hl-dfN=HxS)ewP*4Q=N*0vgHKKTu?f`$BLXtC5RC zhP+P}`I*dvC&q{*gDOM35Qvl|Hn864qjw%9Sw6aiDVnJ~=sco*#+bJM89ESmr?LGP zO3IRgg5Kz|cLp|#Z?3E^8$WGCm9^mmB0@{xox;y=4rdvNoDA25M$kR_{NXh-#mD^o zZ`at=9>CF;gk=G9{taZoXWAz}9hH|nI!t;QfxEd0d+37vIone(FGzwU%dU|idGzcF z&j63IuD9KHSJHh{TZP53BJ~Zurb<4ZyD43Ao_}->nTWU zDPs1NDYfp`%+hM<`V+hco5)jTvG$;71rFuu8;%n1N4@p?1{{LI)g^ztW0{Nn(xvB$ z%FuQC#5gvV$Bf;2Ikve;)6rA&h!XR-%)HTnh)n3AgU^!(y^Tr|ERt!4sy+35QFyum6r09!I)!_j8n!1lv zB$qCXbt{lL}gqrZkQYFETKe=BCi(emoE#feuQ+xY+;r#-` zMk0k^sWn2{eG#@kZEOX-p$)^qprC$XEP*JIs;$_g9Zx`4zGGD!;U|-&?GWQe?_nbT z>0`&ZdVD3)kTbwn9SVb14QA;S>YK?UsZfqfJ#P#P83SGt=~=blGdl`GrPgHnP=i%uN`+d_@aUMtxTJt?L86u#N%l!{LcFRK0;Rchq@ z>*Cp1X7wX>&B*%`treDEY2x(M!%N&A3AqQp*KG+kJ{qH z?eq*i$lC9JedG7f4g5dg@D~6n(&NKFEPIFHTk&cxXd;C5d=<(y>H2xEys)r{?(64x z32g|6Y^Ad`(m~VIPtVkN>coFnGXKCoFkt^2gk&{HVU*qQps?HxqYY=4!W_0n*B)vI z|7VK*?-E>Q$asVHjt&h^WzTva^Q8vHx>Ny_=h$h~#(9#YcUcSX5)r!dNR!UXH+#s` zyqmm}+Fd~>om>5!$ggZoPFk?r`P^>^NsR})`V>vUD47GNTX*FW$sZ*yyXK-rB97in7Kw077wUQ~EyIQ?4>gBW zJdq5$WTiiFk>tW#tn}`4&!@95Dy&5U7AmcH(1E4BFa$JCmr^3qhG=ax@soTFve&6G zL3=leHW?jY!!f*b>+0}Ps|sWmcWjW@nGFYt<7AOU7&O(d@1?`YqH|A-F}*4P-U_rn zRH!!|p>`EFWH&6eMM)x{r*??TW4um`q~Wa}&!*5)>%GM9eZ`#R)DoxKJ84i4Xi0Ud ztTLw|vwn^e^$oZl&fxVPE%&t3mB3Wu@+E_3-Zg?{e^c=U@m3`8jNJVOmU8-Go;~x^CXut^2IjE{WsT7 zwJB+HC!CM^^W*;|1OZ30tv$#{BxV*^L*rCblkPRzwwythADCK`r*6=qX3t}HC{XMd zN$8NRJ`U%!32W#quEX-JRBsL(>?xvyZC_S;tr6G8n~LU_Q`7XNvkk;+8vV^BUxFeT zifNT`cqD}Gdn-_rt3ro7Y33@9mw(Wvq3xhI>pJJxdfWR(@M$^r{h_PZ0M7EDq`BM2 zRtxH^+Fsw&oJsuq?(!ds-Ou0m8Q2W|D7YcjOCo1G7_{A8&wX*u!Sp%s0jWV;ILicZd9 zY8O6>{$NO9IJFD*1_Xon7EdK85{VoRs7@}DAx*I%SL+B1O?ada97;h(awUU}bB%Pa zG5BodrO!%0C|g^U&c`B%+)A5g@|u&=)Sxzt&qW(<;=l&}1>)h{E0V-Z-&Dfc!wQt@ zJy8sa4Kr2Bcaxs5U(f55A;}kkxZJ+3>|jk(zxuG_4*aXlg2U2T%9{9 zEm3b&hpvdV7kw*a)l$T=J31AVd~JAgKUc_UtXS65>)bgXS;MEch8~N!b%U>$*UJy^ ze-4>Hr>tnW(J4%kF>3bavX0R1_;!ZP6*}gy($~CKa1tLngQA2Vqb}Si+`emS2;n$@ zYYe~oX!i0?+@$~D;Q#!#oIO4K<{NiIF~pvkM%9g({h;eSc-oq~bav(A^@04Ew*`S2 zO@`p$D0v)Na9?e;#&K{{*m_3h%LS6cL$Oj+LiMA};gc1nfDip5#Bd9f%+dVYP;Ht4nT%%{Y*EbTRiz1;i#hzci!~_Bcm=r8XO_XO&Bky~wE@=PfMJJD@^| zsVl9de42HqdCy%4zQk3@Dypj`dX3q4flK=#J}o_QLW(}dZCK8UvGv;}l8b#@ec-Pb zJ8JgX*GH19G`TrP<6JT`S7MRGiZ%d3dfOIpoY%@3vgCS7aA>~aZ-7yRQPJttN+%T9 zBOD#!P+lj`)N2?T3!pz2DubQ;pjDVQ(Lo(Z-%LCSJuh)w8iA^M8%c*MUzItRJ-M8r ziarGd2gz>E&(Rnen0Codi-*3IAdAclJ(NxH-eU%pw}qXSJr6ypA+?9&xNx$B`b+5u zCyqGdvyw!xhBOsK_&71R^c=iX17~fN6kkN_)_5031(AX0cR>2Sd}JXV`}7BK97E^8 zpC5&Cafz1B;4YAyGPv1{C?U_~GRdWFuk8b#-Ykud{n{X3qV?;KuO&4V#=mBKRbBh| zQ!4sv_5IxUF=vD8zY$!&YAW?g?R#mweuQB#{^VtHE6X^b(A_ef^1mhHAN2Sa^o1{M zdu<%hb{ad@Lm4a@jbt_qiYhuunhFx%(Jud|95^3qvE@GKULW!DaklV_5zvRSD=QHK z&Dg&J_&*u>zd#ufg)}-o+Y0Jbr#M|TaeoEI_lN-wKK$(L{V*igH=v;tZ4esblYSQqCiQE_E25RB?AbtC+JDAV;v<( zoy;(=a3t}|6f>4|uHGN;V7Jm2NZhO70Q*ml>O$hOhqb7&=iutow@zIyzXgY);&ki2 z|3W}-R`x@i7lCU0P&i~)re3ru!jVJN#tcI=oyPcnxyCIn%Ze^ZjW=m>bG8x|#x3pi;lT-8< zlE*tlaO{f*7K$fuC`gl-HpjqNXqI2edy`-d@yILU<}h4a*3unRm|95_LRN3%vIP&G z#%@@4*pl0vEpY$4R6zqTelb>)MDgmD`-PN)2l*piZtRujUCx)KWbMd#+A-!iE35+9 zv^k`hJA6`6-c}Vvj2S^q86LfU`<9?2QHAcVlFJqBTSFz6(^$B4)g2)$oM=A>^Ooet zj@pMfu>-z*1c~PCx*VZrg^e}xS$+iBbb#kSGNq3vI}z~_PDI$|?qpo96}RlO5KAIA zX>Wc^k~(<*V`FY@=fY#x?*mG`KjBFmF5!g9Xeq2C422bmKG=S=oE?|p`fzH(-1Vl; zxY3U_{lI)4|Fgt4H!KGXlq=Ax1V=wjxV%g}IPIqB2ov`4WHO|$2{Ar^3`r(-_%c2H zbOU$bFyhb#N#x%J+-*Y#HI`86u7ht`3 z-1i4Xkk zm`0neun^ADHJ3w?EM2M-eucgZ$yhIW7FX|EC%@ay%D|IrciXy12{^;Rw=BY(J#G|j zKu;UrH@ftuqwTfs*32X6G4sbd+Guu%{$kU&1B(1D->UzLs{XT&`@2na{KGA5Bq67S zfqOirpbN{@@owesAL0UOe)cFNIzkf4mVf`5oB{5*6_?ICp#Z zwl|*_n_QX_Rr={4?ccpm|JDils>OBm{QcAHmyV7=Gh!o_Yuk5KTa6hqd=w{AR&*UOdN5aTW3EkPlf6 z@Npw(mDtcLA~z^tS;#K4xS>vnS2+5&3NJbmq6t9>6QOT~oO6Wd-+yiniU*NoJP)1} z0FoI^hPq#}FJ zs{d0f`>(jpr<2z-c>5xY@_+aD|HKggx1*RQ4%gdfNf#EbwK_#3#pxgXa_3GibW0XR zPIi(|5eMzPdtzYQ@eHJIMZ}Xg>j=&-EzIBVAVU_}u z?!zk#B)d-0t7=0#wU`U{u0E&HXengKN#Kah#u`42=iW-S_?3hCy7I%R2YyZi0aG-s zgvr;>VKXeGZ@jWwIyq0$%%MFxKE^nsxgMXfZi0^mCQYB%;1&w8S%e2bWHVh*Pp{8y z(xqfiD+cn)+97QFzw1P3e0)K#(K6N>pJu+foODi2SSCxSPcFxVAD=1Y|z1WFELr)r4;8A&_bOxvJ1xLV0Qy-qt)Y7y@1YwMK$I2advZ`I7G9I$9HI* zlnX$^hO#0sQTf@F*j7Ug_wIV1K2sVbnzS*|h=Mnf=&_Do6$op@fMBs=NMX>we9rDcrvd!ZiLR!8YP~2+^F8)U?x99 zji_8tUq4zawT19S?D78>#{T4B56+Sh4}WuVO^iTl zuC;2$l}Ize)|~W`mk!Z!R_#=zv$Ki*8<%;o{?SM;7PJN(=+{B#R%xXufexNx z`PBGTqd@KFikDMD_lbIC0gvp!q5eLQ}Vsi4hU|~E%o#H ze&e%Vlpu0}m`U82>t)}#g--pV4k_enMZf@CgQh&_-9WJ5SG3u&7XUEJG|R7TZmFhe zqhgWgZUxsY^|+riYnit{)%I_s^(TP(&$8hE2eN0Ru>I1pj#d?OGr=)_G!;(v_lLsn zq)MZ6P+^GR-Sx;68ng23>%E&-myPA6nIIC?P%*#g&PW+4Cpr>jTtR+I_C-QHeTT(m z5|s;B;f{UJ;}w#y38xGUzGlF8YWWiuwXO|vX)MUpKO`=P&@YLEo?4RMmLSYF$vc>T zUx5FxpRUO*@G~{d`=;pN=yBDodi6YZcH=`FUA#RE<(|GUf)F1XBY_*?dRr(FPnO{D zRgv)C^G(S*?z>cXdC;fi(#GHn44l}pu*B41N@j0eHP6nWJm}RqI7&_~Lrl`CrFqh) z_k?z*1kTS$80@0vFp1`0Zw*d(2KvXAc7|RIzFosvZvvyEcj#s8U>jr5y^M*an3CD@d*NM?Lxm2WUqfVlYUrg?5&%wA*9V0HJTL~*ps2;M*|5-69H%%VQ( zaosEqtfIf6%?6AbXk77H_ub=8jlP5BppG>c1A!i485TSNVGM_6QkK`8MCCWSO)`y8r@0t>EQHt|d)c~-B=nEEUo}4c z06i6aT05FmQX>vhrM3e~<|=1MwNjy19`Fa%ePy&&bi%4UuoZ1W9gsWVu2uQl_{n`_ z7#}CGkcDir1zd-qTaG7lL`8$nj*T@?)>P}>S^wI=(OnvA`Gl*^1$tpFCO=oa#fL0AIV74TMs|=yuMZH! z$EC_0q_Mq^3enFfvk4JilIm91(AqzBW1$;SUQm%wz$2lg7OGa43XOcfD~WAxVX@FQ zCTF(iAdj)+<;%)X52(D)2|pn8%htdZje%Txu(8mSi(x2lbwl8+(FZ$7p%?gM@yRZ^=+g93 zU?RYwvrj=Zp|Ou4OvxxhsMvAjUkxR=9B-7JPi z(6x!o;+cPVg_Y!WZ);AKP-Ca{LS{xu@}S2BBqs(qH5i+umxGuMQxu7tY;UOS;-+)n z$^Xz=^&3#mh?zojrzD2k`YUoXC?)FYD^$=Z9+I8z8aEX|8{MiZU0B^~D*ZXuO=`L3 z99cFp0fx6y2;8MU;)T253=FD`Tr32m?e-okA)vb<>oJV-$ zijR_6LYyA*SU40nlI$XCQ`C#bV&g@UcNB35OM80Kan|O2-8%`UyI_|EY?)-u@)T55 zCnQYkQUJR;LVxCd%_J!aoW{H4E?H3uE|onyb@5YdpHBwf{4Rj;^s;4CdDS#OEeZ-t z92L>{bVfHYIN#7&-$fk?_vusX*Xh}Q zgqZmpR(lHPIFh>91_Ho)82HtN&(gG{Sv9j)7cYu}`rR;O!bo1v9I|Ey#jA_cH$kxA za*d?&;1#rvx6p`?(L^{)BR*@ezjRZ@H69UOX43Wde4b-)cG7}SXPHSDhzk^Le+{L( zs9)dsEweczZN>WrNJ4mblT{@DRc`-}@iVxR7m~vBqw_%yvzRfio@dP zvJX>4MiNALC?`qV^N3hgsUbxVtk?&*!M-UX7}t0TXdolGnnqE|S;R9O$^P?mCnRtV zBLL4>-sSFyx%jR?q8MPH`jl>H&FKW;iMRpfqmuz4dpZsW`F9*lwPjNd*TwJpwKek6 z+I2+^=y$2EZijBerK6@d_E(|}=OXdWJ<;}q*=?B?F^DFx`0N*>rj)%x@V?`0oxLNn8%9?;!-crW4-6c5HIIAD6HZzz=-)i3|SD#Dnc) z!`N%G4g92uO%)t^Y`d^O9NL_`z?2J(`4|>s zh1}Ac>{$ARq$tGs!qC!K_{ca5X)1tLA*3le!iL=&$YtY^Z3@(?$6&m0r5fWKUEm#< zyJl&qiGuEEx>`O(0u9Qup2$sW?P-KpprLhvw(+~^0C@K2fsLOk4H7}x@C32x42!+Z zmJjDiL7Cb{Nf@$9ndsrAf;sL-O|#A8M;psw2lih`uQFd>NL*Q9`IA8ZUn6s#7Yiy_ zdJ>S6?7TzDd~lXs`_ms(6%F3&zxW?v{D1Nu9#4^-i(<1|!_8$Ap$An>f>sAoKle%x zSr$vT*SZBiPDP8)E?g|kKaj-~v9sagy(WUUfFSc)0I?zZR-`LvIJ_iC7I;y54wsP@ z>;locO`!24f2{Npeay|jh&@URt3P_8tuLn1ycLC0uu=e-n+==~iSAz8P{)f2k}aXoh`# zs$!x0PL5xOK8x9pTR$F-E;_6B8Bqu_{pQ)X9ii<23@1#AdP;AONcod8S`bTmF@x^; zPPUmtqU2IO!!C3Ip( z_}iI5pS|o540KwRaPThW00d4?jve#nPZ{+&T_eX!9fN?YKB+pVQn-rB;Z<`}34Rrt z{N>^@P`NYl_?s!T>Is(q0=pd@cbxCN#3OAp#p#y_`^nn*HhD`3@sS}r*YXw^Zb?9W zhu+S!o+Q2U7)i5N1(=%70yJ$CX`}rUXdGAKIp#bis>nzqQ{A7!iEtxAE|tj8-G>mY zz{`9dv{*At)(v5_Yj(-Z!&|qqnxcYS_>@1Aj}ax=jZKEQjZ|r!nxY|@RXIF64j!Mh z3+ZB$AR#kD!tq5qwUCQ+kTniCSVe{~;b!0{kd_3ZK@Wj&0EhL(wo#D@KpPFV#kKJz zb_tX=E#6ZXk2C>}=;*QEHjX@Z34_oo&v^r95stRA(&GEOqIX^aE1Su)x36z?>Tr7+ z6dfl{k=~Sqevheo_asaBQ^a?fCu_UTtbc2T9P8?d_{BJ@5aw+KcI*=+mCgPt%;J5X zA&IY-K>m%v+GUvFXwY}N4M|L-(P7VU1WjTC^O7PZdeUfKdM-&=W+}iauBG}U>H&4D z%L*A8$n5t*!xgArDR?J5FkmwP(iN=x^*X^vkJe22!F98}xswFc3Jt-vLxm91N9V9m zVjO?3F=Xii!H1+tIBa{)rzpo-oZfh{*wiR$YNac=k_+e$_No32Ny;8Wc&8vP{Tf&^ zL!Toe9Y#UZJt#NP0RhrT)f(oKVxZyvBLAwxy>ONfRQ13!3YtU`vhK)0+yWuNR{)%8 z9rr}J#m{o;#(9g|U6D)V(hdXtmv=ww%AOV^@`YqN`F*H7cXvaNP5pKuY*ehuLg2Tb;Fjf zlQPKRwFp$ zQ^F8Xw{F<1mjWw@PN6>3u~Wv6HT=;Qr_AgpY;u~UiVrz`sJT(mp; z_w;bNTm%vd8_3l@dAxe+jjer%UVbhCr>jw)PLRgWg<==~eID0DP%Fn#XYgXf}+J3iYJYM(bvE(%W$iWuP{Y&;LhcnOTOTDuPvp9*I29APtBM6k-8 zuas7hCT;zK)XU8@#bvaA?Fm42YD(Jo_`FD{pcf$zJ({W(6}hg_zUrIR??2Kr{LRAQ zAcYb`^>9fzm~-hm@GCy=v4BCEr`^NTf!*`(#vJ^6xCjKKGPX@wIZ!?T^}`FEN^9?_ zIL0%_)K#RyxYVfuS>unC^R;~O*oA`ZFk^aprIQf)fhTWlFj&_N)IC|wz7UVXEN6zh zY3x^(ADh`@VbIwa(uP*wQyyCDryy|h?REswv!2%BxZmGMn_+l$zH;>pdHsJ`Vu(T)RUQe@H8aK z=S4&8RESZjeAKPPB`sO%T8x68|GrMx>~{i>yT1V9 z01;AH{-Z?NH)DM{B4(eiGN(5{IKd>i?vKX=yBipMtX^c)FM(OAnBM^KUP(OVi?m6w z5%x5XGz9IZ(Kyn^Ct}ju2M*uVkzvSHVg+Mwd<62lVk5Wv{NMsgGTW;$Ru2ke*{^u` z;Y@EG5FnoW$@PKXzZ7PK0abrT>HA{S!nJ)N}iMYminFZQNt>-tT8}m>`K0Hky10sKJ1{ zgfc1tUtQW8Bn>rjCN>EQkr4!sF7W5VMLD7%w%XC(N9rkiXX1Z%uWLQViXFNOnhii; z3#6W(r_^OIf?0wL(o~%!dNcJm2_}%mI8GeWJML$w+<%UYfUz)>2(p&YHb?nPQ0cqG zsX!-`yF16ISb+;~5{4(OoF^%$l^0n1B<74K!V`arT;7}b!jS!jFL{5y#UT^wrKDmc zWglu8y2SVQEHW@Aga{WyZ+U5Ma{OMKPqj&`gQ?re` ztWtiy=3wD0RuLMH_Kbr z=!f2mTEf|cB89NTlGqoVOKLqNfZ^Fh$BMQywOcCe zwxHLaT5anZ?{f6X@dFERYt1*dPOy6${L=I~^xe)J9#fUQZ~jB2-K~~eq}JzHrr}KQ zTV97+=Rb;nENKqG-Q4JCXUM%j7^}8CwkFv-paa2;{I&)@3FzX#mndx_Gbr>~AD3#u zD&qEmk>^9yk~kL2(i`v7-31XCd9Fr}9)`0Zs7G(P3*x#NhQI)U1a71cc9V?tvJcum z_6!7%bkB=88#KS+16fzFzK)lP>r9N}+{>j2`eYIwDIR%~WMR%r-wLKemjBULiO^?! z4>?XU{3gP5wr?Icz(v}y>=0aIBNbVbuQmOuA>L>MCP~cSdP-!HK`Pb7iV`B>pK*7u zZZ(hy0`ci|@R=a2@H$EKR0F{u+4+j}CM00-rBLRTJ*yS%^6uap|46=nu3mre_oTvu zOC;n8Az2;7aXjszs(7+s1+@h2UB)UG3}Qr6?bw-sdVL_E>%sstBSX3bB`74o6vBek*@|>Smbg+u zUF)KcnH$UEXPFrZqTi7w#1p9JXwTH;39oBl+!?$olgAI{40d9` z+#z9-p9ocnt4-GHAKkgBA%eBPnD#iir< zd?6FF2nbfh-0LE<@HH*Zj3EiHOs$oN$VzIvjh&V+Z`V?3(h%oA_z(xS!P7uIz#PH0 z0DewgQ!YTETCrvlJPKz0=X$}k_fq7Un*3}5iyB^;ie^A*g$v+Lf=6t?pkj!eq{m|T z2%FUQaaH4)eHuksJ5KPl4r-FE91b`Tk_r{ng0v9EDxzo|4c;1r_uZ~xKnx#j3?SJW z5cvzx*N?B5E8rFmG{u>^fz?)BPYNopi9#|m+R8Gu10%U^Wqy0?M8Z-u!7mkfV+PQ; zClg*hgf@$k(lDD66t;e)lVfgOM>MOPe;Q;*iXHkR`j7db6>(F?8R!c5NGq}=nOG0h z$jG=t=-J+;qTZnL>sLR3hLH*1ad61YGf)H##nVPuND~CvKi4v0VJOcu%SZSmF#hcF z+xyGnJB_a<ZKJ8H&$`6gpO!dlZ+!nel=vLy=o zY!{a_bfRwceA^#`cXG6!;llOp3t(pMyQ#5*_kL(F)^A=8Vol)QksZEsC~d|%+IdyR zuSgGMZB1=;!nS7bvb}4PNGMVYGb&PyostGmw0fsgI@1gO`f?{-3UwM(60Qz*%hcXF z`jpW;X1O#>Y%D_~Rr*^9rQ%3F|FOx&*-2Ix6z{{N`db6zlvLRpyY^XECWrD)J`WRE zh#WQ1PHg(L5W^1qht|| z8(GTOxA$wusX+b(G1AJ?JI{_CYWjaTdk?54yR3U$1VjbI0-*__1Q2O~P(+$78j7MQ zB_K^g5NS&9MPE<>DWQg{RFw!4N~D7b7zjjygbpfAnkb>y|Bdf_GxNRQ%>11HTCSz6 z#3$sr=bpRI-us*)tlAE;&|emw5-|S0&}CHKee!=e3*r_d79UZ?4}ks z+wL`d@i3{CD=Aw>jhHp|36S6wcrc^vh5cKxpD*nTFx}#ZI?$QoR8=mWEZSuN%wZvcg1q0Ofa#U}1|H=M}w>W{~dAt82 zped9e=~c~5H^C35Ri9M|xlv}|@wc%>zj<9oLo~s+{9;qGBx6|~Q$M#r$fa{&ClQ^x z@~!~Br?^Sx$l}8OA7eH*Gp(gFV)bqtTKv?!Nmzh^aU^?NrG}%yoRTnntm79*d_t9= zOzY4?=0GIM zw>-Gs$y27LQDeZs9n?o&6YNaG%OEvt-FPqgdJfZ1W;93(TXLk3Jr$;+=&7t}gI3Wr zh9f5f$P~q2a15E^j7&;j`4NyMF)!Zu9YX`7)koXirWPSto*aisV|s+{6|JX=d{QYt zT!8Ae=B1Ai@Fh9j($vbTjdT>P%L1D zOWk6L3BbpYL0Zx_Hw7bNfZhNck|Mald?0cF$g(f20vC&MO;>`^50j?%M}mzj(0dc7 z0#T|6U`$I};Codc-CZpO1mXtefhB%Y^NQ4yEbHrLzCw`40%6WBc zy^p>8*;=2PsLXK4g!Et-MsD!IgzRt>A#wIRyPIb=tFGrW&^rp6gdpQ{=N)M2XLH$x z5#uddXI-~maY}srT0MkB5C+YW9ndx@*$b>~2QJB|0TChXa~ZHOEBI*e^30yo4PSnq z_H`>Z{De%a9Kwy)@lwD1LDH$?7t4CBs6)JBY-!)s>-Y)jqqYz9yp;9M#|aQGw!q;Z zr@l;m`}BiDu@>~h^q%I&k%i$6GDQ>N6*y~9uY*4w#|4LGE@6=q@>wK8Gm9x29#|%I8~zzcQ%%n z)m*Z9EqxCOskY6q15e*K~AkAZb`)J=@e8w5u7rh>Qh8J}FZNtnz2i;eW zBsUCWj^%I1azRffbX4`A6eS$(3?o6p8HNywY1!S->fOptJLN0m-DOI^u913b<_TI@ zq>s^vjIT~VS%lUytz3th)-Im}nGn`0IsAy=>Xl_TsTDo8rKvHgs_6H{NYSNJ8ER+Q zZu*8*10Hk)TS9=0$7jtxKt?QhX0Pd%Up6gewYTy_`y~nFgGS2;al3rd=&dv&8_6wJ zX76yby%7zA-W>;@)QE=OgGkF=Zjw5H*ss}q6;Z$?^29TOeI?5dOoIBk3$w&eIu(M- zW=xfMij6g<6HkToA+*1-J(&ZOoQ%@?MGZ`I)E>5sq1nBi54O#N&XttYhuVD|c>p6x z!VE9Luro>@8Q!aUy$nW6p8$iS|KsRBbC4!$eb%gVhK$}NgLtrB-~--UhI!5H1L(P8 zR_1I3eSzIH-`C!m+TzyroIq>m=PXsMmRG%eWEZ?bF0KM)@jQv7r&Je9W{J}iW}oY? zY=8|o+}@4pr;j=A1o*Aw&u1lB1nv_$jHYt_X-jS9>#Ix`6j-8lxzF#wx{Q$5oZiU3oP(Pxy3oamO8FZz7( z0ziuouk6xGH53PWSgJM#3+Rk;(7bJNL7^{Y(1J-~P9Wu$S!N7*{~Zc}A~2H3J|b7B zi~Q5m|6j%6zn-1*p8^p~T|q@oa6UBidueBrnla(jmr*2?@vs~-W-p0J?~b~QF=N^ z1$$CS!a*uU21{=S?)N!6+1{h}+v18|Upz}s7IDi+4n7+DDJLn70QvF65h(j+fO{FZ z?`g5(ll9uLg7~v7p{i9MABd^N{z1gim9qfiv=dFqtYId#6aq4})P3pD%@e0gJx)>= z9-buL(@IIL5duAv`P!U^frSQM=?L2IP_`?I4o{(xSN3nuj_t_;6={T6Bw#VXj9dXt z9GL^2CMC`sHstk<{sOQ~9R@e8B3cmVL*GM>K;szwdD{I)7514g1YV(4g%pE_Q4zWCU|MuYF_*AGCTt7+pS$_DnJ6vv;lS_hrbc zG38bmzuNMHf~LcM^BRk-Qbo^1FGoE~SMxgY1Qg3UmQokQ`6jyc05y7J5?AH`sx)xI zg28l;^xpL{Ofc`Sl0L97dSNao#I2zL*}+MIisO!^lKd5l?&*5cd6=&qJ&v1n`>+{Y z44kYJPf2Sg<il8>~C_(bmr_j^D;r5G*6;uOW>s^$4?TWpIIlM&|Gl(rTdm-%ib+ zavHxy-i*Fmi5TVhKNEQWQ=I)J9sU~hJ58rcJ%>yrR)i=PMX5Gq`(Oe%T$%0d`l5oS z)*hp~0&fLlWO^S@jL)8R#U#9k_W=q9xjUNWIcr~MoE6-q*irD+A9n0S(Tc6Fn>8nc z>{Wm9I*;zn>s2&uT=Q%Yj{kgH6LjU#ONiNsimDivCWKGb$Mc;Js)tTWsTkyLJ%-3W ziRY>Qe$r2(wL=cECN}=!&iL`0(tyX@rG2s;V9P}AS;T@WOw9p~?V0@4R)+|D*jMow zO;GD_5V)5@J-OFbEn%9P^t z)V$`BFSKpU*F{?dEE&4b_R5#~tvCbaH0`yL$Mh!_3C==2FwTJ)78g)?P63eY7Zt!% zA-M+7Y=-w~fhsbj%?LCovAPrhTmYZm+#4hH$S{?qBye+KNG+mptoHWwZJDV}~QAXT9Mi{o`iZ>pSNuX za0JYx6Fr4sr+Y>$KSN}-5{Dw7RPOkrXG6ip_7SsvVb#Y3CElm6Jg7E`pnE0T$in_Y#rLm@0H`^>@$)hAaC1hr6r24AGnO+Xw3<1>{rrG;blQuQag0V!Sz zOIMj-O~###bpl85ZNRfA#?8#`8Qwgra74YUFo86vpyJkFN>4D{njd&{Eb#HL_b8|P z^e4$uml4gyK{v|tu|i3v{0g^8@zWa1y*A%K#zP3)BB@${P8QWJAOqt_jwN|>DWXpd zT{A)RGcd>kLNgA~VqgY|k5Z=I100#9a1e;3M}bEbutG{nFj}oKTTTeB1&LAedkL|= za6zFy`9183{6Y|$F3>6Aa~k`Aj~i?)sl0XxH#f65r&71#PK9GB$g4>fG9tJ?!g(AHh(0-b*bfXljGB-g3}B6Pp$Ag6jAttL6n&S^)#!nNYwyk?Y`tDS8-gT`c=?!O-z_k zdizQS2_`4Gm?g!430w9vS1_7@aR!Ik9#sMHzK%U5q4GyqT;bVmTzp*s>;=}mGaXoS zv)h;-kPBMVUF=Yse+CAF9}+r!ZJ=t|y(Zz%DZg*aiad@4cn-+no<_JZyTiGqb7dP# z`+ymn)P)V*cU2|L?p-vj18%{11_uv>I3kYPk2XI7JxycG^Lix|rFNv54J0P$m4y z)p>JH`PX+3q^Lf=1dWi#g8swZSMv*_hZCi0^sD3sY|3j6TqIP6paqGwk^7U9vYXa& zfu5aO#GSH|BCA@yPU5VxU@}7%a)<072jE~!O)OXYDL_D;XWiOK?P*u3o9S~Qz$YJ!qvOjrM$O zVqr8guW@3WF$sW04LoZqZAEUM(cX6$c>GEJ(sfp32N->Snxi$@bH~DVH?G)U<2D$% zvaD*q@b=E6u&L(yH_=6qJf9m|_^=9T_;Sznm$AP#BOVx^n8-qvV4mkWrLe}Z^MSb$ z{f=;48k=~JgopDJhBaRSxc`Vf6LH!xaSUJY9z^*)TQwpXGNyPI3(IRah2w%4kP(Hb}H!nKx;3Vs0Qf`Kr8(1KOprh_q|W@ep@BNUD# z)5aomDAtDsP_%uI4~bs8bZHMYM*4VINlWQQLW^Ydh!j*bxk(P|)c*BtGJYF*Qugd=yx*GKO(_1j#Jb89o&Q&iP8@tNMNtywg7SQk9v zqq~~K8Mpf?m$K7S=tW>OKZ18H(LVyaA+Hxw>=$+`s&qzVW3^1>M& z`TI*8xcW-Jp)=8eJnWR~)Eb%s{Muz4^Ea@qjN!O2exaNQF- z1TTpnt_kB`TJm$l2~Uu_`?Sv^cZVE=^zraQ_g{aAY|y!`;}CHNzTpk zRoXtsan6g0g1U3-8)i{%9|YbdhoT|3!4)3btrx4e+|W*zpU~Dy${KO`NFfRZQS3s{ zw&xWZikoUOg*U2!Vt+xQL1FD;-&yH4*?g1KrTKFTF&t24(%Jv_UnU|bhnU$Dq39XA zn0-;*Okk2h#$CJk5VQzCau4>m{xXk3LxO#N;894=o^^fflzz;<$f+Qj6>2{J11%s0 zzq1fgV^Ti0+7~b$(1(bTZc#-;B2k~wwoXAk=3d$v12=?;cVNt<)=)IBWnfk5>VjHM zC~u(Qnb9;JY8t#K?rLV;Eh>?swy|f5>}IS(r@I7xj}hrl;Sqw3SM!J zS4@_v{%oXuY%*!`mRNdkgw2fFm$oEbOLdQz?ydZ7ITh@MNsDPE73|J2SJR2N&e(S- zOufHR6kqG!YDguyZKQTZgv_J{b?8iyL$}&zd?N8o^e+<@Qt4+_1niyX)rm{?R%V}6 zW*p5YmrnGqcbNMZjCDZ{!}%A!j4!lVX3C{d(gZR`|qQK5WFOg|3^KcK{S6lPDXoN}tY*Zai)2fUn_ zQbI+ABj|neJ^eL?n26N?Fci z9OsDV%-zIG&w8Q;48q?fvyl~zP`T=~A5YA51xW6`J3FooEn@5>#mK2ee?sV_T1t7(W(%7v`5vQZp3m9@B$*E~ z{a!HtE#CV%NMSAp8uxme4IOMxC%&%R+>Xrua?Y`5_vToGa329#JQf2^Hif$x@N!2R_gWckJdI6=Lwa_~;^iQK#5LJO}T$gxUKO zL|*NMmh94-RNaoJH~JH3qBu5kVfDle6_GS_T&DVmxHhGh8k{rvDBb635S_O$W`!(#v(b)N}kO#y#;fzJ z^-Jxjdo}K+l+2Q=8a5R^j8|0hwYL2A=Y0;K1jZ)PJ<=7eb~Gr*+xqi0iFUHp z*U!vUr>>hV@@TtcTi$r;$=*?;eP-pWS*~v4u&8;}*W>%3QY@tN!JVi0N_LT$Z6pUS zMmP9cWX!!D2wJFT-WnPi@u~}~Tl}C>D;Y4E;g@UJviZ<`$!|nzXuE+6$tJh{vUK)G zV{Y~ceV&@Xy+p3e^?!;wJ5Y#B+-(EF+JET$HZHjzo`GL8spMwFwBqMnvIIRC=MiWi za-}huO|ET~p0npLCl4g?&aM*Ez}Ea6f=J)B2I>8#2;OuzNN6$U1a8~6YG*o5Y-`CT zKnC?zFdc5!E+IHONy^Xg)miV14uonfd=RB>t&GSG-ER@XX6To9dx{pIAkv7*7$3|N zC(ZRTu;IdYmFUZCJjjV9)87h^wrv{ha(kX06GtH@aM}|%Ys{BKB^NT>Pj7yN+2%U5 z>zVEIB(~;-^8}~z;hVcj0*%X&njM5CNcMf+#{ zW}5%CGiI!yU3|Z89LjcLWU=gf9C|AWwb>XwyhzAc>)e*xSkbD^Clppmbst-de~#lN z5!$3>RRcXOMR3fdQ_YdG>>-QFY)9_}=QL=KXgbiOLGZZrfNf5*LA8wfSTF~F3_JNMI^E~HaSkiHOZ>!=P)p@iExNBZ<*MM`n^HMU@LqR~4J>zt{Y}ALj?Fgq zWV3tUTel(0`ID7X0U2lJO{e1FiMqZWIL&PzyOsRO#P+FlQDSY^q~(AmrIyfP^7cFx zPEM4q(yyFazAP4luZoeDP2~Sr4X!*mlU3CW~7Mq0l9_Hsp^yA z_7y>}++Ikc>Ibdgm?~}&j>E5^iNn4HrAuS6-FT+t(z(4PkcOP60-WWYbtPGw4eIqM z=3THnI#3H>c`HS+j=g`phCW3LFA~aC$J(oq8FMn~B~~DOXYo79<|>?4lE{gMHz#5o zH$U_s=N3223MOKFmR)LzxLr0+C}NA%_|cx0<<^?~MZ3>N+!S>RacJa*RR~(<0)ctw zlBPE1I7%*NX&Y`v`E;Uh-8aN>PNd{_fW|+t2!0%?{8=I5E~M{RVD*89JeQhtmi*z} zC;Ar;&1?tEOOOJ+y|l2lC8S{(X0mpna`wdUA`ub(0+xARKP&tlXv6~)7`lAgaWqM^ z&zEJuUIU9tGUJBc$b`(f+HXJZ0hZ9K&t!16pntDT_^m<8w1$?FT`T@fcxt)r$C zgw{wreGfL_yCB9K)@2^B#Z1j=vW)9E4M51^T90-7bk=wbeJ* zsU+iNuW3W-OSi(<2|1JF>BLp#DXi{%yzI>>8w>A-*VGDLe?wF6*y(p>IVF@j)EWAq zpx9G=kf2xE?aX>`?8Z8FnO<@3&J-K8jD_nXcM^}6Hm`+ms{67J5h+kVkufyF&+Za4 zA$Lo1ss~B&P_zB6L`^cxC(pZVXC*KW7RlFd*Q{SU!f5tL8d+M8@a$ynNuzNkKakrp zCJTtDfB`3}4RJ&^V5L$pM{Rq{hUzr0mQ`2hm+9V(_tNndPOdel$f$X~Q>Lm$xPWY5 zWs1&p12HTT($8rT0FZSI~!qp^~pdU=boX z*6VWyef4Cz$JY=uE-Z>qnH##XR1_rRGZClpqn??X?lmm=H2F+rifyBT4Gdho>kit< zQW1-yw1LToHfNsN(ewatR^&#VU0Ns3qEw!uE5C8TDa!)H@GyCud2T7St(kM0D)$I`1b=BNlQn9)SP#s@68q8e(xcP*036XWz?MSj&s6vY}JxYujTqS zZt3wY%zf%7q4j&xjc+(=q&W(jN}MkdDq%+u1a5z7NrR%o)WB;egBa;`ll5h8G6-gs z2PjgV&)wdb+~XH*o-{wd!N9YMlc%QYuA)u_Ox^6ZH`bPDM~MgcSOiH>sxDF8Iy$OW z9jM(sK{u!^6<_mQbdxOC>Zo7EymWYFQDRdS4!q|5BWn>ZaUH5V82epDOX@q)8ZNVV zgo^g0<$}`GLc#Y0qruIXoamjf3@tlz&v6avng&I6W|rOM`~2?iH6oH75`ah{e%>?n z30^p-->#Y(w7f*k%CKp#oCtH#u?>?4i^)g-9Ft#tO~zOKI-l-2!ey6ReIuMby1K*L z%2P9!gU(h?r_W;-XXQ2GX6v~8t8!vz!}}f~=SRGgOQ-Sl9CqG7#QNaABqVS3wby7K z`H@2KTswpuyDQ!agqhXEIYPCw3s16-$m_=yww*XMf+9mdYnowY#Dbmjxo&j5^v-1v z^b%6c89efFcmU*To7FA-X#NV5J;{oDKULJ$ZGHEuW8~*wkQzLn&}@CZr%v|iV4$9o zE;Ba6u)I6xkcZp3Pw!ugQ3eSoBXfgsfwGU%+~4Lzx1T4Rn21BcCRDYAVO>Z_`}v}3 zO!U-ZV3ym5ETppY%oT7v$^VFFD(DAK>J&N#BC$0D%~r@vSVETHAIT>P97nkbG%Ln0 z1wHt05CFIg=tb~2Yed34657ZJSdS@?$7&duZ;-QRw5~7T8QCe)(k`F#9emYP+}|cm z%i>EiWBEsR-~Eq&{_{rpmD+8}*sxP9WCJTbW;5>QTDK+=7+^3C=?TE%xw7-Vi+mX0 zfcA+#uyI(DAo_hOaUNXszlfC;=(-K`q$l=1{E*<$Z>MB|YN%1^WpD>Wb2amzc~ zeY(0JS8nBY&-&FfHFY;8%th@@s3o-%a?>siS*g&chQ60lSe2RUr}E~J#UIBx zv4%J2t!AZEbP8OyUM>Cz7tDzmexo1cH!o?vP)^is!%z|nQf-togOxHWSbbXEb zHi)U)2DEqCng*|v!it?iJCrFR7{^9R*@rQb+@LrZp)BYm^~q|(lGdzVBTs18u8DIB zQta?E`@mbw5sfCDaFR5K?wV=%15|pC;evmV-vrJM>Y&+oL|^@Qz`Wl`TjAcbKOCvdgqJ6^0<4JrL0S)nv&lp*C!N()j`*j4$p1x>J1> zeeR|OGI8N;r$G$ftR6oUJDZX)f`!P`8lG`#a0MU~9RAX8EvVl-8n6R7O-Vz2Bjex{ zm#Zfa}X)hrL?9XyeKpSFLhw4w(oz^bp|R*@4j zT`5>6DJHZd!=;^M;SpWBN-)GW!=L&`$W|Jjna?WHPP%nJ+P$%T1RJ)?hUUgZs^o6Q zZ}@(cvfV+5_H7xPi%t#bCvRy^ebQKoznVr!ckh#}(&0U}y1YHSu53RwFml{noo+QX zI`I960j=7y)`UbYD%%5NC*3XYSye`>ONn9;@&HYB<&qm(*_+jo5LS3V;KDn2fadD~ znPBPLMkx0KC=>?kv#aqTM40>nw-(P{st9+u)Ds=JcKPMr#Z$La6@qcKMUyivUDvFn zSxMWgEm}IhntfgRS~9OuC70H}a0Q+GRi^c(w-}cw*583u{cIMLLbWByC6y05qm|m; z!5o%NI*}7h@PJJS7BLxziq8G1M`2WpdtjWHb=B#B=?znpT$h#4xUw3<{gEij&~EtF z+YD87cKM*mGz2OoP<)1mwBTF2`JD@pT4D&o%B&QU7Gb#*>o}4W;OaP#!vEWJz%Npa zri=tas$rNh{{#Dj14dgL)%T<~;J<&~7rdY6)lY_;E;x~6Y_T(aNp zlge?Edus?Xqio%@=2W=tj+9K*GSgJES)CNZn#6onw`jZ4eOQdB%`R7sNC^T(ma3hF zM7qMt-KnvPwwx|re2i?xrtHhdqn2ykk8;|Qh9stX#@}VQrF(SdZkf~EhD(eF7o13E zuFlLyOtY8dFts=i9l(0XL@LpX!-I=+kFCYK(**67h$~Fg%i@j_GRegy?h{dJNZvWv zmCD-_YB^$EdD^;mHM;ARnqjCtUzb%{kNgNmNmwVd+6XkN!zP$YPJqX_w2n?Jtxs=VWRm7-Hi((6n9PU+ z%+$!{gbGzI@F)8epg&$Nu8v0Q4$Ph@PRZ@GU5v8H9dN0u!Ftf!RdjuwahK%SNz!k1 z&K1RPIBWRC=fpbBkVM5CG;vA=dLxC_M196v(31Cjpa;!QZroluQnRoA%m=gF@a&V>!A8NomX7<^`1K7nmnsOyK zv}Acobq^QTrz3juRtS3hL2;)4#t@c`RA|2T%pIhwTzb-=xOw1s4V55RD`{Tx^<1|~ zZffoF3+~X|b5?oZe_F02Jj!90t1_p$edstdFFr*^Nagz2naj0>sH)F*HEzY(~%DS~=wMLwUM{U|yvxVV9+W#1AY zHnSOYyXs{(zAJz3-M?)Q#t0E0 zbSZWgJRCYHBVZXdwNkfJvshrcrQT8cSz zzcQqLn7a%z6qZNcSh|QGvtBEj7nKMoPw?Q*S$1rhKBpsqhwP*<{XtVg%zgzQ>K<&{Ey=JH_#lT_5j*=G2YN) zCP?mg&%WX_U$<_vlimGX>aLVsAF22{rjxA>+k-7r1@}#QzZT@8&o#hTMr4_DlMFfx zLD_(d)oZ-OO)jkZdECCwV@0=TWbw^n_keK1V!-Px7)Gd$4OjRUu$f<_zQ@Z}FGg)A z;;|5O?}*aR)kR3a(<@mC>@JNzF3P@lXtEN;&ywVTvGl_W>eBgFq=)FZy4 z4k3;aIRH{BLbUo5PP}e^s1Co>l{GPph@Az` z?T%qQC|o;eweeWa$o4Xk$~cC$X6>}%0yjsG=?ai*H-_eQxHMOvEdokDi2B0brT~Qy z!8PZch|v=r*#*7ft$cg>Bq`-W9)m5s4#0b!OAjoMSR4b0cc35%6t{BNR0>H%$DZLQ zCF>{7%U^83KkhN^p?iZ86)c^{u;0DkU9%9hkXMtc-S5Bs*E@?%gd)&>e5iW3u5Ckf z=-JGj&B@$vj)3za`$`9-b-LN6XMU^LO*+FycCOmkpuX}86hXmk=JIh!&#t*>owh_R z5%c2Npj_RgVNsdd*#lGW+Kjc^z_wO8LNZ7Uyz<({ftSx=Tn^P0ZE0@E=~I3q|D6eZ zGKPoKrcysD^IAIs4C%uM@308Q)SUd-eM#iCSEfB$|PFP-aJF|Iv znsxc;B0u$7?bZ6C=MBl1o`G0_X=@d-stcnR2A#j4xEuRf$*#@BgILBx-xEQK2W<0I zUk2pm#MEt$%jGIL&;Ta&`}X`%j=b8_0*w9pR(7(Y2r73r=+glkXu!sYxt33$;sW^y zY`{KPLa1Garsp?dkFM1*CKx+)Q5lj95bh)GY%%s`X(+uuIpad!PSrFEbmr3@tk@V^Ifl9mKJy7Ap;B3qV!t1Gb92d0{u4i`(b3 z1S{#CmFIvcRY@gKw{p6YEho2aKB=(wOR52PbNE>WN=2DP)wlbVwSkpvq-i5%KKj|% zJ$0A|Mg@ST6zQAH*L#TxaHW=0?MKD6Tbz`x5~3zcL|b&{cH6wZNZ^KbeKdHlN4WZ+ z_++wAj5I4H%iCVaZhvxuttS6LWpBu^H%vMlef{XE-Eq?D^rJzogJqr!A#9_~a{Gim ztcyGIfdsbhAf~?PwdgWJb&6qAlU9MEhVfp z=+@$O(s>2S==7Y173K?$ty%gcfqK&R3og{U6J^`kXCn-yr#%AuFQM`f$&?j|AfI)yWPxg%&6wo{(cVbZ`!p-n!Pbg^(UFx z<2RQ7f4>E88D^tZw{RrD_D(lm%kDHy5=lE~Y!Ks^c%3^?I{ww^iNx_xbVkb(87j4Y z3t@RV2Hy+5*1d&b)S>z#q4MDLj*&Cv&NQfZ-22gFWv0Q+F{3C>0RSYCzJpe?x%iXP zxn$ukBJr*>s45qgFVAoKO0HKPRlDu3G!u730x6s}1Lc#NPAWQKMP|l8N^{5!(_u3y z7aR04De>SQ_MBiPhaZ4S_4q4rahI_l0E?gy{aVltYY+AN*~FX6YvFxPhp8kJq)n=R z;KGq9Hm%i=Qs1S!_NhbSMkV2UN~)>wZlFwFL%o2c>*o>IqFFN&08z~wDo;s+vs2V# zCYZ`kM%r7^)suvXf}%1O)o(x5)s;ymhfa8SOyd=}GJIS+Bpj+#(dG}(hW`ma{gsiZ zyep29yVJetc*|?+dre7h^$*9GmAq`Cmk_p0patJ1UB2;ekIcV{-Qcr50+shLI1dJ) z+%XcqJ2})0X!VoD(om_do5AS05cKtXgnDx8!!te&$&;%ZA9&3rSGieBz_|vr1@##S z)S-K}IDSrMP^ZFWeix!^wCvYCPCeH1<4=Yh0ly2TEX!b@MTCyz+4u9uG(Nff?TjfCsi_$@brT~NSZ@pLi)gS(F;HW@an}eQOHR3<9Jt~n=CES~NMw4`3zH%(YQ zA}b|Be}d#|0m*Pu4Sc)UUtAQYd~B(I`_d;lTS)(>C!)D>0aLmO_%-^ZF37&mbF62> z1f64?YnEuOp!{56#f~fM7Z`3h_NTpxZ<+g4aT_d%@=ZPm@nqn)X?$}`%iqs3e`|xE zxpljSw?RR%?I7pB+o69|J#4yB*ZBb9ci`Xshkt!;5@atCIO>M0MxF=M*tU2rmTml@ zvgFY#81>Czn&!92)1)9vG}(oni@gCc@_99;7zY#!9}TYuZ?{Tz-rqaF)3SRlh$Rh3EiDh`y=fa&tT|>u$PL0gfz>YC9Wi&7+>lSupa#5 zZeqp-Pz>`=#CYrma9{Th)g zRa9*tAa8q9X~w~F!&GbrxQNHv{UH%xF(0EPVL)fK0KeRC?K^2m!9#03VqfYhEGtF? zRc7Gl)5Jm2Txy8sxU&g?ueZLD-f;8WnRN{>v*p78+U7T|T5R&C7}gKU!Ou`$XhHn5 zcpSPN|Cl~#Z$*o$LlW7OmFV6n`>xlU_=%$U0hTaD4EB^{uGHR!Pq|pG320~c6laU; zWbzZu8y9@%q6X0>O1i)U(|lD>Mf6DBd{#YJojuKG_(M)iO2|PS^4$`6vAMy0Rnf`jPM^r4!;(BtEi$%DN?P0pzH1=D zJK$Y#vicizNZ+^gW^KT#83fBz1_ALUe$8&(lk1(6lKsV?fv5518R|L$@*br*#~ePD z7E=ROhK%`@tqTAIWZZtGV0p8tH9CrMp|lP zr7ttzx#qGZKQ?`N-fl5ruBdaI$6+U+2h;>cG-*+G7f&#pH|$EP@|6@tduj|2&f;6T zTjqUgX{yAI*LKT-NLrv3P4Be57fcGUBy&r1EKu-|T=Jeahvww93&RB#W}tq{>zp}a zQ=YI`U3`L}OVQ6buI`joX&n1Dy4yy=Tth2jqtaA`$==?Q?pJ`Sj?2Q zu{Rq$D+cyS5fuF_((d_H_l>Ke^6UZ{uspjA4}o9Wn_Rjbf{FYt;iM5_1#Y`zkDV+o zAMzsrD)1QOMe%6C7#2+I}0+3g53jslRNumFwa`f~zg$#!bJ)(F@n!AXA$ zhOrIq6Pe6+u*N4KCqiV0uBS%(OBUc@v{M{iH7g0Jh9#Pt{fDs4u%TVBfxGyd?`KxO z$h6!nS-d6BX;VVv1FIGY&bGryWp}7f5Y{dLygSwGVC6%5#5it~)aHc<2J%zTp;m=%oX zVXgR9E&FFa1WQ<*;im(bu~#GU%~L4L%Ic1awchE|O8f6h>#0iEcnQndnke)A0u*4m zI5|mpCr~Q{1FDQ4Q{z>4VWagP9zwe7LW!Ae;Tjn>4znp!dPkU5h|rD79OC^QhBWcV zb`Gn}ilzr*2rz#leOEW43kAsyx5sxz+$@krBNWvW=)UPC_=g1w0l zlxf|o=9xNV?77zT(~SI$krF(QVo{cVT!)r8>z`9#C%1;7Ge)Dqb0*dOV(?UJI_b>P z(!{x@3m* zjY?8-neCOighW((9urc|OUbp1o;uL$?lyI!u>~nPnIjl0%}Q2}p*aC2l%TS*nSQ+} z-7~o##y*f$(%OGt9N3N+)F(gE=$MP5(UdgDFSi`0jgdfzI;!QP_T-?OusVI9l9iO@ z4UyS@7?LxtM91%qZ=ESNcvkp$lvF@CXT{yjsK%Wk|FdG_YLCdR@HpxHnE#FX{`F6& z4N+Wme(Bc{Ot;gp^#)RatmqCb3W~3$2F@u!q>k&C0N)aK?`J5D%QNKC1sTO6sDhbB3p5$&g>1he8f z$p(br_SSxV?&ifgM5Ene+MT^=@=k=Y5cz;A)^HJ2z+(7r6ARb`80Dc(5!l4jf9c+% z0R87yzBaEiT4xX?;C#1nUQfD zV@B9YV0z5Jj4u2QIDIt1<2ZKKX=w$O@G(4{Ea@~~#LQl=4#PNgN%Q?()A%cX{QC=k zKj|PyS_m@ni}9Q-WQ2haf2CFjfK&F|jIb?BLXq#`mlYz)PSTu{%s9?HW+MSp1fWOi zF9TAcC|(sUi8Rtq*3THwYD%xf64qRIl zrxeElZ97Ji2GzsynLT_`%;l(+b_cMfNDHUVM9YZH_uN$hJ|}o5zbWmOynZ@JpCyB5 zz#b1pUx$_am~G;p*jQ>-3%6iUD^MYX5h zZKP8zD&f+V8T}E>H}|oVX_2_vD?`oS@dlHZdm^k+BWoRAIhTcfSbm{!2r_XsqnOoP z7|C!>?Sh2$g zTNK;ortmbu(k<%z<{j;0Hp*u;Mm;Yv4nNs~|Gu*SsLp}@ z^{?{*ATphh6ZTw}g#oAsrbsNp8jw&K^thfiHqx$=hI;96i`VaRz7^w;dm_t@g8RQw zKOvf%1>OaJ{Z3Hh(d@S?k8}kR7pvu;S_i55<8xMUyjY>_SM3F;%-*~B9J}bbvMhI9zk6M#RJSDDfw|0xQYeO5@@m-MN;7mD|dY|>-BaPn3O}I8F5Gb<@2WV zybh^Z4lAl?Bgk3biNvxa$q7shcd*vZ8U@Qn$?*HGN_tm9Jo@v1{f_9c_*}qy!Q7MX zJ~~Wq_W3zWJPq2)+4dYUnr=$P&j+qlVfP5LQ@|3=u;s*04v@sz zs28|2pi@3J>$j%dR=;IO>l7Fl$!<<+GbDl#^bq*{!cg{$fHpq~EodU(IY`pn1x>|D zenNvi*LFDm-vFK_kHlCfup#jLHUfauF2Pu~tIjFfuN13wUvY68`#ztD4_LTiOF!PO z+8EjM;v$eSh_Km7t&QTF;{WAlZb0V6yecx~Xwzwe%}{JF5vqTP{(-)}D}tttOmr1C z5NW5ImP#du@QyQ;5H#eh@LPaS zY@?F)ys5@``3S4*w?UjuSyI}Ey$?{wk@R{QX>R)(1Q6b_u1uYZU2m>!YS&a zTR`kfT2}ko-VLUL_3HO;y{XCNpAW1l{hwkl#q)Cqb=jp)JB6^LXulxn6(c^UV%YCF zFG+o~&rEw{XM05o6|J$H{3${OCE%g%o%bAR*FHnIcEzs2P|;y$oiB?o51cl9%Qf=> z`qas_pDoAONQ_(!Dm}1`?V6MAhQV3zA8Nh7Zzh@Z3S8bM111L#hZZ+-Y2BjE{P9pr z+@7P0IKzrfI6__QsjtyDKhA_12hRn87F0d8fV?W(WG}h^4uh5ybD5~WU+J&A^PgV; zy%@i?^b9w2K=io8>K_@p`@>Lxayb=lCpmeXm7-n*a9A?u9?aZhz*G8(^gSv+YSyk3 zB3~20eSIONGl3T(yf)pVBao)Kp7I4djHxqrIfYt{cXjj~?v-eE1O`Ow@UgyVHuB;! zn+pM<dvp zA&-rDmtWrjXs4F6S-^^;=%j@}F4@MH@H~p6_ViDwhrjT^zdGw!e(j&@Rh#OP^K*9w z1z2#?&Mvvv-#*z{&Q79;kL|3mJWujL0J`PNTVml-{=cgCU85fPt%DW#E$hC5nUPH$;E$@-{~^SEwCvy~W86t}W;{WoQ37iA^JtL{dt!zJU^8SFwoLmO%VMnj%Z>rHj- zPq>|IsT04}7K9$pPk1e>m5(*wzlXvX!GF3ac2**$afCrXNS&I_9g#Xn4fuj{?DZk{iH(={VmiOqBq_aY4olx zcv?6*V`&^`2E3O5mdZ{Zgl0bvM!k&X;FFq56@a);Rop(-i%$MjV&3%OU5QQ&e(R`) z|E(d06L{I=V*+7IvuIck7+14ev@j2ijR>B{>R!8p+s8ShdVqAE0BAE~!lDgorJLxr zp$F6}y491bHrmAFDDGwhjJuLsSXxJaGsCN_=#XmEQ8~Jyeh7Jq5hOsdZMyBzYpM9_ z8y)(~_i3iYT@>Lzgv8WbW&m7m0h}h`M>}=bM+F!t=D;9>x7_I(7}l)P9>1P-hYO9# zOGoB6ud%}Y-5g0Z3%M4slTq|hGVExPoE9=Q_p{rfz;^z~-LKX)`!0QQ5EB)yBSvbE9j3LqQAg zOp~8kRPA|Y7#Dazpz(aP(g*dD@`t7B$L*R+Ne26~Ob$|=+&J+(4h&n}>q54IiZ(DR!-MIRcYh=_yE`i20d!$?E|>jJ6CXo&Ux& z6kYhut=NBEmTFzpi%-dw*Sioe>q}m*^-b(8Hj9kG)3cehDZp?(xWyx=O_Mh{m`iz> zaD+6C5xT%lUeruo^DszcBKowy4L`4|@(>3aI`@v3T`8~-<;ux*q=3jF52A}rnriI! zV!=*;6x75@UwD~mx%Q-1q4X{-6aMcVk|(~#p)cBQ`6#g1JoV17%lCtDf3{jSvtb?(nk|jxvrIbpBtTP7Lij+|jqOxQfVMgJkP#N2d z|E6soqU{VB&d(|{C} z!}$lQ=)k`oH8^RoI;(y>fCP#5Y8Szlkmqsomo<$^iffn_OHaiob#gZwC|z~ofy73< zClw;?P1G|$cIB=unL8A{m2ksop>|RNi~)=LFv68sSOip->ylR^C8us(99Jbm)6a-L zRHo*h(tRC&^e#try>AmS`s-g0#;cN<_MM(bvR+?ZZ)5V0XZ*2?f9&OoO9I<;+w-^9&3`$^oU;19zYY4}Ro3nj1F>L&6SiS9>u+w`qq1VF{f}{j zf&TI54=cU$o39unel}c78oOzk>$GgiXvMZ@xAJn%!Jz9V&5f``Kmel?TxzwAHNhpXBMg7eXQxP zuODHTIk0)huj~?H!&7z;cWPJ=sUgYklKuF&3E8bjcV701t#O)IjDASNr02=-0O9JA z0&q(&jCWrEv~G7r9UvCE(6O||rB6AU6vOlKRBys+rqx(({Y`wquEKuHzWKzwh0EOaUO8f}?Q5#UZSB;m^UecEg7lGY zE~sPKu4a~~`N&b>y0hoT@|?KAuS)C0e3Oglfr7a!kNn}#{;h1|2+zyD-&R1qnKrGa z)S_*%l>$hmFRG9Dz*0Da%` zhUvYx!tCOtP14>L4UOr}MJFpQwYseA?auXJuq5iiTwDs;2F{Z1qslJEiagO_)ake( z7ZQScPAPJ*_U$6bP(>R`hJ7N#mbR~9`-+tx?Z5HX8l;kFRGLSdiu?5HxYPH)9pUk*E;#ZOzg9$2*{sLvX)L=+=D^vS3hn2 z$1|~yssOSNR{h8O7t&~VNi5$|U|Rg>$Np*E3f4yz@Ue8Ri-f5JE=XiVXLqqS^*y+~ z7@VvWM84G}PWNBcGF0x!okPs?;r0onQfy;V+bI-y{ z5wc%tL3B^Xj$WmlZ!Ybp@`la^G_A$deo{wfUZmFO3hA8LYiLBjXLmUd(XjH6xF1*S z&ULogX1mEB`>;|HXFvQOm+<#@{C>vSR$g&u6p=uN;7Eh%vx>o)fTbBJXIdYRWPf~( zxb67~XUwdeVxUyu{QXxn!_Uj-x_6oP=WJ|$qjvu>_EP5Q-h$!uJI8I-d!PQr@ADn; zZasasT>EwP$%8W=b|^PXOi5$&7V;UY2i@bMDNz#!O}EcIqwsV zA1>V%Won+oVl34N6zp8Vi+#;PU4f&A1=+aKIU@Tu&!1H;Gp^F$zm}@M)7lju93NL8 zr34Y|U}G$gZM&LH(a6F9mv<9ws%#7K!+jz!wRgmStjO%|0DDO+y8qwQ+wJFYQT~Jr-uBBwO3tn zdz7-paz;W0ps4xZ?5YFW?F&G<@JvYO;ryT(Fa3)S^@pD4Yi6T>ux>`}PjK1^#z`m`k_%VAS|jH#e@@ zzVybbvN*4S^zUX)24K&D9!|KkxFn?EQzw&a$+oK2|C6 zN1*Wa{MkBCxx3ba`|>VD?=U}eb(Jk5^FE0-JJ%RD;(pb+pbJoq)o7)E9PgFCuK%%O z?4bQ}GV=Eqe-BN5caZfY)#1O+gwDKj!k)jVOy2Qy z=eRS%9TN0{wNcmxSib7ihX;~k@Cq-|ih@S&%*I8m&Q<^Qc;?M}Yobi|;W6&FeAkD5U!Zfe`|Rn_1!x-AmR!Ss^`9wn0En$_c5v+ryi8j- z-1*%bNs5Y4{{}xgLL@-xBT4CnG*Q(x0A;d8xkGU9nDlnb}8c(xl&Nq z=_XaVxBKQ;0t~zRWlc8cJ|~9ZE5-hK%zmuXKkUsv{#vtgfWZ9F))~^_!sg~2c%pBe zV8eR67+AWVtjs0|w>$r(6|!yX?@RkvvmMJ1AJ72VfS!))3EXU66$ zSaRIV)uAdvaA~4H*M)54zXr9cE>XebMmk>Whn$mak`B=1pZ1^NdobL^%dK&_1Ifj- z03@&&K@d5+>OnA`Rtqq=d;JEF{n3=Mri64%@-ue!(-!5`V|#aJsR9o zq&r^Rf~u299SzF$s4p*k8>RzR3^CUCrOugt&Ij_}k4JsO6J?Is zKq9ZWfx2bJwvL`_l5b9qjknk#GgqlHemmWCH1O#n>)RdFT&?G+;Ik~cXh)ZsW!n6&gwzoYuy zEwC5*PKN+okcSCSeqoM=Om#zXcyznDtJxKt#B;VtKjtzw{n zhfoP{@c4!1oR=7amxNN&@5gJe`j6c7Ur!ACt|Bm6AW=7W(N7YaaBMpM{hP${b=lKD z7W5y>`@^yQ<-h*%O6CJoT-*VgFtTl`7LGJc6GNz?)M_LZ&^8eQpwld&*Z1;txxcP` z&qIZ4#~Faz)r`Ud_KKy`LtHc*d8}%KK zZXRpD%Mga9BsFeFLT2nhH1S>TC}wsdXz?KM`eWtH9}y zmUd8Qw#t`JHi~RQPT?B?Cs~wH z2@qra`kEAC#D|EAoE@30kl5S4A~m~(Oo%TshrT(sOr`j&(`Dm;W3 z53|kzkrmjQz&g$`5Bi8hU4KEJq%pKx);SO;c!m$6 z$ba$W|7IH4?}x~53x(K9vYbY#2t~zhYNplEZ|d{ZPWQz_hu9&#t?Oc^_mkJxOsHp1 zJR?qV0Fa*8D8z7hL>-C~r4TuqL)4lBiO22}hx(sog)ffeEvpzwUAXqBZN$!&K~(HCj1 z$CW;u-El5(`pcZLl>dSOfSisZmH63*br%RdkX#m?=$`Rra%BzSunWe)@+1Dh5jlU? zF}^M++h{A*W%UU@avG~H;;9uMc{o)pKE&lH`v&&NI;U8Q43P4y#tJwT{u;3S<-DfI z{S)Rv+t~i>#>UKBUxh8z#@Bu^R{!%m|I1GN@L;RSn+_$?0}J9fEj&p(QbzBa2OU2> zFyUW`at5F_8x&oI78(WY*a7v$=v+3qhTv_UDejXCwkhj@xa+rie_0sB#vE3Cs9NEzTx8#S&X2l$?~D1iokZ=D4NZj(4iFYL5GO3eKrvaODT+|P|O zx_{H<4x~2Q-H?$Gm*thsK5Elwr4ikXn$eLV_AQLY$tA5sk+5iCIK_%UtDPRy4YVIQ ztILk^gw7ege*v@;7C@TYp}3V^`J>?cr(6E7RjEJUb7}0b8nAN~u-%f76qG>jU|5{P zd-JrHw-vT$<1XAhtD=3>@5`0bw;m4`rnmc=1^y|;`O63Wr#=Au-$u zl%gQLfrm%N{k!g@Ezb7X2D~=l#U$f$o0vxiq6-%8h#PUg3cCDxl^cm@Tdb3vtOU*| zE#{CELkU+*y%Au zRGRdmO-CMI@4z{8Fv_fJl&6$6ov07&g_;8!2cSKOaLQV2h%{8;d8OlEDSYOitpwLhlN7Tbo-xs0) z#7#|BOP_$YbmYF{EipEl$wqT-GO-zuGq`a~y^Hm`2;gmP0{AI3!63n}S6-SZ-3Uyp zDB@>{B$s&gYLs@7S>IjWZ>gkBSr0UrpSEJxqoGK^zM9USpx;>I%y|dMI5hQ4`Bedg z$0A!++P&YTv18-+-Ba`(ZS`}G+kbK!n=f-Mglv-A+UNUd1?s=!EB@`a{HPF?@HInh z0#uu7t99@csjp+r^%SrU1m(cq#6U~&6Po1m>8Ced6*Q;3;l*qJhr{vv<@@n*e|!ay zokE~9QnZ{KX0H>lB zAfVk4r74tbc4F6AG`6-eZvW!b)(H!OD!EK+DaO| z*c)giF2F(|+pL^}snFaF#)85{#(=LHQU<_~Z0udikgN7SGh$DVsapUH7Zga8#MrcGY%T-ntlYmf*>Zt7w zp^b+HD2%aT-Emls^r8un^Do(2$?{E}`Sx;fH_t9zfV!vg)>`aU;v*#Yz04`S{MmUw zU0xczo3@~lWy>bpmdV7O2wkla9bvT;06d-pqMxLL#7BxEHYp0oqDEZ2Yer^CO{Lx|{8e0j1Zbp$#b{dzHJ^Gim((EL6xjh}tEa_%f zws%OdO1wdrqN8@7`p6pqKcm@}I589-f>WiHvOeH;q)0PVVjt$w=6oW{Q)DFs8h(5G z(_&^c154prYk&h>qB}Rl8-r{cC!>r+^InJLg7km?Up(&L`(}Qm(iZYC$oUx3I~%qW zmh~1^VaGh^ons9B*F8NuFzNT6gRTKMr&^F+bh($nbJa473sAsH0fp2VL3PmX2-_5G zC63d)GVD1$rVV>OK*Ev;_pUc_6C_EDO0#WL6&_n!u@}?>NQl5wfh+)IjC{|iq*Uba zRz^@xSPD~s8{9&*GXj{p9+kHwweBtm)WsaYpLv~=tksKUKyt$Q?>DH842GI(Mu(tQm9h^m(>K5rOJg@++w7wvRtGEdO~uyB`dU#I@Fl!~UP$;aJu}IvF2wx_xZ^n{`o!gViQ> z7G;^-=Per!)-)A^bENE$QgU7g#7~{6hdMF+oLYf8P*y>)0j*|zDF^5PS0k}H_4ph> zveDK0JTh|MQ9B31SY0d+L6WlRX;h|6vk(w=g^NS(r9bok`pq)PJwcv0Mb2bV@t7KV3I#L*)v38Go0>?^BHD=ovmR*Ww3Ma`Xel^>*;&=QH8iObj z3ugTQq;8=aKz?^<5XJ~n!Z=ur&u@H8hd(J7KD24d`Xd%`R^vR9rTPtbQT>N|NIa%s~3d=1)iew{Gsv%sN zWF4`1^;p_Vv3$N+iXl-3>i(H&kSPcO$nq z#NzLSjR2;Uwkir>0PE(F*z;~RgXUsa@R_`Z^h9&gaDz$@_s{!2acwfO5~Y33Lmo|7 z9QAILF|9&(3#KHaY`^Dh97&g&6Y>5wVq!TIiwCR!)@l5EUJt!Nh2WfU^pf!+*4{_W zFt=0R#g$;|mDloC>+ej)pI?KHI>u6-t)}h#s!2N2h~8tW^2z*mR`$33*!xs`nvptx zxlS)fDw?eyT6jb6AU~7My#7L?zPqh{jg~}$28Vd1j*(hzC|&_%G8m6^(CN)FytVaL z@a+@+b_v34sY^bHT%cXLlN;zGv1f9owL)Vt>u4-E4(^)=L0wdYRkv=i%>D^&p?=IWHPPBlx zf4hUNH&u_0Woa)b>pZ{B zyk+^P*c>BaR>YMqgIO$-J`g*zx#*<;SYO(8C|okniC@*x?*n2-KH!inbtiU1?&oNh zqBQc|W>5=QDhu9q-|#RIia$k%#5O;H>AzZoQWwVnr=k#L$Hr!z!Maf38MM9wvN5zB zxCs#3AsnpaKX5*l&-RB#4KT51bzDDI6unt&fTEv36zfar^sg-v70qNxkK?& zu@vzl)DYdl?BvVWSPjuV5 zKOL{wp0(Jvv2{G?JmVz{E{K^4542M?%0u%VhLY@KDn6O$-w3h7uCyz_vK^)sV_R^u zu>RGZ+o3_<^dw`btmJlwA6({2S}c(36K;RQD{TQ;BpMY%NHP?(ZUGvs_F7gVR2o2s z_(qd}sX>l@S7zw81>@Z(kG`Oj6u=HF1T=|Y+QpctU+VdxMTU6ufb|UGF_2ks zv=Gjt4Sp{6HbHzPDst6YN8$Z~5UoS4c(H~pUm)0yc2rvOjDO%gw-s;@vP)l=~1D?omXZ*-oUseXg|)EuCK9qUJ&WXlBZ`WVFw8XS~+@m)(MGF$0A6YpV1N($APR%Qp<%9;V_u&PM}VIuZwpCBL=Wn8J#0gT!~|;6Ti!gg7Q^ngE-wr7zE}6s|N6K|Ga)5^L-FxPwjFN=6A!U+J*v z&CgjWvq(v3q*}yB-zmuEHrm0n$8u!627jMQP&3A z>Xc-r4xO0ZOb2CN572PaaBs5Y;~EYOu-mhO-9}lp*8S>TM1s-q`ivztnR9>$vwEP(_t8K- z&;uIc1F`Jfnt01hZwCZXst4(bREPn-t-{U@=UF35U3ULD+WOnL3@b|G?wt-l9nV{> zSoIAb`nKrn@{3ny-j^!2hG}Dmm#v!40w`nB@1w&eHjy zy3)#)Ci29oXR_+2Hv2&eSPiPnwKsJ>904l(Wa2{FJllsfobn3E8Y($Yal9(0y*ZPXGRjNKu@)bU2_pQsMq+I~t2G#Y13 zj0bKAioh8iJk%`LcHNITIc3-%cDjA4am4*T7!k%HAes-5wiLY&H*C^~sprPE5Iv6O z11PDYyFS3K1qc5+fa1@l(H1}Q;F5jUk^5JK(7Uo0_dBvW&H1iiDGBNlW>TRpggY*) zDLeKwYF}>lHRgOWG~#^EA<~7sl@zwtp@;!>8ib@j~)z0gTB`?enD?*Of@{ZxJg`*&{!V9(EQ!OA=R1SwcdVSl`h2_US;oWIRO_zBEu>k>wD z2WFlGQn`Ni<;yxGRM+@_Y8td z;a17lT*YOdGnM$5I=}TBtm9Wkef$DLYeTT-dg6GF6RYA}EU~2@p>A5EA@rCC8rK?Q zwD2z_S|Usf$oe({gl!~Gv&7Y`6^aCMX-^j4d0Cu`r^tcHZa&(Z-JB72y?qR38x0mH zX;9dgo~_9UA^af8sHH}c&)c$nY3iv2?7-{ijAbayHv7K3a{Sp{t>)rJ{^jZ7tgyVH z<%joe9Y#Y`rR2JcJ#K+NmyQQCr28_cjx>z`YsQWPk#)VpA?s`3U@}6Clo16rZbPExNhd{I2T=Mp0<(rCw z%SR?1C>g0$9%v56uMLkuVTy3i8VqzWRc?f$jBXPNc7(E)Z;gfI&VDf+MjLJM;^(d? zDqg|Lxyq1_VD+2xSR0o}`JtAnN{h-z9%6+4-QF#}5=I5G)mCd*W!^(VoC4tjIK&#a zsFpKM28V0ZdK>m_b6-~AI6o}7tl;)`^1=8B5e40(!J*>rLWTQ9GDktQIvKFKrPkp(G}H#a2d|3g&` zaLT9ifA$-2AdzZJf4SS{(1NgKQ4raTyE-Q&3utMIYhRam%963JA9pv&F9CmUn4MBK zh1x+C>6V!)KoE0b8cc}hNX2{}3a#V>_RRsI9C2y>e6&k|grH7);8da#TMbAkV23Xv z(XTg^E&s@R?E2=qUUqo7{FjemxReCOf>S)FT{sa^gAGWItSFz(+zEm~bqeZM~gZYosT zLHEMXn6p?Osx|f8DMU6cgIRlWegsA|?0vas_D!*u_^U}pXBBIcWI&K;28eKav=qA| zo~E&OPi?jlX4ON{=i?a*Q8p=5QD#;9V)1b)28@SDbRl$~3|%400Ge1QAp-T&yYdgE zn4jB~sG8Hd}5bWA(m~Z-`(# z%of5+K8x6QvVIl;>SZSt{09u49T$tNc=VpSJ^r;8i2f&cDeEY8ZKj`xeM)mk29atu z3rx*@oxKvIpdygq5JG{AH`>C_fu^6-QE8_8{Y07DYm3i_!|fopfN=YS268XZpSc0QU@zsQ5A;@ZvfeS$yy?F)L;od4u62qNX6iUV zdw-;2kJNW#1?PcH7=kpFHx+~^%*0H058VC+hO1q)l((7Yi9$09Co1pors+6novxR$ zuw%SP%{_^L#L>*~RxL8x43P{TjV)TqcQ{$+o|gi&WaN~@<5NlTulo?X{EdnI?E+B9 zx0_^vM5WPs__@WYRkSYPF7!+$CJlTGU_@MA+~r6+1A)VSKA0<&NAQARYur%iHf_=0 z0TkPIha9p+whe>()DQ%^wGsF}yTJ?r2N+2LHF41-Lqw7kLOF+e;;1xWwUTxcKul5v zB%$kbPv1!c$Ihf?WVFVK9?=%BH-Kklj{_*4Kv4D&2 zdRT~-<(K?UBbNHZE*Wk1P1uUtIEgW)C-+NEUy)g@SY;0z=y4H)E5@5^BV^yg>GDnYJdM+&R6g5CYmH-+H}IPdIf zmqt^@%|>oqKnk(K4VvJ(3te*Ch4%Ru#(ankS87(Xdg1Kcs4=xSk(V?XB5&T#(y%S< zyX$g59D3;6*y2qWJ6(IM?p+_E8mBkQvAKjlXY1I3ADeY}2I-zpuXCGTI4)ccT7{?g zvZZVipi;!n=OzBUz5m?1f8Sn|!4s(-+vYP z3RKx4+PP@veGG6XOQnTpSWoGly-BE54KGlJ`ROv)d|uVXb1K76Ufz7O=x5RL5W$nR z^+=ZA^JH9s21Q(2X?fea)qj6c&V71-1C@*Ib3d zkf8~|M7>n2ekv!Syq#EOS1-Vj&+Wf~PlkNaB+bFbPTpxugXbbtasclSy3kLRM@C+7 z>z>ttnIPi?0ij^YAS&_2=zC1HYuo-#U8FW#l; zl(6MYpLNhl>$*OJ3Sj=r@3WdKfJmwurHWz9K7!7KLp;}U@nG;GS3oSfWb3;u0XBUo zL>Ce6x0{{N3xx}+gHX$VmK#|Nn*F@mj{LZ6F7Q6H>dvzfiPet3tm9i#9r=r+|9aL# zgD5WT>wIdGs{|tgkVn_psmaIspGaM`U5!MEy*_|Ue0U!n@P5PgtvP(Flhih?i7?)4 z=QJI%HFR{iw3LvuqdEIg#sV=Q>x6ChLXlod(1+|tkFy_VmtPoPY8_#ZnfMQ ztArVNsy5Plq}>hiPh9EW<%sOp#5Atp<3FpJn!wZt1N9pg;!aFIRNs3kyKXVTTIAhJ zu1hhazo-_b?X_OZ(I%;M@+5VYOqRPc&npXUrU~fOlv|Y4gJLillU~*Opwev5LFLYg zi*t``r8e0T~ck!nL> zv4hzx$#c)LzYIHEc=KSMnV(JHfJx692Xv#PdPCp`T`m5oMlNzcmzMGdCh~!se>^|y zBl}rb6MTx(ip)kj6ge@)^R@Yl?z*QvW&7vNILR}<&-ih$mN00$6N#qmG*0WkrX5K7*0m?6K6*o>SR*BYOW^g}^vj5KlE z#sjR5^qM^YzxZHs3n3L1vG@=B*fdNfx2naDP~@Qb{PDp#GIQ}` zh#Eh|tqynN{O3-;w(OYmSw1td*mrfUj1yd~K7M3u!1FSPff~kL%)pr3)Ke=JB;(w% z7Gtf8nDp*z)+m2MN6h(Vn_H|%UPF>XZL1++m_8XoO}pmN(k#x26DKoWF|(yo^Vh^0 z{Kw;#MYcJPRs=mk9pKg=3LwOkI0?FjL3he{^B3@Mmz~aT#+Y`*=q#OGQaZ#>Huitc zgF~QAgVnr+Iv$IZK6B+DV7-Pl+FqDSW<;&h5M<_iX$bmjBHPerHlKNlHzeT^W(8NM z=T$oA+pgl%N*htP9MWf~(7WI%L6HbI+B2Mo<=i)tECQaO9;W+cG5zl;ZMWSdZsI1k z&39~W`9c&=IiA45{E){SaVYjGmOgY*1<#@J@A?z}@lLM%XRX5(d<}`Vw6o>m_}JOy z!q!}mm-&(Z;RgT5k5CiavQPbnqn+5ranXfs_aST|AaradG3iOSPwA&!WNj!;IkhXp zFE?(rMnmIgTYGK|WpNaCHs^h8Vaf)KCD)WJZ0q;i2k58J17nJe{d+_Kcbpw}KIvEh zkU7$2;(4-B*W*I)3bwA>v)@xNvVB|{g82{$!HCH&>ofdFcV1?GU;tYVPE+R&lI-@2 zMaoS_VYo`^28p7q@V(Zm&(zD%=`(=a89n*AVrOluF=1E^su5Y&%v9!}{DzO^(io%o z24fY;L`O4ja3WLZos7?DFFX`|=87JO7KPFDv31+Fy=J~LA23oM*N)u>Y@3F)IL1}{ z293|W(O|nW4kg$w<#}hvfyr>N7_v94E$Ry`R=u>1=@^uO&9ME`WU~LKlZX3Py@|iuSb+mOZ=pn^nBQx^71j8|5N`a;le6tA zc;Wb0ucx+BS6{-C%nopo3!8Asx%T?cK)G48)?%JnhJhpNcQ4ayL}Dr=UbH&Dzef-4 znbKJMvpIT(z06SrzZYo9LsVz&w7njt*QP{8+ z_59wobgqQoUhs6D<|JjP71eu?DT{xe3*<-deJe|}gM?c*;q8T}p^Rvzr zht|G|N1&$w_QhUB2M^{p>h=vxgh5TkxGQVxnxpXsr?%C}j zWSf(Lx^k!5Z4Sl;|90@CNsbETPT~ar(j1hoBSNh0*M?S3!j089m*n}!VuCF2=-uYB zMTdrwZJ)O{l*`TGhIex_58O{!Ehg^eCMNs3CEz(YCQb@Z3V8OOWQMTAu?dA^qKUIs z;?LcfVW+pN^Mmbvp~!i7%czV5@w+7U51lvg(js|PIgD5*U7@?#ywq%^r=LzDVFqWLNqs3Nx_2 z?h_Y$Dk}sZe^ELu#cWT41qRlXWR{vBTA7bIHL;btU)3AA-wh+XPz&aD#MObY$qr5o zc`}c#a-c8$!*cmCs44`1V<^VGY!o=HRL>e zxFHPRsb*8s;S)@C4s0lP9Z49Hvw1uoxu>n8FX6dMm11#4QRtys`trU?Ciuv|Gn{{Y zO`z7|ApOfuuSIVS!Qul(3pS5#;yA0GzD`Q?TL06V&lYU~*+Q;nO@X$pG1w65YMi?D zNo@A#yi>N$J~sTh*i_x@@DrVdGqp-qBpt7L_^w1wcC{cGEte5&3&AhBAFW$zmt z&Az-#75ZbACS`LyXfX+HCVnf2)qFKXO2msz`JM$2q_H;+(>FlIfor>t?^&qoGA9`H zYLUP{Zl#{`szM8X{MdYZ1Ez40Q_1MST&yuiTfFgI6xmuiWj=^O+$LgByLi z_o^S*!VEtbvtylLwvP+~UCY6gy@vNgHN;&x)h7$ssB4nl5KLm z*TZVF$}ogs<*BVO!-lxfnhtewv9{DI^I=JpDXQ*K4L`1!#Nt0L&c3Fi%_e=8e7Ufi z`IqUpy>hjxU^nCW{eApy_{p$+iF>&Np?D4@mhRUF=2&47zo9ny`-3qY1efuqFUX{+ z3De$9$4XMnxEc1v3q5d=kx_07)3YRqa_p3df;+_S9G9btvS#(ae5rkK1$!9Dm?jZl z!=_x75{$5LFw4&2Bp3CX?pZy486H6x*(TW#g74G@V+zq%RNzPDaxqgm>Uoyw7G|08 zun3rIQTR4y*GyXQk@|v0zt8b?if5Z$n;-R`@(JD)TJ@86sDr$po*npPed__SDC|M{ zm7`pb$hq!sf6mG6+pAuC8%PCcB~B=TL}J>*4y&7@%AF_uk^6~NC;tKEIWkC00qX@B;CrX3zFB+HTbbdy%gCv}FK=@(x8&)|w-zqO-hA*qDQj7)Q zaS&)&U$>Skj~frG3zn2Ml-|VKi7~*bCHpq%=+=hd=@Mp6ap6{zdHEG;s$RqeCMq{$ zCe|3E)D1*6aGG%KK5s0(`7C$&>*QLslYv7MYAYw@{C7qazL=x773(~lL;v-b^OlB* z!A~2e&R=nB4oeGdT-bbw?^OAhwdD0&WVOBO4H@sbEDMsff3EI*6>i4=b9w%7XKOf_ zV%>1_S$I@8uCuT>DE!)@-4~YVVdRI2i`A__{;#BIM)iLJ_rk;@DQ+V$zk@%3o$x(t zoyEbgXqk47-2XIJg}ttBUGd>m;r)46QV?UO^7S+XYfU)f!gY&FDo|S-wtpDgpah!( zQw+rj!`uxbV`3{IpXgN(AYAce9E@oy_H+5!o|g}UY;N*HT70UP9~Xa_E`vd^FU5!= z;~kZ!+NX+g%4=9A_JwP3W@{2&fkP`R32z6JXAc;vuie)JJC$z{&{PIdBL=pLm1%#* z!3>UwofUbZ1)duj8;J7>t3pagKZ4NNd|Cm*`bm3UjVq;+D zb58?l*#6E-<+nCQ@1?KZ)^TlM*>&Bmceg|EJ}K(OV8eG>%7($e5dw@#3%){Z$+M3> zW7dcGYCrlW`79@hL%;|`a)RepZ1y^3&y)?7a22-C7v&G;X*!KDUVrY;$O4Rw0KZ<` z6}2%jO%mJaJJg04nzAR+fT2fOrM<`2>$+7Hew*Icc|d)WU=cfUh=rVDL=LiQ>%sVN zyJ9q)5Uh5!$pvy6QZy<$6z}8u#lb+`(?-x1`4Rm5GxDwnGr^GIe_SdEOh0!6PE%|* zZc^Jd2_jnNqRl)LN-a+*Wp(*C)dZXIb8mLbO*_HQ*}@1fFkR0?t$9#&V6E7cQ?lrH zlT(m^rrQSblilRs>_9W#rObUbP-ou}ddGhHqi=RUnPsiqs-L+d9tUoDi1`P88_npJ zn7Ww(voVy$doJRh&kSzawexw=((9>tnVsL?M=1Jq{Q&qYdBE!$oUN9NyfqU#g2HPE0MSp46MT zrG$ffu@my!?U3B*;^P+@xgLhSE8K{Yt=c9^cKecE1v>UP$h>MVl&FE?F?X)dp^0v<27RpKP$cR8~`k9?xiSg;wJUJJ5R*y2w^+~p4 z5GT9+wtam~h^GSP{HM>QrITfKx?XsbPzXN-U!fXyaKOPuXfxvxM#0dBi8jHq^$(+9uQER^xg;XWp*JGp?dQ zr3!5+&b7m;HCYt!p;$NIZ?m_EliVmvwgvldSv>XPzG&Lb<5LaRjo;NyaQ@7usRtiE z=A{!mpyJ~2W6C&B zpM9C~6+UJL)3r=4lkUYw8jn}ypB@>xf@5N2 zWlTb1M+F!j?wfp;#l4tyhpMzxuMpK}nox?>fb4#6tF*Fnk}!u-uuEG6K7OX$wWR>; z5RbFTG%0RhQu<&G7eS%r3yTYv@noXlWY{?sL31{?xRMp=qF&6w3UN$xWOYHwB3~)#_Clc` za%wi3ZBajv9kvA@91)1^c0gQjJyJgj6$rb0#^^E0&~UpAtw<4`(0}sbZ=$$#jdNugj zwqGj9JBE_k)Z^JEZGXA0O-i8n(`bhG!^xW&%TLtC-}W`H{#IYSP`3T!r{x7~vpBxH zbau&G#_WF}ZT`_g+`g^j!CA9A1ht=UGlRQbOLaVTwnjwF=WXEYEoui~CP=(G8GFW* zTZ7*TB6H^l($rIOJk@3FXF-CiF&=bd)0D)49I?`ts)y+wFW#bTXck|KYmF=H5q%vvUP3Sz&M<+pjG=J{_V^YXH}A%l`| zegP8v8q@h1x>?>B)d#D{jg5^v(r@BrI(vg=W_-49K3KWur>*NxV{}0H@+tLh`jImI zPU<7%lcL$*t{JVipIiR6@nG$x8KK!^i3e++qz48VlRW6qtfUau+TrAR2(S{Lf(9FP z&wUsn*>x|W{X&13n&8EJ|Ak7Z&TR?hVL_QyeKHR<)Nk9{Weil5 z&cexg*VnnzBX&gdLQb55x3$|t=?O!X*Jm2IMB*js*^ZR{5^Q#cb62)ZO8Z(ljYmDd z2}iNQl2+;+5GS1zTSzKO>Y#{91)73BeMt_O>8m-d-HCKL0iUZ3#rF5`q`KIl2fB91pSsRIEkU2I-YttqFl}nrD$vQkB$~2L+tO^V zK!?q0a{pfXtH|_|Z;w>xX~!Sh%tS^R%aijQUI|@pZ6@D&GIgJ#u{-eZQ-@Ax*jcsc5VjcJOdr~_;At(zFsTH-yG zIUeb@BX6-aI6C>@=QE#NMxv1Lo#%Atrxo@24&uL*!Ei_-zI8C9p>w; zEw*;gT660o@pKJVh@G|O?be$HDhYMJR^2<5v0sk5oj%gQl@`mzSX)*VpXP2-t(+BV z!kvo^QBx%oi#(?&$9{QPdicgvqhHdooa? zMmptdpWSkw=%+T7)tdZm%70Rl{QnOI*5wxJk_C9e(4VTiX++^TYw>o=J5ScW%rhHU z@_sV>o+OK6lu5Sw)H&DbdXi-CmiF=2b&GCjPtbMVD~4&0vmfqzN*RTs?{^I1tViE~ z8^Sv)FvRSR>`|uXZ-%E z#J$(+`Fx(|Ip;BsjXWwEOfYiH4_!0Lgvzj@w9PA0$3&8~2?>2V>ahyqR&wdu$ld0D zK^HZ7YQ)!6W@l^#io@Dywo?AfG3Elmy9Q>xAxqmJrGn$;JLUBV@M(gnMIy%nu7AO) zRplPQXINvXmUbkASsxKEgynhm_L(#*N&fKLu=}eMp`#k2$3yt`lGyv6U}h6quYDgk zqtS8`CqTe|5R3B`KY3I`JU%zvOP#=G*_nu zSN2H93M+Y9;G18#C`0=|fM7KH7)jT@7#Y8VFM;HdbZO|oSZ3GNzG8X9<&}fp+8|1T zS*H@Etp@FtTu~z^zXNa&RZsrBZ2u7-48)a+aRDP>Z!oTW=52j-X>X&&lkKTC?Y366 z%|eByrDJwMwKn7a&C1HP;6L3a68o%kJHo`f9kPI7n>69|%vF$+aUxuOqjYp~-(SH^ z^mA91U(<_dDj^ThuVv9G{{{SjO5uBVmLPV_;FE$1hDN{%k`BwwXyt(AnA;^VNW8V+ z!1OuPeOMrBf?JW=aW7s&)b7i-?wb`e<% zOSJ+EO&NA42VQ{fPVDg%fcq{I@F-hmZjNGrrIci5$)u3UJ6@X#FP@1{E==b_B>Z)T zQ3^a`%wTarK9hf9$OO({dGwJX_|YI;ir_fy{>N*$e!q z7M>br9#PC?V*L@eAwLK(GQrSjU5~ntm;5&~3`PbmajUC%Va-AWlXcQk+|qS+O4!`j zuB!R^@}(LuX1ql=Dx(#7O)w`MdYL1^7o8QJIbF8kFJGH4qWsNHY*e9b?^I}f*YJJy zvm@chrB8$vr~KJ5{~!Lfml>Po-$9l&^BGbL#qjtEQ&ldW^KT?IHOYrqJ*}QDFE39{ zKq5C-TrA>$`H)F~*8j#!_Xtl$V%7Td=5Ziqr zsqe5HyV~+y&3Zuo&KpSS1v`ZmIHEl33lH0}ljS$&w?~`vQJVB^i_~EApi5pMrI_p=zUD*MsKneoFy58^btnip2Je$gi(dg_BRLY5``bS1#2CQdWP$lZVcPY`?Yq# z5JkQ0DnRgjp`#%uL%?HFnD4vm_KX?9Oyc7bBeS_TMYNeor9@+E-v~#qvCnJvO|FUD zIsUah1(s&dbVHp!0%j(XW%ThZcW*V{AMvleNwgxOcNJwL_gN|o$yoOEGp&LdU?%NxucPeZt$Dp63*_Fh;*#XH(B&QnEf?>b&mR;-)p9S!n9a^@PHss zfU0nHc4*o@|EKA}*pGCsvCX(<>BZG88O`hXOmb(sSi-LM((t_h^HJ75$4Jc=B6er~ z{XjN)Udc*AO^YYRz~@W*5ln#|Cn9i`(LY1D?g*eEa$33QjjqzpB>EIqpTHu++V zQH2NOW1uBYG(u@=s>ae{Y=9GTgnnSILV^_=i+$asf=*MxC4kFd$^Vm!|j%T{Nqk0eBN@fxUZc=igbeSp! zU8I`EgypVbd0p!>{nWYYijAcCd_1vhwyIaZr~7L2eznE8uFhRub1x~uDeP65W(8+r z3L?|{joDDQPS~@iTg^o`&-e2BrZ7D0-Gs=9i$=gyDbLr7a%xxZ_;k*f04w9=IQ{Q;#jijNr`=!+xks}0(0~xm4I!vgv{UV5!Tre_ zIVbP_2-g2DB51<9q_b{|et&8W&kf|m2;_&lYvuEy#DGItGimvJhrD<>P zzxY|LD_z<{*3Z^QTsW^&nRC*TCO>rlj?1Z+O{fY1bm)V2i_Mzry*m%)#0!M_C)LVo zp%SzZCh{Gh2Bz6_xL9hekG8lmc0B69!lyIUw?Z6bjELthgff0z>_k=_23S|}jIokh zf$;@PrX9A%XJQG^c!84s!f{O{VU9 z_O)l9ku0$DukHjM*Rc-mS4SWbFJ#+kKlkFXR5Q~n*7?WfTC{n-I;81EWVGDL ze-Q2X)%oA8;P-riv5+fagAGH2=~VDwhli_^M57y}kL`>W!d;lHR!BGrovo6dbbbB2 z|H>F-Y%e1#MD$EXoBxR;+l@BE2=A7wNrchos@}aHDb|Z)m)0BXeVU3~6U7yj ze&ob^CPQ5~sj1J)Q~Wtpa5*8I2ops{4LB$rjqi58VRTsTK%KQv?QPYQ23GIgO#C0D zhuLmE0*DTvGsOWo7n69?@ZKZpL&aJjB8fO=sKoJYUSf3|ww>$ua`hE6VUr_tOhgcL z3Buj4ui`=p?Wr_MvQRiL`P5|p5VdhqN}O3|yU{LUvS^Esml!uE(#+Ipd;C6usF>@) zc2bWGSSlp8avAixKAL!MWL@+bE|eB6TK>}IH&M6N=W3}NXV%oYDMw$I?n zmcAHuwV$VNby!_fiixRw*{9pmbhF$P{+i5_isXTvJYWAQ$bVnLd(rHMPrsVIw=12SKkhlx#U1;b>J{t(%YM={vjMAjvO#_AnfD%Rz(D z{J|Y*dW9ZP=09y{y%++m_7A{#NuH}bw#p>4yO1fdbdfs^8b4R%WoqF*CN*?PWN9^9 zA_2*L|Lk6M9+P2$kmGU=!MLbxJxyf@jAR1^*;!@o1t<-HrU@7?3)WopEv?y7M>v(Y z0y&-f(fU|1RMSwC`#m2S4Wt zf#owC;kYhBM?#4RtdB*y`E?QU>yC9~#7(Ea3uZRk6rz0|==k0K&ui_#OrE}?=Qa8M zub2d+pbEjj1^wjoAl=^wnHr4y?#8tmoGqomi z`rB=Xa~-=n6Q4;S=Gyy}s;?xdasc9k)m^~Kl^I`R*surU;v;kt&Ruvf zS-U!n9G3Iuw1g7*rB@j&jWJ7Dhv%2KW$Hgy`yaQ!?_acKt1LxH9Bo6P=@=e0g@Owc zG>icq(S`!kiI0s~H$;i8?llIpFb3vND;uyYGiFE&=a|wRECGP!sx0QjMjy_BV9$Fz z?;<}55J$HdGIdv!kzy0#T!!LgGZtxy<|Mg5PrVFzUT(FZ0MvR|fy169HF;{&+N%uL zA>E3!soYSk53d=SjqSUDQEPc8ZnRKSwG)y1L09H}UwYgdPxMfSt^SbnEJ;0x5M|+bxvJrmmPh#URr3O1{Kws9SkpV4CuW05uy$4F5^PjeQOPW8U2K-Mqf|HdY!AD46 zfV$pr@s;28-iZY0hPCanA^rm4ayqXc&nXJV^Y*bsnKjSf$XQ3GA%k#6oLQ51R zbir<$NVa_Bf?4kpoaRTv>0$*20#%udRw|R2Fx(;Be3?Oxi_oFu>KXG!=ZLiIlTg>z z$iiRCwt|jtIJeNrPA6ApqPuzYW!W2hjDaQ>)qnyhI?rPhB?Q;4%-gBf0Q}=2$=nL! z4uJ?mOC9=FO4DYv)R$v{g}0!|D?Y-XQS-XNB!$uAS0&UfnaEUKmX~vB%TxyrJHxaX z$k`;gdtvaGoiRcJ&`!YPfOr_O4^*9xm^ zT?elF4COGwtuAqb4w=0zcr=(Cc_Fj4CT#!3rJ{(G=2-3;B!(yATtrH@!=chu8xG{X z5<5D7mYU`_m%c02_fv>$>S9*CzEq1EZ7Y0~gE>}eOK0!mLKlEMI5(%~FCoE7>XH3f zSpW07#w&&U4q{Ukfho_IawId!MOkSm@3h+Cmqee&i+*K%Gm1ysQrR41ok}K4ADO!T zI;e2!yjCCAMY#R@L14eWc@_DNsDy?8U@<*Y-#~bwf%s6*+a%_sDD75&4xN?}VpQ(u z8?#UCZ`se+wVxX%<3+3lYSdqLcV$ohEfTzX{_L?v`f>V}5N`7Gi412I)b*;oXPtYe z)I>i&>r*X%C0p+4LfA@T@lepsfZl`IZR1Ud-5YELf;$+<0Q}xGfhf;W9s1p~xm^yY zPW1VjI+*}Sm&^chVtWn&2`Yt^)CzM1iTIt?%+k)MDpC&j#k1mFCM|n~$4uod9l-HM zq3ElIh`r7ply=$Y9l-yw709$FTJ@(i#8Q?4QnN1$19GOo8`eHx!Bv@O57VA*pR57d zLD4>`2%xz)XfZ$I23OTk7`E~yIYa#ZOXvZJk{9fsJ-Xk@C@8TnP#grPRD|r-5qb~b zGrIO;^wG*=%)!G4rJg|H<_emB{* z7OU{(<|vB^Zv}O%^pPdvN0#y5T14MBkoFcbB7OD6ND52TP%*vHdH{u9SA*E2wKlD9 zTEvmOUTN?|GJB0z@RO2!#{)@b9s7AudMWH=+wraCN(V7-zBhouY z$J-`mA2v)hFE)aM4l>~{YFCI^#VRhfo(+yNkCN}5;q{^baY-_o;tKo(vOFrmozr0z zxfFK!7OfWvmFxXzwGifVza<%kS+EdRC!Sg`>|TGvO0sm!CN%qEVzQ|S$M#sJ+*2}g z#{%Mc{qocb@uF{j8F>p zgcRUEXFw^!0OoCH)*`LN%9@vg62;xIRkqgcY)v-{+PTlDzi%yq!p1Z3R}gp9p&o~r z#&e5PyRwQt)9xRbY@nxc2Fc#QawqL6pC5DP+8$1yrZ{IdT=bz4Sh53LxhHR*NA^hR zc^=c*?AMff{MNMFE0g-0>`M}|8SLZ~o3jj}tI16aTA}NI))E9RQ;iao5mgo_Za<{R zhU4lfru}k6NNjEdo5$Kmq=>cTVGVhKqfT@K$xV$^Gz-@0GH^@s?u@_AwN5M?`^r~T z&yAtw?m<`b`S+Fg`8eT}`|otkB2&NfQ|!Q?%K`n%nM$!`0k`42>y4a@$Y`B9E| z(fHMpuv)-eaF*vA7f#@9K&sgBY@s$;(Ig1cZiwhC4MsQZFZ|^P0Vjj52tQ-O2KjvG z^lmEcPV~0k<8>Yzb8-R8a#xt$sPJ4zyQrSuSoEA2a~Fv@`&ou5PjMCEdYzhW4BXSX z3zKa%oR#}-7*{HiHO1oFQxX^ahmJ@F16u;Wh%jo-j??=u_wetrT-MmPEPF1IG8awN zxuel6s&0LwN?5zpdG$qjWL0f?vEZI>W&=n?`ZOY!zDFx&#b9vD;*Mt zw+j$!DFHYW@p#@%lI++ba~yCJE;{o^XtZ9*p7Ez&wa1`e`zxTOHG+1?Od1~4_BLyk zNbEh7f2(%xk?x7r+;e9DWDsWdsORH2Uumf3*D2-Z8VN6 zEzXFa7NEMAuUQUiw!CC7uG70S4YMEIydx}I@m}jv$JTMlsg5(r>{UGlc3rM|F(JY} zJ4uT94{9YM&$jFgwMe1`tiF!f~KXDvOiQ-le_LoAoMxFgb` zY?08J`>Nk{o&AGUbKA^;dQYk-1Ht;&llZg5S`L0K+@O*<$o*I8bWe4zfPAQO0XLs* zpzP3WEoWffgAObY@m(1I%4-m0%d!?ODfg;`K3P>i7UJxvFkW*4>blde>wjOIK-?eu z@b??>pd5qC>b4fh@d4weQh{g3Jdp9rw0|2Ru8J>`f+D~X!F+v_GnH6U{U{)w<_V~V zDasbGW9tK#w5LpXH`a34GN`DwW`Wt<{y4lP?dtHJL)_jYUbpQvS6auVGoF>J)WXOE2h%@ZC_ z(uEIAQSA)y#{P@mJvCy?q`V~zjJ?zmk79jOGM;f|}(=2KwK&!OHjL2I3M9u@Jb%TItSN+ttCB z=Cocodsb8Mi}#{Xm!hgaiRz+a1s}b{mQD~YweiwuxWA9i zW?N~C=cP9=<{l;+FF-VS;rH|2Rmgx0YU{{>Yx><{%R$xso4esZOIZ4GG0?kcSWg@3IS|9F?& z%ec`AntNOIYQDzvNT$|@!V`~2t{GZ7Ms!)!CJ49lZMGZ)hD8WD6mki{t@K4&hDqU* z#FK4ElY;bDJ3p*z_H3I;qKoY|NPjH1p2*5K2aTwWrcX*AndhznrUw>bxf9;WcH-?EAhJ7O5qA#NS!$ zx+Yhc=TelIWr{*;VeOQr#Yp6Z0TT-k!QD}^z-lA$79Q36&(U9Rr zo)`ZlsA7rQ^rH*(YxxN;=FV)VNv%7|{Pyq1AQD?c9@t;dFJ%xyGo_#NcF~)& z{0(utEFC+F-PE}Fv2#hm9;qf&71r*>-Rr{+^ox|69Z@}M)mn|(+AaD~*1g3huI5S} zyDew%8ZyhxiMc}LD~mfz!MnPxjqdmJnC(vyi{DB~aWDQu=>@&fwLcy~BX|oSp`@S> zN6o2O&g{klmDwqOLVB&;0huCynMBWUt5eHEGTN(sRpJ-39{wgG3 z$E%s&p)~DCaVsEusBeOjf8}jjE<%g>>rJVVFUR6mh8JmXCL=6O^CC_@7F$xWyuFot zXoFEqzD1ff(~~0g;wxC}xutIPSZNd52CWy_Rf#!0wKdN=v84t7M}~T3Jeq&8R`1N`F2=Lp-uhT3-?u!~K9e*9pU!^!?y^aV* z1})s+=1X9FA<)BB50KkFwJIZTrpE{}l!gw;Ao=wZYF<2D6s`~IXl@^ch*d3 z1gG;}GqvDQi__v&lPY`XRSSq9&<);hq;Q;U^+R}(?RV}-;-x^qS&qh}u2X*m5Iz_V zy%s~VL`^TSeSedK;M{6k9dZ(5a1Dw*7o&g&doG5CemUfgTm6cQvP$fukH%5M8;n-1 zAe)_3O+%hq1VQ#m^OX6vMmX@XHi_phYzn)I-%968SrE?5vb!6WZFk50!Y7P`6(^I4 zqK8DI1ki@{{VMZ4Gm7^E>r`s=Z)4|p>bQZhum z+!7Mh?%w94ugGGZiQmE%Z-OzUeclpcj*Pa96(r|*UoTIFf*ogd^Cg1{xC2)3dcM2Z z!)NKw6M4xmv!UGjo|_YR`LApQx!8v(!Iho(vkA&r82TYee&&X}Z<@EJl2bUE13y;o zWjEud=DpE?V-JUekJE2?$o20n7=mnQ)<{itHmum$)@mz^fxolrhvp%UCs-px^P@Q@ zr_lq3b$v{qR&I#p75}k^dOEW9-|xC5en}0bd8m15YG^|h(&D+YW|(B+m_Lch7RS(l zV!5?JkLfv1S=tZiIoK3fM+IBCsEtiuyLdt2a&)k{0N@5x?x6#$}LFTIu@7+%%4z=yl`)#yM$t ziz10|SLB_KVH`D4(3p+vgXb##JunIw%V#uS$8oHcNHw~3Z!C&(?y<}K1~dt?Htp!4 z(}IQt=Q@gyvhmxsHFuO`zcLy2+yA+6xMN$9xgh3qwcHF_>9mn>+|`NOAAWpl#;X4Q zNUNE3KGT!@EU34q%2hNcW3>>CE{$CKsxT;!K{)T92wF(7r{DU!AbCnpA{Zy0eVFCO zVR4S&-T_JIaf+cnzZCQgG zV&TK{8C(PZNg?<7lllX-Hb-(2U2j*Pyl1Dn=Idp~dSkyzY!|ztHu#VGh8e~cGmaRX zofp3%()WEjn7Q~dHBq?n3R)M#cq&Ee=ZH&Ye}%KyhF{5RIYYbUI_$GQrN3(_$)xXT z`eNZVY->gKHjG5B&&(L_`i9kZx^tuLdZPT|ee}5iW9A|ky<9%aC#pxRgl;J}N(%!T zt?!kO=T+zthTF8Or zE|X^hDkLivr`D2WF}+9x1tLmExbo7-!UYfrS03Wn_No>;-DNRt%OFR7hy>A&sW0|w zMEdBq&CHU+KzT$p0DHAsdD?vJdWx-*Vw- zjhu1+uNL~6Vl7W)d@^3qKNqsI8y5I2%oasRpa3=53G>;Im(ET16%noI(k2B+37w;O z*32l3M5o4lQH0eLW!~Xh>J@pb_^o7*k;^A%NASC2jV?I0zq7&vKP4M^rgi@swpEC3 z-NP%N07AsdsaT2r-*0``*ATF8UC!29s#s46-J+`Z| zYR(&rvnuITV@a@KP|yII+!IGcsDGIad2LTPoU?^*Zq!sw^FbV&N9%#JmEOL2we*J{ z<2k#!?`q^kqHJG01qfpk83sijvuKT|ngG=Tw2a@_^dlTL17l!$H}Z8XdMjF<5vr5l z0$g3_Or*;VH}hCFgsG$pPRi~H%fGOG`JP*m8M0wCr^}d^FQVo6 zt)sAB~uyT*O;gzOJ5r zf;GhKLf|d>4(FVzA$D)$BhV}dpO176ze*2md9+xe^yx!TNyPIg(nPq=$8qb-xx&J! zBVJ-ILejBLBmN(p=eCz^q%4N^Un^a@e5RV%dBsJQ$JzcX1$cd&Q7UJbc&9WxPA07( zb-KK|P2%)adGoilrDo}y7?*N<+iS3&fKJc1oWtkR+%9QNNAdv|Q`$wX6L4jQ*%jaj ze|3BN7lZw#;SSLZ8-&A^Gy)1eIco1~Yg`!5=Gf{M8=gE-PliEUk%QuDGx!(@RME`m z1T2dHdEp%pF_?0bxZoOl*l@+ZlQ8~wWy*C^AniEmPqsOi6VVsaC+7)wth0OM6Muhn z%d5MRpZ&_5zK0+9qq~elGo?J0{_LH*yy2K=i~QYHpoIGVj8_t?AMy5kFjs`_0&sVsbe|QnZ7qnKf7`Tha@75iXI8FzlKq?% zh;+hmx)mSQb2iSD%#sA1+vPEvg}LYwf(YJIy4&;5!M^OzD?x?+`WO@f;5V76sQmU<%q;MvOm+SKer=er-B>O$^&8uq!^F!_uHKl zk)VmlWvK-?_`xaP)*}ZdKFJ3X1G0-Eo(-`oJUOOwRf$h42ZtmZ8=r-kDkTp>U zx*D0iBx1H@#i#VchJJvdIS?tH8v!bsP|q3O9v`fy!QBi>e(Dp+k;V;g!(QYpZbXDc zcGzlsg|Ljz@T$f-x}BgvxYp3LcMJcNE2w{4HAbO%<>-|yob_W8GDnd(}sYEOiQ zmu_U=ba#;-=+dM6{Kj(nYD|mftiaIeVa7wV&9F3W$KXGXpJ~B?=hHEEzD6ce$Znom zJU`hqyCO!}R)#>P-+JDvRM~LmfZL+! zq0Vm~H1wg&DbRNbG!!E7jEx5o#^=4>LSvEfokyMMTm?Mh7p|-1$~q9v(^AVfoe7wg0f{NbDSt zBRwXGar34GUQqpmK?5-TEp^rdi(foRNjkvD+j-g^AnVG~$n#@2!cFp>iT(kK7{R@3aZ#y}ni{OXBxT-(_%y}sr> zevH$R$YT5bH~slA{tOkrmA0@`6T}ceD`;B1yC^bUiu?ZU28k#ImFHK=Qo1*w&}X}J z!ToG^bbU?2Q`I~lQ3%Gx^Hbl{d6lze-9C6$$LQIA#}#;$$FJ%SD}VcPC0CuvH~pF zl-!#(VN>O!$QYjnCN0WwJtLlnFATXJTO-baUrcvo^Wjwmh0kqbbYu>%+Dkc*0*ETu zI7uiq1s{glh+^BYc(E3#QRW0(}``^0#>1#*uwa9^Q%Yy;$bGv*a&g zP=dRMgB+&81kHw)G~>9T0bUR8K}L$C;~p;`T7#|6&{3tM5Lap!%AK!?3p^Byo>DZV zH9rs%v_yLXpK~)8jA)~>Ac`V%LV71+HoX$~xT|-6{&>?@YJJncp*BB6t_Gxcl`XnA6E`zJJ~W70kQhy$@ZAtSmcz|0A1_GcJ5I56WM@NW$YL~S~JKO&7uw8LOE?Y_^M+= z$bqFYfBx~%tPX*P8A-bVtgXdDqgLYt2T|Ha84!JW>a&X{i~?|{z&Yyjn~mGf5~kkw z97yzS)ep_ymcUvek7@)j9aeHFabU;~MDq@ZLQVHa34$R_?geJuN*RvJp{uO#C+JK@ zv+@b9sbL^Df!-q-+ar;W`Uc%5l&9SIb_ZfhFRk3?KZ7H~VDa}<;P*w~6O9K^Ma=na zAOF5>zL%kc)KpqCR2-*zE#Q)m%x_w$-(Kz4YC1|pq*rYrBm)KLLGq(_+2KJr<|-UZ ztiq6n1S{osCI<#>rcsGghyla*-VQ{tqc0b-lNZaDf9#r&Pyq*kLF?1|dbiFzpaAyY!g1)|= zmkz=7XyvXMQq?P1CVAA{g1;9I#~|06yWGtbjrV!j)zE}!9(51LKeJ+Az=5^Q2tUR5 zu@!O3TfJn1D^Tqvkk|9N1Z&}F zjVZu)4=lE8r0o`s{ymE$@ZMet;D3~qqAjbl9Y5NsPb9u8cB6kj)XJ8N*WMhZFcGE@ zNYvS!V)V>$3y~`*JH$9ZwzK35xZQnqtetyzB!a3~_i3l70B@80HkiIERKu1eAXs5o z@ysNvLs5`N4l3XU$1MN4+tSS!+FjQ|*wAHWIOf*8NY9RD)Ha=b5$UcN+FE7qSa9{( z9s%m3;JVc2EtFA{*V=pH!qe0vfshPlZ~7@Oh`jHE?|65+F$67crEhQu@%R8)?eeR! zrVP$2*H?L_#@QzLT_D z7Ksc^=i7sHexS#uJ{&CfOlnF@+fsw=T!cL5U5XCNaUJVb3^4M8xC<5oo}&_H09d(f z-de!(xG|AK1_Y=EtrMf4?AHbVs_P(Ebewq5zjfcc^8ix(zG#ri{$p0Xk=SvuQMVj* z-N%33T8G#9&E_V_?9UK>(960B)Je-b-`?G19AFUc&Ta|nK3>RIq1C3xj^HT6GFP2# z-f0^EoQzKp)x#|w>y_B6`~fJg9P?iv@3&19h|?CRm%pXOyq6s-8qY<3DOrugpx0v1 z-3&`gA(wY2L#1HIYJicN*y#-n_bq|RTLIZ%6oI1ceYB||5Bmm+or(OKs2azjU~pc_ z%;2TwVYU0wjhW*cQH@@2y|%Z!Ajr;DvY0u0x3!VNuE;tG4P9`at=A15Wnin+kh4xp ze$md~93G!|;7u)&W*o(szq|4><)p#aW7g}`8~EP#&_$dR|NP?3^@k5Q8>ySU&m|?TKwEFEFKlYp9S)ZlktErU;c!8X9L2r@Xwp zYbCadUsUK6WYjhuBP<9Cuv0r_M0l%9$$_{{HRyARwxhIG$=bY{SVJ`M{=edLtPDvP zf8z;}ICOuHmcJq&mEF!@TjYqo8Ne|5$1b1t;B+W{cf_4>w{p@&1X-=6Ftm54(dqun ziBnDv#$Y01hiJ=Vy<5L8&%b^7m2`^~F_qFaT^ucVnuWY*v#<{vT-If_bfD3qc0;*0 zKIXY0&AB4yWaA!Ms$F_kmUR8BmYi&z_2#H15v6(W>D5MM2~_X(qDD34vI=j;pnvKl z)W0ia4qT-=tj#aB2Zp{CJ_GdLSP`=PyX+64tnu<|`7h(dWp!5Z6@@7Wn>_bBLbX%# zR@dQ55@?R2boswXvj%no`0CXS3UfL!w%Ak-i?<%%siwtRedY$TAyg z{oA9+&v!`kU@(E}xYWSV?zRd}YKj0Jd?c485Up|eVRlA|gA^Q9F#cL;O0)X%KCo`n zPQ9J1reo6ATwo&Wj+}c-dmE-Z84Z@X{0QJT@Y;fu(ZnCqr4Rn5JuV<}yAPP9cX$<$ z9Nj=mh^uvIp}nC@i=aw6bD!ON5^NW(nX_g8ZXmvI6qGDd2YHc@cmISw;KrTz#oyL4O}wa$B<&27mRC2hu;d+=Q~uSGkblkZFkW>qSBltK9iI8dKX=Bp)Yn21j%2nqJ1lp!fL$vv)hZw#x}8Y=V4H_0-rTmji{nl?bsnHF zg^62kA1`sOLU2&%(+W%F-a)HBuv5yHCc+K)uiDW1dGq~6)&L0d|1Hn-62HF@($k-3 z{kMGf_p`f_6|X`#Y$6*RMMNJgcoJ;QO-7rR^e(O0!eOB;D%i8quKfN&9{0Z&rT^00 zk{BDmUu^#HU^@FVM(?pv-6in-GpgF_lnASUSh=AGKBZd_CfU|UVovVo$z0ek%9B&M zUTL1uC5DO2I=0Y?y#5xVD~`GR(91C5H8Jh^rLf&DZR`(1(ylpXT!#?v zv^f%~Lr;`!8pPugT;)7Hsf4cs~vuBhu`mmKl7@U zutA8|$k9X!UbV2AQQ>%!rFPc%2v^j=&V4jVGF&+4F1%4gV~Fmu2%U$2Yxzj0nsTl+ zSW>(fkM8(%E(qJg?fH|BP+o7!`Lj`TtpUW4?htM~A4rV9aoVOHH zqQ&*vQayU9fif7Cv$3bowjmlIe`vq2k$+U|AIaM}hLR#_!w1rFBLF z7zs6Xo>L>DY2q!Gua3;z$l=P@yA=vQ?t2?7%E4O8do&%8IKS)KUy9A3HZBSSeduFB zaq%RJp`r-7vUl}4+0KO^TpfNh>2vw;Rm`JEm<}Sx^_M&*?c)h?aGW3W0h{8IS3Q1t z*aZJlbIj0eRS(0%dR(*e>0yLxAis>f9ZbHGKf5_iVk^i3lhe|j_;TkVYnYYka>ycK z5D_YRowtT1S5adn2zcZs z7?Rd4S&Lq~Ekwws*=c5p`9;sS5R0v+_hgpgfK;bYnQGGRRCVBwmf-Ku^2*h}yvyV) zjr8KZPFB1(u%zWNXd&TOuKR}*B}C~TBWAKjM5+`!9w=}#UCWTHpNcg(H}hGPnZm|C ztsj(Iqz>4z%k653ZQkQSg1YfEJ?XvRCa0NG*SKfTp=@Kx_`w|<)A>!3wKT`q#&Ew* z7kBu7?o?F z&prn(om`;j>nMYe<5GNt-6kgpdA$5@+r2mOcX1Q0kM#=Q8^K%w!+xwiKlM|zd+o`i z)gEH6z$;d~FoqB4H%0AHiT9m#KJT;ecBF=cDWNpynB`um7Cku(@^f9#idER(J5?EL ze%;3u@ajBy#9Wr3L6y&7mO+i-EV*aaC8>4v5cu?Z<`YAhoQ0+% z6>LutY=*@kh<|Gb?^cnXfHEk)1|pfc4_s{#8z`Y^vQ)$bXJ8z*_71E*^!aUmVw@>uv;O6T6)RWZjlSCx(Uj)a^n zu0c!Vc5jPrzDPSrnA&qtZg*72!^4$niW{4<)73n#2jd0lwFcK7J?pxVYI4Wq3xMC! zui3XHr0Ev^ZSPgBU_t|J+TJiT>>#4?^@557CHl9NJ-`iU(@_Fh>%Cj)2nfJvpm0ZG^n~g=5dYTB{n7 ztq<@2wGON?fN#PS-F9}4|FU6dwrES9HCPPm(A%R>u`ojcW)AA<6Rz;w*xu@r(R@rp z4)np}lhOF_iA4_ctq))5!HCWP$fE>VY%>|2sH2Bmiz9*EMX%bdN09Q}!vki{w5<&D z8=4-&?lc&0c0wQkyjfRKU%fEAAuQ+QODTKwF`l)dXTf3}_=jR{4KplmlEZf;AFX5D zRlDdet;&8JipF876clYM!_JoL#k0RfIF7k7Q+q}+=Nq79C&7bh{F>f3h!SkjLUR`) z>+cS#HG=u*-VtvY&Nc2Q^R8e>KB{c&tBtST^GUn8s&o_ASLGYVswt`+8`vM`131oi zAoTt|^TWBHOdI7KZWolncuZ^e_6uNyQ3a=8_w?@9b_o&5Ut8lKWG&1$k{<&aQ9G;0lV0et+-&RYHMN=DDY) zmNbb(e7FPoQCJ4pjV4-f6(gta7i5&yYrDY=55AnzP4O^21{^0dqFLOWgj3nP&5?QO z4~HyPNoGu?aqN67lv-GyuKSlDC-?+2?R7Hg_7h-8{qcPcMixqJ6a}4rNt16+n6v$z zgt%scOWEo!-{mGDUXpuB!a0#i_O(qaDIG}~lZ^)u(O)LYWJ=Rt2|1jpZeJ{Vm}Xiw z%P*^IIoUxmH|`CSDIL2X;s3lpWNL#;H&^q7MrOI6>+{L{tm^p*izT<1OJ#I3*}glj z$77n(&eYtw)aj|8{&+If*+-nzY8H!)UtJ>OKSR+^oUW220hZ*ae|D-bDi9h>Ok@!1 zR_ld2nU*O;H7SzOoM2Wx{moE_oN2_sJAMW?8TtQc%aIeWnWGq1_UV=6U!#6PVGDB> zOZG8eCms*g%pzLug*!6g9)zTKY89x1uJ}+3TOinf1lTcC*-kJd-1RjK8Uc4$(W1id zf%RYEkMFw|yhE5tY%_6sD&?=#d>xPIY`#u*Le4bT3k&5gM%VbJzLIY465&2U<~Q z9+FU-eKI|mL%*$d(m<@`cJ&E^5k$f+1cepb%Euy&)y`G6_1wdKQ~pas ztb--Z7D)u|rjw9oMvv=gwt_!{O<;7x;xX7f0lOZ?!t^|2B0%Q-0Gz^VQYJ$2l>APx zw7r3Aoi(49daIZ6;y%z^&KLnP5uC%#ck`Ruzg|z|_eSRqT2$_pc4>>*eTD>cEN=Nh zax6fM82}4$hEe)qa+8&T02LWwz>iF34FesZ7PHXo(F1DWN9)Uu&_lS zzYbkB1j8WqjA&%?)b8XBR1V`EUhj}K_ssN$N|BGTMqN2!to1H#xiJUx@%_?*oLo$d zMjV(}DsV=>hlv*)tGx;*wgW*|LR?mgm4wUdrcT^3etiajm?G0v1?DNkU!;_wVcebI<+57yofjAq>YV%Z(= z{*tP{PK>|V_yb~_>iElD`xdGfW%n(%bn0iCmmSsdNIyCCd3J(+Nd))Kv(vNhF7yJK z-wK;!4i}}I+sp3It@*MgInLael|gu@e}ArIXjaUebNqpE+lac!h4dl&{b){dp6gn} zCij8;v|Rw^MSwk5r{TTcD~;iJ!fLR^f)&ZLSFwt91HW+q_>#SKlz4}-^IVam?V$1+ zhnW?MrN9IG1*@@b3QXWpQ)CKW`a#J_Ahn2L%QeNos0v$Xzvcyk z?@u7ETf9X)d|niYy0M)=<=Oq#A*R9V^U&VLul>Zh1w4T@sb`jP+urn4A{|qf*#UVp zir;gIGQuGZ7{R(%@*0tXJ)J zHp7ia=s9xdHe2j{^zCL$Se2j*W9SNlpe!9>8<>q|SWZ#g3Pj_Tk3-5X^aHQ_KMVJN zc+1P{P8W^3+Ve6m#iu)KeY#6`H3gfw@K7aSOflyt&&y;hGhZ4CbTX#1RUuGJsvyP!j;idKz*`v@)ZZd~I z-DETKR$b_q1==LoZ!Ch5DuvaSyK;DiOB5kM3>}OWWVwkA;t!HtpQr_%{EUGdV3h8J zn4^UI<%vA7I=Bb&oEpEmvYjLjNtZTLAzOj9Mq$#t1Tc2t<&bWE;q6pbAL4@?DF7F+ z;+mc@0RkBTqgJdsGqW0mETeBum6qN;TSS|B@bFR+7@N7M-=@{1WHh3qxL!nOM;}a^!;J(VRsbC>p*NRD=ZtdWh-lSm zu1KeV5Hj};40Qv2HGGR4Hy&{uQxP#O*3F78$}m+sbH?D9e0tP0LYBmWg}VT;TL_5n zeH|QTttvn~m`W&J-&*#xqfNuB7IGU_D)Pi%0%Vt=uFI#A!Dg)4cwGV54ukt^Y$`au zn4rm^nxhvDtLi7nilGuI=1ihu7YkI)+PO6mtRBp&4%jvYW2@ zGI#R-JnugmxWCRHNpM`Wg;SIttGVVM0{gG;`z^UGy(D|1&}wsMTwgTKCyHUrn(=VL z7!Q2kwi`RjFUTVt?(g0`e44J-Xeq->;E!}1>^ly$m!()ZJC@K~X;8r}kA&d`S_~(1 z3~9mpg-#GZKbPOx6Eu#u5#$gs)}#-mf$h9GA`LpO+c9WnrjqGTA(ByA_iwkSUdh%H zGdiYbB&dR0)(8l= z#JJDT6{fk+lFLhXPPh^+XlFl%KY8^|EPlSSs^P+pDXG93FfnMb$I#^769$FhhU6lF zxXQ&aQHkp#V%>2Fw-|}YC$Tra!CW_BtxJK7iiH~S`Y8$SR85q_71^RCfv6*J*82{@ z_%GWRD1Ij)?X35F7>QL;Hf+zdtn`kRJ_gf{;`75&(2q-p(Ue1yP)-t7Z{SipI*uZt}Qbfz$ar%K8^30UU4G;D8D;3%QXBI@DMIr-UcO)r3 zJhnm-feg(mVPvsG>4!`Czgw)|4i_B8zMej{d=cvouur&cng0BFdoWlgEPoaS#N2f9 z2q7D$)(PA_KPxLMFpnXCVlY!C+$d3-i)_5`9Q-1Ne6(REYZQ4RDZ6{Kz^W+S=hwgl zz3EBlh50B^ERIj%F{esal|>n4(~U^u~lE2OGpe;2)` zgjj*E(kux%1@m)qpcVO|qV2RkOXji4@ZJlqZjX{Lrp64T!L?DqH2GU4RB2rGrz+CJ zaGgLBfU#3~@?UmEa>Imz3LS4Je4wnfQ=)T%N36@9B4CIyR4Amc-_66{j_o^kFh{GK zh_17KJMssj^edbFc{wg8z>ab=(WgPLU|8rm@#^ku5C-Hk=G;}mRszdB<^T2I0lSkB zii%W9HJ#`?Z|qd7kHaTCJRBANt~ZRbU4cN-ukb2dwLi zjA_w@$wkqZlGlcx?`P2On?10Ld?mqlac-4r)x8r8;Lc3#hLUnxJP!7x*TP5xoSuyQ z-nAe(JoOG@5VVrz2>42bGmk3Et*Hscj$9B?gTbs4=u76E9hk*2X;;(+Fo@%&rLniB z41`bGNAg72=wvzDN{h`IhwvWw9{{i49FTNOYvl6elJat`b0XLKaq0I*{D3&GN!Rdx z>_o+;KMoHwHd9QY>DNLlgX{c!cKTFvVMXbSC&Sxm(ieONzBCrk-1hMQ6*2zx68-jB ztDPZm^?*A$aK~)sHGt;(ai1hSN&x{pDkv(2|>%3i%W-j8e+WFxfy=PmW^J zM{D`Y*e7QDyhm72UP-gacO7nbA0hK&#}jTe2}gE@;qw=r!8xJMfVj({4@?p)d-`-X zHk2B~K){fH4@sYfCxCsCe@uA9X zrb-g#=oXdtL@pS-yN2d$-wgwTY>&;o0o_abtW~3;lh4WJOqx!!9&WymUrrQ4@JKSa zo_Tct9a?GqAbZtmIqJUolyr;Ilh8&#b&W3P6aGA*W!St@!VN^vcF(1Z=dHVM3$lpw zptb?CR)rVZl*p;K=gQq9z3Di<LC7=zzA{f!Oubj8=4nO4j95nGqKMi``n~ zO8+;&^Y44o>WiPXWv{*GAo)jJK_XGJ;M)$~-u}fMh4>*2#mAvnLCVo;ZS#>ZItODN zG1{c8qYm}V;I?F1qMWXG3oQ$TwgV&)5Gj`e`mGJMExfR)&d1l$b31pkY==>?&V`_~ zZ!r6%v8-Hn4?XOPb&apd%GVNz(-ETks7U8!+;eCMbnU$`rv-f~MG-Xl6WgE#XnVBR zS(ud4zB|?}TWjB#mRrw0A^52AW!v{^WlU7OqTVqPdL{rw90@Ukl`=nqcyInO!W`x9 zCNvj4dg#oW28K8>&NPKdW0-DO7)Bo~*2-9jg_A?3#QTh*t@2? z7pXGmLF>9T=LNzjMB*TfQNHdbM;8GCL4N?cpC20$fbf`$e`iu%n<_|ku!!V8AwXX3 zc28J}RavUu?Vtn5v#U--Z!m)X&m}xu4}uP(c>NNdRoXKrFiX z5u_bv{f=D4_mxywU=u+s3igtQ1*k~NSF$J*rnY8Tv#gtiZXZ5w*vGAe@F?2cm1{mF z6$C!-xt2X%xf%V}EmP8rGISdaOiTT4yfVpO@k|gS&yRkdWUskHUWxX8^o)Gn z>;W&0^3si_f-KLn0DaV4%(&qHeKvNka+x9G*seydd{9#WB{G)8J={0(hG`%uiqHQa zt*#KnGKZkW-2-{rM^PTlb}j9Ag@H!)YFp+UB`p60d1EE!YfE*aAhayK(~TXk7;d%l z53t>#VTLU$X^wz1z9PK69qBsy0b`|drh zKc(l!=SU*l3Py=nWT6{20cHl9Ske&z)s_S`Ec?37+iL_DKdw=^zD1?( z8B$YajU+!|OI^9pMuvz@e~f7-6gsLZcsVo=(cB!)x#(YANtpkp;h0)n{J3}#oX zW#QR(UI^RnRrlctX;xe!tx|oxDP%R!`zO!K2gZBlBxjh}X`F=K?6o{4^5%&2R!t=1 z<52p*TV_&{X`^jB0xiBKT{r=2x?lX(My3QbO@K`^t<05+Xvt)O6~f|7cY7Rd zLo}**+{IpcXtZC(*T6k7x84&Xv+KuI$xRlG#kUO-=@eAzw2ne?Q4!d~ zQHa(;)PPnl;xk!YO59dK-zHK9{5f!YQd|h=WmfVqw^gDVdF}S67wcV>kiD*#-*2EE zdD8p8o+M)tb$>zZu8Po=vL@1oYsrKA8=sWlx?o^cwv1dW0g2HyRIU}-1V4Pz*Q6wUQM#PB zZ*-3}1iN*oJj+8vGPA24H3Z{&bc+>s$cf^IUnDtTC!6l`pX zt-5;@&Dzk#_{LH&rlYyw(JY(Yt?Av(ucs1h)qvH>pwA_5aH-l)5_t{L8QZspG{vWCR0gU_WW8UYt?w~hw+_T7 zMaTB$2c)~F`giR39#NnCw6xV*Erg`;bX(Fn&!ypmH&UJwdIbC`*_(%9%3ozW9H_HL5(Ys1v=&_NEn zQH|YEW9iAVoi&N-i@UK`?pBWmPwF4562yuo1ga;81?2JsjeR_0L%`s*w(ONj>PB-e zMB94RDUrJb;@X3C)EU`2{r9M$q95JBPZgBn)In(n?V+6^+oZUz3u}p+p|!FP>@4zT-*pTTpH(tZ9BK<4vVGi{H}EevTCuxy zRlecX|El?9qZ-WEmMU7h=PF;NG2!3tkzq`rRW43@s(3`EGFc>PlIGssVoFr;6~7{v z^x0~}EiIF;D;g;I_C={zaqMdlfs*!a>?j0N-AM_Ag0X!+5uOiic@u9t`=}*Cm||@p zs%P1&3#@}oW((|`beKKj`)=E)z&>Pi){<$Q1%uWn(aX-#s_yra>)KWhNLXHCDq zTHR1Pc9h2f(uKlP;ffQpLz7~{BjY>X7rKR8U^h{dDOI`GP`Az%FPU=_@+%Ee`|u`4 z9a3(u%NGXwAuZ# zZDjr~?M+m_h*PR$3eeT9a4>}@=??$}I_f}^1EwTs^J=u4A~)K0EpQ#hN}xIVS{U*u zpEKf~W^@wJ2noBoJ`09alpfteK-@{f;HTUUNJNizkb@ym3<=x6Y#Faxwm_gR*`7s3 zcS6Zx&+f*h7W5yF0x~>MC9R+_e@rWlr=+0KUWkKbDhRK& z;1URXx!I+uK2z{~YFoNa_j0Db?yMG~YKqx{)X_18qj&Z|%G&u&A#=Vuy?UjCbO=}N zn8I0Ml(M~EeKYsjP}jR^dttevGg#}6sE{v&luPP8ll*d=tV_u7?5;lKtyZWX?oi0d z^#2jSNvV-_D~^9$m>ZKP&UcGXeC_NOcbz%@XXWVkbMpHuI5njw!kIP5`Z3R&_e7lE zdZ#+KAzwi)u=`+5lz91)O^EYqeIb1q9y%YPz<=3sVbFdaF|`Lco}>P|DMcp8(Uv2(iVt%3j=M_ zKI>XUHFrm^b3}RH4>Xgi-F2{1gK-bbDaOT>9u3v~+8z4#uQ>e z>fDQfmf?yGG(QKP6tP*@w-62G@M4>x7cJmx6AzAGvm^?}O!I6Yln)jU`Z{oyE{{wGLIW`)3e7BnT$jGl76SaEwqBX2D_V!57R7Qk81oEJ1!u=~ zk&Jm~fyJUNlC)P#S8CkL5@v?SLv!T)IUpj)PyHNU)ojVl41 zT-QN}vSAlf4#kMarZSyKl0X(OQIco()N@f)JYs~E$tQDOM<^`2F*f*p&S-raJ$rdj zoaB7>gER>l8(41K<-z&mUTsHplW*Bj)?&GDJ<_Dv3hwHr|DV4W@E1EKdg7NG2Fg*e zbNQ)xhk)tmva|&y2VK>B)cMbeeQ^v|lOSaUIc*%z|*^ihIttwJQ5dpb4ek}VY1XE^S!cv`xmM;0^%*NxxhqatG zPlsDW2XL6K_!k|Ayi-wVjt0!a@Buzn5iTfeX{OOOo%yVz?x2`y0I-IYZDnjhqU|gy zda@AZRVR945L!VcNXP_Xw=A3=wv3?TaTB6htC27(HS)>VS_~GFsJ%C?Nu&!)4JJL% z&*Rk>CAuWn8tl;|-CxyH1Dj;F;&CLxB0nO?g48Wu6DjcQF;3)v1f2$sz=fmVi$w1K zA#Qo*0!Fm1w!f9(!6)K{L%@r(;a1&eP_l1pqsg$Sr-Rh99G7-n_64-WZb=>O6nc4H z&LQs+n}%KZlCG5;J*%0PC3N% zDx9jLb%Zg1af`^~}TzQzACSbl4k>UY5x$!De+gwvC`@=pzrnzTB^gZVm(ev3kT zPbIQc)^m6Fb5N|ULG7s%d%z6E4^7g{w{Xjfv4psfA zX5t(vIDmE-5txe;j0?!!dG+S!xJ#9vLz^M1SlqSoCh2{qA6^O>Aa+)J_;4Em&1}_m zr0A0{={}zUZdka3MUC8qF;2?i*l3khR;$v_NLsr6Q!gon=A%1Z;@imj&raf(-ih!_ zJga-Y3*ttmv7n31keVIAg9$5 z522D$Ntz^N_~ALdwt#n7TvN=t8ktSn3?Sk%3N5~;ZW2r3K&LU4q3&zZxxFw`Q2;Bn z+cD?18hKcc@qxJ!Ha_AqHTEojf5)Ph~<&uv?m%Y0q&Q9D^Iew-m`1C(| z%D=sdz8dTFjaT+uL#hTk>rPok*>e%&l=u0Xb1p|LKU-{frXARS_GK`i&YbPX5-0Go zaE}VtvwhLpX!VZ-v(-uCzd{7Q_;6+=@`hC7oz5y^TXAilhBnkHk=u`w&lXsWsFK!m zYNtEKg&*8XIYO+K5ITd3?!Gl@z`uv1L1rC!SD58Jl&Ga|afVq#i=s|gkRkNqC|UDjA_H_Q0yh?$KazR(#A*jSPTsr_)H9HbG2I;vYkl%QGmc7_MoX+u*q#X2?> z1_?BfVYYyKpm}US<4I6$9M)|=rI=GXy9Z1J8um~tP5d?(2P{E{H;i`Y^dU0|t_PYr zt81n`uPuS~Jk0!WA;^z~y@Q~BMQ=>c|zw*9V`NYbK19t=Cutp4LtLY>njj!La;dDM*`V#M_S7jyP_+ahK z-*7LoK90q9+v`h;y~BRCP>sqk4xtHk?f*Z5h0!Z!F^y0U#=+!}ekLwGtu@h4gkc^O z%*V(G2a87&uyaAR8jG-|qv)MpQEQ-U2&1Pys=IrUK>y@)er)DPfq$uneK*)be=0K7 zOA-~Fz6hj-?>hMK)~jTF%DoHSVedIXU$ln(nSm&=Em_xqmw+aPjvAQmhA|ALHcISW z>2&+*G<6*hF+B0-KfF z)4Eb2tY~Z?O(bvT$qR-?i0IW!pvbAX@YSA=mHNS{=(v62G<2a8v3>gdSPPBO=Gkxk z{ZmwiGO~dPo9S*oyGjXda*!n9=Mf;+aB&3%*4(f@r|`CF?!lsg$&UYHaqABg#aWJd zY%)A8nd|b{p_c4D?Ap?W0i9SEeD2J4?G_7IOaH=?oOT~a2`YZ&=34II)Y=Q5ksB>C z&wq8aLgn>Y<#IW>Aff3#U4*#wWm&OUlnfJcf42pHz53}Pf3eYEDqgV)y}6EOg?fhi z6N3s#)`k`pshw;V?BeiKTUQG437iow%W*_ucZz_y&7`C@dNP^<$&FXF`J^mg(X#k9 zpLeYfHPe*!^8~TDCF&iPM!?VAFjqS*cO+6?*$;c{-fZEv@4{<#Dse_!+Yc}~fqDk& z2UnEfh3-bKst*`^Gnf)PUe0K`+&m)I($_FaYpT?CAiC#-==C=C8o1}HIUha6pi2&9 zFwQ&{gYg^6{jRy@IQgx2)NcGu|K)e13u;@d<0~4HYc=$BMBS;iyOrfsl*l9-Vd&v&N( z%pd(FUHVhz?Z6tMnyBl1!rg-UT)B$|$0*BwQ5q!jw}O;oEY+YtV#XY^`I0b9S@?>_ z`jqs#rpKab8oZ9{HTAxH3xGxaBc}SGoIk*N<`QYb8=_@4G!yTe+-3sp8Pa{-*yr5! zhgpnF{3Tg5VaD!|hN@Ji$?bxdO>?TE(4(KGjLU#ox>DLckUhPIgm94n7RP?^W=kTP zg$VT(Vmx1_>|MxkY9GS%*dlKtMG)R@AezREjik;ly%2=Zaqi5h7OBwN!VM05#`LhI zGCz#AFeR;DgHY%L=SgyhKF&Y&#K<3Or0o-Q7_3%z?w6SB_dS7E8I}YzM=~DfQy<)q z`I2)&gkxL=mEs6!9yu^pan}?@*CrAoF?a}tg6&agmRQ&na1{QM_I%*az;4#Q+c{IV zZ~OGq!1cG-d-Wu}J=E;oPNVYvxP25IVz}bI$jQh0w2xQ*xrT#=y2Fn_RR850{kdcG z+*WATSnpntB*Hwlh#Zd(-px^im*E_JYaoARXH>>Ji?HrlDmxc zsa{RQ^(ece67}3f;mS0e z819P%qBOEW+vz;kA|}XscBz#9&3^=Vc1@=+`Wl-a$D#=r=S%N;9$}CkFSDu6wTPU+ zy+~U-YV7%&yGa~iQ(d)v+(14~(G z6rOBm00pwBD(0B~n(8xBG5+*;<6Sh&t~aO7iboz#?~i4+#U&p6OgAnq!JMpBP`L_x z#@QhdlQIiufxbmOC#;P{m^DeVD7h&z<6iYD@uKrtir`E3)cD!|y43%04gWN*QUtVy zCerN9vm(2*4F%m9E&DF-3L`ldjJ!ydKay!3{3yt>y?Y;!$o!bHz=5+Swy2QN5MH~- zEWQ0)<^|>L^xFmzgbZ^&Db_}mR3J3V)b9cKvQi8IaIV4F`PA~2uA-Rcrg|b8NXq-i z-SQ?>NplH!u000);w(gA-(2*d!vUwX`%Cvhwd~ z^bvzP@AtxwF!Zi3gxX%)Z7~9k1)V{v9dp0u z$iF!CpF!Qvh-7uMY98(1H-5lIXJp!aoD#~X2(LJrIr|MkD|hqBi3CHHQn9O{8T4w9d}U= zltU$=hm!~50nVH{DG}YByCxheBiC)qYAG&gg#qGohUlql`OEfPR{&gVcSKXzuaA@ro_bP1>ehk2ga#tzXf4N5Au_4<5RV(&9;8Q+LKA2i<4)L|;cm zJ&Gfs!!Yi3<3SBi+WtJ1e~)ceaeC}N zfL940j&M1E+S2KO=*Tt#7O-58HAaojX>-0ma{ma?HanWB zhB(8^NIxCPV-RhhZh1!5ui3R?dt&(PyF(-n>S|;~+XQ!KX>{De6S5|rOg5}G?cWmi zpAo>1C-~kjOd6-9o5uvnBx&l^FXOeIqB-XAx~m@L!8yXkFz=;!O@YvWwQ*(Z$3LVD zkb2Tm`(t%3`s{JgcorG7PYS0fT_lfy;kD}Oa!?MzK1GKaNaz}BsJ;Z~cvQ9Bmb2A3 zW{()@5rhbLDq zTil?$KOSZ4L)K#yaN|{3j9$I%^s^S;!GyBoSQA|&zKR0D!HPq*+-t-+Md1RSV&WS*(uTL1?iv1_TOTz-M$*{2+XEj?zW@% znXE{&!m-#(>4ZNNUs1)Gdj9Mc%T5)k%6ZXp4c;UM=DuJenk)RUm{9 z;5M?2U`Q&!X1__*_#1P|15veH-{b=(vQSFRcKyHqY1Q{*7)fnQR$XGO){)7h`hD2?E^jpcyvF0T#ANbL(}^AXiF zUznl8o(^m);czMj&rUT5AmhwdV&;xOc19V_oTsrmNUu7G806QS-g_O(3q>qPLw8Ws z9ih{HS=3h99O9K3q_SG#i~u85^h6s&4Q{9Jhg`5jFWs|#`t=ULFuZHtvbvXuCUGJ| zFXUg(jqqM!iGQs9c)&o&2T=9D@4#OcEN{oD$12U)n=77b_z})$WkLc4>Tgy~UaU08 zIpGd3{vi;z_32~UwP=Zh=Gia3WF%5x60=^Tjf9>ye&7gd%%#OWylvWr;h!s)KVpgB zf6%?45;;hPe2CPxclK-V=co6z_H9gz+%=%FzI!gpAO=Y<+zMdZ>ED30hgOdPD9)Wm z--XuG+bQT~rFsTaEjaZ&+e7I}0bcc#y4k_$3ys@Dt*7GaEGT1lA?ECwZ)B)}Qes&- z33CZh|KT=eA2KLp`R_%9)0r6E)+`cEi?b?Z6u9S9gq0#M?(^lziF2=luPz$!v!b;5 zKq^^l-9|Ms%&Sdf1+%XnBgPVwylA}W&^ODiarsN3j**ISks}W zX#k6)bCjHTSV4G@G9y5Lp9+$M9g>uO19r~58Q>i7$@0#$}?izKudr87#hHnx;(xsh;iFsVK*xcv9`4_(I_(wQkmzcMqo1M?z%0Lu z-2%@}fzoQv5Gr;(F-Gwgx$%r-peR>t^-1xEAih*_TNrsVYSf01qBdG@Il_wUf-hYI z>>fWK?9*6JBg(9*?{WX!&3$Zv$8y)iwkM)rYUz$rp{fUbVNDR9>%P54NT_iCZISW) zF*St6KgOjs^-LtRH(GEUAkwM7v}TR$`TK2!z?xXp;gbqI!!vO6`JSDuRaQL*o@qRb zMTRJmY!;hzhI*WM(M#Qb-sLP+$cb;ut>Gd@@-Y4P>rYk(07+j)v(;4Hud``#>1uVn z(^sbL^x|eYQwyCJ986g@!}agi-cM;NH%DHCqc|>a@G8>JhWX`xATe5SmAyn|@Dl)$1y4waF65L@yJ4(HcY+2fpblOQX=v#%eW!kf|{m@I|31tG-S3CeI47k zkfpUrds~g@1sM$=Dbw(GXL&2tll4Wf1N9Oqdg1vrx>8q8F=^;e>AUww=<_|sqR=Xj z^lRcz9t+URUpV}Xo?-u|T^GGTk62evIwQ3KvbFk(s|Pp{H-n7HbFuBg4GgD}E~Sch zygk;P7G&M!gm{KspbA&!oIHAK5ox%Y2&LH#8~&-G`o|UeHB0+*H{9RHy56nM@R8Nz z>@Hn=f$_vV=GSOnN(FW1quqs!K@R66nYz8soC%hx#~3ao+g{p0JU6M8=5@nj%fJ^U zK`grtkzh3` z-V=R|bYJ6+D>~Qt)Qx8zfZ2pGOD8E%DCOoPnY|l?M+V#JqA`C3o&T5pW~4Gi>1g91 zj1hq_mgH@P^T~$8)DCx$*-C56K1lREVr&;JMrzv+dDVYEeKil6h;4hDy^k*bPGI={ zxoWJitN+1mpqm?r-FI4jQ#Wxw1@+*PKR|BzsSro8H`FyT$R1*S{t{))n32EF7a^YP`srasiT*{Dt|(YbRe8wB;yOg*c?^KWNJbdw_*gdsu z8@aZE9SSSUXBLrNzk$I&4&1NH5x27jD%U`zWT&9w>&ES1iqDdlqpxI$EZFfk8bE$O zjhty&qvMfLutFM&fvOdNih6Y}{g6G-E$}}JKG4m?BxJ~PTG<>ZU~(V*F{nWu$4^} z;*<`Yy#OofyXivt=&me8h>f3Sp&Ii#C(FGjU7mQ|J>g<~v$*lzl~IMPIX8=*prO8x z#(S0x>&B5nGGSZ(wY^L9#4p&W(D`66jXeULCN-O`YS+YndR6RvOp>i=deXh_o|M&N z!)e*MC&oK;E=gYWh7&!lp|4O-3O6 zwOpZ7*A%6k66J<(>uoJ$S25ZGI!^K|34<_GV~r+fG)tgl$W9UpL|VMr@h#VzoOsP^ z-*;nNGyZsY}%J5Zl=MdP+QErU!a4w#r5j>oex5A6BQU zuu8dkmno5J81qnNFF(Po8?fJ}eI!zR=xtbZTeM0xlYwHKm;cyUcTGuNt&US`qT$8d^NNX&;Ju9nO4OES5W3PJ__7g(%F9a$$~5X1M#$Krkje&vhs3M;u) zU2*so-%5^$!wp zCq|gGd!M`QFYZ?;`zGBl8s_kTQ#Swx=WanoZ;GUOb}LcNvK~AT@4QDkqSL0;w(}k$1FI` zyU3b$^Yo5f)e(bD?^M(7ZBeXvyFA-$IFVT0U{u_rP-qi!x5Osb!*$^F+?y8to+obm zi(K{>HO?M!j9*djtok(^{q+pcKadEJ_<}am4mH|##5l99j=ZcnE3fI(qjXJPSZ(Bvfa z3*7qW){K;gbnnaYV09wss@Zz7nSMC(D<0_bC}7hXa|}g_@@g<=8OQ~Wg#5LghJ0m4 z4CI5Nc8ltrqmxUmFAYl@UdC5E@0DIrlQmn`o?xptc6tACsV&ocpw}Try0+LZn2&In z2>1zUzsW?(O-g%nyQ3{CeQ;TGT?_xQl9Mg&mh{wzv(Xy5px(y0j(Lm~+|8nQ`sGItzQZ#gxKc?&_8J!i;g(Fzw#b>c*18+Qyg8B&?Xo z+Rm>Oi-nn7PD${p%ye2_S6CBQSh8dCP%e+VEnx90az9MW-+r8m7Yu0Z1}G(y;}#Sn z!nJbf&3adwdDD}$l}FZEjZCJtZ9;cL7gTCt9PK=))7Ptd+)Zs;(J?fUQ~Y7GJ|L-j zd#rAr(a`N1e%;Jg?p7HP+}q#p-dncxMQ)iJZ{IKKF@8_B#~E?EIL>)Tk@d%yS9P~L z*S2c+JbXYZUcY1c<;{epj?<207me`QGOF>fT4BEhzoBPfrsnr|zLRamh_je^+Io{i z)31X2u9?5`+upd(;9UYX=M5anp0bK}3UkJi)1-Y@8^zDRL!y1C3MH3vOD>ERO*GgA zm)tz4_;hyBC%A;gdF4~u()e*%Z#X{-JL`MgD|QYYly|&kDCKVO(y{Pbv)Ybx;g)r? zKD4~VbKy)aQ1GCp4MZK?egGpW;Q){ds&qipfb8T<9E(uHVCwhNA7$%KIz3Oj$;O%s`8dIgA@Y&38 zKN*WamIP`i?q42am%aK$YVMWB!SLJL#_AR2lpFI=DS|? zQj~qu`}K1?`z?jyu}O!X(jD4mR6T{!6NNT2mwv_+zwHRT2{FWmM|?!B27*^LU|>6d zO8{f+V1|g#einV4xUxSV!W|kNfYkL z;fQIw=kflM?8kwSiJB0LUUd`sx>;inz7Ch!$zm7TzQ&vG7ZbP-j5a#l>+c(C$5iy( z%^+#QysjW?DF~>!61+S*jJ)n3sWRWbQone7-6e4)J=FG=Mbw+L%DckY86as#2GUT` zn!ANMqG~}Uv}<%AB!9CJQFYY zCyf0)sLKIET<$f6u z%+(t!{-$c3e41Zytx+?6^`jUJ_#W$IYfdUiG+>=A)UuT7SUrHNal6|#U zVmK-LzBg#7zx|QnzGLX$!-&<2#*Z8UjHXqMMCR4obc#X2xkM*=i^xOz{If*)KEEVI zAk`x&txENN&QrNukD(A-(8xs$q*3y&+l(oUIM3YPi1}f*UT>wUD|{V@vv^R9MTAY) zkOwD<4nhYMxmsI$#cP77c$;wX`EijFXNN=0H$7%BvKOX$9p+Xrb@o0@7>BBtK~G#{ zxy~QszU4A{xGA&n%kso4tdGl8^t@un`)3}<%Kif<`AdAYTI|n>qpLCBhmVsZ11Y@U zK4;{z6=4yf(={!FS>xS!XA=6Vqt{G<$7oeyub-vQW!=WOp;r|n_^UKlk>U`a_zyMm zS0cNoAMzC$1#NaMS(;f>I4ZFINu#~go6P*>a);Riu2)CxOB3bXdRiy$Q>V`tZr#@V zcqn{n)6WuZY925?tVn}e?Y5v6KzMZOGn!4IO(r1kI=1o>Vh!sDAQ54pHO*~UUtb87 z&ISB!hgLsp9FbqI{bBoB8LLnrQgi3UN1x0Ho#jrymIXz-MkZo@d(%P|_Vj+v*5pcq zgfDEX0LFhB?}wwZRO3UVtA?9KH{5WXUYd-3L>qtXGy0NXw^+ecuc%Dc?sh1=RzEU9 z6MNq?IayLQ?)I9kw=ih)<^Cn=T89o*wr}gG2RTnOkZ-QvA?p;TdhO3Bm2c0o!4ulQ?=-s{(@y5@U6cOvs_8C(vPlJk(n z8-{ZTf1_+nNeP^$RTmBK83$krkE8BX*qf4z6QYvGoHv`;RiHtcgv?{!OV%69k9j^*7Q&98BADBHj3vc|V% zv#UJ;aTB)|6P;^Mc-FWm_#_+7_LTG_H&=BJ7<@Yzc9}za(e;-RHuNt-r<6nKDQURU zRAwigkEX0b?;tr+5HqLD5Cjdlrf4n2$0+FPmSnvq(?o{asbe0zTw4nNsnlj2r}xEN zE~~4nomC$g`(i(0bjV^1VWh6(`M&r;qKTyUMA!tiD6Gdhj0BGSUSz*Z-j4mwtHIAo z_g;sZF*S$Q>|GxhG8CU@mNa{(u7=LkR%Fceu^V!UPIkB5qk1Pu;k-WyeC&KzIS_G&9R|~{)e?i?=iSJ;v!~Vy>m+cSfTOxCs*R`mR${07op^^uJ|qP%TSNN?(b-%%Tm4y1fTrHoJ;135y>M?>L2v6p ziVNyuKr#;ZG%eE+hP=yMFa^SKZpOAQ?uHxqdlS(Q6n5+g3II{)r4r{|#i(6ah z$K|g;cx%>*mvQufGXTP##V9 zhOoVYV{qqvMETs|t7C93KIiHd9Z(N<;ZC@E*Tc3ULza&Qzv%ZrYyR?c~#+EmZXbua?5ZM>Y8BR4=)52iHGlSZDRLc(^_>|X?r&RaI z5|^#QI9_FLC)m;r9bw@Ogbc|2x4!SaQ&CjQTC${kwK+hehjDu46EAKm^e*|Uqr;2j ziXz+U6^zv@lb$;}`bUcsq+hx4Nan_VfuMX**C>zmiM)IM(j4JGnh3vps;w%`{(p_O zRg!we2%m}*h=Z*2u}JS*J&gS=IkOMF6XeU$z2bSKl5XpYVFwS|euvl2K9{!*RSbKt zG&mKut63OL=BbzL8|wY!e*Rh+v|EdYx-e5Y|G*vh%_QaWL*q!>!{HbJdcAUv409u2 z2crduF2#yi5s8eG;#?2!2OjZY>w0AF5cf4YRI5cAoypngQ4RFZbHmkfOf^qL@gI1o zDngeLD4_s}3dz5`EF{{-_$%yGy-#z4*+t$a^LONIUIa}%T`%iwXDrb?Tl&_aWBQr- zz6|`{|OV9ev9<)3zrY6hJq)aMj~a-5mrpw&DogES%pj zC8b36Q`%G5e=8sGu%3_ZiC)2@POmbv>Wd1W^bPe4K<;sNq`yTeS6(R2m0JxX@>iZ; zgRi9zuTc5@j(#~JdFQ?{`f>(u2%X#iaBt25^J~>&g6z-K1pH5lY7QS`{|JN5;k*9; zyMb--Z1xxFldUSTW3YJB+yV`bo0x@m0NBmjx?qJ}u(g%g9nEw-H4xu*8M_c2_%0d< zI!MnJcVhMD48d4+mnA&%C=5G$zKLzoz}7bLv`928k%Z4ccSh+|e`Qgtkn zr;EsT(W(*=bcFMy5B@s7Q?PS}yJCQmcQEmvnl;$L&*2k5_h-@A=c z!3`MPjR39CN-(Sqjsc1!76H$`pHK&Ynp-|azRrfn8O!UWbQJ76R_LWS+q}`&X`W)+ z>bblWaC1+PpP(7hT;fTcskadPed`N{z1i=Qzjf>x91|lK5?43|&QG}s-IpFI`?#=g z4R}ZIgkW`IfthxE15L4)tF8hob-xD`~mz5AT?cepdQk zK0{TTN+?Nvlzqlm4@b3T=;qHneYKqTQ1Nud9?R9LC^M>FnO^MG7h{y|exZS6=#O9F zPF!AET&&CeXjbMWD$2fF?E9+1<65Hh##8WwIyOC{HtT_>zL@F4?t=L7!Z~ct^a;%L z)rFwOWQDnzd9%xgr%*WuTHTgkz1UkKS`so=e(bOZN95tqqqQsOWy4#ONS}yjLpLc$ z!g~*Umqwo7oRAucM=Ft@o(70kFq&C}OV9S9P~js1Qag9Xw#IWeh|uphE>c;FG9@92 zsq38p6kQ5f5CRh2LFe}Qh%iA9B9`IjA=t6A!Etqa>Bqm3`Wr_m_wnR- zi0!wpur7HX_4r9fn%Kk{G5X4VKOSqSvHjnV#qWQfwa7B7L3bc+*-E0)@3XdIw(aAb z5Lzs*6HF$>>0(BN5TUl)xB4u+4>L-&8O2|A$m{do``{~Pnto{L8}dND zY>0~iGGB#ok>07G(oax1g2ZYmX;`Q?#5o)6;R?1D3r)U{1LPJ3!eLJ9B zh7ZASJys_5G2pZy<#Ai%)`-*)=qVBQAbNeCyPEzn-!1!V-T29)_it!!1wQ_d1^IqP zf8qYpZ8Fps=n+o}{ALsj{LBBx?}mE{V9PG+l)N^xhE&8rhRXWg>B0D6!-8TNI27UrUq(+Y}|glDli8 zBD*$ntTZq3C|Ah7Mu(|MOk+kFDKa4IK?kmW=+S}2g>R4avqRm?XWRCE)GhN^l+Uf< z!suq@MKnsA^z>MNAqZgD6Dia0Nh^=d(L=|dwQSkS65w3W(_%f*Zn)SGEtb$P>dd_9 zb6o!N0&Mc|nG;{mf8}UYtDYHoFFiV)I2ZNspijxT7wSIM+X?G02z|#@k8zmW0qKsL z7s?y;uXc56sC2~bIBy`SsA%>ivL}M}Mvpm9CMtvEb-zVrzxHG=s zCW80QH*|g)#Yk6tW=edpNSk$Yx{1N)Qj+v;B`A3Dns~V+*6K};OkbLt7@Qd%kk0?= zd@J~9T1egOgYAhzFNDh6Z|Bi)n7o4C_M!FZS)aCfW^t?|e%`Ho`4x%L6Ln1Cocz^O zs9hMii-KPDBdrhO`^voFb)Re2-qdRm$rOKe-zeRJweKH9J)9q_;a)_)ndipH?xGMI zLmx5m)fXinYLkc73$4+`7`+Nfma_nUJc}*h8PDxn$XsIuEx6)lLgG*M;CuM$L8(Ml z3S^{^L(ds-0PU6yL*YEQBh_?Rd_D1UuP(Hjn7>(cOw^Nfpv~>sttOA~z9{++&4dXn zspE5KN~#dO7SnSpu4tpC`qn5x!9Ulq0R;P3gQ!2DB}OXlJy&lk6dJhxB$Uyw9{mh( z(rxKK{Ryy|=-49{JM|T2WJ3pXuWNPGUUyIHQ{o`!fMxZ_GYIt~)zw}oJB~NgK_XNU z!MG2uDs%m(`E^bo+9ba3f`WBy)18gf^Q={-GUBTj&lf+hLVl1X{{I;J5_qWBHtZH1 zDe_X5WGTjy#=ewoly!upPMEP~#=d1Od#hrE8D&XK2$d~k5E3nxu{7C@wQQ4R60&dK zGpN&f&wIY_JDp#CC^P-<=f0oo-mZ%}{=Po(b$e%U%3?1yC^NUYhl=G_3*u+T-cz_l zLSq@ruFjU0m@6%aVJCV;y+WhE!D7qem9x`SF(82ZQQ-JGFJN0ZU+y!T$oalTEOgTN zBR-<%{*2FbueN{jwQp(A#gA8mLKe~=2Bnl`e!i$LO=ltjybdBepAW7_)eg>me}dde46JRKuuGp1TdN{`GuJVa{Z5~v z;c;{1=HwAdxs?07?CRs_BF=6D0Qn#*vyYWv-EbE^x8RsaK|yUT@W=sP`!|yycU;U9 zSUg?*`pW#pOS4}XTZ0FSc&#u#)>(IE^K%oD$g6to%zU57Sr1*@o9?06+^anbxkt)X zf-)@AsGnZyzc1d~B&Y>O9s%BXdXL`5WR8hkx(|N+YhQS?g_!O3MOsgtQ{RI#SJVKh zriZ>^$az2_7}=}Ix)f;^DvF2hyOB!Pmx|BSCPW;ZeW0zIe{bflUHfd;OuFTvhUHK* zzXz<>Y8({-dP9ZmCe7?Y8CeN=`6`))h6>|F6$g5$Z&I>XRL^VV8hKqUafg8#(AP&| zvS-j1C3!GZd7NBLjEZ&*X9K+uXa6oj?(V z&M&<`X0+l6pYOkw9t<`|JM}d48QqoNJJdYIh>s3MTg@+hUmF9P=Cl&ZAE0u(8Ag7- z)UCH5^4I(<{QUC@$VrWXh9p(*EwT#qynMWQW^e5voO6x$GJ+WLWARiOo&Ir+*ZKfX zIMU=#{cIx))Pm%O3@EHyC3A2s8YC$;>3;K`e##-P%|B!=P#)17ac`z;1S98L$QJ~q z>YD4Sh{jRd`A=ZLm&d?RA>71sW_`U+-HL4_LGNyvShR%t3)^o&-z$}q3k1EA*jx7f zkV6gg0;rcTASQqE4;&a4Hs!ba=)X~Lhs@`aKQwzfnjbb)<~N5uopG##PcU8EovYMv zm{U_HqBkeQ{3Jf9?CQc{3Wz0c=0VNW7q?7iuvf&$4<`$Kn~t4WQvLo=YDKlJB%l2B zn_BR^o2`vD2UzcK_DSJ!4m5rUu&=SiO)*v{peVwMaelogg9JwWy4F8NZzxrt4c>VVw zB8+QmA%a@=-9}=`Ez#O1c%8Mi^gRoDVA;#JIS<)=`P5RVeWKgxj;4|Z=3Xi`Kbdo1 zM50ilzEJ*~<BCWGF=P8*)MK; z<_yrP`Z(eh9>orwnl$;iIQYiIU|LnDG?}P_Rg?b*~aOhjTYCv50%% zO@Z0;NIO)>C7Jw6THdQr9+jFff!Hh9=OJdS$!k z$J}acwgaiSwjgCJ$#rlx$=xzI&%#YXfXTQs9R6xqK)6y)%l+c6E(Jl;a0`(cbL?!n z-<(pRgAsoJ^1?#tWYxoSr;Ux2w{I-L3aB>eYT*ghGXI}7xyF`E~mT6l;}Gce06;3aR@ zRXYLwFXwyCF-qB-|8ic(J@mt}%B4t?+xyj6_%KA&J*at-bdfyLE^ zthEIP6~k!dujSA#@v&kt&GyUHxFmR0FMC4 zpi3xbvU6kJ1Loi}_08KoUJ}Nhh8_Ujbkh^1HU9xj(4!oJ@evjfuX6x-9H{fQ{i_2~~h8kt_okubF30Nw!TwzBttEhT=JF^BEo-SI+NWVZh)4Xlvt*Cy<(CH5S54)2JL z0W-8EmIe{%xwCii2P5wNyvF};h+*DF1PdkB(wFLhqDupH59wW1V+#s|A)ZJ}&mZd? z+k%B#2?zc4w*ch7!$(3U*yiqm)_9a~SJG@1Hi>Hc+-3H`JHq>+glCx?kD+_Ca~JM4 z3J1DLyv zM=2NeCFhkE$B)5!@TJk@0}ixrvvU_0<|v~-HhwIP1HC0?)rli8z}3+zJCm7wVw97O z$sQ#-HP*^s8l%QVfuG zfF$zayji$E$m;#A_J0QVZLnhp2=$1e8;Q&6-(AyYpPY&B=rNe^1`1-42W-Y}Z7)qA z04F}35^a61<_LWe+2yed?ElO1P+c%vqgDe**njXBR)LN0cGMUkflSPxlXHTh4yP(kNA!iRG|IgX8Z zefy4IzC9nJmwggbyg|4pM!p5DbHZwCxs~$9jl`LODW40{nGX8P@Z!ay@Dpi`p}s`S zOd~#jPU%aWP*Btc{#Nk0N>6n>?PFITbt3D)JY|%)@k42K)iQV(27kqac;D;gJ<_Y- zFNH6NU6}WuTtWdKnK;TEljB4^>BdjT&4Bu6MY}T(R(loEP9-WvF3S^46B#&|knu;- zj@_RO^-H}MWL2vEzRY(PTbnj_%v>gycR$Y3|C&Gx!DO**^d9n~9VE2)pb7F?=9~n5M1#JR1Lg|%1IC|Y6U<=n z2da@rjDbd>rh$VQ=O8>v=qY{rW@5Mf?0+TpzuRJj7pzvmIlBX0N28|ze``zl9xzfL zO{xV61>%Gd#jnSWnzq$7*hB&JF3F&#m?K!_?n=FNdXz z(rB#*()fD|ZP8?D3Dj;drB6Xkc3{gFdzJjB0B>KMTJdTEhcQ??-S+djn8{b}eL?4t zxft?y$?WCF{TUdtzaYVsQZ2}i0yVi2uV5yW*UF5)5vNi{;8na|aNWE)i8^$5AIyCz zU%d7@TGZp^4f0F(zF>Lis+$dQHdBICtz<@_?DSL1=#yCM@KwUKbLqUMs-4EeN|m;b zZ$(|W(@$MGO!~&F-mfcofAZkgo+cnS{6nAf2@d=D>CT73sa+spRJ{x4xvWpdM79AT ziECheB6H3ctPyym+7wS!-Ll(9k5_a!H7)~CKUG{<0;0dHUe5@Q2;JE?m$MUPb27}pu#S-Az^irx!<=-bF{`<3z-7$NP0i0W9e;gcz(@-V}F=QIYAqh2eVp_7v z_tuD;sZ0_E$bI*bY|S$PMaVNUhLFJZCPou2#PV`5Z2!j9$7FA*{z*9$WdznrO&&=z zNLtdz>qT#LQB%}cs~iU3GcHCZDR)uxjt|UY7a=P*oSp%y6Vj~rL{s@<-CFbf#k5Bs zip|}ZM}n7fr$jkk1I#N_CpOr*dWIj(T69U;XDk9#SgF#kXfESr6|L z7U~AaqEygpCF>ru6ilREj~?++rIU<%@X=WhQS>I{If~70d@DVTTu~a7tDSc$d*J;h zuATWfO!onsAgyy^&^rZJSoR?OZI@nloLtVUJ~7cBvMWXei$c#|>lFl5&#RmWuo+FLJ171jcuD$X+ca*3nty~>U^MUp>cQnr%&U>Uf=+k0y zjAh-&j|zlepsggVnPz$GA9+Jh#&Bpl{^vT19*Cnd$$4IBLPy%>&(Xu zA!bnw(I{|%Qx^xx#%Oiv@Dl`wuET;3Jc6C^JxZ6fVEJ0*7X$OY2`y~Q3{Gn~Fy+ae0I5L-jny#;cCbkP;gCoMp_@CoPsgp777*r4JOQ``qOkni6|^L!_2gc8nk zw=S1Bz2XKuKEEBK1r}*L(j}MJ^t7IO0U&_#^BBazA-@2 z;mLHKE|FSYNsPFcx<^}|t(nk!;e6Y^GKt%0j?gjln%C-T z!>xHG6m`*+x{(tfVtVv;*$)ZuOt$73ncZAR)2b%znd}CB=$w)A!ovOax_W2p0AUxP#?a7qBa#)zSN4PRA(U$-y6~uLk|6*=+pM z?)jaAg57p&ofO8>RR*?F^=_~5Co@Gbli|g7(*>uiDXb?J+t|ULu`fTEzZXfrZ00CH zIL{2IVsKV=h#yLTFH;rtxJcrh%q}JneXl&_uawc)m!gTxTL1?> zmS-o-x1rRwh5t1T-$Vd_JEs<|WBBAui~%OYLElm80YqCXSBwJ*Z3DE?_`(#c$yk%n zheZAp3x=HgN4)%@aj5t^n))8Ri8@mY7hE70FHbfx)-!3zDviQqe67hldiXh0YqKS*`VNUqbMahv>uXZo= z#>;~d8=NB_xZAX_--QHv_yg;EluAKvOZz_h>1K4G8RQTtN{qVrCO9|>;iiC3$B?;3 zxY_*Ap@-M;FJBmN2{4&@tFlp$1Kq3NxaxypU0vNbPhhXyj zZS}!@mIP6XzJt1zf_HQxt<_zHo>*-a*J2`>9{qbc9c+gI3lAls=a&0EU4e^WltG@8 z8f>hw|8AUZYR0w1r%#^IX79~ioxxTs>F<=v^@TQ!gn&?4)$o;o);NcAUg}OCux)Ef|RC#oP<`XuD1@+ zLT^UpzJJeO4w}lcwr1M{cL~`p9lJ`_lZ5ILs{>D)|UkMXAL`o zMM}c!xg)Lf)}YcmHzI@lSPBLYUn8P2;=sVCf@maZ-8`4sUg?Z%0R1vL4385B!f#+W z?f$$3G_k4p>(uz+f0iA7Ma)g_+A2UjADwiux?&9Fj@$F<3UEm<79Q3Xm2rKfSxm8H|K#e}e4!2-#EpJc)K*#$ht_3HkQ^u$9JFE?U)fzG%P{JnLMok$WY< zq~H_iqtPaCPq`_~o0E&An=6mQX^o+FKO)2mtzLc9PK0e(;Ex*#40%rpgnX3)HzhsM z`Oz%N^Cv{^hiGhrRH>IPz9M8*0c@J82q;!jR~Gocd4IZ{Zla~M_)REiN8zZ}=H)V4 zQnQYtwBZeY4B*6OFu=z{_$Q1w*uxDfzc#+mPa#k6ZLw5!ZjdfA<>@<&963&xB3-`BV(#qBaT_@E=J{-!dJ(*^}7 zTO$yCp`RMq_d7Z@j4YMpw;dJK^_=XD6+EaR%-qj9p=E7W6c3bS+@PS zp$yt4w(^=jW<~k?CvyXI`}gSupc;5= zJX|x2D*oGTd*kojx8JeM4K#gjVe>1($+yvOMJ51r0b9P-x;U5|fHA2R7Bi2L-zqc2 zTqyY090v+DWGU(fnot&ET;&odPsgR8Ij@;7FIT{3t@&tj6L(yKGs!9q)VHG1Go{q# zTfsGkODSYax&^^5Wn!csKjdj2bS8b)Z+bJSEmrr|#pi}pM6;@q9v%D%cs8Q#4kqwf zebk%CoA6#bwnu=HbXKx*Rd>W9;L}-{LiLemeL&QkiltIN6m`x&2Y%%>wqQDgRih5O zh(>2vMm>A1TD93UfK-`%*4=%?jgO^jcwXU8K!OIw3|NTJ2^Xq0f8bXoW?HStf}9Jm z#yG1I;JdRaq!aqRGtPP(%*a2fr8+Es0_Sx4SuU8tqp1IELbv4$Lb6XrIRSx-4fh@Y zXa{Upjv@ySZQh$(DD;!J@tjTQ%XuY}or<7|QygFUWaZr~a*1aPA#tI2ogWr|Mot8x;jM?~C3tHfS?e=K9B}48s zBEMysEHX=EB0HLk^ErlzZ^Zn?9yzZZ?w%6Hq@j|r(69aFdNHn)Be5VwnF&2;`Jhy4 zV0<$@iH0qlz^`Npe384{{U3a}$kd28_14A+>a8>jnK&4D{b#o6!jI1m_a@-Av+o%w|@;(U`3mNNvb5N-x?I4+`T13&S z5#!VQwA!L7_>|8kJfNO&E$o|l-+%yxiG`Q&&(klaSOReEdukwgLB3hZ!SXM$*og+m zXTT_QdVL^~3?_F2fszaZm;odHF8{FkU>@Xx`_{t=OCC3*!p6HoI2@5b>)qSrNvgW=1DAu)hV9N zOqlki3q(nTqrMof!e@Sn19d~PbiEg4Zwi*Z{zuSVMA7GP{~mfbms-<-DXLc-N}~r< zvKi2_M-dV5Ob%CKiiF;B8e}Y<9r?W>jT~twK%9Nf7oYLf4lzK8%6OGvGW7@=5#>KI zffIu4?* zmG+}ez4=nucUXm9eRhX+3+&o2P+nlWqp(JEE1-fwu&7&!d&tCBBb6_BSuQ`AGlib0 zCGrV(d)i@buIPJBrc#y8K=T7Ds&W!B7Xj=Y2PByCJYl4|IZflUU~4$UajM``Fx)F- zz2fDg=rHu!8=`NuBaqsddgNzu6yZJ34P@^i(jE8U_DUt)OUyLQOf`>b2LTWrLFr9M zg-+enQ&Q7y1AX_hIyzJl12gOi-56TDnFKr2c8-G~8?=G|r}}2iV5^x7yzQSU{m;E{ zuN4x(D6J3vmB*;G_z^9 z+o_H?^L*$l^SnQazp#0}bV-l-zm9_P*E$o-UX7jm3I zWNgBAYy@ZGDPsqjkaxEuHbu}acOnhy@AjJfI^M(AI0ZRlF2GVUJOhoHtmO)7pt@u+ z&trQ^U3yOmH28nr(O6aZSa3TN4uxLLG)1Uzw|*D-&s}FG^|vdJtG$Jl=6SvWPM1)y ztD^^IR^@rR9FIPolLqw9fR#coAlm7usHZUyjv{*7DUrc!h~7t0c*iwBx)P|->P$6x z5B8(=zP1ELUZP7hL$A^AC@|~hbAXiQ5dj1RC4hhZRZ0I}N%{8eL!ZWqY?g+Px)e`^ zf}O+a0*@G{61@jz9e(B2#U8P~x9t$U?lOlOp$)M)^jX=h;SRd{{%exAwg1kId$=4U ze45})-zAT%>?PV>1{p<8;M2AfskI#4-XghknixWAPh**bKCad|_%6{G?R6V{$hjl!#)+U|N>)hg-*ZUo^)xK>@#+8Mn<6-F zKM~{JoBbb7j!#ET;fEM@7M^C0Vf=92;>>yf)9i|c2V!FGBtvd9Fzh*YI|fbAG~116 zZd|(ROIBK&IJ;s0QSZ{+^7GD#l}BUao$IpbH1m~*#$f&LQ za^>jZ z$F*8I=;op)4lYBFlDP|Wf020NE5OIIXhcI^C*F?d7SL7Ck7U}YL1 z|2^hEr268e!HcZrw{KD!5%0UTBb(Brt-cQK zwoJuJD~_*wl=4bX{wKcTE-wvN9X|Rt$%rOBU1yYat)OmT(UH%k;*ax*k7Q*^XB8m&&ReYPL~b@7%W)=8q@|iVehYaL-7r@9l|~?cLRrr9UucABMvEt{ zNv>r!z%*B0)vXDrU%rW9O)=zZE}l|f3ZyA!87=2E9Nf=X)^;=@occqQCW#@RR2&je zP3;rOUZa9Tbbr>ojCwK#iUOqFx1A4@oS7S)f$R&wUym-s=1B7Yd9;vBvjpU>b1?X5 zAsNgt)}DEnv=1wd}Nsr#T+%3bLI{~uk3j%X=YAd?MF2hGp zySCnY_l&?DMQNBF*-M~g2-R8nU#K#3`J#+L`Bh92!Dbizbhm_Wvux1c7VweH>}~sT zZ;x&d?Z6eF)p3pIa$k8~W5P8}F_UbC>V}D!!WWTf-)WxIgV=Lg%3I~|-K0~Sr*S(? z*!qw>PlubEp7y6dgp&Er2V=ZeAm-PteMIHHZTTx`mIZMyXQgYiL6ZA)AzRqovc-? zSkiRD!_c)Pu_Pd;U}>UdpxhCpt(Q>`>)dJ#XhU8Nw>?TN=4w64R#!{zmIiIqYg)dM zT|FUb%@cGJ!=SiiCSvgT>r8Z0k|j|Jx?HNp{miADd&odw1?W8GQ zVvxI9$^PA5a=oD7@X0mD_-R)Di3^4hdVBz;^^$hEE!DuT9yEC8bkQI7?HU9ke|fE9 zDjD^C_8j$3acM=x1xlUHU_`Oiu7?a@dyd^0$Sv*?EwFNz>S4{*%)FdJ4slMP7H;rd zzte$gjRM(uDtBfzRVf37y^|rM63ztIITe2cozV*I{@@Z*jfFn)GL8~U5;$VqamkPn z#{lm*Bt-fOf*-KUwms=_dE2UA>GW;{dkuo8x=>My<5;1}^ErC9wUcDFD<%hBR?X(l zKnc2m%G04t4jN%>yq2pIZR@K^>B^bruC$=CYO>#x1Vtc;y%`k#79Y2*u0#EqWYFUR zfUI%M*c@q^EmK6bGmcb-=BzB{KGT7$LmS6J%ksP-D;7=1C_5Q>ZCqd61ox3JF-n({ zFA#Axa;et?ses0{#gWS9^*NL8AZx8~%Ss3zG3R6qj4wg~T#$htB~Cp*9yF|;6fJxG zaxhvF1~f_I8}XT(LxsVP>+%`V4JRel>+JBUx@D7rV_zhB!t{3Z1lBQ0u2?Ng#9s{Y zyw~t`W#yIQib%A%s7;(xg)$RLj*~@RaVV2-L^dSAI=-Ma+OO2Rt+U6ig+Y$~AWv0h z1ctZ_OEr`PhY$-8Uh5H(u?jVin~%kBAS6`%@YIi@GgCJ{K6kwg@56LJ5tE%g)gGMW2_mTUV7#cnUR@g-rH*&+jy?z72PtgOz`WP_Y|Lz1NYAU51Anet9tSniXg%U@NFM|qSn z^Uq1d?+)W@u?v#c>z}fWU3nF>0Q`5Jf5t6diUroJtb8G z1#bPDm*ZKuKM!6kJiGhhr*0d-mkKn|;{WsPMk2#8fhYU!RD4p@j7|mXmr2MV=hCGr zXUdIfhgdC{+dD+cX{#EL0ykY;zZePKZeY9r{Tg1BgPBJIU@~~@8XCfq7yvFVbf#R8`Ipu_FTgpp)lqVnqRtj zdd!dO273QM@}TTS6BBSrMfYW6NuH`Y<~MWF@v(U;j~XtU&ii5Ee&Z~)4-LgqNOSKu zzO%#+^m@N)=wtoic{tv-i72glV(YRC|Ib?zuNp6 zLJT`hDGRXCiz*G`FIxPJ$i0E65KLqZbLv_A9P?TV5I25q9=vF3P%ee{lV6Qx(#}5* zW!8TFiN`hdD7BDv%RvB)+l5o0`CyXoA3b6ts| z2>%mTZpYZa{7sLIWCs2_P0$EUIHFMO^dSz;nXRW-8Ow(D`(egQvM;)c{(r$7y2;+b z>Hq5hfZFSW5!!cJ#2H)df@|Uus)syi;yPt7Wv*Ii!+C~&s@6-?LQkF)z}QYVv}+M? zm9!%;57!H!_t6FKVRMp%XVq8}#tTKxFK+uqh54=9H6xz!nNujEh5cCvWiYku-4s1T zLLSz2EYrb)v+`tn2Cc$LGXISnNn4vRR{QF51{Q+MkpW$Z7 zzQAw8vN8*H6&69?b2`#!)UJJ_Q9Z1lhl5vAX^KVn&|C2&drGRo>222wUH;=os-Tu& zqTlt_U}hGwSv)}t}KI=e*yMQiZvjSM1?$FMp~ zOIUnjDRyc}RSIa92J~tyY$gG4okdIW6T zbNr{t{+=OHaiWNN8$=5jDmr3u`2L{%ZjKoEH9o_|mn)XURD2yDpr`MKh-yLs`3*Kb1+Q z@|7Uk9yjyrA3TPjCRJp7E^Le(E-6E^*z0x2m+;Ni-f9qWQOy?piAtz8CPr}3V2=Dl z5pWd>-z~TcTfNNX=I^l{6mS1WVSu)*z;!GbtXkkQ@&ta_dqe#Sw&dYUALPIrN!VN! zF}UNXu5HpE`l*0Cj#g0T2WnckWLW0qnfI%uz+#wwySRjoAyhfDpeC1;J$e6EjQgJ# z%WrAM|3VZ$fyI{QE66hRvN!4n+p^wlU|xLaudJ4q&>J|Sk*ePTEP%p)lVuHEjFl9o z{W%0_V}d-mZ$!oWnfqHKYom=M*zgB5@KFUs=gL2`-xxY(<@gy87gt$s`$>x`rKIxR zg+*uYG`e8aM`}qxz9vu-VfQ^v z$h}qIvN49Y2#R5r&d%*j`oP43hr}&DdBDI;S%0e%QafTE_;^wfJNbH}tXg|_Av7&D z%1BL`Av-HPof`mGTY`3wJTCBWN(>aP#m z*dR_h#k~MYTCUg$?vCw@nLhWjeI5M<=IN&$EG&U@e-xp4ERi)p??cO#2F9wO3%-!q zaHHCk4w<3WrQue@NUO}@?kFQmEKzw+u=7WBUpRL+g)_i4)`v5U{~-QA^||$PJf$KC z1U-yH;EHshMRc~+lP(|Yw~Z=WzY?#OH#tNYXb-kr?-YfN;}mfI$868iR<0x1Rb%0b z%h7j_JG zf=u^x^Ei3w17nrE>g-Y=Jv7rFGk6O3!_af9{X4b-#ddlO{|Uf;?fGr~(M^QCWt3mZ z65t>BTTNoSlTDvG0;bUy(!k$-c0~O($}qzo+nD7oN!%?MpQ!hAwxuSn7(bg(7hYG;>DLc%)F ziH)A<$ts0t{i8QwaLz6DtKtxJsWBRi#L*z)YONkR2(8pVyBk#&IBH{x}8!{#qpooDBqs7x*E8)dsFGMKI z)uC4?K0P$M*bc?+d0&vrlz7`+bn%c)waIi7K%%rJZqJSp_tPdHtbCs3j;%^CqNWvt z6=X|q&X!;|7qEX1tAKQ@7@>zc@7sJDo-L5s-qbQ4oBO)q6}+w4*jSZ2S3~jj>>2J3{k}X0u4^5I%qd0N?Yv)4{#Pw&+h?{k!GWI$)7G<%f;YJA zTG@{rE~ZY6n#h+1*VTTbGbH@$-v;0N%f-ZRqP^tJa)l^`0xLe0#nbV<>t9$y39dna z5XCq^cMzmhVQJ^2`1fTzWeZPhK>g5u`b8YYntU#^jn&TW$vvKmRL#2u-PLv6Fh|%K99gPd~W^V^Wk~D=JT+!RJ&bf1{{Taij?#+R{{*}WKQC<}c za^W}G_m7}DW56pxJ}-Sv%uxf^{Cvs2&GQXR1FvZToeP&J34RqZ0gTCXgByx5-b&s6 zJgj@pt#lyW7a2HDsFpc0@Mx>$`tMJ9tNBEC#~MOA+}_w(f4G~yLb+1)6+?CL=_2k& zMaSVCzk=Ss{G6=}iCz@QiOF%OC5DaYT&vc-{_uTnp3c38gNglbc+YTV6fXP`eu2jp zs4NX#yle2j5Cp+=fUs^QMfn^^*Bm*%=gR3L*_{I;ty_5w;rfjz$CF`k7+b(X689?IE#1MmBLZ35&h=>`NkpT<*7Ia`2s-^wR70 z5!cT>hR_H7k1=`74S4Y@44WgU?FIb(X)B!jA2D*vO#weuRsfz!PY{98Cdl04wWl~8 zq)p8@5R)tVZB0pYO9PmRAxc_>#Xg1Rd3nFe|M7!frQ-olh(U z>r1;#FjSSjD=6+#R=L^82t>mg9fEOgrwPh5ALa@-VK59E29y{?N(4>~wG;HR56TcV zO?x(UU=&Aq@n_nK#qhGWOXjQF9irt6;X{@;cN%E*jG7K%N_{WL;Q6f@-Q)Ragw9Vx zmIt`~?qPaE)gedDDJ8{=S z{xh~m|3^zzW7eB^*6XDCk$_UX*8rMN!5=VBkRG{#zue59&g$%Jb9kRUsN;ft){f1I ziY-V6CQEmcCgFs=IhmF{>UeWWSaYuzNiW&f)M2k8t-DZJu)9GRb|KfH7Oq$hNJzmG z{ku_r{|xvK-O(5z*~ZLC%e9y7vDCcFaYifnr@4b*|?mzV*O{g`TAP z^ueV=I&p5{_TBJ;ytFT7?2uUBcW7dnZEACOK9Gl~iVToiPrd`2(;xpx~^8*n9d zl=Nkqgk{VMpD3Tbg!4Z4)n5c4m`zz0g?CF5m`d$%M%l`Zj%i7DQSor_!kKT4ByIu0 z`fwvBz?OpozP=mPjbhf8MW77cbqzLG=UlT-&KL6NJuF-MN5l9`#sYA|2CTm3KMoYb^1f0 zxayl6R9PDT9xrVVMpO}atL|ko0D#-qqIop zfLu|+v8!CO8QGYzenM=+rF4%gOIb#17_gcpyLkx<;5deEg#uYW4>ra6o&*1#xf%Gz>ljkLjn^6@!8=4 zbH{Ne5=7Ops22R@c&z8ar;INhk_#EMK$5tec3gy zG2gA?)3#YZn*O)n8_zs93a)?32PQ5j*GPN@-Sor`)TwxIOP>*k^(bH_`>4uR! zOvpULW|L*xT%K<>H$~}r+;$A#vgIb|1>mIdTs(KJG{%Z?t~d+>BqzmAPFor1Bj@HkOkTKWflIGzH3EyGi!BKKcrIxG!PpD1~0pSn6or_#&$qjZ)l2 z()yNip)tRP%e%|Bx-J046JYrGRCBdM1uL$}Vim=OUM zi}u<-_7K0Aox^#76uMfsi!?s6Y7#Eh0Mfq81|4^BSRn5M0uARrxKrvm3Ba{4y@{wOJT4g4|%4;_r#x91xjrC6aLCm4}|JRi!VD|J>qHu@_Zuq}nyx9o+R;K>K~g9(+yKkP<6m5c8RWp*%wO^7X7)$Grl7!^TqREqjouTP=c*6A9c?MFw5YWk@L$>hM zmJ##X)w`J6GU2B~A0Zo$SE}`9fGghO&8fv6No0jg_+_u@GX#I5)k?o3rA-GhAuaB5 zpm2bS?N{qp-1g$*++_TU1MA)=DS|3R}KE#+9 z>C9$hR;!o!base*mfvGKJ9siaGc0=;(Y>u;g)$jR89 zq*W}~q~oj{Y!yK=sL;NXM^I9=C51GLkU`Oz0$OdBu}@|6VPbWe;#L3i{B__o?F$ng zTaBf)dF2v_&adkZXykf(evUM?Tvv67(#NY<0bpHX6&#M41S1z(ZOZ%45N1S^4$}`u zEJLHlL@a3~U~+6-@~WXyjos&|XkW@zGY_tCFxC!0A?WjWej_Z3)?^;ah!Ph;sm9Xc zd-J>S^p>vd^cRnkp0@hCav${`=I0_BHtMi*>em)|vjL!N--oME52?U_Z0lHJ*kK-5 z$PHn9+pb7qe~VPH1O_(EXmhc!CCHDdEGw)*HXFx2R`zqmM^@~XkX1*lBSdJYPWD6L z%R0M~dKuj+SR%;Db$Lmc6;&%HS!NTI-w7OtKADdREZUeU`(D z-2+dx6vE5@ei)h}LCRsq`(7Dbe}4h?f1DX#Migwv=s}R{fZm9kbB{jr%1WlFB@YWp zp1E~LqbSl+226!?w-v{8i{M3p`w#i=A~^>$-6=SGl;XcOuOJKTR&Z*bjvzi1G>Sx_ zkZuGoZu=~g$PlDKHU}ihcjAoX?{95dc>iDb&tLBhP+qZ{O`plHti0k!A(oVs!_Asw zT)U46`0XUUOA%$M=vSLQ6@N7(#F!GPGS(wm!XNSqq}~^^n0@Jo&>GTX`$F5ZG;0Up zt3!bZLMkTmHt6AT=37mq<#t|hQ7~^AKhM0U@U4f352DoPIc5^tro54IIP@WA5{!Cl zG3O?COv2|syt_eD-0bx_aR{;ODRiJ(PkMcvS}qOx5=kznPu*~_09|1Hd=bhLO|h{1 z<4MH~xXASvH+>s|VshrNu@YeTZN0p>NR$;x~Xb|UrVa4hmx z!KYe|h?{!qV6q6UKA>`M1@UR_kd+JoW_1 zWN=6{!NSv#kU1&K-=8!J)EY2Gv5wOXHG_%O1&hSU`Q=j)6q@Ubc?VzY94X?W4D!$k z3;sWE+DYsJGEcuH@c(@&We4DPBp3lnE5{Xf@L;k{fl{{(Oz-C9RGL1hIBkc99MEqa zeKdRSN^?~XC&+<>Vow*pVt9X67e}Gd#0t*}CN?$+47}}v1W9eY_>ao%CQw!gU#c^TRA%Xg-3yt}M;J<>lF33G{6|8vx?m?1;w>wY63D=&LGDtt=B?CVSk5ir2Fe%M zv+omF0RgDFlN-2KocMf^ckwy3Y1$K;^Crq%Dq{?8I*^OwvB?v%lGfB0|6!755B$cv zrVtu3>Fx3X`V5=0*Y<*GzGU!ZsQ=>2!cM=mTA63?U zSw`?7Lt(nb@LGLv`9eXgr3iWTOu3znCv06RsJO>L zZV+|OitP&%+5cl-a7a;Y3J{x=rpIvj(Hn0WYtTXCF(jTp!B#Df>Bl2_-fGfsXECfu z;A2wd`KZ^{EZEa_1-@HOK(+O>ez}|2b-kRHmxk^ay;k>}cK-T|ParoW>9v&azym6f z^#dFW%%P-&y`*#EM=5pd{$hnfHG}DmgGHOT^w(|i&!a;tI4g+Y z>}go6GE+O27?c9?ErhGo&~DVWu|X-Fy+$5lUNPQZ;7gt-Te=a8xi^l^4M%fU&A|OgdIu`fuqd_% z)h^K#HWb^lhk^)SU62SV>_x%8KHL*|iJhnsYvOl)!#rc9Q#DqT2 zm1B}u@2{6NBm_qJQ1=3bF1vo%wfYqb9qoR#csKM78*Saq<7=@g`KDX?UvszX4*x;H$6G2mG^C6K;McJV1w^EY zEW0VXPgX#B_H7E$rjfCNxYAPGJb$KKilt`QGuHPrU{Z;C7-R*ps;?C+!LA%HiI3J_ zx76cqb&dvAkem|6zZDKTnBg5AOxHL%S;foW9P0S}hN4%Lf29?h5yV6nKEGgSP!0W*KmueD1ax3GgHw*xintwUah+rTvF_mM zN_+fX-*!4X+pM>H_Nw=6!D)M`o-%azoVk@G7?)wchlYKfyJHmcLHlML=*ramMnz3e z*#C)g!5hx0Ifz7oj1)LqH#m@>5LlA{$fBgdU{FyL_&$^$gFH}O<9FYdy`A$!iN{Wo zcr?0**`6SGoG2u%*D}5uyON*VIo^2NPb>@;2l2HWo=1ZE$ia>{Cmo2l=yg%f1Fj|0 zH)Qxo0p(BkN>r~smiaJpAv}@(f{1w0slym4$C5){Lur&cuUK5n|3|=_rCM5d&qe=vF>#+|e$7 zUro&gr+2TLHq_Jk=g%L#-E{r0lel><5q#Ca)PLB+z$*ikGocJq02D5>&(%O|IEp|C zB6w@X`B-X!=Af1={y^4wv9m3n&o1LwuRHO1!1%mi$@ld5)5E;=s&dGDQm+<$sTYQ!ECXp@9jwgoC4GAc}Nnnij#LhJm>y{l33j80+zB`cWfB(NFl_biR zd1yE|c4ZY=p(W$k!Z8oo;aFu<_K}q$gskM?2wBNE&XMdmM)oFq^LrhQ+vjsX_kQos zALTgS@8kV?JzvlLcszHoBrVW6(rhl^}00!&r{eo5BpLVMHUO15j1&~ z5UjjBr*SO@Xuf9)Eq~1fyd*&zT=1O>+`7?K&(CEeLzS=|8JaQr&y`Z_QDlfd-v@qHAY!OEBETV*j(DltH@O%{kqpg~^oRdRb($5 zjJ@f}n|S=x93L3ngt^MUD&5_bx*TyzzMSulo_D{=*;3W#^HMPrj^ytpcRH`~HwjcU zj*N)&6cFzY$sL}5B)Bg9@=z^m=X^4HcRI-z&OH^#vqc`9nb=P4`j!Aa1h9(IsvJZUmBtnaX-* z(SshKl~zD0qQ=}C?L(41>hNqD{8921A+aAL=!H(*33_NGWny{W(M?6EbzZgleL?s% zMdFsyUP0M_Vq~&YR z?wYHf4%;Q1*6s9CwbB9%G4l66V!8 zl6kwXN2M#Q*f)s=kM`25<18Tc^GpD#(1{7mJY0gj->Sj}7&6?BO; zybY4ov7XZrL4Yz1bK2=zkE6+?_vmO8lT0rUovwiDtQUvsa_2V1&{-SjLQrg&4{8?gX+%gTpwBM0?R){`pL!i`b2^IaVu?MdaSYS{btj#Df+cxQhlId zLCkuEc1A2rMzH=X!EHo~|E8qOSF?>CUw(D-#2L<2?~iMzh)B%a3^BLbRXv^?7n{u% zUJL7`sb0d%SLd7emv@ackMTRWO$*4l#;q3_^DnGzId}2#FB!p-R zn*GZ-RNs*GjO3`Jmr53^E_c&Rlw^el6RwY~qOT^4|JkSD%ln1zNHAzpkmA$UTUF#` zsAP^nDo_cCJA8x2&Xkgmk|2U=`NDs?WZ3=5O(vmskAe!`C%T}6rkw)_$R=|%GNGd= zH|Q{_OZ~4#;h$dp*4>jQaVRSv9LSnGW{^Q5EjsI!M%53hl9J`6Z3ocG1tgF009xY| zK?Fh}U#OEgX0|vbz!K5)RDewbeeupnhX!anX-F4_P>=~HE-Pby-x&zaue91<&phTZ z@S>|*9)ij2q+u0MK5*jqB#bzD*;JZ=r)a*bFWp6}3`D;V;{wcSrKLlHO=pMmjH-ub z#Byy>AHRZC&f#x@Zn4qB^Q{`5vAtJKa7t6!b&0>uxmP%eTOK|_e^kbiKG-SZhPnvGo&=b#zbbeK$4=s>abHTZ5kl`OU`1it39>bw*DbN~hd;gKv z-r5C1F(W<&Da=bz^ZN^I?_YQN>mL0+{@MHQD3dg@bd(>^#d#6g-51l;^2awpCto`| zdfg}%ly?*NA#j3Fk)*-~(5Wd?we_IZjK9^8X$C`TPB2Ag(rfn2E{vPd2W@4?DhgN? zWzeC^HaZ@}#28f6jjWFp6-yFbBJpxmiCP4?u_EGKhuuWaF}j#bxq3sK5jO|>r!Q$0 zf7lq6;>V~RO{A$Dt+>=IFWpIMnr|WXt<=n^xn?YpQ%gTPn~QHh4hm1waAB569Yvuh zbUtz3Z+llaN;Uu(`71c5pf6yu{g6dFU=3CSj*DyM;v?5ue8qD z`gcUC97tSnJMO86c^hk^s6Yfe*fjK~=?x}PsQ>8?56NBn*INF}?P##zTsDfrm;erO z>nMG(0kOPE+`ZI-_{emEJ+5i-H8NM~=AP0Niq4s<=>Fj5*!3xbpio`0=3-eh8(34C za}{Hiitqe;%=b?RT1hj0zUmzm_}mmhQ~{s|=cfqiyEyrG(1QBRXlt5Yzl~A!@Vr%+ zl#>S~^eV>5X!O}8zr&@STDD@zQdBbjqbu9}h*v_J8NB+4#`2G;=ywH9t-}0(0n{`4 zmdM<4sXl`6p)K{}k>S~?_2rzJBZPPWAOn}3EhTs&UV}Dl?-5R-9>(%5K3P=#T3U$( z1CRQ|Wm6Qc;jJ`0-#1+HMA9;VRM|v8tznDGWn8TVL#%JR{G{nG_;6Q}wg==%A5x|- zgBd;?e!-upD4R?#97QaYqgYMG^BNA+%KEmNGp|38vwj4s2i^TiTk*;ZtKiK2X}Yv0 zDRF}gShu0?vIbL#ZpW@3YC{nZo8bv=M(26^b&HGGLA;`K(0|Bh{7%b>QX!M!UzAKpY{rOIw2oJi@j>%ZejwOOzC{9NXqcdIoXQ$;arT6Xz~aQ$}Bq3k&KM-rOTm#~ye-wJ7h&S1$39IszhMhAa+G z@i%Mrr#bVkfizY|`sfa(mxt@xc?uIDiawT3TlJ@s>iUp1+;f9`gkhJhIv+&$-TZBO zxUlMM{vkfl(@ zj8EB@4P(oiB9lLQsX@bA_f-rvfJ=Q28_;IKgY;+c-%i_K&mMS%xlsx7F0w`BB;QeF z-{RHWFEi^qKMsnD10>(M4*J;ATZ=kWzIZ6W7O`ZvWJ%8lz(#b|jJSf0dw=TN|6GW< z&yWSd_?YTanG`Na=81@*yCUv^SO<`r-XqmA4n{#iG0Kx|%@TW}`_}lvrBQbHWd^SN z)i3IL5cG1z5Q_c@Hw{AMHO_r^+bA0~EG;9t`WzUo>YK%N?O4muplmkmMMK`~lZiu# zWN3}~weQAKWDM4bL+CI-sA%1062*fCgLI#KhjQP|67|bsvM>uaP=*o6$wXoJk`r*v zm+DD{??Hiz(_a2zBvnc9%Ztt2$}?GzLX3!HE32K3cIug3$czODL7_N7sv60}3PH9n znb+!mL5TM>7_?nhD9|MSSSz%ghWTomTI9Jr=yO{^CbbU6==N%-zs))PbLI9VKKeYm z5I7Uc6gbQcVa_gO^=B8-_b2*)pejwe+#&c*4iz6${g8868ovQ>dwqMqkIdh5;y?F0 z>t$1GT2|ZCm{&sjq~uK~r5IXOHDCJ#Zm^ZRdurwg<^dA3wzA@Yl2X$2oqoUlR$5S> zJ7`1OvPzo=m_BSRth8chY`<%ywCBLG9YcxK%ORH2VHg0h# zw;NevrWg35-%55M{p03`G(>>F%u?{4Uoih>j+kg zcJ>rve8R{Sn;Kv)dfaaLfDUK`=Q|EEJ#pE_*)&Q#LkEp4+x=T^WulEn)x}jlCitb^ zx=3JZk)0O`GNIL}<1aw`GVW^!(DRajJ1^LDv4{pY{MTNizto=gwm|ea8u$EGV)yzf z{v^@L4OB`|P!sYhCMS0@RqCd6QF{U?Qlj?b9wYz&&=sM?ib4@gVJpApK^&sjJ^cUf z{|27{-1R-32?hhj9ek(n(p8gcSmDL*;CU`~-KkrHrU_M<&3M@?kyJZ8YyvVk=(_x? z;e;~d@|}$Wmw0k8Ci!bUa;0=yfA%0c0{f|Jc>bwUIa>+3ip$-3K_0)9#s$;mOv5QXG#o5zcv5TA)x7*2;5 zsPmQdYtdRqb}g@98nZiW5u=|zwgul#?w=_+7il+#;OTv90@rHgCr!8bHzWUBX8-jb zSgGja;WmD9LPt$!1WyAD#x4%vgYT_i4q?tkP9DNt-+61d_8|{94EnX zKl;*1qDV3<6B=N$vVgku(8gur2u3U6G^|7C{pZ10{W%E$HP$3o!mcC)>7y#AA2J(? zh$WrTQvOaLZ-2fYhh||*EHPt;Z@@sz5ZPrup21(Ix26_TXRhCFs~!f^#5>oc?q}&J zX>!$No`1z#P-`|W18H;EuCqF`_{E!G5#XL;S>A`)CrpV$-TFk{(sBTcsLp}xixv%7 zuhiz^g6GtvC&1|#GzFFmE*y1GzLR_Am$o1sXzdHq{{)Ty(~J5yy$%9DMlgY(2;D;m zA-aQRlpF}iC6OGWytXbSjGF%hUCM^Z?dgIbF&Cy97kl`PVhd`hS66bJSZ^`_J88&c z%&COM06OvJ|M59LLQv9vK%GHf{Gm8C(D1{8OFmCO@))z8M{TCx4xYgZaB(n1e&LQ# z_4tlyWR-FUZMVWIEyj$`5m$t|;91W68$S1P+Hn~m9(tJfa_^zghU zMWw7@whl~T>~819XRlAdX&8pwEVe@Be>y!sNHqqPG=+KxkBC3CDX$dNO0jX7PnU;X z+8$ID92p$GeWsNUyP;NOVGs5Cciy5u7bXP&=l%WX0MuqcuNKXLrcKj_Ud_{16N1MO z20Q7v>UAjMpjK%4PT&FjK#{p^H0mQ}rD94yH6HmdRyz2B6SRdxUt+Uz4-fP`p-V(b z6H!vrm>U(!9@)xw{amDIJphm9kezO|LWh*By+;*T(p|+cWh$qcHH*qP0>DuOIVT?j zMUF1rbHszzm5fC7u)6#PC4}tYK@$f?apMm`J);!;yJ-CDiE1M9&L{NKD($j14iyR@ z`QoH1Qd*U&1ts51BBiXgq8QB)?u?NR8zappow`7~s4M$qK;CKa7NR8O_CrrsgGTqu z=^w4^*I#LswJ~!Q`kTIl5>K4DycDcgJA!el59i=X=-D`@B7wccRT9!5?Gm#&-T4{8?{ zS6GXt&l(z(45n|!MIBsiJiF10=pr*T(D^zVWPYt^zL%Vi6iOe0fT~VpE40=(yXP@R zGN7@~;ToKUR8ILbee$lD<;Nv z+BVqzyb$d<-kehBg8xh$YiO?y6`HbTGZK^B(9NI7XhyX5^E_7*P(8O5yf9dXXjLhe z^i1zs`>M7Mmf>1aNcTRR_5-RxM=%%e3TDtLC|<$}4ZJdO3Kk*@Kogs~*K`cJp9lfy+#cA!cHC5Zg-wh@nKqI7%u54Q=sR zmVB2b90ECY39&s~wT@gY!u5(-aar+Nor0u9#_mbO#(#s(v%`^Ga5#);Uw04fYqz(Ec3 zTA=U}Xrk!cTfwQ09N{230kK?uJyW_5&+!kn7H?ru>&NnK)*bSlx)!#O$wUQ}HqEv) zf6QB-3m>!T?baXMTpT=K`+;ahVCD*e+dmXk3B{%63fk%05jMSr5M4LH1Or=snT8yh ztbJxGDKnwGHa}(eZO$7t3N!r~IX}4f#BOnd^=>~L(dsxv2&j82Ze91nxTDXx1nx2B z+PD!XKHAZ5)=^|&)1mgEK0E$?5?p-tgXFWE(o&PsbH#ho{J8sRcfl=%KmmZY?g8`j z!1S@u2|A)lE7Uf0KpVADv-KF$CC}g?uNUnMoK`+*v4tg6%X3>&^z)NLMgabYBLV0^ z6qruoq@AX6ibkEMdPS`XgV@wGBo2e(glv9$R52!KGmu)nug!5Ff6X+7jD<5#Oxe1>Uz<~N>v2aa?JrMmHW8ajXUuoFX1iS(`ofy< z^a#$&h`{UVNPN*95lI1#W<5#%_bzl_%p4Mze_!Y|`TYPYWMU38F@i%>xA872jlQxO zbVKy|vBeh_d5RY`Qc1MPcoW}*yvcdf_y!3S6BlZhR51Pr?gacX-0Pa2%Y%gH$ACHi z59z01h$05*y^^0uX;6+Th8NkE!FxKQ@ieViQ%mXQIcju{Hjo{?p3(4ZSh7uCoS3J( z7o-cFS~HU(mW)jnOTngWj!%pBmwBurV?s7Q8RZXJnP>t^sJGbG5V#kDq|?;Sv%|e0 z@bh41=vXy{HXYK=?i4uuQP#p%5+z}uSCSONSc&{w0|t-8RoN`XB!z^+^rv>5LgC_G zS9Z)rbZlBxR`UWLLbMxKo>e6+On0;MeL#Kx7h64 zh^c3|=cX>Dws{kDh1x8-^CKxG@}LIpW6I>-D?axKvj>1G_$=FZQTa^b>jv3Qs-62! zgRqUb)g8h}mez~emd2ETu0EL_;)ukfF#EXWHUa(1Rp?jlmPs%kJ7%50?8Cn4Al+}I zdaweKAy*p%1$F=jvJMuOg|6OD;^kM?$~&t^KJ`5qqRdA$G6kA8KgMX$L1EtOQ;VY!imbRW&LD+w98n%A zuhQn?#HP>QwF1nadtHI%-vM}4seON|$wTjgg%!HR5Jx)q2dvanyNMvmV~hy!v7J>#^!+&OlYH&YP% ztzF*#xk+(grmj`&NsQntf&8`BCW4^!ErVJy$jy;5)X(L0=6s!zv z2g4;Y4i?+==Jm|k_1O@fAFa)r1 zJt6ysIT^j_IYj7>Q}bpt@3>h)$WHve$d{%YoOTcF);UCOWD!`!Q)kM7yGn8)p3~zR zIw3?oM0ZUi9S#x0b~{=T({blqP$$WC2ie|W%fe5??u?)<(oMH(eJO{ne{4{_!e3nL zj@;SvtE1fXZ1>Tyl^Txp_kaP(l7I8KRJ;6J8KX5<{Tpl!*?6unkS05I%!sMaucQAu z;PM42f~xqTZlr9OoCQtNqpEFF0R|MG!(E9^7HN6oiLlxuXNti!x&Iod$+Tx*|Sdq{{UOD3jUBb=%UI zF#*`3ffsk87Ck>9*YMpdAM<7*r}0|?5PTRUC>;8bKmJIGn4}HC8W=w>Wq$gRjzl&& zjIiTWd1NU{mjCRCSH-(`YULq|kHVoc=xMuCMLuuU7Sm&agxPiU%d}-z*u8^$&o!x7 zqvcyKgh~;!OE-__&nC>#zn%NavvCyNtgO0c&Uh3tIZPJs3MY=$O`Gj`oxkMeck$st z88~7yP7|py&l`&==E_TX!t|+8@?8a%>Ru%^SZ@5H`i1L|pfvShg?RZ`x)D2p@@Eyu zC66WHOk=v<9Soj-Q5Lg0J7-KFx-u+ogq*2w_tl@1F(OR%(}&x@iz7_WC}F2fO`?|? zN0K?y3dvW(x~O{R zuY8_+zI=*YLN%zn`{E)`{?=A5DXiZUpDP$Zm>rYeuO}w`g}W%xqiMu!Xs>t;0{S0M z`};AEaGYoFGKprUMkDu$gQ$JNvKjL7bY4EJHJY`n~Lpy1FxNIY(|$9a_E|AGsJK{<)Ytk&;RuDJJ9|7dB7<=OMjIUwNs0TPR=bfaY%6C>xCCu>@#U?y zj^7^7m6&cDQqcGoI2{mPHVDj~HaSWLbIXRjTW1*qj1$0K$*y5u(7yKPp=i4uK_DkQ zn=y+o4A4$`MayJh=NEnjLLBIEY$~%Iu8$xs@>qqVd`hK(yIc7>-Y`+Tl@0Z!0E>NY zKU6y^^f{P~&Fn)Qj3cI}2^Yx*qmo*92aK0MtXnZL8@^~z9&)7B%ehe$QR(BD8#J3j z0qz~g>{>Frectd~@Q%Bytnt#<2IKVz^df-a!X2^);$&zFTOp5w3jC+544EmrkN zBNi9W(itH~j+pel=U!#69pjkUCU+5U+Hx72O5PklfOCoU936Y>f&4DMkWwKgCH@xX zZqp#Jz*XI`HQ+HCVtoUal{;yzY3QjcSRpB$Sd=SM^I>&Uc<~+9crpqxl&$N4493ZI zH>kMBclGZ!ft$Xnj=6DfkBjM7cI(ESJ+(d09dxoB`iPXRA{q5UUsK{3X6!LFm-Jd| ztl;_t?F`1Hw`8jxWAU^GK-enEF6PF@jE;25b2Vs|?UcY{h+;ZoKu>o^Lxof3-dF2+ zt&gx>1J<^g42?|0C(QK}ZIZJQ))kCTYl|!omJWruEryX@4C=@!k&H29$k&6EY;`-= zW|yBYnt5~JU0GCVW2P`L*!WF$@_;s6AM zduv85i{|RW7Edol<)TLXsJRqkn^P3W($!Jh%i;tN>%OKGEiJr^1M$Ol2N7XUyd!M4 z!#mocYZv50_~D6=)`nRv@Z$LJ?AZ?zmah4h3UeO|p1AER?Tal^WL*5*3j!1!>1JoU zUY*zg!RKD~6UvBc!ZzVK&Lb8$%Rd$cnKE%w5#=0nD%#NLvD;MU&_XK@-M(pseEZpR z`4bZoi~BUiil@Y+ovpn9(b`6h^D3(U!@PHx5U+srr{6Bo&!r&MMFKz-RD-@}Ejftl zI*w>1O6m;*j7v9mijerj5H8WDrdJf;drQ5~#Wd-)<$6@O>bmY4&9n(H3GYhOeh|yPWn$CiUkg1Gi!UzTs*mM9 zvs@qjB5uY|g>NGPkkPl-p+154MN8szcywj}AP``EV{~ zd~Cfon;{n8RSUgbh5EjvwMbM*soO!d^1CV`oeF6A41Q!I947jFb;v)jS9$lH3Aqqd zIn~?0Bs?{{Zoj{Qhe~Yf?DmK7O#!hf-6rv=*^AX5JZG9g)~4B{8JQw>83ekZuN`C} zzF+e5G5xUHE`aub7}vA`0j>AHfx28^6-fC;;1tNVfH84q%@*k|9lLmA`h&wm)(+XpOu!vK?E)%xzY^PB4wo)iSI>m<)XM378 zzeqb>fr}*DjOnd3jD#znowINJu)DJroH{mF>RuyH9#1R7hgc^Ij6Kp4{i33O(oJR~ zm$4b!jJ#}H>Zqtobh4j@lX&X1*UtK0-UvN_cAocjtebI-IcdYUMk0&>H(N@sS@ zDz*=#>l&4buzi@fR6L-4mO(S$xgLEbrAbWLC_7;?+}G6ai)DX(#4FL&(R;GSs(si) zeHUv#^c@)x1&zfnUHma&PFJ*4_&vrPZt(|>E-Y)Gn0aqI7m9S9eLv%(-)nBZ`31zX zGY;!Y7&*sv zcW9uCUXg1ZR>HN%rL@wdN%pMZfa4Kk2;-dh4;A=t<^u??QdBsBpXkD?8nf1PTT78& z;(S-0N&M`YqKO6J>^W(txjs)~=mgE~rI<60UCOx7GsJ5unLor!B28s$OwWa2c9vVh z#pV=ps*DQiL~}|ykVFE#(^t)X3wPOANJl^Kiv@6d{!wUXIN|x_qt<3@+JNG3?Bque zo;mMjDKtnc_=#+7x)n`!s$_{CS2@#kF#(0+1iJh+FKqEBtad8oq--501x0`Vkwm$R zF?rv2$kFyRoPLo%=vMvq%%fi?GlyTOJRMzm1+{eGeSVXSC#w|+9@AYp@~`g~$E=kt zmFTI?y2p$=80&M?;LCbOT9!Scv+d415+6)&Un8z@!V3qNnFbmko&33S)WB;wj;01` z@jH`=vGKqrGg#m;q3B62_|5(Iy3KsebSpL7j!&i9cIHz^PQed1>0A+#J#seV+5^oT z7wt8){E-2?gWRLIn->wN1XE+u*WOKJ66xWSi`%)}VnSeK;PPgwy_HK=@VjOR|!?SA8es%Jr@-c|Xz@F+Jz35}=*Axle`$BmSIm!$y za;nIOtA-wqyoi{`zs*0N{1wOjZR9O|QSYD&wlOqO>zb0wD>ig{qqUznmFz`jP#Qti zv+td0u7CUUtQNPMI#_fiU2aL!6%UUb$?L8?!LK*FFSo|z@Q6tg-eC73(d(8M;7-As zM-U7`6~eo2o8C566QcWvujm}TN=j1B`@^m?YtSA0Ly<9j?#jcMn4nH7{#V*{OHbv& zOo(&3J-!H+$r>yJn)cPSQtoq8%hdddQ}OGf_ar~wF>HFN8?g1d=4%^XdU1bvxX#wC zukk0*@QYzcWat{9f`PfIso-ix-Ow%Tq*G5%UUomq5jE#C{C>Vq8MynZb$RCi-wrLz z8jq(b!ZO&q5Td!hK%)z5utl-aiHLb##S&`M+ZWbBBh;Szr2z7GKT>!k>HN;b1-KtR zxYC4Yy|v?qlk52%bxb0F)V<%%QHz$3XiY zsTvD{C~DbXQAbir%+M9U?Db%7c)9KlAhfv`VMAaKgu8TK+brE;M4IOoebsRA+*uvm zc!0GKh>=w*kWx$SW4?mnzc0)AG&*Peu2BZ3mCJ&p81$3FAP@MrpC0SLGPxD&i0v48JZt|T>iKa~@`TxlEg1ufLu5|oQ;H)2MeAVxo;Sr7xwIsiex=-S$QpX{s zj(n3tF*UqFx+Ax&gZk^9-N0K2(05PRrG-x0r8z?mh<7D`-b`|aXw4F{Tc?t%`p(_K zWt-)es(zR{5aS1nu9+t%?dT*!$mE%#hW?co{YB3jW!OHv#MfN|gQk#O5D85m7NxXD z;h&iKm{v^pBjDfQbYW7UwW&&&?QRw%b{-ya(!k`zzEJkz=KCqqH|t4K2V0< zkn$w5uD|HC>qp&?*mC49-uiTNSe*fm<$i9-Ra0_Z>*DV+@V}B)BuRUD%kN9G3h6;f zl8RxP8ru0*?q-%hyijZwd2iS5J5m&4eN-M}0nz54&!AILNN`qd(thD`TTw`&(S+}V zc+XaajEN1?`p@Ds@R@%vAs_%vIs(KEXz0ED1^H_Lfc;QtYa(46d;vGAQMULb1+JTY z7>+9lD*hT_?jM0LZ|_mUC@5fZYTpfoZP|3yBO#S*1?M9a4#o$+%SlTJf_}Lzf$esD zR%4>v654Y!JXl0FwBJ_IWU+O(%sLIEJp)ylm{Q5bluy2y+U^+$WOMGieXTSHTNZp6 z!IY4?to<=D$h{70t43*}%&thRO%K%q1v}z1Z*%F*S5;^#v<3|U4M+xTv(Cs}PMQ=* zc^hwht)%O|&G z>|#~YU8@iNthzaU+GJK?{vS1BmRm0DfAQkIGF|gG5{^rfyvK%*t!ri zzVM>7M}lnEj{0*ZJ~w4Tr;@$vmiGII2AyT^V5eJ9*qfx$cI&UWUfx>vHP+cDcG>-j z;%w9QIr>ma&{Fb4u8)vl#P-MY$DMEG>Nf!L442OX++E1PnT025>Mt&1D&i21NDjK{ zI_#A)@V31p9_y+QT~BijBhgRH;a~@SBbd@CR_e+Y5vp?KR0@Od<2-nJ?*@(0n&m; z{%6{c@~mz^=6Qf2^4stuUijJ5;lt9_&e(#>R!rD+n-mVsj>1nUw7eAd))vJK!@{MV zHV9YY2_qqzGTHr55mMg;&wic)x|rbXV(yej@O95G;mUICNN?Gzc=8t6Q%^MjH_rSe zo}{J}8$Rrf?nxfsJQ7lbm_6bgbhrbSh0;Kg-Hp_F`h-~^_qW&wd^>bzR z{!2-r|Dg|pS8o8=g>b?QmS}4R_#q&{fB}%QW_l@T-jZP`X2=3T+MM7}10c^} zHRoOeI8kJ!tV4) z$0Ji;hH&|^tZYF5>(uVpgi_kJK}J7$zrSciF|98H$M zyE<|SwllK5&50&OiDU%Mp=ORJe ziFU3#^en248z`t_R}e*oTm)GwbYa=|he6oag&{yPf1HGW+U4JhZ+ICP2r!7i-IV*B z7y^GAL02;^#&;bFywj@`lxH!uu*8yka4tRcX3JzC4pNm_@VzPBGiUTgQy?6M=%}1 zDJ`Kl0b^;aPXl=(YDE!HSGUVb*rM*=sIK`^;>47I+dKwN2PFWutw6xA=@5v8<(E_v zaS`a4q4blT^0OSLJ%(`bCfG?2C38fBz_${rY|fWR+nOQQ;TV%!0wK|f-J2H{LOYV?q z>;OwA$sK)8S!uW4`zHujv;Xma%fA^;oNO&lrZ9n73DT4y5jAYvtDp9 z>G1z@=ih$C^QlWPOTQ*k5nPiRl7GHRD98ZJb1Ch+k;{;naJxDWO>IJ-IPPOJS-(T# z*HAgB+QA-#jUp;`ib|k0%2r~je9ZD>y0_E(k(TWwL~$*79=h#8By!`0g(th3gr<}$ zF$sTnPzuXpbtf`-?H;(m@;x_)S`bw*Zo6$VZoR;9%S8gf+($SX(oaMOh)AEKbvG1} zcE+NNl!F591D*r6E62d#ws)sD->u4LiaR_UPqFQ3i?- z00`bs_;s={3i3jM24`9?=qx=4BucCePmgG3UyWunrsIImieW<;x&E_H_J>{PVuzpN zjPnEY&ilMqdy9~=5d`T#duaZeS^gex*S*-r^JT7J^v6Th4o=WOR0#5d`8^^*)Y0;m z6IWomF#t6=6;o(KhPHg1IuI93;wfxOct64N3x54i3gDL=>$o;9YFZzT=`!#4EG5+T zQaL?YwV&5(BS(M+anL-tWvLi0e+!`)4QM$$jxj{$73H@IJ~U;l(mq%Fx{!*ACsjj{ zuRtue36L8bdPE-ggeF}C^A(h@@CO)}MP#?kWsMk62tq)=FfIFamBN)O^0z1$^k^`* zW&ao!jBCcgzJK{H_vj>L96*Tr0D6izFn4>^W96N<>wJbWNF@PQO;l(S9h(zCl&*`v zTm4Kd1|xosA(-6#JMJFDmAqAsetja&rj@+1MOKKiw!k#RILYU(G6TLu69g$p>6N^& zs1Uz5Wh;Pc)Agll(RJ>eZJ?|$IUnDB(YdD;boXTKhs&O)uEh8iePKd0Zf}Is|6)JE zTxXA~%zLo#)odQm2}gNeVu{RahbdeSLsYX~q{6m8zD{Nz<=OO05>r zgLJvI9z<7S>^oX}Yw`kJk-^0k)5Wd4Z5F`ls5oV#FEr!sR(!wg1tQNaB3lIvMp2eV zS^;VdUEZ7x1Lb#oQ!Ap6kL5`5r1Xjvgg(D10ciB@XL1Ha=@)FTdbWbCE;%`(KE=xi zTkHxf_;x*uGObt3VpAEG8$A^cNVF`+ldGbr{4Bnm)?rq)Hhjbel=*#?Qp1_~4G{cb z0X^?btkOG4lds@<-?Ngzl#Q6wX{R*2zY0&UC_np`ir%%PCXdjAHqT?F(L$byHB)@tMQtkZOs_xpbag17QCxtr?Wef+EXc$TB_a4( zRfQ~GI;I@YfAIGIO{}sN_RCja031karVt+hDK3g?o{R*oG3;F(DKRUO%KIW=Scj1Y zOhglD)N9vsBe%B8X@(O8Pwkg36!+vpGiwx|(GsV$geX&b%gwKgf-@m>Kh^k-&iB!S zS~?Qa-or_;F`ATMcq!&e1AxE0Qv)sS3=K#WHfED9i;`|D?V0l#;C4*iY9N1#dUy7; zv+QaWd6vlijk-koXf+*Jv?daeH2_@;5G@x_$-W_n?dm5WaR-fBS6@P0N^?3%^&v7UvW7Krey(2Bx&4c zJ55QMDh&gklPSS+d}MKTAKEO`G~?CX4yBceo8H|u<|LekO%T)VCzFBPO6-8KrxM-9 zH&pl=NZu}lj{2ru_lBRs7;77=Z>Q+nHDwqc6E;&+<1RrqM8Vzpa{3i?ql*^sy8MIV z|JNV>Z2Q7oh-R6(KvQH2i3P~t$a{wQotud-N58L|1Rde=ZdevzAqrsMJ~pMPfxNlo z6p3VQ%{4s)S9z+WnFEnh{Oc|M;T3=-`@I8WZJ^2_fD((cPu4g)LMG9#;~^k+N{+d? z_rpqAYC!Ff(_|E_caY=%Gp$E$M$d%lb2agVS(fChZc^pc|C z*H#$eW&B~2Xy?Z#3$9>9WHE1E)srP;PBXW@Yw*vTyc|8Z!>5#%gS@M6uMuV%<*K;8 zq6AMi4j9=C=nza>_JbSo9JjDsZ1!pBO=ou9ylA941+w6Pnu4+#+KGP|?-B;Tf48(l zXZMA-47^cRh$7bCHxC9lfwg1TwO`fKmn0t$?jO@}uSxE%Z4n$7M7AR15#H_o48iPSL3%>Z`zE$Kbj{a|37%eq zU;j^e`q$>enW|$C8__YeVl$30I-36rZ)fis|+@?LB>H%7DN3 z&epmNhgzL5<RW%YjVVYYijHqYuU#Bk$(Q}};m zJ}FSy63Q@2ys25`$Y}YU$>4UK_H=*ZziIG4Qog_5!({q1sMN)h0dU4tcaCm@kEvHD zu0?P($aHq6cjhYQ`4iJEy(nL$(I9aqhimP|X&jMu{o1hxf)v4)N{IckhOq@s?qK0N zj1d#UttE9Rk0I1tBy2T3$ei{HlM)>l+TT}@-cGNr85?%279i}5vM)$9ePrQ1VG+zs z$UK01uJy%O1CH7}Co$TgR_m!JcS2<~maM6#u8b%CuXu5Gh1fl1YOrU=>1APC&9Q zDQSiP%G`Dy$YRYC7z{jI^q7JlZ+DXplT$Gu@kdOYZ4K4sEve)DNXmHrkDk@9-wXz0 zwNp9J#bJRH7kC3rL?^Xq5tB_C{K1}ii_AE`z3~&E!Fg)Hmn!&9~9xpcbW}^?*`h*{#NI>Ul{&;h}@%1GY6#6L8d^yh3v5+0ONRK8s|g%!10i^V?5r z_zd&O^+fsDn}HW(yQrq-)yq1`Gcw1qmfKd{WmZ=)4r96|hWqs2eAy@{SmO*70AyW= zi3W#(I{L(q>~NNgT2a7W_#G9S zX{>Y5M>X}jzT~rJAdl^en6B*16R6a_u2MUrEU(a&m+*eFdzVw+hz2Z=$5s!J3; zmfNP?EYPu$&#g0-j$vkSxfv8+Y z2cd*J?xGR&0!zbbh9HkK1vi9NO8`wlkf{P}u_pg|gevi@lBQ!gfh=jcll8GU8t6(h zcfrcorwW4mq67jgl_q-S4kwUS=pQm#f0x`pP6`;|eg*ShD5DyBK7^=@IQqYAfu`KO zDi~#LPu*3_gQ0t|WVL!q;EtGc_V@$(vtkvVLm{SDgvbcVvO@H=gIB2WFFYd%z3JGp z!?zVIGENtKtTbj|$X~*(zs)i0MRk60RTE*z?~xIu#Oe8cm9T?1thv})GNkwhlIee} z)Mi+6@V!+H!w(r>2F4|yU7Z2a+R@X}8l9k|c_|qFO)+T2eP|`H`2C1WFns6gtZgnJ zIp);qd`5AJ&Z7BNUCW~p3fbuOH3Kd{;PSw+v(8nD@4cBXKa!Gf%mQ9?3VBNiL7d*R3yTjZALhNe!b|W%2|_O%Dae*HHKKD2WFhH<#iL5Ke^M0umkj}@ zWazT5>G5!Aa_3>gaR&*U*Wxwf)Z#^uxqZL+KW(nn5kR8}wDJ5RuBz^#@$D)GCCySW z>FwR6D#Nsf&)b`kc~`n!6~b=R9zYZVw7%qhk4$K+@l__gy=NG$c(8msBVF1le!_;X z`%r;r$=JP10zi+!tJ&i`_~`(X;?!(<&xWZa+LHY=?SbN^5M~_8UWHg&6xQgvTdr+3 z!LcYWL~#$^(Su<#{GN3T0{A`;Qq2_Jsus&pA;=4;e5{NmLjYGvJGHTjz6`oq;ACyt z*+&1SV7Ymps>E9}Bwv|Rw->atyDZUpU)C95SbSDJq}36GXDy#7fzg|h7oxA$`_eLKYv(HnQp!RUweOYEq&j*b?Iv#)Y_%=se>dnveCp=B|^Q~$#Ex<2-MeWCkamLwIJPY8agXOb_bY!G8 zUX>77Ed~b3C%iNf=3@#sr^J19!dfb2_(pR_FSZzoUWc@nyzD`WeEm|=C~`5+i9vgi zf^T-HTUvB2Z^;2ZYE*TW;xv4t36(e{VLpTMEb1IZOlUdgMqs^XE_cuYKiI_HDa`W0 zDcVWXhQjaiLS5oAj1)Z_;=MA9sIN0vm-l)_Bs{>fBBdHoRs?8!Ux|@ zi6HpD3H08$s`Ec3w(#GyVbBu{Lu02Zz&EplC><12fqqQ`wm%Xzc8WZB{a&n@O!;CS7u-r4Q~8z3PJ)w>QWE^e@?E%l zK6E_UIo-0%;*8@|n(AGjv04yIUb1KD3@QB_Z40CP`f=&a`aSJf$AYBk4Cf0s8)Y#d zK$H`rXlXmluvo^AvAdeu{GG7y9y_XYR*wPVI7d!T0K{N&7|3J{Jq2 z?icu~*!b%p?#@aOcjtqp)s!ZJn2MSM&H~ds?Ds4I3(Iqgf>%Ok#j#nG1Ou^q*;E~* zZvFi@hX0R95OkZo0nx5AT5apTxzIZA>TA`zL=p?n|8Nh{*Cf)G%u4#V{@cr#lK)5B zo5w?;ukquRP}w@C8fz$`7<-aEWf>uLtYeq4C4}sTP$c_ckZ>ZDHDeuXD$6hkA?uWF z5;L+l*6(N3J@?#u?m6GSzt`*c&*|KYlleTK&$GPW@8@{}bA+>J_);THU6NBGtwC|Q zfqUI>;XUv%;#X)y2o7vEv!lJ@>i)qP-c;H*0rI`jdRwy6n39Nh23oobukq#-6Z-W^ z0{mS^Rj7yyzXwkhuY>a}^)b`hm_b24mt)#d6{IyXzEqu4)(MPUV#>x}cGs^t8UgvJ z%?h5)+rT>`zu+7$Wd+|4vk*P*IO5Ry=*Xk+yr7b=G?nqhY<4A<4iE6*%%t)v~nl*4&0X@Rn%5W~Q(;99EKn3w-(kYM3xznkU z)+Pb|U=~BZf3^|ngdTvM)YUNOLnXB6sTB?O#dEt?QWYckqs}|F7J2EU$-p7C_S<3cBof%dVIcWq^R0bt@&A@9_ zQ(@Mua@p2kFp3A{y8ERFo`?EM>|`xF);QkY8cD9TcpA8mv5$fXK1ec#9DGEBgOv;TB^^p{T#n0{I*3lL;_m|9T+@e*qW1oP5^XpCJyc8MRIsh%nfH?f&P)e6Acb z1V+{0g>AI=_YO=D1IqDmpJJ|BZSvL)Aw~zVn%=D&4B25waa=zt4&eb`4YlWp>d`b* zbd#@wIvl6NnH9R{{X(CDTyr2GpxX#EH<+FaqRj!D@pJ@wbdG%*+i=n<*F|K#n!`eN z`EcKr8BT_!X69qVo)Jf?UVkibOuOUSyF7gG?>a31SH$_ghO5Kp`}jT?@OLtGbMMU3 zZT!jnzB&;jltkvboWIrP7Ve+ZtX+hR;nifWR$kh%M!{)fWd(W?DSADVDaInq`)klcYbpS9z~bk}dtozHij| zj~TGA!ypTs^}t1d_tOG`(TaOs{?o`XuCRvk>oB{0LLH_0s%@(LQuk~TCp~W{8i5eJ z$L-Vnbm0#22I{kEu(I=2z+d=@NA^8ASEr-y>oO@Pm!@y73uY^_@-ytWgCOJ{@`t+$ zqI0ygM|4wS&J&c%#7{a7dw__{n-CTc3NzalFj&i29Pdts6!uTZ2YDl*FvBKAyN&(V zX;>_8KjZ`s1vbZ429z+-3^t+;6t6t&X8C+8M%S|NNSUsdm^mB&vYM(Ebp1|0Z|Y&1 zIypQvc8*|~&vQAT33tETE?cK89p)kgN_JpXK_xyYK?4HPWJSXN zLssTP;^$?j;rF=LPCD$7z50lvKBD|HmCPRL5g+!ZK9C~?eyQVtqrvuY+zS{)Gbm+% zIK^-S4Do&-!F0iF>no`cbJq`E8IhlW@6`p!55z3$2yWE)&ih_xI~BBPTSBa@U$ulF8a@&kv>J?0*fG0#qu>D8q81w_t)~5a@Gs8ikGomp!>niT zAzrL2e`)W+Ih>ttVSNLI&B)Uu(|iVnxB~Xxf~b|T6@F}-wZ&zARF)mg@C}jh?jlb% zq$4;E*xnROo%U0EfZ$N?)r2siu%B~+Q)zK#zxRF=iZPBW9F^rh4=UuI#-4(v6xvtN zSkrfB^Pj_PW)+s_h00zTv)9#BU7IR>l{OGLGo}lnyZGiKS}dkzA5aIC0F{f)Pk9`0 z_gkC*gU`mBN~7PO6iV6nr)p<1nku8AMky-88+Ge4rrM2xU+R((+bCvV*ua_GQ*k-M zT62X<(R8OE?|ZCTB#GjiJ-nS&tesT}_-I!d@ zfq_i*i*!+PR1POT3=y5+M!Y^#Jw%_SV!q{+}dch5RggASo?wb!5%!ZeB*Js!H;UA z?blOe1mKlb_%i&C@WO@2+g&$QlD!%n|34l!sNE!2=l;oGMah6=2YY9INEytLixN_m zwBYQWpL>J6Vi_8I(F%V+mLsluIZ6l}BU}_D%$VZ%(OyN&8CmWO9diZz)cU;~hR*dx zD%NQ|d|~z>!ul1Sl^bdW9)Rc<<2xNlv$=K*-hSXpvcU4tW7Y(>RtbCaw5Zx>U;?ny zd6~pg#+%A%2`Uj=u5D*@pd=|nlZU@RbS3}lH%%rxumH*`cfWD#XbtX(Z@?tYjo%9_ zyvAs5#Clro?wugPMMQ>7akz7H_HA!vX3$6*VuB2&t>y};L|(%@ipN)lN69>8=EjYx z(%KN2IOC(lqOcD3zAo#PSAZRP`fDhsLCt#N*)_*oOKGx1nS8OFo{y??LS-LQXJL01Fg+DD70CtB^OkJOq(#Y(sfXV{tBkltHpzV$C7~1pLfT3|=4A98>Qx=}aFqF} zisZHD0Vo?U+_T|=gQ87oVDwKtqWb^<24IXyID+HX>O;rr@K8286+X)qcj5wYB*ShM z(xsPTXlu%mx9)t7jA=l+Hu$jT@`%PD!vOPaVmf2#(&!kBaJ!dEYXz;gRgokVL#x8I zuaso&%o1=Qn$k!ypy2y(2ei=AgE4b1HnM0yUl5&CBxN{I*lyZAXaBDEXaOVYTy>Hs z%uCXRCAB7qeuP@LNix-lDq}}fG(bvM1N{n-iE?zB@QQUL8Yjd-_19C%bl3nUuRRy; z0VV)uMbNgiD+a!U&0J0fY!;xGs$E&&AeX2$6g^TyoM;(qQEdI#?RixXR7tc?f!jRc zMEwdD#Xv_SnrN+--4Vy(zj}0E^a5wTG0(v9DhdK zpSJKhcI4k*0{T_(O@#xE7plb#?E`N}TK{VY!~aUVet0GD2URgiMQkvk0Mq}yEd&9u zq%7wWm1L0i$l;)yJu^1@Ikies!bGV~7+hn!)KEG*v~%x%T)AvG;0liLm#y4y9@DjJ z`W5&a|27ajsn8>UuspKUlf_UV0{YdR7cVO;14qMl{2&gXOuzAXUz*#%J-5m|Jrvwf z8iVyj$u55rz^eQf&P;+y5^oyNQIQrd<;0f#Ht1Ei zLH=X$+To?NSGQhihYHIy_}I;c)8Mq<8RVTulccplStOQ{bmjNNds9l}5o-t?ZWQ1O zAOpvqqj%I~?5iv_>$@8y)p`JoO>Z6(o@r4mzCcgl27!Owv%}r&*wu4~p4Hob^tV8h zWln4XAO~c>+qbN12EgvAWtrKgy`B*eIhcMj8~WY`${bcvvq}P>;lB;=H^!xg44|02 zVe?e1C{BA$$U_Boh1L>ml+hW94TD&7uhU9t8G-yGOwoGd6?DYO%4U||qpI|-H>Ef) zC$y*rq_Q`otso32iq}>6EBvnvEykBRLPV+1$MSNQj(`w=ENrMz|E%Tjy~UAUr1E0P zj?&j)EIU{{2BvmT(57&PRp{7Vn)AaM@92sNp=4tRON<2Bot4RMW5iqc=utUhD1$Rm zj;$Lvr@tV~`1>YYc)VFsD-BIp(3?KF_foygS7J(OE!;8-tW&@}Aj?>?72sSiJDT4F z=+4OIw9CTfYgnQWHHepwONxw*FzX0t3#QWI%>csyiryv)|D+N*w~{ZF&JF;S9}^%1 zS>u`9?NQrVp}v_JC6^{~QnlONvLtMS^>rb}D9Yt@+Be79q*klGZ;@vUKt<^SXZ8~o zqBbS~H|AG&LE-%r_cbZ-vI(DzC|b&VDdSyT6!1N9BgeJhhH~F?}&qVFt2e% zQY$=_mUhe;x_t+It@g`hNv+){3r`9T;Gl<|0(S8A%6)iE1@z^VKh)cwsU~1@qy%yT zuz=c@{L6~y>7DljO{^3kw^zYnE*X^cI{e3b8uy%T{ z{?}0ic(8gZ%0nuAsROzY4VF}3vYNL-#zEk@PFQ037xDG#sgDpqoqrn6R(+%_NUMBV zXxU`?_JUHeqR||GA9oc%JC7qb`=RExLf0S69+q0kO8wYg=Bhmsdze9GF9rH(^pW~p zK!{lJasKx8A^UF}ARYQC^bu4l)clwRm(>vV;$>5BQqCuplxrCAZENdyXBbhA9(TIO z4j45c?L?y-)4}GM{yQ)l<&KIqwZi9dcu9Mz=W5BXNq}na>gmTp=vtuffb*wt8_8=F zE#oDF2-l=Ll#ucx6PGa0Cm3oPmRVvE{j?`iA0nJl=RysbT^wU;**$HE2ymf`b{VNK zy2s+BD`Fj!-~xl=!2|0I*q%6%;W#(T5I)Oj)6nw@Dg->}R4^jxO0rlAJg$&SwO`^b z1Hcp`v%`TOg<3&0Mm;5Fbg073+q=F*MO%sndo@r#%xVV-U_xi23b9Tsw{Lesn1l2Zz zb1l+xGnBuTsJhtIgV`}SY-9bgPqG^rgb9YW*eCU+{TeQ@Ill#$NIE6I&&HM*#Jh@N zc^1?Od>AYKl`gkmG>eZsZd~WO>z}8CySYbs&(s<= z436o*8Rf-y#jOB?^8jn_bF7^O4+0RUcbfPZs5ts657XFyNReh0h;wkVU>4JB8B>2# zrmcp_KPc6qG`UZf!OXFhDHP$!h_pe=e#^+FgK;>(@3n`RHn2fvs(V1x+3U^$)f!0X z0fX^-$Jor%f*Aptp9=~A>pM&>3s2Nts6LvsyT0T2uNH@YjNVaS0SRX4G>?-4HlzY= zZi`k_K%@H5Q_ooJrd!qE+Oga!Jf=P;9N+5<$|X7z4ix<6tO$VGJ67FG;sFpfVEIPY zc}(b1VIrV^-x~;`05mP6PzE}#ghhdd<}RQqxag|5tG}%zVppcR0PkH;#j)!7o1*wk zTKzIQC#qJ5jSC#j8qX0aTAU<{mAd(mY$|5V=0$v=_PTMOAI$1h_u#jec9dw6=GuuMcimIb`CfORx*Nw zmaeMYIiR+^eZP4Q+sy0VSxZEim{j z9fA2~M30%|d2y`&Li^fJKcYfIs}EX{-g)?wLGV<`UnWx5JybAw70D82agcM}YYGuX zi%KwcL+|8}EJyNagQ3+E02xsLTG;vqjyia;Kij@#v|IH zZRnke5s8_bAMBkhwC$+XWA>?3F}X0R{sW#O{XQ!=)t?0QARCovf=Zg{GDcWV&_)HY z$yrx0G;Z>}pi2_REHJyzj=VFuPl`48e1N+(-g^uR%2W%E6vxfjBRJ@~I+u7$+VvNc zYyNOhxM#pYx~N>X5{G321qn#4wjGdGq99WX7(mPTu}ti~AfF(NOIk86lW3OHO#!fw znnvf@u#K>;X@!d04aItiN>g^GkWMCERh8Bm%@WKb21Hy&RC(VnQ!r zKs3pX0>vCLfAa<})BioFfRbb@0JMWWe`=lL8s%g7BE5}Svjuc3!^1#I|Euv!mC%E% z7hd1Z032FcENw4zlcg^bl52yn0&mKNW6fc6os_ej9!lGibs6*cohCpp8nz7}jLT{q zOfYD@8ITEi8^_^849HyPUzJ&9h(%GnUQ$}tADe)3=lADDG%&;MeUq2pGrbuW@R(WD z0dXCn^X242F_i4cTsnaG{fdHaz!@s+dt0=R(unkr~|6Py=C1}Qvg|K^u2}aOI*q`{F6=oP34?Sbza0sDt9FPjH zz{MWfpS$Wqc^wvZx}i~iG(EJ^5a9V*(B;y5hTxj*FM3|n0IhbwwnEnfQm|B~wh!OS z4xy_7B_mUd*^2vQR9cW07C_~#!R zKK8(0UU<Z%4(iQbKmjb^ne5g3 zwiS3y>b}YrxtRgYTy4Rj#b|J$dJ7WpGS>G5{Lvr3-_FP{_1Yi@ba^p7kM;|+^j_+)M6OwhRvQSrh@U7yixgLA}7IOcEBTk z0I;8lS#|HFj@~OXBc?zKMky{^FmW`V=5|`Bv&9+D9P8%-c-cBoZa#o{1tcKM7=(24qPsW3i7AMt85aq_Y4I2b(#q$^}@8DTh*^xf6#Nu%y~%G;A)KzmXj5 zIaUvZc%Dru=!@fDi%G7Rv4^JC9D!)`wB9Hzm(-UY>)Hyas{QrF?s|FW!PdFMFvx@x zG|S@b0=Fcq*YVqZ@0t&Y0{|6}0w?W;ebs1v-0+q(FPAy{b=65{`qt7u0jgvVbf>|+ z<{P?V)TzVH`eu|Qz2aUq$ScA~)w%sl=%>Q=RL94MGB6U_=J-b+_6HKb_jq);8$c}U z|COrm0Eywpt2B(3FH1j0iz$KVnHFjbBvV<|DG==ok%9gMpscuj2JlQeG4-`U>luq6mE(erd>i)%lo0XeEN5R_A@S)d z%8h5Rfxc#>x)p?RS}9UBXw^OqPq`+^dfS3s_B?yXq{+m-vgcsQmRtnQD`>=#dT4hA zP=<*w*AJkb-lkpep%(XaTECnItmO>3FIwK-c^C4vZ-%R1j=mKZCc{!N>x6G&>2M0m z>9|TG$P^Bqn2`1n!1Nvsn-yfBxh;Ax2`JiXz`oGRh|k<*imlsxVALLkdXBxn0L-p0 z%QJSmWHdQy&Fr6CQQV*K_V4r)T%?Zp_aOw#r1&ESQ7d$D^nsSqt(C#$gq~Lbqp=+r zY*w*}5DTNMfrbDaYbJ;Y3}%#@NtHZRu24qk0~KiouUkh?(4#0>FXX$Q&m?H3gS%zZ z@HtEE3abqH0>?c3o+^!$mYG5yZ)bF+;jlsh#vgA^-!VG6Z94O!vvXYum_*A6G@Mz$ zW+u?J^|hVU0IvtZ{kG7mhLQBB&hh{mm>nP0uJIS|2r10CQ2eZaJ3I8nwY zBJY3%uE8f)CHahimEOKW{F-BrBG_XsM!$QG={uct2?LAA|G~{wto}Xd3(h!7hUL#( z$N%-IEUJtBpa`k;;$FC70H`N&kwVNw1F7G2eh{P zLEdNx(h#>$9WFouWi@PjsNcyyj)R``L6gh1N&KJ`Ks!`Vk35)36StZfu?G1j-3`Eu zk5z4gN9^bUY!SIKifGwI5pBM zyLdh^YSU-b>S>-AP`(@mbaPW$braMj0K?VM+XtafQR^4Bi}25^_E@8R2^u@%K;$pb z<6w1f2UX(LCO}#_D%yj>aiU8{3y3E#b&VCI|E~JQcp^ zSr)1J)~YI~hZVG*@Qhdy@jX3~ySq62M-S3JxuRdj4^f+KTbyWqobNyWq3*-?M?Qk72yKnbPq>Kbla+St~-J$*om$^G@c)JJl*_lTDQYMTP8ZakHknX~=ExvYk{ zyYu+#JSXrCR8pH-D*yj@?(|YMcV|z(`1J(JAkJiQ!)OwSE|^%+P+0CKJ;27U_&B!x zVqlbNyLmBA`;8#sTS%O37kXR_0D{qU1;e9M-00de?Mv|&xA-HET!Y3<3JC2hyY!G2 z-}~vo2cTlyHZ=)Y>|Nh5vBIavdbxfJ8EQf<7ifY$5Q}uC9`P6M)j}X;Jsw>H{ER&} z;Uargf;Nr#w(*O}jFQTrD&l;YwR6Hbf0uTwJTWJ|);42f&BW5AW#vO^14Jb5<7>?t zYvUSE)B{bfchj3@q?*<-MbHDKZ|;q@SrpJ`=hqsNn4to(p`uK20LePNAQE{5AF$ai zv=6lS)c`I<^oSXDv*(qjB3ckNdnZSAzp``2Fkl1vR~h?Q>}OB)y}XW%F*ss#;}ZmC zsAow$J!?#dlU$bjd1a#IKl%T^ehush0yBlAh0J+at46CqM^{Q(=zj4ysOO`W-KuAg zD%FSAs4_K9eDBHWNOhBPB`M1l%ss;t*3%sus{L2&W*d6%w1MUVt6l&OyB8seb}H4? z`?clKmCUnC2iOeklW1{SUl^Akyt=z1kkvUvitD>M8^=InWxXdMOaWzIjvz`c)>>34 ziNw-3;T7u0`>ZKtWGsmz=QdD0HAv)ibnL&t00lC4xs;q1DH+>edRY1c2C;yVNXwt+ zFL(hnOsccM`gA|vg{N*5EBCu`Lg?Bk&04iBY&A2h%@n41A*`(&esd|;4+5o zWqwTzSkk4OI~uUuc}{BgO{`F7adsGgLd=3~|FgN3m1;NKaR@|UGG-)%bhVH4r3Sa6 zT;wn5_UWe@lz-IpEuKHByuT?W|J#6IQ>Sv2<37-R4uuL?hi$Ln z;f<_pv6Kf*0Ntx_N+c^A-=A$d(kBdQ-IB6(mL7eSZB$rOoGzB(I5MaJw2Be3pQpW$ zsry<=2wHQU?u##=sGRXwC3|nB7qK}vP1yskA}ub6gYP@}0Z9A+OtV(<)N1Bl>h8J( z6W&b!VRiwOTwIc`in;swMA@Zef1T_Q38wl{8;VyHaAospnNhZ2JWT(oRCG#Q;cw#0 z4@4Pu97|U+dvqZ^gDGKWZmcFe0C|c_b1;I8btt`y1SGLt(d9zlq3WQhL%1i%mT^sV z%bqIeW!6(8cH^N&W!rJT<&GfOS zVCEC;Zf*_m4Yn6h{SO)bX+pqXL74zBx9}&;2=W02ML#aDltdup0VvQ31sRgk1B1!H zXwMU!Jy8e~u|(SN^ZZ|%Pcb}F?YFg^Ra=e*riEpzBoBDo%C-VJ%bB?W5Z4+Eq9!$O z1F9s};ub^IL>>V?e18g%uCwLtHu_9>H&Wk+!p+m!cW8{JscG5_EIZ zBSX~5fyZx`1;fn+MKc_&40Sc9KiJJF6ma#L9oKdhkL1Cfzh3$gJ_?|AP`=Yc?34k6 z1M)xgoRf-xx3bB3Mh03t<|xNGMk*YO4@7}~Hc%Q`6x>SDIf!&*C(ttb=7 zybDOMTyw!H7JT#w`wTNs2M&Jdy+S?sEi>Bhm>89Q|M^qj~DURQ1hD`PVOrw9nqiDE$HvGk%amN zRA_|i3!u`X8zHF!wGif$ELr)Q6zmuRwV%PyajB$laE99{eVbKGMJFm(U7sv9>Kt>YfnB#Q ziO^tI<>+C4lR4-GK9MjL=0g_K zxi*L!CIl(<6!-C1R4b%rj(Vy~y6~h#8V^gq1e}v1fc&f*sS|hpum@G;@YiSZcmL=A z-IQ9E;(dT&@6TTjxP)N@LO7U%mDOy;k|NsG$dHYZfd(W52r{rop@+V?Wy5mM%*WSH zIf3#IFv3r?^sVfujRW^eA3w4HQbEx$$9gC0foFa|(=S2aV8-TguT|}rqIKcznil}@ zbvrAaf~_|rZ>%VkTSyKEmNUP+0qS^l@IG!{l5^1S>oR}y9%F3m8*6@d?&#RML{R}= zM%3iJK3n81WwbP-he*I?3l!+z3VTR_GHe{adeFBWA z)V1uo{e_>o zr0FeCaYN2{@V91Qf1d_Y7~xz+K*9a_c>iYU${z;W^H$KXyCnC-XVfJvdujJi*kRY2 zVv)doY){kk`W_l6Jv#2G=1eknd;3Uu^V&vK4+ zZQShOJSokItIW7~n!U&$s@K&VVqr4s`EKBCZzt%vLT}7jgg<)Emixw7&MJlHV9mCL zpAno>@WrC`9E->Li&NK!ws{_|nXUU7@Mx@R!1fzQVfq4~&~ z&4?q#)M_?NO`6B@>?+BoFXjYjSQ5~$1Ifdpni#`f$2R+5(9eCr#hw#psCe=s+CrGX zjS+u0Jz|<`ohEh<+f#XC{n&wX$R_Ov65dAWV#!tGtL;y~dx(e+?9|Df`wzbBpHPw?F@vg`{)DEj8Fe-sU%V#yhaLa< zN2{+->1(3pT0 z9l&GZ0)hnn5_h|40-rwz1Xiv?E0qc%a=fpgpG#Y%AP?XPhdXKalc>#xgJq_MVQgSVEW1Ac%JPpN7#ee zC!kz)D>2<8OQe;j{hELCio%X0U%@1Te~2-l`ax4A{+Nlv{+19;(X8hqs`u}3_M*n@h0#_@nG(U0O%Qd~icKmS<>IN1*t>AESzUQP7j(z{VW9yQAi+GCY7 z^)rB53QKHJ6B8k?QGMcnfLedK+5a8iC2pNZIqs_z3({+!A5!FFXLzPlf6GM@0>H#S z`?Wv+ufK#8#<)U(Q*eEtVYF@-G3C7qh%2qvcl{nTHH(*W8C?+r{XhG=ds}Bh&}(K4 z`S7lu-U_>|8rWQHFKT7~v&RQ*(96+6i^%2WxxU{#wt^kDb$8m(5o{2#XB4kGN2Dv{ z*-H2sl8Jo_hk9~8s9^Eu@8VPgt`diRL^%V##N!{)!79#Uxq8;2^k?VH=y>@S^j<~-r5@U$StHPi6WyZm+^ z?^yP(4%BsEX3)seP{m@D%sOC^o#Rr9k$~rc35mK65m`Oiv|3`VzYP$ z_Wpixf~>=w9VNKufR88R*lp|IdqS*F@bf8O)jAYGhr1{GE6Z>&Vm49yENYfoM9=QK zd|&42A>34ngzLpc)e7U8>myl(*aeOOiIrCmUP}?)zm(y_8j*nLgqno_*p(_jQJhg? z2C)Rp$CuvN`0>H11CPJ|>R>eAC#OF`kj=J=q&nb){@ikvzocjK<2b+s;D23_U&imB z{&72^{7?KV)9yGf9w`s`oNx0v8^fg)3L#7mEY7<#&_shV0(LtMig!HoOZakI8utBq z8MJa-hB*Uz@_j^71kwad2)$oYnsP5XE^1?)Z6RJC3<~?+)~b0P=qaK>Zuf7SfE#`p zjPl;I!X#6`Wgz_gm)ZET!*XcwBt{Rn4r;^YdGWrle|$RXkm$Y1r=P&je4`FLr5c~g zD1rvJb*@mL<6&c@`}ZIEo%;+xmd8eeV^%UD7;}l!86(#A;i%tp!=`lzDaXumT4ja~ zXQ#$yAmJiUG(gRKlC$mdlv_WGBJcg%u-nQ})C}cIXZI=o&NpfA1570Jl-PfS_Li^U z4Hl19v(31aJWZ~xr*pKnit>*Ezs0K(I+dl6Db)vvCW6e7-t#bsFrG@MeeV7cuRM|UOi<7OyP{m(WHc&o?TgJfD~-^ZHXJ=e|_9NX9&H784lU2 z&YvCeJ_J7MGZ}QWQmw8a`#|0U#@U3E{PuSjCz0~zUh3V~B$qauWKvIt+AfaTOeNZQ z*uoZ`gf!KcJ$^ElcO~s3c1}x47TreXZ7t2Biur!nLa5hz3qBc8Kq_sZq;peuVMiVmDdL=Bo0&+6V-j!55d6(NvJ~mPTZsmU)I`{Pl zsmRJR9xah02`@Z`KGWdd>E_-c?AgT7G~9n12$JuiX?uGYGlZL8T0MCzWW`QBm_=X# z`<25Q{mJ1xTk2mQZ$HB$cWz-xZ|Rm^my~GMz3iwIac$qK@<5Ci=pfek%%`UbUMRiT z&ZOn=3uqB1`t}$R!FDwo<^-FxX%d;B!1BGX1bCXg(DdDd{3-eu-fGb~wvW=HhhX5Jnu?ObXYYgDuFFifP{;rlcA@g{iL z&#~ATL_m3mnm}ixnIIhutqC*a?)c^z2}U&~9~_#TD&y6_NHWsBu^&~cZhCG?Khxew zp5w*1KEJ*7**JA8ZvgW(Cn0p;L%dt0_N#08X^Q)T;WgUp3S?}^@3zE)yMz2oJbJBAN|KRMs^xyO3t zh51PPqz(63GVi9<1{-B^_kFxfMorzf?v5F4)Ciq4s?oAfZ*9Tf*if`q_nO1VZ*-OI z36?=Jy5yY_1zDR*F;Cv{`L9PPmd*aJn`F32W_Fd`W0UhrCrb`3&GBY;&G#&#H-ma? zx)K@}&PiF;>R%{sFhbXRr{XsUKP`1Jd2Dman~ok-%3LLW=7r3Y6Y@4s@iHrV6NTR3I=zN zn=UUT>%7BngrCQ=pSb-P##$`!pT1hX_t>%XY)T1}^($`}QR-`twel+kGu>r5|X3%yYgT<8)v7z#UW zzxJ8;Sf+x&Hw$t70g;8X%L4uJ{S=bw8cWiH-7n{=7;p1ti@6At@yrU9Y!@et27G$z zKGN(9F7wb*Y?}KN7~<1ro?M=Y%Z^Qy-Tf?-oH4FSw~CLS9F~sX5n1{bi?VF2gvBu>?OO{*`A}veb)z`-`Tr3wKs*!!-fOP~!z2cgEErdK zzKmh5<5Bd8EeTCps;V++UoVSNnRE@cBEZwOwiVLr$)nWw?<8y z=_GTqx21!qhoytqLft#PT;*|di(O}X-)Wh2#OqFtHmNEvR5F=-PM0vC6X>Q^ytQB} z>%K`F-hPtxe6oMghpXgmw>cz1A!o4i9J-!m9GR`)vtBjTR4a<2cU=$;s(TUsWVj$_#fe}Rv(O8>_Idcy@X5#00zO|n zRcbs8s0wguJo+PKP~B01>|g|SNRFZrck5M+CR^4M^iTlz#T;?J66YT}ugN>K#57{t zpL^QZxygQMs$690{fdaB%SPVP*G3sA^|37^uGmPwiMHzGq{B+LE$l3{JR(H1HD2tO z+m(bt3YuZbZZ~7+duVFyan1y%a|=qtIs&72??)+=JW%l8jX%Aqi^}ncO)_m5zs$Yi zrZKcUj>Sy$>rKB8WSX9`z`Ad~2yaWzlTMJ@m}&>l(3?fB?HWeOmnm%hc4EbDuO3}T z#+ZMI>nYPv1#U!CTSnh>q^@pU;21~SDbKmKSY1~E?|?b+*WngQeXO|a+&O`1EFIpP0x^~6#~UVFeJT^BB+ zCucm}8%<;TZ$9gw<{GtvYi$`{%d{Q`ef`)yg5xQ6J+~exqq}nB^?)^l;)0Bro#xhN{f5I+Jp3<4BQPEj{;;T*W2=yl_aL>=dGHt+>87y_R zc%vmM>-%-Av1aBLyGLuc?Lya}nwKu4dszUMWWTiLu{||isyG!SDr;6OTUE40ZX9m6 zEO8YkGCQ2EXYNk20kYw-M&I!>O%2~v|CGgng7@tAJe=y6>0pLu*3EpmHXR%mCh)G7 zc^9qVpx?CQH-6J{yX2{ov82yu{rzWJH+fjNg95GvE9@-QWmWAisXtAeX_6Ue-xdox z-I!XfP4;Q{*bTh5rElLsX~N;&EJ(3aC@oT`f%kN}sr4ps;7}4@qo0dE z*-G6r_L%=nl-$l|F3LEq?9yk_&{BsM2dqTE*F7*(U@6uVGhNIx)h-c!At^y#IzHS5 zkCte}qx&{&1(j4PMPP%WvUXWzg{~G?u;hsn+|FC?sQO6NN~Xi^A#E`#7~kegvN_AA zB)`5?pnQ9vXFE9RF;y?<*Iphj*O9_g89&nKn_~7ZJ9WhwE!cXYyEdcX5O|P#DGBv3 zj~<~u&F|icnk!VW;YI^7yu{iOyl5Q5ubEI`ottTgrDWP|^cbcyB03Ons{_^(Y^Tdy zr@l2;kc}h*t4&M}I#tL9$41nZ=}#>r46p_X6^2xx;93{7KIN$`eflC6>Iv};nzn$~ z`qlaKs>lm>qobk=`Rj&bekl%=*R6zjhya!hukU zQ&U;%G{fk&bdR2my(W^V+)#5hP-^iQ)~Gu}eh0gPiJxTU@#woBkjaHUJjaS(eyG2^ z`=-$_eQh}8q^U(vP#*tM%VzCV4{mPSy9Am*NY?uLVcawWzI5Yj9j#RIZHt#)T0J2T z3K|%9s-P)VTOB7Nja-u+6g>Lv)X;;`rl>C*BvbqC7v=>}ih8E{W{|&~6m}-%$|L2} z)e4*CsRpOzsf-2bgpmf{l+m3E$WmHg8m=>s>$KoHF1{?r^{newX@Yj(R~Yv5Gxbey z<`j3>0$0g3jLl=|`a+{g0e8{^XSGLk9Xp*@Y}dCuo>@)2aKD+Rz&=n^bbI`3?MMIq z&?Z8r2*xvRJ|L+2b|x|=s+^CdM%PA!F5gX?rv%LjOH%HH@iBYS_*zo ztN$aYeD5)8=Y!Yt%BsXLFR7hFMWpmxDusGP&{0kPZ)IDAtyRn|z4!pkr^qErz|K;43}YoO6^rob8WvFlkkwH7 za@TJ--&gvUZ}K)h+ckMpw*u~Dn2ig& zULn}|dWb6I&SE&lTu)b7xII+3qvM2KkQbS5(Oa6z6ONs zq0UtG1)cxQPX6Q|zI)qqfk~&~lzTP?;RwZ$+lMgHi|0!9VTN*c(4oOIZLEjCJr;M; zTP!gRm<&BBZ^zm;fFjloK@dR)yaG3uu3~*JH;iuJTP42?y~l22F<8>j8RbT|(TTb= zfhi3%$a08^7i{&$pB%t3%nY>cv%;PrxguURT9I{)yByz_X-!qoaw1k|PGW9Ou1yq< z8kc+b=VleD7}7Z!kv}l~x{O3z{nQ_;YbvPQJ)HRMAYs6f;$Jns`WjONJd7(-CAJzN ziXFv8<#X7t&VGCQRq5n(Yp4mSc74Tesk0&a*_AZt?E4FI-4TsE!}7#OP4$>*f8sO_ z-{={C*qiHA-dbnU$HkDR$g;GPgv!}ZG^W!xTbqDK2b_wHGLuN@UH{BF(hdMUG|JelMNeH?ACUob_2LPlqr zyg=s&C}K!O?%*stLQM^}_CAWqJS0HEWkx-9<&$V?jgRg`g3e&K zXk1K_M?3NDLG&3V+qJ2c=%uQl5_H6ZOH!5cu*mMt*AB_ltsYUzoqj`#!>SRy&aOZ~ z+H)1n*(ze;)^~raO%mkXCt`Jm%5Po^=I>fwpyRKt%-W7-T$6EWL5m4+b~*NZ>9*Yw zter~F*+iw*-X_pz-u^HS61yzEkoRKV^PI<)kzZFU(m*)SEL=sUYOQ&)4Af4Vi$Uiu zx?c`#wMxsoI>7yGe`=z_43lHKVr4iyzQX4~T=B>9iID2;x%esMCf%`v?#O=6{`vY|Sl7z(sl`@7!w9O5CnsSb`tG{hBGKwRNjy zaGW6OaYWUQJ{IenXO&qvT5WFVDkRi3+&N;qwIc04S>4>+m@A$i#nx!dorZbg!`w7# zA;~*bA~Z~BvkL0QZ$MLmritYOZS=z4th=|a5(iBhHitKVvy@#!Mo@Mrs};@Wh^9&& zfxu~7th0qsYKJoj6e-k#inaSopBqs(tZHa`F4D9eyPXCjRu1*%n6HhIO!{5lJ=HOE zIgGN+e8*SYe^PA(i$6Wj<^ZfY|Bs+ha$=UAu z<=F*$*0!OhNn6Q0E5eZQ1s7$1a78Byhw8THWyW_Wav~NWxNq#H2t22-?fjIqBy!O% zH9m;9q>CVQjIjMWu}XPM-%fN0+TCR$$g)Kc!tPF=;B*6tSMbwE@<3EoDwhDVtmzh!Y2bD?ygDw5-)_HPd(Iz!8`FL!@Q2yKh) zx_ySPar|=Fx>H>da;{M1VrvpvrMPQtmZy0&{`ZK1)y%ax1YvY4HJ#4i0*njlL;Q0%Peu?5B7Hn@?` z%Wo$M7MV`d;n4AfZnPj7k51U^Po{&fK2z~R;&4LDmKy&RuV0+z7w(gp;WcDu^WYW( z)1>C}z*r8p?+gxTyBqb0%2&q>(e^5}*_RzrZRsB7kJW~!wHXXh4tAdeu{VBD`NNH^ zbLB*$OPq9CB!Yy_`q(%USq3Vj!2a^qRWuD;>GP~U6N)Xh%n!?*{PZ<|xOu)EM4j`Sr;cTN=s zGv$nY8tSb_u04EXYAe0)PN2XoAJjNQcYTf?mXv{cskNS?oQKcx%ARocHtVn(WVt=0 zT{9&ux}$-Y&8s?kjzd7L;2H|6dpFsenlc3{53 z)_UuTNTC4`RSRENQ;Cg(l#JGZFc2DkIYx9witab)#*d$6FhZW=Dc*1=f-00yc1y44 z;kwe0)J;%fY1FOlteuOP)K8RQ4HVLYILF)DooMg`K8O!of6FHz$M@Qu)RPIKOqF*o zy_TPw&cV(hUgM*ez9ouCE{=6dDuRo+-IHg#wz^bTY3P89R-;-?_)tc_-vJ|P-xp&cNgWZ@6Dodv+Cvkw-oxIp!iC4UF|W2 zMVzDQuZ<|TFAouFdY4cd7e&@rCPL5Vt3u=T{qf35He8|wJu5j~ZW1VbGSsf*>OLDr2 zZ@Qmw@?3J}sHUi5PHiD&ia8#ctyY;XwnX8D?^tkBtgiA==1Lyp+89jR46cmeKzi5? zN5)jkUsMg{#hUq2>o7AxE1G}RVBbj#8Yrk6+`5Ai5dK<){>q?0C$+g!y@9~B&xsY1 zlx(raFQGHxSj4pI-UF$ie$Ts$&spIwhRG_$5Q0k(!*ASb>S8JP}E^0)Q6Qy6QwfcDIU;5LPo#mW|V zYk3}*W5V6ccWH3#v7ic(;g(ejLLT+?9bfC!jT^f@)DY?{(8W}#vr-uS?Ms5&?!+}a zRoNQuabEehQQqDHkC>HNl!&6d{qD+FL-BJR_h!fA2Q;=#%dNP& z!>u?p{0)K_@(Dvq?+bZy+Q|H!ItTH662k@&S7hlblDB#rbLQgPN4!<;(^TZeE&bk) z$sPM~u=6Qa!4i7ODF;k!U*Ts3g+p(hdKD!T0Y(EGi-Sei88kRh8;xu7ko`nHkY{;A zJ4kt+ajv>1)yuQ{yECpRB7vg6L+Q%%Z|xlQnf!o~O?;Lq zh8jD6YWE#4D`5$bS{PrR=w9{V$}ST~O%J$yNBEHc;>T=ysZ~m)j$|OupnN{l(vSl!>cOP;Ybh zlqbVwH)fR|s(Y;dw-tU=a4)UCZnP>IZN}a9Jv?Lf?3DrWAc}T~0$iu!iO!A10^clk8Gxq+Ext zC|PKbaRF+SIj$La*TS2!NUL#zShF095?o;8Ez9J95^7(jm$w&)>2MBEwH|-C?LT1- zVEz*Ox|xU?$QysnWH!~`IcE8P7<RdbN>0YO2ufLL~}FZEp^{8WlMkh01GZ9m+dXNR;za~Ug<)W zhhlr{!)lcTAcVC)xLB;^(0#SKDFrhtMKF1gFZqMxDd%u$4!*g=_&&&4-S=u}=zETY z4O~B%uG4u?Wc<3?SUr_c8k$wUm~ywiIhuWMx%5azoW~4|RE4K3AqBBWix#+OL0&-b z%ZFmAyd1CVZPmiTi9g#v;40JkznqSJ^>ed-+%+}KiUNdma-B%4urH^m?!|jqc*Xup zLyEWLT7!7GM4TPq_LR1f#Ej5C0*%KDWCKq^9{;9^K>zx`OT|FnIZ{c@G22F!ic5U_ z9Nm9RG5ZlV`tQ(1N=*MrmSFG>(%dN^sG9$}34mZZ!uU)Dp~10=1*)U@(pSnx!to}D zaUEnFLQhU3#Y4braxaI+9}AN&Pg_%`t(YwuEL7MVryaem=9`#BaoWTjzD5H+@3>>K zo^l#zwea$ascEGoa5F7YQ*Q1x2=R_kpMIT>RX}*6;zhM-uW$wE=%oF5 zfhHR$mIl(oG0GKo>5dTU*=^n{S7)z_lE;?#*%i6-x?^&Fld@*!bH5Kjy_5}ME?bn= z$vl&DFXf!X`uTK*bRfBxGrGOSaVx2u_w9UrKcC>uJ=c`rNV$6i^TB*g%!Sp2h~ln+ z26J;(%&+H;G4|)omd*Gvgw6OijD$uNn`n0qAF6ZObkIrbn36up!sm=zxeNjKUhK2k zAHz_q=_zXd;~2VUR@HJLevLcV0p$NNoPl=LCKq1W&w(D1F(T>c6ij-@793 zsoA6+QbrUo&U=X(k^v?uV3Frh`d}no$Qm%c5h~Xzk)$u&Q$Xi zVlRC$NcjBw?=*l*-V6oulSW}~-_O=Kd--kP0}#^?3HFeStOoT_ZPmH6&KiuBpofO~ zJ>t`dlx*s9A>SXzX~s&0MXAi*JS8S_KTS1shX_Bvt3n3&P>t=j#=Rcy<{!vo3-+NB zK%6WqqdGRfA!^|rR!HnU;rv}}jb$zRGal)lA6PK$ms%_NUI~j%CMu05!S3vTGf z*cndWL)Z4$N8p&wnEA^kGMonPi=_^CBOBRoN2L0H0b*y^bvQwg!c!c4?caV~9Q@HG zjp;s2F)sfcEB@8b?LK}bGG-3)ea6&;<<}37#_W50j$8FJTnTFkj=)B-|0*^87Xd^D z_#hh=8tjQdx_x6}x{Qp_N+V^J_nsoP&Sq-%eIOD?f@jm^hVugWiEhE9e_inX`C2jc zZG&B^{GDC3d{V7`Mr!rx+^VJ?11y_d`YLt~>GPlMj&vkr|6Am99X9u|W3ToqSLCs1 zt8XxSJ{oT?qeg3Or2B)reZQRY_9vX@mJhQdt@Xlp4krLku_%?wP)8{+Fg84IZ6URh z)Hke#6qRs;hS_|M>H-}Tj`{g!dt*eQvN4RO4CE^YRdNYTu~Cb{SsZO z>6BA?ljZLT%_6ntTUO#UD-_$P|NTQzvEYEdCZc<3!&V2$w3(8Tg(c8Pl?=d>fc# zu7QpKHs`-4kt{k=YONt{#aOxcUNF`VvEVL%*z0sdDN;Ybb3_x0tbS9X;!!2`@3T48BK%5gAI&P%g+mV=LF&keM&#w7yxUw{(r(79gsd z&fff`cb8Kqlvx|l=@J~m%RXbJcv?+1y%LsOgnAc?X_yspFBUqtw&QNp{9h%KH|HRp zd)e==3$h^JGb!2)5(;ZJdf2x&b?MgOcj~QAScJ_SM$=fj9(%X~?2={yizygvt2uMH z(UDGvG*|C7qzIUESnh9mPR1(A5}NgO>D!)X=-BTrc6B|Z zOW@GiyN{z_l+3%sJJ8~KJjn0J-P{rDx~+_{w6Xlau>B%F4R&7 z-dWkxWBMF~NlLF?stKp3r>6+v!$wEx|we|J+E^(4`Bli{QZR%Mg;r2*};t)iQkWlSTPaHCIU_IR~adXs2`y_j~ zv2h}{v~siJ`m)74Ygv2{Klo(9o7nBly+_Pl-nphFJ^WtGqMWEcw%Ee>QIo#(SN`>K zH1~0hfIYXEv#bqU4i3!^%A+#=)DDwx*1w_$P2$pjFcwD8g(@3BmZJ4jC8ESPK)HMN zsz&&PZv*S(Ht<4asUWgYtze_d18uZ%*kz5bJ(_6nKX*C%K1{~qdCd0f*OyhA;iaXF zNy*9fJA(>whD)mXet&voprXUm`u^!^`_E0U>iGNd6RA1Ucl%PB7wmKT{**?3TUiP|&O`Lay1?G%*8=m55HNbv^m*_kAg(Oao0W z^1QgUPZq??!tXk?9E8{27FF)kM|k-CI=|;B`$d|4r|&@Q>3?3yLa71WzK9iE_GSz7 zX@-!9-seFWf6@LdtbAsH(MsPR(hENQ=<#95=A+}4`o%~i^^1o+J0*4G<$L@pw!N)C zuVC8=_KPmO_<6Rn@V+wa>Lm1$0fA)vE{kn$r#OuIAMnFk$|~J#bQrY+D5_0|KB}nb zq151K7vz2{qWonKDfMvVV1eh;PR>@t#cim!2vMT*^sO=$l5`{`)zs4ITwW$GBRgE< z{EcXU!QHiZG6d6P8f!^svr@P)%*(Zp8xDjYpakL^SSJ7f9qWG=;h&)Y_fbvv4Vq*| zI0Dk@d{KG^vHfVNyrU;~F|soh4qDjq_&P47_J&F#+0_hVl`yv9i~=@Yve{3_AEY_M zdK}ifDdT=tO1#M{1&Q(fiI61ccO(o-G+!ue4XP2^lcoC@Kk^MB%ax01BCstf7XO@6 zeG`K^RSU6t-8Q1f&5k^ETutysW9Udu(kH>R|M7?|usatw`_9w}pMX5F_fULn3jBsF zpWSXFBXutg6jm6*8Jcyseq{k7URx{R&+2L;AYtJZ?`>wGB&_zzt-2NCOJtIgrUar> zR=hjR+vWOXg#rilQ9g?5B8YXEjE}Eh`SWKH6&3uX)FKfHtr+i6_1DWhGTVhE2NK_N z%dy_nPK^%L@tS(Ky;xC54$Nq|%aW?lC*tx%48kBov$nR@YWv|{Rgdsb)c*@Q08sho zG5GIWy3Bb!Dg75>4wo1_5G3vb3OdTqjJN7 z-DO33197f>RX&Y|Exj#KhYAm0Uv>yT^VfMEWZ`Rg*K3vtn)DvRcui+^8Yxe9UMbJ3 zgi3A37Or3VjGdPALpU#c)iN<7N`xHQp;^&AtAq*7({z;n6vl~(18gA*Zoj}EMVz|R z7+%TI5S|bkk6fxA{z~|)uq$~GT~|Ljcyd19GM|HTCJU89jQBRQEs0|9&^uL{#QRx> zwk-8^4~Uq71pMwFh64M!T9}-p@$d5jd+`sfJ?s7Qmf2P;oM#yNT^x8kf&b<{ zZlFUp-c*~$B%V_DUC}~)nAmmG`c+*&E=x3(%_QpB_?^R7;~T_Km;1@E5C=-0ur9S~ zMk6Cr89f3bps_BY@_X2Ql+K(qY;E0Jk+p(w#KN5M>ET?oYBT{Y_n8hR{YKoJF(80! zMy0mXvge}&-rj-vlDFmO@?Ta~n@{H0o684L{bs2=cquYHpH=x2j>mcGM+aHxNAu2K zOWZ6#O6vwM!}LV11s1x1B>$jN^r&HyJyYn;+%e#dBT@y+>(DGfbGq%t%U6fKab zEQwP3-gj)LIX(7vP3zCAf$apY(ue_J#(H|5I{J`0srC2y_Mae*vafM;zOPi|cd~q_ z4)02fPGM+OF#F7sp*60_HOXJ0VgQNJ#4u;{HcXDiiq%9xMNI$W79v>&;wRUNmg!*< zc%2q6wehwJd4^O8-H)%WqU6;;fxd@kWY~3Ajl#I3i<+3}C!7$@$kC_FXyCog#;L=n z5mh?QpBv1l-cz~gjesHH_>G`s6&Kz-wi^nD zwxw`hoKy7tzS9D(#>^8XFw0>|6)KENGoue{z0Y2d-N&h&KTm`B%eYLeUg1=&#M%CE z$n<%p4H@<*-vPL5{+o|VD?88_Y*R3B zgKR9KN2`Uo`z#+GEH7G4pdeD#FE2>e)hL=uU6Mg18U$}59qCjVd1<>YgSQsFb?}<5 zr#EUG^0x4)m~-O>DBpg>4ewZ|F-4>=ZaBwA{oU9cxFrNfybgp!CuSe10{qbA=OQ}T z-RrK)Q`FZXog3)%Dh^?Pcxr2GC|KX~X4_8<70pt41RvEQuPUck=4p6~ zA8MC9Dk6!Io7c5}q#q`4gEvkO^1&bz`=f2p&X3dyH`M;;Xb`_ZFSn$pfUasrno2;@ z1r9AtGe!din$&*n;sc)~>a8F1*b8X*PZ&5$g=3%=?&ta-o zybSfof@TSl2b*s!D_{nk)Jpc9Fje}55Y&yRwHGU0Ryoz6Hq94zDyzXUA(7b8ZajD; zlPNDFy>>;GZOY=SrYk}% zvY3;$yNQJjwh@PHdm_tc*oCHceQ1B0qbpkD7VOF}O8_X^4YWb-BX8# zACK$i%F&O-13~P{C9+H>_8uwnAfn?Of5TxuVscYIY|vH4u_QbvJ05-eUaNlwBAiDe z02=?rS^pP#{jVSX!4eO_OrHpR;|H%k@YK!>8z)h z=C3_sm$`Zl>@T#V${ATT$Jh4r>8TnQC~}BhW5*Z$gp&6)KDY##J#0#%89JJ zp`ScR4b7|zNciwpfkeCvq(eaU@xU^we@D+2EXJQp-cxWyx}K;xC?S>ET(*sQijmLcI~BgolzPKE>HXXmbr5o&`@!nD;GiY z*8uGvbM!RM@o9r3yT^CAUmDIGZCcsujGM~&^;<@R;rBSI@^PwKLFZRt?Y_U?EXovb zk1zn3`$3a&Q1#dDvT^#G50y@(Y^|i=7YDTTcGg9HE8B%ey#k!28F^vODwn4p#>mu^ z_}fc@-@kEoUd<_ZOEo$ZrC$I#_(K2vIcFPX#rkDMWRD#V%9s3nG%pjR2d-w!KXydh zUM3@6?TYD(c<)Xy;v(oWPdzNUX3vK?@mzvH6%3(oUuN+bQ5wDu?Lej+?}cjz_M$pT zp$8mU!98y^&9*2bky|$k&Hy7K;OO-9tkREBVE$Cnf-A(L3)a{V9_Rswl_kCpp3Y%3 zjY^o71x4A$xQZmEO{A!U(xmeNZesZz!oPe-8U9Vo|0`Vn`^Z1UL^6z#cv^Ea?kfur zKh!5X@BV)H5f^fZuGq2205@cjvO}TsXTn!%Xn#tJ2LFBS$C(jj>fQp&iU||K3IWEg zyhznUW%|Mo5Lr5(J>F4_vWa@f2CJ&gG^39wC7&uP?hvOB68no^@6{HKTUCa92%2YO z;=u_9z{7H*&_rGQJSK%{7Ay@;6r(2rzfHt+$ zCx)|?*y=|NnbTO0JpP?5+U$n`g8b`mtBDlWUlfKF?mIAC;mPb-tXS8{AHy#K%sKr^ zv(SsHo3rU#Uedw3uo3=zC?$0e=X={fgG}YYWWW0r{qUmhH`KO=oKrXUGhsoYSL8UaU6ze34bJY+qIy{>Y^Zhei zTxlc%RoU|_IuQLx2i^g~%-Pr?%YUXlLP?Bt6PA3U#RDquXawFpMk{%FtR;Kpsosc|FT;C>h~m&2fmEDfd!{}0&D>*7VION zG;$!v?kCa0SCUT+AX-%boKteoH|M4?%daPRiqQ~cCLaNe?cSpHG&G~GjrEE0%ZBC8dLgRcc z&c@Wv6@rYN6^SZUmkyzB*FzUDK-n6zEaI*dcxC=zY)~@J_9(c09VrPWQyj*oP#nUi zp!Sp`k~33Rf}|I7o0{zDj<%NV%RmRljhkLAycDgP(zhHDlF@Z%hZEwiwin2GU69*F zNF6}L5Mgkd4`HN40Apna^~gm+&V>-)x@WNI?L#V)Bc1o`4UzS1V(4Jh`KPbS+ce%V zoV0*ajSP$ZT8~MlENM|$w-Bs9*cOY0I8>@nDXV-kE6XcX1{>x5u=FY1Xvee`KtBjs z*FpFcGlmp@OZG{t>BSU;Ea_1qs<>`m8%G0S$rgz0w`xd0WCOw`5_#Utmil*lgti+| zo-f;De71P2F~M_|UvKt4im=6?iK_y|SJl`@lRST4#k!6TpkBXLY5&) z28@%iT}nX#jL9zl7Yd7wKT!*&f%l6@MB6)9hntrLZV6&m8s-qVFe|s``D)vZ&2)yZ znf{oy4n`+UHJX9vj1&x;taN}P^Ch9KDz4`ZD9I*fmD;gQmMLqfL!{d-%(=99vnf1g z{{Lo4FGx5`{f$S+sY#N6k^J-%xZ<2YN(TC@3DIlNfVXYa96XJ9Pnpyo4Gz|yUe5R) ze(PalI{Mfg{VA0!K{>W+lQhL&kbA;@OB6LxRDuRG@JflJ3`Hy3el!7lXEO+-B_3FF z&1-#a>V$-aXfob1jCS?%Yl=*TUhZjH>tkB1qN;aHl0AqT_dTys-d5-UG*i&_ZiX#n zTaIQfBrzg0+%Un3G4a44)$1Lt(R8-i8&n1n@X!r+|EVXBjLVugn@g|w zF5{Ma5BS02-tQv}kx@A2d-BWNs%z795P$Y>GzLsLC@R5EdYP7mqOU`#Sz#&8ZZ?Jq%J@^r8y+N_*SljrFyxn8h(s1pg(BQ{QGbmx{^u%iNSdB!%z{ zzSPsqWb&`e=iPu16+<&%7+LuYPy|HfIKtT@m{z3Ilg z5LK}P+Wv%OdSg;d@>0*pe%TH4qGnwDm7L&3-IT=0c9R|Zq^J&p-t59`mP8nVvW7;% ziU5vpaeo){&$}TQ&k@QMqTth;q(X*W>_b!+f!!(^K(HGp|HmNz7xDfFeg18k|C2HS z9e21A4L<9uCX{BOg@NVRUEll2CBOiNB`gn_iUq@sZSfE=cf?eNk8kw`GVI*-ej7QD zss*&y!)Bkh_QynJ0pj?u-5LkLoAn`Kjw=P6D1FCcn@`N~Dgb!-J?Qp!-*-C|wc!Ww zUMAXacJr#~%Sg^YJP4RyN9iEVx;>$CrTKgosa%b4MG7$vR1)b4?Ob!GGnWU0uqX_H~+(jwDt`;0tpi2qEV) znI3+RzUEnMy3`x-J$9jwA!+!;5c#s?kWfi<^!O1JO5L|Rh=@$zmZp?vhPy@TE1ToV znXSwse|Rw|r)LIIzoR(v_i#ThiWos%VoY`zHkKMDEzu5@kJn44KP{*EH@_ zn|}0uz`X2LZvN8LWsDSU+1+iM?>pHDe*|*qauASEc#4q1OhZQ}mA@zagmCwx^*b?C=uqc{HhsDxy;Uh{i*Zg|y|C7IQ=db{h}4`Wp!tjQLeU0)#~A=x=PZnQoeA!^ecOZs)VaK^o+1;Qg1 zg(g7ExE5$efkRZgiuCfKNiG$ZJekEWeH8nkgW$Jz*puUA8(;w{fFDE+cs$Bwe(cq2zSuh$DqO>x;Swt`}Sl7?kAx4dI8}a7{WWb7WPi7>Qc}Mw%_T3q@*=i93301J{ z(lL^$w!9VEh!i@}j3Du-tXB6Te(N9__ZD@W8B=86LfLZYn>&)VeOms~+dAS8YIvhn5idG8T3-9Ka@eFzc2IaseHr_RI!_X+ ztfCeWltOK)pxjweJ&El${vxVV;m^B|ucA7g?7$J&>QS~qLn;?l6FT@RK^7e{28893 z@HHHq-Dr|Xa)5QwsxTHsSg1}hi42!psF z5Akk}f5;2H!9uHvYp%K~j!Gz$z1n>5%a@;J+t$TJ7>;@-v!o22k_M=>`)F~}{}{^u zsyhB3pbEYI2x+-^RqoTv{fJ-`fg5)D1lCxZk*D`E zJ+XQb5R-^_W0rd%cUu}Quox#@AbCE4@ogq#Fjiv+e(2(x==A4iLAV@WI_m0IX^rU- za@U!n^tCuBe2APKF7v~C3nLOz9p`o*s5AW^JcAd7qesKC=-&<-juTCvU7t{2;-iw~ zU%$FQ?IYTyNN9c{GM{1j@UqQ7p{I@kwr=9n6-8%EBHaVltysz!cd=~rq$`5n9LLIb z0#;*Ne`Xubm@(;D&Fk$q;k)cBae_Ucn)lwt55UQB(-DSq+XvhYd&>JtYkO*lD?42sw5PY7#!#AxEr?orOc61Rx`;BDk^(5T;=4$k;iO~a)`u^Dw+3=pSy`dfamX{3 zsWO$Omh?v((+|Td3^6Atd~>R{Q6C#>df%kI?`s-uk_U`1qI=oN-_SlK{mMoX`Avd$ zF{|pQ->01(jVzO)J0;372azppH4@Kt2}JuAfon&lyg}HsP9J)Uz3-CNv(eRPB;GO5 zi>Ya02Q|rR3VixL6??&&(@Y>B_<^mX^G&XR)obiOxcvk5*=M&rs=^1~$7l5yy-jBl z>bpaizY#4+q6b-^RkCviRELoY@VRu1W{Z!NcyJSy#MVv4XO)!=nM9j>{VLaXN5dIM zDDpYQ|LO6Y{WA*Dk|Zg^9nqJ{YTnY+1&3?pzQy(qC~psDjp(Wizg5aI#x`GQ9KR_E zS`dJ8^H#RdYbgi}VhBY{66l-PkpEXEzr1BHeMg%c`8SjQU(PYo?3t7OP0Tc%U{qH3 z=-P@T0d{%yROtS>iRP1YCf~A&3~kg4%hC(TOLw$37A<)Bj7Qk=v~V$SwTjQ+BL>98$KJY(mnJ) zwTOR(`J4|6Y%|`Zg1R>eWGMpqq~*+zO_FUNuj#=>JP=8@zGK{R>iB3>x^vsxs;WCB z3nh)pq^2n#svA=FV3cvWayh-33pOf!g&bBl=6-=b)FFzqaN4H}g#TN8 zPRG&p^f6vS6K}8UF$ck=6$Ki*pt-h>4@#kH4e@t7?i-ma3%yQLl zUs0Cm8O8Lo$q>gIyNfvUf~GhJn8#0O-9QA8qd@L1^akuTR8kCC^hc{aF+Z&MCKx4`siK1gd)fa&P{(e)`vuWi}c0Ybh02 zaHw;iqkaY#+ojSb@5sN~G61v35v0|%7KquAX!vjk%_D4UWsJF^Hv;gr{v%|pv;=S$ zK_-NbIgIaqqLGf|hwj{U3uNw4@aq9AuexD)>3vrjA}V#j)K1|K2y<CjccGtGj?3jB37X1)# z>Tp5mSBg+~Z+iM_Mw-N9Zgu!^I;n(vKQ%*CeC$mONf?Bqty#RXT98Ji_G`wopHPGhROEAkg|atxX}3lH)q&JKT6 ze^vlkwJ79&G=%8Cs8B=me6WIRlD}10EG>CiEz}oc8`a8$xoTP(C7O{z#AU@Z-;DJ= z*Po8@BH~M4&Uax??H60=e-3h{cbCRVRxr3aQ(#RzmA~<*_^q%Xnwi|KeJ2=7PtS>r z2T3eK+ZCsYrrL2BS|>a2itZX3r;yh?4wX~J;NI;c;<>CP-3JQZ@o5^3kO(fe6h*QM zbBybwAEuf_c2kfTT3ypl5hSSN1Nm7kAt9l%wl*-#lK%|72|-m=b+pOZ5)U8$wDpQ6 zjWRY4JR{AnYCM`Hx+n}Et|0%##xUfmMfi8#X*KI*3GdC`{q2qCI*6IS%(C9b%)%y+ z*=DHk2JvQ1+&anJVSpJ8wds%y_WxOE0VEGlystRJR*^G3Tt>d718FImnv${HEqY~w zk{PwG_%?}R!I*OFG8W|x0=xwc59(sI0T~N!%=iFq?k1p<5GUk^}3n7 zm`GJ_sW5HqL=(Hclrq^`AQ4TgPUdQJ>Q3 zwc6RYxLBj!u8GOG8>xqYIF-WBT^&U^_PElNwzsV&ZdIB2qoO586Vd7ldt{+^f#L1x z%2mSe(Ba1L(@=7~#u%$O>N#RE48j}2$1TjKwUJsBtJl=iRrY(TA;&mv1c9`Nv78*UmPPY+#bNTVzd~H%X-(zM#S#`FLbwxesRD? z+gYX7)~tw?!I2NK`rZl=n+$%uST_>WvmaZ*L};!L{z~}O%?0mwbu1k6#i5_5*WK#D zNLzP(Y^zk}GQ!&07#Z`n!kA=MprGYNjOWU0k3^!4V7Q;NSLwXWwic1NB|~;!P#cAm z2zA=HyMkbnX`oczNj(3wQoymIA&B7YDSjs2xFgUBID|^bN3#XQNV<7fFvy-<2e0&- zYTicBwvrV3V;?tZo+V>FBw8AeD`WB`6=pf(UUm*y9{~j+=bfUWB2hiP^v?!YK4bY_LX*VT5%ipy5NR>ec@L90G{ODUzn~ zHNwUT4Xvdut2deZ6)Wy8=_HlhH_n9my(o*D;DM69%AUHBL_=uT>;gSZU*bCq!N7@|B`V}jxq$yaRQ3N?``M? z8IK6aSzr(;$Pp%%nJ~`6w6jf%lC?HOxny!FD_XO49d@rwsznW*@z69hSH5a@h%SIF2S7 ze8V|+s$IL5637WnA0L8WH`|5+jKkl*U5k32Hew5aRZc(LWR6!jt{q2t^oPG_*ApCd zUAa)(4CFeieprD_q2bhFgwF$n8(?G;OMNU^%LH!y7IpHOW*PG2GUIo z4onFmdImeW_bKKj#v;#8L_+Hb^7k&Zrk>od9`9d??J(<&E%(1EziXOpRmF`Go3vm3 z3P%v}p{Jdf0Ah==?LmSSQ+mDYxKNUeFdryCqf(2+!QD?EWG{S?M zGn!%^zkAK!&Lx)EWl*D?=#@3kJ_@UI1Fa`PiIJ79$!;uB#3WvwbU|m>GtKhy5sCxltAxqYfk zF9PJqzSF~&pDFY|lpg_6HRnMEvpMA{_^Brq>8A~}U6$EcpcNt=`*hgG`sj;_(gN)C zUx5bY{s@8L7(=6LuQg31q{*PikM%UfN(nHAUY@7pYnTTE3?+ub#?MFu7`QuPez6!- zUG(QFB3nyV7#1zf7eJ>98p9|uxa8-avYv+WNx-@pP05wYLk&bv} zQI6hSw|9p2?tPy0J)M&d0n;@nN(aLNH?xN22J(9you>^?m1zPLib2z|P?~Yg;mQ#& zb^UxN3sbOU-O3c4Uu66%Mw*=g*fd5GZ$5lML3YEE@VI8K ztTM+Opxu5C6eM?~E{u8bz7X^rtw3#Zy%MP?*JJy2@-7^B-fVTrnd$nVl zmZy3Q<37R6aK}^a!b#mX$PcaOavXHD|HV#<^Ihx-4!%Sn9y|WdORd5s;bk2{pVyI> zqaBwO%~{2)2#|5Sth*1&&Iu1`STZ3$9z1>1_~B&S@jxc8V5c-^WKKBx6wW(Oa9_UK zMlE~Y*+Hv}3x>hNO`StU6!Sc8c92rJ15-`5(HASu*uUlfm!0`PaMi!ft^g%%$*%$k zC{PVpc99{V(Zf-0Q->^hs(wF+f>yCmUS7RfNqPht+uE!}AT*P*&_5(Hb<#n0IRotF zphIEDfCrXe6NVVw^tbI`yij28ZlEPmfe>BqlWl*g|OcfzQlT01rSm_k*awbHXyG+l+W`%|OoEAvnhsDA1Ef-a{=uC&&g`o<0B3&TZoo;!_=jH_3aPqHKA>`r3`0npTrwDO& zHVabox;$+OW(M{GRDw26h|g?gp*}f66z)J42ElGd_EzG+&mX;n&VEz@tzf5_6K(Hd zPa$`nKzEpJ3CObaMQp7Pk-;~ZV{4U1JPcwDGzo4zUtj!ll~w_dsFpHF_;Z?mBF2VX z=_&Z8R=3T&CGyv=Uwk(mf8B_YCophjZZ9QhOqHS=v8O%F!7l+KJt9>p(&a+p%@k*iV&vGc+2iPV7GDCcvpzgY-_6g*>zcZ=B{}pc<^4G@A^ZoZKMGP0Y%u3 z5kHYw4+|NtExFI;%E1w#vC=y|JDx}un9vuBlhR-6GNzzW%5W!+$xDmkqD_4J2wfTmi7W`Dap_HTW>v#01v*Gn7inMAP;zZ{j#3Q?@vC*oE{?Y!U3jcq2q zyV&(*5q>5xkbginmh$o&d)vZlG#gALWCd#Up}f03i5k{xvd1ZpD8oEVNy*l56c4YQ z>Wpe}sxnN&QuoRW&Zi)Kf|-dkQh>joG^36ViDBw7O^&sTksb}zt{VMBPNcQ4quxm? zn;dIS=elzq75z$wuNeM`4=4P>V!EE2TRkW?g8HW?CqZx&S+CGZ#UBqk1>Hjy5xBjB z_uT_8kPPNTemxoE-OKxbe1j$94pu!dI%DS*dMSMsbCN~&=WPG?rJ=dW z5GuQr6y5CHTnx*Xmo=64ezoq7Nqgq))6%h|Qah#=A%OdZD?f=Jgrhpk5Ba}~eE>Xr zL346}T4=AHZV4Y^YL5jBDo+<;XnL08TKba$jQ;K;n7gNk-8`qma7@oonA|_k3t!It zZH;Du?F#9mqaKjwtAlz?;x?3(O@Vzav8HCk*sTkIv%UkKtp24^pSdxg3yob9D}8%? zS&=xDuawlp^3IW2WP$B((FoW@?jx| zR33cea#`u?&{upgNm%mK9gYZzW=!wZ8o$yWx(rLNTK~Adzcf?LaJ|@9)bLd3>FTDi zRClp++^En2s5^%3%*XF4)?WxyC2;B_CF_BiQ{dp_I6wG^nV90G}dg zV%lMn7D0B7ZTiGYF;Otw@n@}*&F#+@r=#fzOaCYNgNxlcT)n2L2Z)uqNg&tH9y`w0 ztLR_7O7|?kz5nb zHI2Ugo-1C&RF5OeBZ+7au8C5w*~Z)lI#pq%;5=&F=o>pN6UaDNTGqkW!pGr(G*L}* zWJZ?V5nYa1r`&|$f!`q#DvjIEi4%N8; z&r^B?moO&S^5e+?AM9SN9_Bdabtd5SY%0_gqv22-H2K)Kjzh zQQOhBZ59f&SpQ+of00vQw5U{6y7bD5vrN2aHaL=#=O<*~Tkd^PjHVu`Mbw~m;{0Q` zMYc!e*kBd9YjuNF>rJ==v*jUea;Fz3e8m^wJ-PJMFBYr24TUM?Vi_<(R3UZOZhSBa$6tLV~9zGt81)bsZK?$-j}RxF?K5q*S=>3yQZtVnyo535sZxCRZohE5E6 zh40>rXlT3~NF3}=z+|X$e?*I9O64{hNMOQ*+E37a3_yZ|d{(6m$h}xZ{^9eOBN;x0 zYl38;nd^2Bj(c+l2Z0z^Cg_*)y;9Yux;~-lZF|cKri;v&^)Fu%Z18!Jl$4TVQlf5V zeiESWLiS~)gXVXC|30as%0>}jr`38&5y>eV{f)t=VQ=G#@!?pBZ08Sk3bZhi`jy#| z8V(n#lpvVYx^JxtlEIhddXcL#PzkAxiW5w{ODa#vcLfkeT}{R7jvS1N zL{U@u19FUE5+9I2#U>!QFNbzRQGnul{*ezzP`|V7dOnw3O$-li=Y(KYRh3I|X?Sj2 zBxs%@;Gp+yb!PgnbpNnxbo=y0aRViY2lMg>lROODK&k(mw|9Tj(%WweTC-o*p$56F zx{YM9Gl#z?ots(n$yH`f+S%J)m+}z=n(HVH=3c+HGRxeYYS^hqzw@lS`+Z{oC@!mJ zNVta*T1>Q7XtNSMMc{P!mgU~;;@+&E5xsb%#HI#>w@?5NA{a6Z13H4ew(q*Hl6NQ; zdQ*Ru3#I>Asz}U`Mkx$DPOZCWhz=J{{FN{?TK z)s@hQ;43Ar>}8Ac$L-y4+4=X_a;$l+Bu6=palziSG`&WTU2;=Ajd~Y57ki2G?IA;R z^FS`9X8#K+dxt-pP>qNwd{0zXWwq}aQ1}Z{5sgo9Kya8VyG*!~~J-ZH9= zXiXDNaCdh(NN@=RCpZKP?tXBG5L|yt4U#!okfgrKyQ?N5P2jBO4p#mE3gH3sm(@Xg&xRTCFb&>Se?f z2(UD~XT04~mb->LJ|3B+s}x3A8u3V6)u3y5`Be+ zhSo4rmCU-}>>VFp zG#U?n`MGq@PNy?DO+j{5K-k$z;h$tP`H%`qP)>ZKO2TKvFlh16XFOdjncfMC4+5-` z;lqrQy5&UHo*O3qqn9}7c~=-AVEMfjll(HSAhRbwl7oz;q7UKUh)uD(VA7GLxw}Xn zVb5B(KGC_bhF`{(U|uw!aW%#2=#C1jY<>NKmR_abzMB|QEZlBD#qYt7SHGc(ost}V z6tp|wcdrGlIp=Wb^onZ=4#DxlG{HWOYQZ=>8}%7Xg+HY0g23d*Im^&#ilz5%<1ch* zJJ59mWisdLSUj6FE-jUbWHFvyM-*5i0Zz8<+~e%0v9+S(SFz+W3%v{65gt$j$wgojYrwxq<`Zb5;}hWT z>|LQ?o0&-!T7b>iMGrcyE^laBL7wFmq&Ak(yJfBFf2>kGn-8#9nvAGp)%|TA^Xq{; zD|7v5E=kqF)~tPSXvoGSBYBySpZ}7q`5|VtY}W$p*PBEm8OG2TLt|v1^n-&s2RErz&aW_ zqt2|`@NtP-?^UG_Xo>E$k~cIR4TLDvYmQ8Jh+rkJr@LF9y#_A0Bv*|No`o zTtNUROtWf`j+~r|D(heB^15({TM0^zHq<>Sx>5D4kMntXU`~6*;_Gmb%eMAY*ay2da;O19Q@J{-{&$pO z!~{rO+a7S0N`b)m>qB2!EJ1ZZGw6!C=d5sF^RP)^S9$RAG+q`qu1iPL@uCYyv#E-m zksdPg2$R1nBxPekp;~iJHN@P3tHRc1SW8a3TiPdk%0nb|YkYm+merO%1z8e}&wer8 z=9TO%*z{9P27WJIabtiAvey4@TvRh_!tQib|AsV{IorrzUu=gtBSTamb~qN5KbcIv z;~F?-b!ejxi8^iAqpNtaafvIM@H8SP1XXO&NM@1F^+4%6NQhx}c~FRl_PhK8o=1XZ z)RPmQ(#>C;;C1b#S0q%CO{r(*+k zvd6yJ{I0+p&mx?P_i^QZSm9DiPD~6&{fll%%O4id9M5mcB(U@k zlv~qqoNG4C&Sc_r9%8d#4WoV2Zg<@|P9x;D;ZImiHSlbu_AxpAfN0LjjD^%Th`&2J zH}9+D{fzWux88dl5fI&cFiUPz=0kTx{-ciK4w1Wv%m+3Q{#Sk8xr7=cQ-4%*eVdAi z-A(j(uE|$^F$;XtN{Dxcp#v1Q0eSX3>!&}$y0*-}Cm(hExbo)kYytvr?~AqQo$-C? zQ|kILYRc#A+7~SCo)twvtC<3p;v{C@&WW?};nXBcQtbys@rQ9Tsjci#fS2EwzhlTJ zF@l7Ez_Xn8dyN8H*cPWzpyBqD3)N@jVjvV#{Id-D8`yZ=253hBo_)-4ruu*Xhsm0 z?F{-ws~G0n-;DedV*d}*sND7LM{_yW=jurH$myJn`)UPEK|layQjiVGBmRg99ga)Y z2p`6*7Q0#hHvCF8P)h2rJ(>(VOZQ$npft7 zCIv60u+ijx3ICm_!;0Q5h?@{QHiT&V2{rWjXSLnIy@Y`*S}0tt7qpa++P}@;u5%+# zd~m}|oJ9zyf0T0B5;C^?w1opFsYMe$f=f&(zR9#sm^?bV3y`)*)Vs(vT_L&0wo{)6 zeVTBYV9c245GASsfw;)ml%L~LD_g`zjoG@1fK(v$QUHo=> z@HfYplo4UO?4?J@hDS*b$3zUAiN2?M421*X9ea~&}Ort1mncxV4aP+H09OT zCq*m|8+-@2)4ncq?*lj;R`o>w<}34mN5=ybdJZt5-#kIx43jxFYXT4f76`}Mm6zkR%jJT4kZd&K&(`PI&;E5dzcIDeP1Gd^e_c!*1>w08ueFec<@jM) zb@%i}SUv5Sw2W(9G`V|`UD~{JeJ&90&(|Qka(}Y&rSY8|Zu(ZVuRQWRACQuOuO$gF zsD|%O_C_;`YPM^WPVG=Q;d3yK?#)Np&&Q9L`4_yaqSu6ut3ke0-=DvbIfmvx^76C2Vq$mg|Bkj6 z1K1M@R8NJ((0)ZH@cmZBZ7X}rFDHFwTN1s=CNOX}@Tw6v-Sqk9kn^tpOX~g93JE{2 z8n9c;e<+6jwUfsGedP-8qv{VL+|88;oO;?8n3{~}mKEH9K8qRZxc2ASn#9TNh z&m(W4M{J#&Kr{0E03)SbeA|?vJ|I^fJOaIkDr8KEfa-o=rrp>H+yLTJ)4`TyT~~UQ zw_(8UC@UQsxkdYns#>^4*F1g6#mRHm5^6`;WP!{riuIL+@x{>~K-i-2A1nj%3a^lUwfO~eOalXbdH|DTtx z+fL=Kna&lGdu)zGz~{1Do~9mvy1nE`7?8egb`u)-+ydBjb!cxYOT_e?@9S<+duy&TX&p`Y%2QOzxalD%;O%3SlX-T&Uu|+NiL3cnC+A&dxVpA(K4GI;pi;cgD`txO+m4%O{N9aYHEy01!vw+5VaU0B*S5ClG zb^_8!XoT4S{LJ#z?@4Y>XH_8s9@WL5Jnz=Gc8uH`M z2nIm&s$GG<>LHyF3s39`alpgq2A}$_?#(x!qeeP{yz7O{-HK70_E+rKct!>6v^*iK zFm&)N8Fpcji(gdOj$ji%NuSWZn>0ctOLdHTwlA}F`YBy8vs3w2Eu^s+1@P21XyKbn zegDBk%Qczd!+sQye}oO9yL&&)@AAkD!e5_+RKIs=Nop~q?pL;jBdK$iXNPHyd$AIU zX|mdM(u)>Q(Hvf{)BWQ<8FDi#&*3&B;&cDY*#liTtb*ar^gh$q$)W$>>vXQOKXBcM z@&j}-;){@xmmEFQ40SBg<|$`i#YG_!$3G_9e^jyu*1-ezlG`7{tV06hptMXVBw$fa z1CSyF=FZ`eVK4_c4yZl)@!$$pm^*vhsHnK^%F$882~bxRe2S1Hi(>33jCynXq)Q^c znN7p;Y(0HnBiG;ZWa{qFm{{%l@>1naXslxHOVr_foD0|7+0n!Hk)@+tWA?;hR#II7 zjo0kMup-q)$%ky@UyV#0_z@81lxTz%e??Muu%wYoNj>-WC8 z(nbA_{NHNMZ#8acXCYq;9gz`F2|W%dV}2iL`NWsjAxA2TfmP|nH&mU*=TZA&D75`M z@uHNLI;BdSmTr7ug=g)&>88G4y~~8o6m1cd`==TF%n=Ivw!76h7t;2nz+a?MdaJP@ zv2u302xd?Sr{-5~HMCq)wy!i$x;hx#8tUs3oURCiCak)CDMWe_e|Kby{lPF{=Cg6&^IvpLULJWT1SjB|9-7|gES=~~gcef)nehpse-h7{F@QTs_L@x{ z!*Mf=ddY+eEs4&_THc+7VmU1Zo>t&}p(QsY$!^F(J+{zlrU7LTBVin#yiOIO6IN=v z&`ZkrvL$&U15@yM>)Fl47S#vRk)bt?g`cPR?aN-n;vaX_-m*d3ufh&g3Wz9OrT+3n zR}Qc93|Z&)2^A4hOkN_n=YEdD*=jBIABeJ4nY6yCb9mPq5Qu^|$Ou&d<;hCNLuiPl zxFW&-*M+xugU?r#oH@hw;#`3hrDFxavqxc6#A?S|_^`J z9pQV_HKaOYFP$-7I?8b^PEta)GGTak?KYh|_)thN0A3lY`1qP>R5PTTyI z7Mm%E?-0PE79^cRoU=3Eb4irN$BNJpvUMih<=LCJp}di0REsW>wvWlQa~?Yg3UuaOs+yq!z5J-#9*47LDU@B8pM`|K_sGju>sZOA|rz1+-)d6}kc{@y6^oI#Qve1r{ z_r@?kp5kA0Knu{rQxb{OHxH%0Km3HrofZu4z%w#~A9;Er0rH+ly zZxYV)f*!36Qlrm7t)I84jy$MjXW)=TT)fdNW!6kOez>agFA;zs)`wcJCQo47Vr0#X zn$S$+(8GpQxgBV*q}j%f>P1(%wo}Zo)O4KlU++zs(f?gg7=49%63`~1#nM?jLig>} zy1V|#^+Le>__KgB=e}(mgfi8+|Aho?b2e<%xpC1J)^Fjw|{;Zaf(zHP= zIN*?bQ@f-u(QXe0Pj)(0WW=*glrb@8f(se{2PuE{~3>~l!G?!wRn&=>$8kX zj`{Eg4^-un&#|c^a_06QG7Wt?R4$0Vr}n$VA8|$y;I8sn&4&<(%(#yS6e|;SsYPPP z-@i`wOXIkf?S^d^nuBW%iI`=sBROtTlTNJGxvbl~F-xkLV`yzZh(p`0#MIc-l;C=p z5Z8fnaf#T$tab?eQ6!FFxtI&K?|)koH!`t+k4e^w9iJHZeM_OIV4Er%#K)Ty21fG} zMuv2d3%+an<^PBl+mfU8g$n^Vx>SDHGi00LEk_KQ+VtG$FVWY!G0(&dXr=mf@gwCz zt7D+MEuUe9R@afM<;mLYQ2+4DviH@5Nwap!ilB>FcF9i*h!!8D{gS|na+>jWf@W!0 z1z;R_)K$BleE%L`k1!@OZz-3AEt&{EUzw?E*!ZKL+Gc@qy||ly4deHj<$%v8ba zB@T%s%V7!fvnRX8c&s58O6SBcr9~UB$pl<=}(kz z;=)ukb5{hOx)QQYlkPbGV+I1&92UBb^!CoKVWPK}+aV{9XRU$P_MT5Rsjq3d6UFC%QqFu$SLx$J*aL`zwY&nC0rFk7&j;1CLYpBywlIBYq;MnR&a{m&*?z()KGx=`vA{9DYOz=9E_HnQ|&;lamD3 zV+E-4x!fwNL3&we6zgoUBn$e9>y-Pjl_FFzsA!$?2h+Yt6nHDWo@QJ)p)pj8qG&DG z84hjvVok4AoE4H!?aMHAjY~y*A!0Yt*4V*=^V60-piXgKzk5sqJv0)T=P?v0P4=Jw z@(bOX!z}BgxQ^d}%a!7FxG^tmF$qz6n@0%Lyf!1p0w$T-fHgpxLX^{ptcX5pqahQi zDUw&LyAOT+60B@4rQ-&2_4qbyI(PB~f8H%XO{&g_Qm>BGbUsr?o4w*o#zxl!zAeg> zAM1QEf!iW)*yqC?mPAf`^JYBtDsT)ruRT}i7_66eO&8j*g>X;DN|ZdazeQl1T6>Nk_x_27&0_2BO)LPeo2jI~VjxVOMt1GL9wb3z!X?^6PeLxGjcUC9(%% z^BPUz`C+aR8F)a%Al3J@!E(eRZe)PFWRAOVr2k#{k|6z)go^l%HylH}6J zN8cL93DOk9%z+AIQ+1F?5VP{utVs8#E2gxICIwVB)f%_)FiIFbvP+@@i-_NAE%Phg z7^$zw0Rm?3%|O!e;U4AK$uOiBk`b3Zs#HHfH9xeerJ-QI3@#^HFchtDgvTiwI_xN~ zkv@+AYj3-?8boNPC;0};f+4M3mb&oZ_Bxc2P(4tExTAOUY(D;=cI=+2RCC<7qyDXh zRUc^JMV(ybAK&gEI>f;)x%6M|T=t^a$>g+hG!Z2&!q?<6DuG~EJ=*u}3~cttF%hlJ z#`~!;e0O0)>RLZT`!H1W=h=cT^x&Kw@~|)mQ~Q1ne z?&;x{Rc?>W)F;QWZ}BQaM?GAeYbv_GjI@KFUlE*Z8h({gPh_?YLu!}$HA>zfKX<;E z6mpN$m9H$n7x^x%&dQbO#sRtJHEWgLGg|UUU4Q76#a!J3+YC#6Mhcn=0D-{v>Mlsw+CbFj zILiIE!Sa(7cxU}e3GTfi1iZp&uI(#buV68?J~@1IG)&xKIzzn~q&*53Co;=IR(YL>@X0>& zqoLt*uh2P~amlFqsZfBZC0WHExJTzp9Kq#{Gg1rl!a9e7hl-^)DT`icxb-E!;RgYM zgF*}U@Wud-;utHqsNwQX2#QYk=5s6)tDw1#^RfLG6kE+WpvgS8l&UTS1!cZ{a**Y% z;0Zdp?wRMw;*gajaz#X~nKpZO9ocyt3P;8xAfeaDS$^s`)J4R7OrxezSZ5gCIshFbaeeQ!TbkBMt8^m`+nBA0$1^TUu9b(%X`&56#N^0|Vwo+s^0 zFRZYsa;L{ZpJYE#zVTg3*S75LaEq1WJB{&&3^OUXI8R1cQxIj~~T@5QPBfU0i{Bouj zW7<_`<|f9aQz$UjBbX4V(~y&vy;% z7VIGS7j~6iRdmhv!=DjGDNIMzX{~B)G3|d@{Oy6s&vrOYrlDCu&a1)Q+QOrF%oe() z^G7I*dRGr-vNs0e|M_M8x1#Z1#vF#mWs~*7w=%_< zUE0fx^ym57Z>up4Idh!)m|lk|=GlA!qYs#s$t^&&eK7_Ve5+=-pE6 zH{KnIboz9aiuNhY4qT##hj*9D3-GxN!x@?&A9<8hb9#`Fg*+&Uj0k{NQ3i)LxL3!2 zv}letRt(BTHJE_terz=f3bXgs3@8Ihy;pN4&1*NabjgxdifiG36N()97SC!~t#sXU za0Z>z)q|VNh%Wfhdtq&!2YJrm0IAh%)ak)$>L3ZMAzP;s-fG^Z&<<3bcwjh5#_x+$ z61SQxrR&33jL~RcH0c)L{`^!KO{!33T8@rtHgcR^fT>Mp#x$fkQZd(Ncb$gr;>8q;2PDA}u$Z_y z)an==Ye@qcJL#;usN(gH3jX?q(u!+lT4ErDjx^k9zQ5wx)y%k>_A>`<60&7GcOKaq zmzEm?Bk2WJFFdzH8~#V%(Z*>D-&!F3-mc@e*Y?;M9~TR zf^RT)q(=Nm*nvK>ISXhXt0{xQEog++q5dQ%d^}y<+>^>{r>a`U|D27V?qpsc%)aMB zJe@nA9QYl(jRq~)B1q?29IW$G;7OJF^oDTohhn|Qtgsm`vh2pGW@+OUsinS}pX@bP zNiKw}=q<Rbo4D2qSZvUe>N_Qj8B zqdpS#J6|E_xkBR``O z5P3WIYl9kUjmRkKK_>nMv<~DBBg4CKkYai>)etblmXwpK5|PBhJA^y2&~@0EjacGj z?V8EdGLTuN-F#`79XyfiFtFGf7J7BWCU-)Mk^8uoqFo2p(hUn=fsHS)1FO`TQ?DH( z7VVj5=*>!oCDV<^QzclSoMtBi#^Ld1JF5Ra{Yn7oSAyN$plV?)&7Zt`Ic0Uh;{W`Z ztBoiJ@4fqzhvDcGNjPo6@s+^Ir|&96<*JI^F_eVD&1QR>c2fN&*fF@kIcy3!m0R^S zaswick?6wQz9$Wq4FDR9110tVTO|165mV9hC!WQ;NkRU==&SB&7Ys`38WO~J#jrIw zlOt~kHSb`*#@EbgjHt@+BZF2=@rlHKk+C8hTdE2=qBmy|`Wzj|sZ3j8=f7LIq0F{U z`9bzaTqy&xYZ<6P)?zCS{-ptDr!hHxzz-hES|}^6=j&*nj`BS&x5gM&HvB2H(Tucn z&BN7-xwhkuHK-+Xkuew-wYNmMz9HWYK903C)xo(%QcV`=IkfjlcP$r!CGCzYb75i6 zspfGJ7)bRY3o;&fM8Ad&kVT40G2eTJRn)1i;!I%b3j1fB0k) zudphdCn|SlU;pXzfdt(YBN<1G`qoK8x)xtb@nhtu2cpEdg1V+E`ggMN7MB*(q|HI@ zfxOe?Y?nPl@hOu2nTjLSc8`uN9UOW)!APBlaT+E%LTgW{x!ljFdAwv@bllCSLEwEd zke<6Vt~bx+8tyaBYtF&xdxZXi^~^Jg&%Qqk7;1GIxGsl6wLZP^8N5qCds(`gb{{5E zRJB-(hgY7gKfjB4&^K}^E0Fu3K4P|pV+eCPPCGw2#Q6oo?{8y+^a0L6LCF!ir(X`!eL?Y#8e1fL28h zqb>X$o{cfw)Gnp)2=OIXBuWQQPoP8qs-clsrVH-XOy~TlsiG)-KjsO+=cat0XNF18 z`y&7n1(x5PtMSzusb%Qb{s1h)yr?cLBtsJ4(a;Y8=scGJgrrsnxI)YD=y69J2Z{O& z3^~&l=o}8$!|vc!t6ZZGp4)G+@!{_mdF>d2YT!sPwz=K= z>W!gIO>1&$UvK63#(#kcOIBqI$E2zSPl%u7BGV4oaVxYqF1(`UV^t}b)1M|+^^;5&chi2;t+{ri*TfOsYEdM-7C;#6b5QTH(GcbsAAI&yX|w#aFXHm7C%fQO!R#61 za_>N_iwRRr>CxZDb`vw|zl-~@kF{T)lrOP;VYEkx$m*Ko!%COH$jXx;>55x1YrZ2= zaYj>2q+(5Jj30-}g+VyzrD0e@2SVQ$Os(?@1wTmn;Ue)jl}LD{ro7cJ6YLhR=ZrbE zf!i-fm-Xuoh(RwY@Nbs3vstMan*0?s$HpGi&8@fjTZr>0hbripR#!}^8a6n@%h;-& zM_Z)-g#BY*2NA3}Goh~5Vw+2!?|#indvw{a=>q zZy77ircDc!q1U_HqD0iMRW@TDN*w*B>~b^x>BF)3J%Lkvsl`R~7aC;2P?sdk=9$U^Qt=2%AKPDF|Jmp%kwx{q&7)bKmXkUlFEB59cK;9go+~8}>HNEfUp3>WV z#KTsIx9WR_nJDU4D7{7d`%#|FF#of_{~PFOk1_(t3?D%cVzPb9Y=5F>nDT%VqM*~> zAlKOY9woW~F)sX*w)~u}Rcd15ddi3xJ#Yjv@dfR^mN-|bbj~qPyuw2^ELxet84F)} z2-D8YuU+_TcqxB1P)YjdDwIK&^HtMXDRMhO_Zku?r>y1PUPx;?0^bkYkP^*61ywmo z8=(~EHfL?e{g;J<HK~YCKdgRdF(T@h027jUE;pdg9q|2g1D{cX4So9ddZ+P1*-p z5bno43&f1C=Ig+>1U5QmhqUS4d$}CbPr9?(UttXtoo z!X+;DOaba;2`!1n%U&UUg;<7NIemoL8l~v`V45(#D~_u#I)|X^E%4s8o=VQukge1a zz81f0u81nQ_KTjWYQ>lpGkx@Pt6%?!oEE2+;qHZT-xqG?QlctcX2goVg6phY+64%r zd5YjoU{jCm5tKi4`y%si4_JZ=q%xyOuqef#O$|HHiN%6z=Tm6rV`ZuxLsanw+t~kg z|BEW~_0aiwUR-JU#~bEH&gf=}x!GV;V>kH!n60s(XXiY!niPN8=BOqBK`+?u?OBSxf~~GsOCoE zTo&luy~u=et5Y8j8Fk9OLGE<58mKYQRwIGsc=PYsp!roQV#0EcqnA6x+z^4CFrL5J z$m*Xn@S<`k*nPR$X`byhAtk*PC07K=S382`sC`@FI9A{veg5mW;031d4Fns!I+G(9 zlcwxo3H5=h#_m1hziAAimF;03H;FeAdOQ`0h~5YTEBhFB+(%$$w&Yw$aYcK_I`1`6 z+butxaS5Y3Tk9ank90((?61NNwshgFVUr-X$~dG}9up;~Nw2=!OI2b19Q%D} zYB!{fCvGIM&DOf4Ube6?+9#z6#$0)T71tjs>9UH=$RB*(HOi<+9vl0{EItPmGJED5 zzs-%Ovof8qFAT?iv_LzdiKpyP+r2qVZtPKw@9^ebSM(RfueBA#Z1y(rwRBx`g7x^x zO4gxr-3#B!ZeLv{r^e_c)Oz5X%UkH0TyGtw3UAXVkD1Oe)bw^)_i2CkKnz>N>`qJG zn83l_dar{^M%0EbzbjxGIYNvE)eUyY0-L5%CV&?AOMsgAFDU$fBKCk5PK#qR4D2Sw zbANIAvj1itF=trIXGf!XMp(eToB*4en)mVk%=^*u9_@iH9^%PD-%3>M314|W%qR%P zxssb_S%B_mWU4XBsivkHgg^M{6euJS&Ab2FjoBpETS)`u5VKPs`dWW*SZ9DvkC<`p zTVX+irBRxdhZW~OmzL5)Ip6nEX^tJ%hbs^7=n?lBrx<iNPD;xz}c1-yhFOeGZPk$5rser^Z6jMtccJp&%67+WdCZ60rn}^;Q&`{3e|Iz zG$t-(DE(F=?+;JK2HBmQS4-~7tdz|*r$xZRUbysAHwjS%(h%EUHAnE`abSZ)*J^9u zGQYIoymv|J$vYRhqYtF`vk&^?pdf|P_wBKk<%4)(9>_|%)(0&;h6vL+xPd3M!r9Wp z`X*?e*knoF2FZRqGLkD=m^HUf4Cx>v*vYyCD_ODlJ0mXcF?{ctC5PFDIl&;oxX=5O z64M`T#IbQhFwLDhw^63t_<Kjy z=(Ia%Gj#D~LDAk206Kq^1@?Jn0eHpIYy{2xjTG0(c!@Ef6nCn>I5e_z971h>zQP`C zcxp$4I=AQ8xQZ;D7TpJd-9p~3KYj6B7bkQVyom(^BeLK>xI|H!RoL!>NH8!;GeUhI zJ{`?ruL}IBUB3mfh&UG*1!;q1OcHR(;7QUj^N= zvT|cRM}zO?n7Bfme|#kKPi_^}G9;C^&@q{3A!SKf3Egge38-$s`>h)xp(csdM59<~ zJK-_{o$VEZH(*J%K-zB`{H9mYP7dz)AJ6?EEc=yG3-DPdW8&xr3J3V3dX?a5-$la$wLcNX#NGbQp{0UZvLoCXE|&3cgW>$T}vSikvS^TIODmvjeFbn}Joal;3z1P++ z1Li)+#X#REAHZAJ2>N%+0rv6u-xStpQ|JbSAr>zS{T3dc|ax;$gBuT4^P< z@``bXtmRco<;@>p>-1{ecbtC1@jtAI8qZQ|jcV8Lr_#~EGe`E7&*z(=Ll!F8G9zYH zYv0+VSr!9&(u(#@P< zd_7Q@${n1q#jUWnfJUzSR*Zp~Q}$EVfM%j%yzt-`u3qOKskHu4WjG~6V==h7zguwG zxMk@V@IGXPnQDxCYIAK+`Q=!=vMw$F2zP3z?^HY3c{ z>tgY5wvv46z> z+bp(O+I-RD-CAAL@Z|J@W3Qbz%s6X*1>kui7k76&>(6An7Jt?{{Ylv%fDPzeRZA!@ znr2=(96YT%x=&!cb9(RW1gZ0f9b}qeWezf0VirOf8WFZ5cv1&?lDPav`;SW88M&EG zfAfTRJB%w-E?cvAfO+vyPW!9~V9Pt$V_`0g6jnTapj{_w7b#?oiS3tr=2k=^;HrUM zb~1LHN$jM|UQPerqV0|sRM|d-*jtvj2s0F74W}Dn%!PFmT>inoGiN>Y)FA`;3Iur# zvUPvq*or{#2;aiY7@yZL9K!X<(WjVlus*IO<(}~8{=y6QD#Is?lV)J+6h<=5lR%Eo z=YfN`l&Jy_;)^reqd?JOJ5;t6Nz$pg+5e8;Y_R42y-j7VAm&24&I#S)wcgDut~x~T zY_z3D(b#T;=?rb3Et~r97vJ9oG$I?$vSOOsJkzw+;;U6ZpAKZDgJz=PCL(>5 zR`?jOu28(5${fT;6B8>g3nV)=BJ>9wdmN5L0foh9l$%9VH$Udg+?p@ zV`dJP>yv0|he5bhdA~@oB@z5cIw58Y`XFIRGwFjQ7IfVaW51$#!p zF`Qs)&P?EKC%h;25%C+yx~cdc^Ahxl?wgb=r$W4NXStka5)+4>(56W8xF z3&mB|)^~DMftCnSVCy8=9dfmp@ZYfmL+rsa>TDPpQr!6F5u55{8A?K~FYkowFFA1b zE})%q0%)z(oux86qC zz3}*B+JJt-8u&W`J&ba=lKXe`@gB*@ekqQbn=I*?iOS67qMM=Nw+bUPAQfQ)6 zx2)?^B341e#qE0aOLHC_4(@0AJ6xVUTFyMl4=x0$m&g2s34LVd%tNVHgFDOzX67$E z+OzgdF%dUCQq7n=-;yJJWeF;@~cO1_hd6JNcs9JT8yux_6KB`jp4Zu)wwAJ;j*lO5>_G@ z8Pe;)Qu@yTv6c=H=p_06*#<~8^g6HAj^TFy)c=X}M6Vz4`J7>3WdeYU#azU@A$TGslFD@{OuorN?-T!-OoN1a@X&BD`BB`W>zcp{Rsg` zb>}4(QfYbqe05pP5e9^AKd6bhYfU6o>7&%+BlbNuC{YAYsq|}{jQdL>3rg6@e7~$y zkc`IM4D7;tAK`itOw{_KJoT3&g)2!ad>{zgR2_6q05B(#*yS%@&GsSl-b%(NIYpK7 z#{}2ev%PN&Tr*oQ0?3$yGi7)ULHSji0&&haSyO#u+_Tip8_H-#E#zNz?J9{l?5BIp zAJ`vGHM#pD7RMS6;|Hm9`2Nz0dbrk%xpFWjIA2$p#M_W7y=-I>Lq>Ws0)B;`TSp?+ zXXN^#zW!;wf|MobE`D>etOH7jCl9OIO8LRb#kHs4XJS_9($ZEB3v=n+rB%<+cpRdk z7Cirh=a)1L7O0(6`Ct<7AVJbeR|SuOAku6&c!mhX(gRw%Q6eXSP94admmg4qqe*ys zZ?PXZ6_r}6za72Z>Mt>k%p~FcoZX|;>>(zL=S34;RTN9}7uyr1gPwg!S}^`#0A!Dc zT>##0R|2H1#LzR+_VQhT27aI+HsUDRyaTYlHUK~pUGpt@1|H!5j|bFN<4but`j(%0 zp8hsK4}|fMl#0&E1-lCLF6T~2vj`v+;PT6f)=%6BZDwP6SKG7wyQt)50jve=d&lqtVrd$jkj;hyp7|~u-vae z1>EBLwZc)CwkjE}X_d#?f>0mebm*n3zE+u&x$j=Ec{7w6Sy=f z+99slniFd-%P;TAr% z=8z21;+i(pQu!bj(*Dbx&Hk#`&30b9#x|;tXs#R?OqU^EsHX8#-ABq&_(^Xx4(n4`s^B<3+;BFJZ=i;=U4by1bQ!UY^Vl02z0?{I7hq39W@H;4SYS)W#WQ~7p~@cN3Ywfo-B7mndNH+V99N2v=5jiai&$`lvh}=s+cw#JZ(qcv2Poi4W8%NNToMs#cJ`v1T zo$|J?6chosjvSZfS9*g%uhZ(b;C#Phy2)(FqPF`1$xKdL--?5PH?F^7Yt)l&=)_z9 zU{cH@=k5b!2W>%#)iHE>WBf#VlM!IG`Pw#J}LpYF9gnwaBF z5+ZoL+4ojyB7J-K&73ArVoO{#VP9*R_00QTWvAs4vJq=eV|C@qHu#g2!}3 zo4BsJw4{Ntc_&TV?DbFjeY!a`38q-*4R`0g<9#GYaybuc`L2sXkWaJ#^+?GZyUTPF zNkQs3*GSrRKTIy8ejcj{EVEF%e)UFImc+pXyOX!Chej79ilrj|*J@Jfu-Z@J@Hs4{scCO=MWy9yq?wtGP_a&=M8VUI6I3#i7?+c$DySrDWnl08U5ltwY4&5a+OS<} zWf7*uPb+v99^X^z@pW?f%2B$7ntOI=n;r?8P|b=c(whUZ)y|*Oq##`Ut|n*GhPndb z@_7G_Roxkc2mD~pQW;vb(-kB@o>DJP#43zr7K#y=nMnrjkKUvvh6PK6MRyFS)U$N^EXO>0?iTL>~RJtIoYUx!{|i-dZt%$get4aJ_FheGkJVf%DY})u5Hf&SxtSs zcjdGjj#&m~ZD0yBlBs4wL(SEkFD{6qvwC=F)MKp|q50EC9k(rN6CFK>@% z0WKVX>kSkp+p8-`7e}1vE2TvX?>tT(46y0a-@+u^m3-=-5A%+?vtm0$K{Z) zgml>)>uB(u*UIbKHBZE8=Z#QeNU4E|-2>Xqk&wyL?;QAofIZ-()E#P`T^^-yMi%og9+GA?gmnhLt1#KOtB*P$vwx!{|s*z4{eO6=f%OL9Xd?7TXU zm|X7H;I~RKj8S5C^(%@`e(bQNjz7b-{zPp29Qj1_tPhq@bj~YegT7JJEDXi(e27mrCmHcY{cGY7 zIfxu-;#ZpkSRU?|Z+FdOqlt-G>`!SQh4PM5B)F`xsTtXAFdfN}J(dy!c3F|Ne|+wm zM;GU-(S>6m2sf(_BKo35^s9>8LLn!nc&e~UcBa-=i=aRCpqg4Dtj3t0nmS@EORz?b zb7)}5q#+*;0jaKQJ%oHMZ!rPFj(kHZjOC|HBGslg?}L;YN(HNfhD#XqGu)U=Zq#NB z0i-AklZwfPFq9g}1t7~`sfuE*mWwu>E@D2~{DPO!{S+dOtMYH@;gVU|J_ECn9A&?4 zw}Jnib9nzg@8<$CEBuJ2d>5YBBL2S^d&{V}x@}wZBMB1RU4jP-P(W}9P6Y|>?(VJ$ z9-PA6-3txDy$BlI-8H!LR`x#op8L+dueH}!3n9Rdu;v(J_R)KvbM%_w)~Oo##8zN| zaj*-=08gTas4?UdHo^~~I|*9fOUfox4+L=MmpNomb&Usp z7ru?Uh(DU53C?#L76;Q%eE?4IM&_z|BY3TVAY@0JK zgQQ%r@JZ2*v?!7y)X(=JFD3yu1f3*$om$dpJg1A0%>c8YVDMVVmrX^BI3H$=gXq?k zpvRKaQY7f(LbLAulRE(Qp89#+0arNL{DNH;PO%ZHK{@$WM$%Q5Xi?EbfS216(w{MD zE8bn9ioeux^Vnn@VmGiysm*S+_pxG+lYD4||NmdFBdJa1_dJc)546XB4#$os8|6p< z$A;bfO^RLI#@twx35C)H@@l^sE&))^%616U-M*FzP?v_7*yYwnfnR~wG`~yA67GRq z&-;VW5Gk9U|Dl5{f=MveD;jw(PxwV8W$nK95GNKgs7k`t@eYn!tq zNG${GbuN({=A%Kf*;z-N5sT*B19IDvH7W$=1L1yb^?6>*PS|H=vJxv%B9=+a$Ub;j zmXzv3jrn`@PGw+S5KIP37a78!nh4-+u)Bx7m_YlXI+~2jj zBzgxejZ=SRovDB6-#e~pY?geiFNL%F9btfGQ(7h6|Kyh^eeD|;`M@`g2W$ST_MAS1 z*8df8rd-i#jJ%bBg$6Wp|7a6HyJ?{!x_JPUp0Qw)2C%I`Bt^~N__?M&x2GcNIKDEK zgI2@UENoe`Ra9*{vqw@y0^x9f=6i+WbN#O$bfzW>GJu8o0c!D}MxD@`{$n)+T8vt)Qao~dg-ymAAd@z`@F@Zl}%Mttp`DOK48 z2P8leF{Jvj?6pHYOzvGXt-fRhqQl6B-&isJGS>pX0al;^u%~hEIlN6<&g&wlD2iBi z>#zIT!hN^2{FF^D1gyq8ww;5T){gQ6r07Cpf99CpxUJ;YTp;%uk6}Vta48kvdXPUs zwWzQmxqiP0A)5p_9oQ65W#V}1CifAJA4*PwMH?# zR-=4+!{kB|JU{TcV!C8|G+fD5f7k+pY}_kyy_UD21xJr|G`9Qv>5cjY0Ts*d47Tvd!95J&ZZ;YR*Zya73$~7f%P6|cp9F_f!1}Zlm&e@^`eOJJ<7^puc?yv#zlHl=6=kO z-nd?Q3#vym>x<~QrPkLe+B-t?S>jHq?F;|W=l+MguUty{T9d8Z^uU!C{=d=bgKKQ0 zeA6w3Cz~T4e%|wfD3*6j5GiyvYOEyISGRK(WZ;3_LVF zv374z!s7`IvfOg|)i0cB&Jwe{Th?;UcbHV?hjm}HIv2c=DnMn-I}gMB)miR>PaT)X zv6m%BM86;m@ff40G$NOA4;$DV_6wbsu_+Y!O-2e91q}=6k^j73a}}A;kB-YqkNG;p zKvQnCeWcDc!+sxKbW9)y612qXGV(1Hp|}t%%GujS&w0c@^P@jA;}oKek;<*60%e%2 zS5m)KPP8MO6bXZe?uR2QBqU3XWeHBSdEdOk-mlXA*+_#$gGF_d4KeuS?T8OkQFjk& zYJ9U%5t=_Pg+-A^4>69cL{b(>;zgH$3xDs&4nLSI;<&^1hNq!3-g{(-UK$x6)siJa zWK=z`U)e&QN+Weg?hWx04IDi;(*tLI_5aVF!9T`CXKAB~7VzdRbP{%4!P+;?$t9pK z?|?%Xsh@w>oj=!q$d zUHTf2G0Vr=Lz?T-)!uyb_8QNFsKuGlX4|VbeE8_&?e;P8?mB;V<=pocg4NO+5ohi1jOzc^7(RkebETwn3Xj!It&psw=?L!7fY$V{%0%s zxIcWE-e7wR{g9r#`KdFGbPi{cm5+^La4Vf|MAMg=QE_o`^9q5j}g)T zX{Fgc4^grnt2<2om`zm?>l_(5Ha6CvNZm*3cb(Oe-LeS!v@XB}oJ%zHdnB$SYm6sh6inPH-pBOYo^}Sl1DrKrYDdsKqSq$Qo zEw1!zc!4HOHd#i)lC;bU z?VW0w?29S2DSoKYuF2X#S@B6Z1Gf?y#Rbui0XKs%)=vOw&a0=jol?qEu^U|a86($j zCuaOsrSW$ZpaH_C+|7i25yv)~geodrlAoruvPv3x(IGHZeVSFQ)1={Oy68S{D~qKW zIAv(UBnutuoWlDV`>;c%*T&QxkxaC8i#KiE9*4JiA6K1y8zQp0@%EwhN1}^thi5uQ z{!@+R9>@y6B|2+li_+J8^1A%p{JV8Ewx2%|d{&nJS+R#lrdv}U!fnM+G3n{VQ;O}e!c8@`YqVOYDU>@99F9J<8P0Bldc#a}QqoFmS*nOIyNhW^E zPaWJFwjudE7K}pu@<7r^JS+bZ3M1uKfP?pok3C{TkEiXkiZGFt(Da75B{l_Is{VgC zw>1T&Knwqgi~pjbgbM;}%CfXW%4`*)8>B9oJj8d+?|PHSuD-LZ%1{kq0B4q+(LE7q z(PWM~OuqGmkvpI3i@Gl zO;a)I(mK)gIqM`hXxc341WSSXX<9Dj_S5p*>s{BA^+y(C8(t?YrWXOZtW$U!)gPZv z^!q~scL$wkLcBk{xkCsG%9KJP|AE-E=}C%Wpla+L)ZabI@dnU_{CTU2;#kmi$0=to zKYZ9vThCdz(s-NdSq5}9ud>7=JwWwWT=ma?W>i@-ePfH+&i^3jc+lp z(r=!_kvL;ZKSAA$2n9Tl5WpBBF(=r2GYt>?*yILi2@v~FMsi95B6e1=$W)GZiyf>8 z;YjOir(|nowBH=+DBoYIi96oXxDECFzWjqHEg7x7R@u4J_M%e2E8iapXGAy&E-`SC zqTKJw6J;6n>8N!Z9xoy}1$xKqC8sy6 z7S67!^y?;5(oX~{ojdq+{;H)r9;Df8bS}XDx{S{(Hl+s1myUJd>-R$@kRcfYM7x1x zr}<_M2c?Ph+um20hVL3Pm9q1n&Upe|sWFjaERH$Gis+u=I!mU?upR&L3Xqju^n(2)n`G9Jd+X1Bf_3QZxBzG_cqvg=-2YnD$~vKT z7k+B5+7NtyQQU4qXg3CJjf;y|(?s2b?~#u5&}jA0r~9hnzoj~WZ4!bYB;QwTBGHPW zR#HNBksT6AzlrG}6)qS14VPIStR~+yhaZl79-FM*o8yNw-Vw9J53y3!dtJE2j}B2v zI9e_r>(OiT%JpCMizR160{Q+l3@<=ZNpcfi(`D$@!svTYfN%7Q6sZ+x`hN5$D;-t* zTMX5T$CrTeA8h2ywt62MIt+drDCSB#tz14tQE6uWDXg+M=lXj%pn9@8+QhZ5vih%0 zSN)wPnd71Lb)oe2*h{CH`qF)xF9YPs88}L7=R4B=UnbFq>UXI>#kuv`fs>L`X@5_# zwNDcpFZm*3kPpk~4ixs!HFN9t!jZvk$Be%L^1%{d#l$J$RvZf<0a7V>uOv2rNwgOZ9KRGR?=7zTGJXkZ}~kTa=Ha^YU|%K4=%#N}p;`KV#ED6LoAP2*71M8XhZ^J>D^^|&g+ z2{l4${-%M;Qe}46160(L>NCrnghy-0n0)XYz;20QOM|bN;>kg%f!>#$pA0FFGA7XN zxV_ebQ#m7yRR@M0Gyef7bo}uZPo}lXQ*m(*q@LAU-Xb*2ULdZpokn~1%cbKYH~z(I z{vUnHf7=1jB%m!+nyLV#L6I&xO^QZ*u1<9wObLqj!Ghh(>9T8oHssw>fBK$KAz-q# zW88oLv?u7va5690v)FC4)y7~D4|xg?CIz@7w>EYJza>$lG7%zWJiN11wir3;0fVx> zDiY1Sy-oVPW&hrl>~<@F*>4m1z>B~=TpOb|WyUm=bRyJM#bS0y6j@vT5_FX2TjO{Y zd~@;M>(3=qxwH*|~V7@=d7MBK8bsng2- zfJDJ4i>D*GLgMt{6!RLC`PvWWL5vC8AOVx|1{Yl}%P;z4!s-zskVE#E(7f=Bm?JVu zqP8k4LLgIe9;d}&oKl)^NUqJ9S_o@Ee82H75E}aHza6s`==)3G9EJ+{-p5L}9k9~o zvnhNw9Q?#^SL72b8(?Aq4D>p2J)L;n6N=h31+=lom;VeO(Z9O7YEvL(i|S+FNm;)s zh?8j_^#P#+9NO8E$v;j415_iXsAg!m#eWm}|GKikl@0%EtuHX*B2m8w%z2mMQ@FCyzw-9?bMg$;1LpCEfsTvor0AX93zmrG0r7uUbn_Co3}ev2>}X8%3N z-VF1)LMLz?VY7Rdv=MTG`#AQ>VR3z<%W)!E!H_EERSxLBj&yNPBs3cF$QTS>c?Ut&-);%J-IH0kRDUG}XPPGgag z-aFn1cR=+B-0Bqu?&>g$5tJ72tkJ-WmpZ9&`Lo%#!1a$JAn&IpMa4Ex0hUx^Tr>`+ zwkX?WB|4TBc3B{Iao_fV7M__t&3x^3tSNa%u16RRjkSC~A|&3PeyIRy43&TuNae5l zeX$SX;7sI>L)K_L0HZr4Pu_92c z%fy1Hj|&$0|NWuz17Or@T0WVQk4(OAr<+eA`}y*5_4RZ!Tcge0XT9bZk$k{*0cfR~ zzG5J_aN`&Ghhpg&|AWti&$VRt_@m5cd%_MOD^K*yTZEcFyGN-DoLDMf@{{dNsM0%n zY8T$kCz$fDU}tG0{dp;TJ^Grt%-Fw&5T3c_UEk;Uymw0C;}aAr=+2H^2?M%-`8ZmeQ9VCuKQr?w@u^c{k%~S_bnX z{8leoF${TK{VI+!xHwnGd&M9&+eN-SF6(6rYKOu?Wzp|`zTEqvrq&}pyQWd+ z*r6n@H8-ZT`Q+bom_C7?v`mT9ud`bS2qzD|Wbe@lqcEN~Wx+5eWT2I&jR?;vVcs3C zh<4mv%dE-{4<1x@Hs4i>HJARFX|~?92-3OKN~P2ZQZ32k`^oxFmEr`3B`!0Ubjgci%EQ+LiIN7w& z(IE?{V|rcd7X5l9Q}I$S-{B!YH#+2aL#V7K???yxJkP~y4n0WY87YcmcT{XFsd7;z z>w0y6hg#|#f7T8cF5@cRe4M_pHydh?A1W=;e?J!qwDTJ`!hRM7$~%{S2U=S5Z%G70 zS@qPV5*hTbs+pYjI`5d~+^?Mr+=x&+wB*?RrzIbq9#~u3cD~UlOBS077J(oK7l13H zSl@rZJKe3T!_Il?^Wk^+^2YDh%apXKW^SeT8ierr=rkcTZ6-NzlzNj_IStYIY0#9SO|2FuSfWi%vg=EW_QRItlMzlTkG3${I9 z@hd-q^1dJB^swuUXBFG@n~xNod0dOCZ%e~!L~sZ?`ODaS^3!vCjyYAzR*97fD*HAX zC^k2Z38o)rBcuxv($Oa#km#5HQ)Ikrasca8mmuPo=2HqKOen$B#!<$dniBy zYaD(b+$4qHTXNlq=Sj%?ua9Ne_R~@i z9L1ahCE+Cv6cl|@h-1bVxhQjf=gvnwp8hQo+aCPDwJI~l*@oRe5#t0*$cWMOW2Oub zC(It{Duf;0rpM^@ugmC{b=1~P?-SsG)v6?^-}E(#gnqLx5$D>EKHG{W6n3YbvEh$A zmKv220fYMm2p~soe4YM_8>jZf8>8EVJ{=thLY~67i1=QcPj3-qk4-;aSV_c@?gWjW z`ypLbe_dNM5E;A?fxF^^zZ>|7k2?yxdwMF1s#;S=Qw=Diq=OO@i>rOon$Fvcw+WrD z=3fg0kCxH!LkGW*$rVLX0iO_r_HvQwYT}2gCx~ngMGsxZB$xoHC<>NDszqYR1J`Ch zx5Tq}viyP$)vTT_Dc=2wS!7(e`68BP39^~~;a_es(eAILDGBapLw#T`!aC8=Fbco) zT2xShkbsK_jPI>I6lZ8S==z=Nu2UF5vN3ut`x7V}pF#hAB9cwMy*(yYi~J&`a~hxTA!lIF?a_kAsCD9SWyU?F)-OC%T*H z_C|=e>U-GS4;mm}F@g04o`0=jg2~?p_#$i-QlVt@kn~(@D{Lcdu~CcRM&8}OEH!0` zf|oF%5pMHa1_Vexu0LZapQgWK5MCnm4{LpcMa<;R?rGNf9yZ9@-k;wF>-LXyhj}uc z#w_tUzD>kg>}oPoJ`!;GoXPj+Tz$2CK=S#0PspBIUlDH^uur5_xV{yT7b@=<(e9NM)LdoG*?HIWY zlLquSl5auw#|~Wem_%Jx&xH3BYGlfF^erDwlvm^@s~f`uVw>;B2zgpPc1ZT)@XxMV zC9f(heN_e#7%4fEOl_{b;`>mI(k@%^e1woiUQk976oQ|&@oQ4Jm=Q=51 ziu(8{aEzlJTMWV({GYfC^=5~;{?Z`R`YA}%&vlAfwL(!Fk}K=QXB^e8cy=o{ z{_SQ}t($$Kg*<{Z3{SDl5XZ6oUqHpkUT$TDxrJ{sr&8wwGk5hvr$PCr^ zn6WYuzbG}`8Qi>OsFaU56ma=NOr`v@Gha(gRh|I^Ch975@OPQlll3w%dA>>lS-2{D#Y=T}-~H;PqW)GA+&zo*IF=NPrAmC?x9ydd94 zX64rt-ADPNJoOK&zVP#di*!%#V`9o?3nFbE_LEl~4+mbvo@RWfHt)o_Ek{=U=sKjK za`8QXRyn}8KE8OQ8M{$EC_dAc=-gjezq?Bkbgv}^V;xw860c-QgTW#`I-r2pQw@Vv z$Vbl3WG5#lL_;N<6#BwJLBZ6$!fVeZTq3buGG#>H!;mQdT^dk(6d;0_Dv09xdSa|Sm(_=}*c=jB`}*99 z5J7co$iAtP9Zf2`VUe((ok>cqZDgkUOAAAH#ERZ5)w*T$)ck5;;2D6PjYv3K0zT(`9e)RO&rQ^ zPWT^)B!_|9NQ#(yqm2nvEU#w{SUfztwKZzo(ihMHR2n~9k+USGv*>qLmKA<`3Ae{* z0@Au63EdhMi=$bfr}TZdvDglr0jUa;-V69T9A|>Y*IG7I8(l50)@1wJIAJ2rWC0g4 zWC5~``e@oRBs1F~vJA`-p|OU!!^H~&`^c}*yUm>>X;Pi7D75&D={PNt;uZrleJPg+ z)_{5obMb^5F>!-`%G4^u&(5q?5#Xo%Sq6n0V>;=$XV=%)u_y_*H#h4VMdAJ5Ail|Q zv%t&?WXyv$5tOMrNbSxU?M>DYs>QhXFMJ=}gP24mT_GC|=V+yXgy3=RgV(Oeg`_J_ zT4(;ymMc~@s$o^I{imX+N$JGvY0}PZwsR+w{$lKa4%Jq$e?&9Z8wANkTPn#uJX^{& z?L8}5zF#WyRF-(i!Z0HYc_RD|RQUTh-6`$7o@rVii@AT#AM4KAa*exYU+8BjYsXVE zpJ^_Y3FmdjBd&S1Zv=p;%AsaQ74E8FHQI*q7C*Js31=0Nr0 zyLS6raoGmVw2(=ufE$koe+)OY6R$tTU~E1nujWs#ajy{*$DHIvC19C`Gfyp;N$jK< zH%xkU=W78+fb*DJz!;Yl++Pw6KJ&jnR$-~47PW2?x!Wr`NHZCd25s+J5*cG*W=EGr z{VWft7^oovK->bK?GUjo;+Vde?L=dOiL9SAp1BxYS2P>_>m8Hk^(`fRn-m=#-9Xt? zBS%<@=w7-Op~$sc$;SS-lC%a)HCj?TJ3C~0_?ewnatw?ZHK2eXp`wb5nbvEGz7kE0 zGffP@+SvPgPVs3VS}5rntH!jK0^uAA-)x0sIhvkY~AfjyfjE|sAADkMYqFx zzh5w=!;K%mdbL#EkjL&CnE~d7nq4Z7HRm^OQt!EqR`0uwrV9B!3&ORWF&*TjaiNI5 z&qU1k$=tyJLw)H#7{Xd~l_1B0zC;&l_Fco<6nWq0ibWp29b5+Y6t_mD$_Oc#CeRHf z+08{YsoD1*;iZosPnTNEn!nPzA}X&koGDr*bn&?3x+E3C%Qs{0Y360Bb$PN=9b{1E z@KUuehz3p4q*7%kSfX-)@kwVT+eT-1+d@rAZt}n}>+iL>hyR+JR}P)luFg)#0Zns3_bEpGU=bW2hlZME~Zn# zA`OSGwj@&&A%7qCF&)U$t$)XcWD$*WPy#41m#Uf%Jso+~i)F%b5V!Sa0!|eIPuD1= z0ers*sbbcYjq3M9zLu9i0}Pk9AZHMIB39hfVTW=n$*o8`Kh`103`=kwSF;y>)1fso z6{q98`Xo&TR}_h$EE(!bFkACEL;J@Ei77e#R(U*4xQ!GFGJ!(9-6yWz_U>u>=V1Nk zTO5iLzR0i5jbT<<*^wOoEXbY>G?dU0xu%5!QPQ+P&x%P8{XBx*me&DBqkiW#4;B8JS7P|_! z*4fdo`1vE2(YdSiB?AMupB!SmHSJ;o;FaHiiL%!K5Q&q)Lq%&)(Zb9ICHk`V*{n=_hNgU)S>JXpsdONBE9&4x#s< zC_-xg2O036GA;`4-_i!pc%!1~8@gMt5C>3Qj{ zF-{r!LfH-vNz&cRZB~cpxWkt|ie-Id5Ei3u}hoi{Sj?;Zmm_OMG_HgYMy;LtU;#7pN zc-S+UwbsGeSKfvJ?Nt+lc6Z@d11TbP0-D+CF!E#S@yZ77mIfEe*?!uILJ>HXu?AlrDV+<;_q6BW zb>J58|72|IaacC5JGmF8!?~DTn*4t~#{TDbw-j?JyPqHohlX4yyx8SJ zEggQzREEGXZPO@Li4Rw#e~QAX{?67mtyfumt}fqT0vi40FIb<3YPqnAk;*C{!%b=& zD)x&M6J&@~N~9clw!%T2M{i#EMyBJ8hi3QamTL{B)m(>q0yp?#^xMC&wm)$D-Cw!O zW_4iW9Tem$J*lNL^%oPM*v>JOIQ5KnpkOA<>18g|6PH7FO)0I(oLgP%_nWpT> zb?d;{M(tebACzY>IaDDG1H4ZAD5R|pc&C2#7;slw;nsHF4oU^Frc0XU+!9+V&s8lu z2Efp1ZS=guSlHQjtAK>jKmhIfkf>{y1J!tRMs2R9D7~eT;KT*aPQAy!qb$^W7sBQ* zEx{8$R=rZFJB?*BT{ink+JZ-v5rL`~2FE2sa^|Hg6gC~a zi|ps>&V(}C4Zpsxftl^ALE`>}e&ccI6RGB2R7y@>4Z-ZbjWIldHr{8D+lRM?#UcOQ z-_yZE&1(YIm?m?@4~^XXtCm@KM|I$KR4H4?#cFZfxuQ8^fA$B zmEC#V{L-e^jUt?w3;|j1gnXsQK2DE%^=Q)kGGo#9KI%m#HCiDK9IoaSh6{BqtbaaOb{t3t2umu@)0*Q?=w_L~kHPj1xJz(p1A zK#wf3!!XIscvx=c+=mF*PTTn;jxW6m2 z_DOVh6~7wF4MRc~E$I+0OS%yWNTycuAswe*f+Gs(&vmmPG;;r06zUr~Bp;H(W**p; z0U7RDCbQe)kEUv-wBD=1dr zFihCaOF}Nh66o8V_@9hsEb;4xst4j+AJSiLLWeHi!Rjq>xfrhH7M&S2X;UTTi*-%@ zcYJ+K&j3qljWcn6G+y-Mb2#tz_jTDrojDN*Hzm0?nBI9k=YQ>d1R$tN#P;a>W5hhS zbidb6Mq2RVO@k>~KUMWo>GT}CNO7kqbycgLHm*p7udy4%CR*FuG8~EBmm1BFn za1(OgjaT38&O0g~vP2?{+nt)K=cMf(Pn5YdjW1k04bIC}`%kY`7SNDN5~e5t9^CQk z?R+P&by78h`iI4r@!J&mD+hF9260Wb-Q|#w8IfLb`dEKl-Y}&D7fE+AqAlcG^~e)~ zJh4_*-87aKQ$u^RLH8`aoo`%-1kmh1JShkkQ^67xJ(fd9=9Jil1anTCdC??(J{E#O z+d_-i6fwE+_|&DF@4~#5Xj2X``NWp^VLix%M|u1fIQs(Idc<`YRP*Br5H+wtl5 zlI{@(SZix5CN62fS`KDHW0Pu;N>IdzZ;NVU4+(qp!Bk9F^Z}RBzcx}}f&UNK z3{dSCsI>FZ%n+LqfK!ng(lY5;)d_Ld_bHAhRtCoP^q+2U^}2b$KiBS;5Uy7JfPOI$ z{V6h3|8-)7Lj`<&ZhcYfvMk^Fu>PcIIHVFqCMhrFk={O3A5oX++iJyGW+)4>RK{VX zu7*lOvwx|6VToUMjmYNWri1oiTy@xq9BBW%Ek1Kqql?Jud=hGPbLm-i!O6+Q~Y+q{3F6LM5 ze0|nGUhCXj_tlnL_+1XQ4N6Hvqu?C(_bwJledc zkL&UN?7!}1Z!zxHK85Y$bI_*pZB%QXPJ9axPAp|jzakAbTb9Z@Tr1t3pMY7T2xA)@f=K)QAWtzHw)_q> zE3diy@CXqSM>ll?HSo^yaa2&y8yZ@=Jga#K63*@c$zkM#oGx3rJr*;mS=60aWvxLo z*KccrC;PB`IG|mMs{jG~Mvbx<(!a5eF=5ERti>YCx@J(;#T=!W?JCIFr z=Q{_Ei^0$n>LTO5{>=0Rm}QD3>&!8=p&)By9Xr|O~0@2NrNSz~T1*HF=NxHfns-x~_EA#dkv^dT~l&ZrnLZ#p`xa(2#zk zRi}-HydOUro$Tz|1N5I-TNOIw$$DqYBg#xazgz12st$=T7A2u)Ce?a#DqqtNtqrdA zvB7ewunv_4e@vS;-GyMfSs_t+E@RD_B-8a&>HNscqb+ClR^m02O|pc@#Ork|wSQ6L zy=E@b)NwsoWlSTTZ0ByG`NcP~QJAm{{8L-U#-ee{WkKTq4O0K6RV&SdR=mkT)sk|N zCTRi=xY21+h@nL`&P47ZI>Pq1JLy6@-`k<7ue^ySLuq>9Q%Xf1OK!IUYPKt(r6}r# zTlErQ_79+q1=L&3GZmWDxyB>|(T|Y?2_s8ImTrr0rmT5$7h^4PK$0U_?{8a9_jpTY zp%_cl#CaF7U_hD!*yhKxY<-90!&Ak!3E$Ld^Gjw~&1UrcBG$!8?=5Cif7=4RoVBso zg6KT~561h*w5Y}>x*X4H$1uC!%84dXE$CfIf#$WArwk1&K>YbI!;n#quELUS#WIid z6?>CGqOzLa%%!dQd;vB_=^Gksy$C2F3kK^BK(tbQCS)rJhO2hveAhpFCS^+B!)o;6 z1+3K22XtVi;7gS9n!}ys)~gUAPqhdmBTD=m>BP{RIp^eCcWfIWHJx9-NQDK#Uw+r3Ek5i<5uRgz%}8bYhU z)Ympxo7TluQXk)se4RK(E!nLEE`#`TM3Vf=T0t^j7w1vtOjsgz`n`3v{Ao!=;798m zwCf6n0B7%rjk$y0JT1K>foUyN(E2v$assAQVEo)b*0jlgLV6l&jBz08NTBL`y{e5- zt3KO$F4Lo!Cuu z>d*6&+q?2V*AM(tmL$$pFgMb+uNdyxov|>KX-lLY>_zKIjA!P-S{f4O=*f}?1)6%2 znr;pdt%yx|ZX`ZaiaM9Yg!hq5K;SFaOlXRlFT2aZA`sEOS9EqhFVr7&J4)r7N_4y@ zPpIWDi3H8ln+NKTXfz#^q&QEx$&d9iBYUp{zesV9&f%*FKb3(#S6Rla171pykdRj# zH7sRU6@rJCpn}9==Mc`kJ+9-xI8Tv>KK@F!#@31OoY^dMI|J$2Rs&{n#<#!s|T_8uvz-FzMMxzb2 zOXk`2``c{(j=nkAf z7;uf3Q2irIx5H)+_&1{D5mXaKD2xJsRBPo|HMM~PP*ru&!W_7ka0){Bh>#)QSxmIsq@7%Z#wDSt_JR3S@2pdGi;LmMjDt_FHQ^3vbQ`O{7 zpEd>p`!h&XT2BUKtJSw08dEyF(xNXBZ>m|W%Q?GhENAz0vm%(7-tm|VQ4O#hJU&;5 zCK5}xV=ourP)#B#SGMT-=e>oJY`UFetS&d69K`adqT{nfyvf3|PjfHZ(E#SJy9XKY zwF#s`+>zfRfnlFyQ2D#N%EWZlZ-rsWs_7VOfB-b-y~6=O=sr?NMcG=!TJuN! zFM4Os{=3YpLAvz9Fg1v(yoG!5&7h{fX=IwOh{8fxtkoXb^sS_cRkFQ#>qS@Fk5j)O zL3-gnz~bU?G|4)>8I{^bx(+78oG(GiOLLJ>*{tGboUv9^JUT4RPob58o+S~5VIIF9 zRD-bScA(TH+Xwwd_^kcjHm@FS6>He}JuFI5ggDM%1(XhV^xSAD@xYYbn`% zvzYI?$BK)SUl~UcE~_@e)+eH+&s=UlPJuH&u+Ia(gO?>@tMDk9_l}p5Qque3uNvsd z1VctT;xN_BC6$Ke0f;lCCgB5)D2bb-RzD%HC^$|)hWkh|bLI&%CDt0qMT4{I$)#eW zwxpLbl@2^LoB2>>bvwW$(OSkG0B~yN+E+xo8SGY#B4-dRO4DPq={Zb=CJ8RbR$;Nt zNmb2)$oGrR2T;bM1VsmLdH;~jY@LCqQ@_r*$>eAH(T9jR^)JdYi6*b!=P}0vTJrWS z(C=XmwK3V|)Cd2j`@DEY)+JC~dSp|AePDB&54^usV=I0mpPex1A2PI(P18?6v!?gm zr8mec+G1@xN1D;}zOOMAdRV9<4XrLucD(g|@~HNHSUvArf|E~~TK?J_j_`(`ux5=* z0)p57hpX^!I~c;v`fn!fXbMv_OAAoF^cP$r(wP5Z5K<1hU!2eEl}1GZzgl$l2HAmr z-7|v|h;_j#Js>Bh3N)M-5isH=E%6y(f4lSlGG;uRwBOr2$KrC(4Qy;~Qf!3W3Y5jV z3J*bxQ$Bw`mO|_;&R2}VQi3o5q;N3L$s*8Sz+3)>|5~FRt{q z3&W2q-Dti@kmv4B9YFgVyTt-j%CgvkMvI@mQH8NN!w?S|PKEADag_&z+P+2T`$YIZ ztyc3~z*K|i>Lr5tMqCRTt|Da{U?W!aCwtPP@(_lbilv`_L;8$6)pG@B$L77-{Jg>sNwC-^p?eR- z_Z5Uli-X8omJD{b38JUXj5wVYxT_`9J?!J4WSoq+ty2%d`sVfexu!dutVu0QY|Wuu zHLKXk5xQJ?5BFvgx8Y0ih%&(Bl)l`7D_?QhSwu9dai^ttX=eb`13-}YyD0xwo(Mw> zg+m}mr6?TA;_7xYuYa9FWHSOEaZ);sWBzWR*HkhfYmq|WJy!jGXFNp@_Lao$NzD&UV7%Bs@?8?!gs?BcMv zK6q1bO@Z>_RW{|7K*Pk0gdeoWrT}3d4peai-7;DyNF+Hmb2Il58K?g+&`Cq-539av zC7rtJ1Q}(pwFjdpG4fd*2AXc|WNZ*#2QaDXpnBLB&_{nb7dezBc*8%pIihFBbKl51 zBk$7P9Tz;#^1RLk$fK_+i2qBoJ@@+NEEi(BUEFa}Vl&?&M_c_OZ=Ps4B895XzRSy5 znyBh#Imuw=`{cN#24E0B_HxomHdil+&!E5W_TTVN5wh(MHj|C6)TkAeSk!y?(I`ke zUl(U$%mrZUO(BLMAUw3n>W4fS@@zXeUdhEW439`co=T*lfF7?TF*YFep#*Xi6+N1~KOM(|RJ#Dr#gsReYNy}TrOI#H7G zS@p-gS9}pq{wk88QQ5$GRY9`06wl}Csf&tLckH@@9f=UoU#ti{#K`Q0g zxf4^z>I9SF2olpMkVxC05<1Jq6X{F*+AdO57^EyiboC%QB}Id@)DIoWb*PO7=YarM zT%E;G+6rs^{0G&bo~0UUKL1>vcgAZi71yrxHW|a4C1}dxD&CV4nuTu9St8HPhuT#I zwW$ALtnfqq4`cq9H?NZgv_#D8Eb{p0^Gkf+fjod?)qvWWN7HCFTt2hDB}V$~DsB>| z6%20)P}PmrbC%<~Q7x!ltv8J&RF(r1&Km88{ASF07sh}Aj;Yt}B2iI`H_jwH$IG-^ zpq}iEnC^o{rM&y?dKL~N#j$X{ymtwhL&AVTSx8>L1#UU?OWPJvYKso3b!@3ET_ejW z*|dkIh3NeQ4ngb9?q%WOpLJkZyyqi?FI-mB{&;!hVcfKr(}nco)i2^>Jv)jOsJr{B zmYn}kDGkmdI^cC^Y1NFu+D&*!Z*@R(%f5Y6-gu-wLk;j5dUJ317m{(8us?}m870C$ zmGK!kMN$*rrAR?6-#T(?pvW^$GB5_%>}yyTNZ^WnVt-BN@;)M7=BdWZ#!`0QFs6zf z;drTBg9Tu_b7`o91Z)B4BD?YbL)n|aL)pFmhx#lD2f2-%mAY^5@T2!*jPgX~$$ z9u?Uc>trigl6`E6A^S6o)W{wxA$zu&?EiCHo~P&YJfGj^`+xCr&)jqF`#$Gd-q-uO z&N)N+MCuW&YUN#(a4Y<{Scuw6uR^p5(mk%`1T@E}z>NnSn2E+X6JC&Hzv-_ml92eE zF{{}$Sje-M6Itdoh2Uf|?-f;%*YBqQ6>xQ#JO=uj&7uo<>$0i+n?2$uyCn6 z%QBh#1AA%`y3*GBQZz|COt;Ek^~`Ez!a~|>E6Sddu8v)a{y8}MsHcPsd+$BRTE=!M z@LhsdOVP}c3v&_)?&{yHZSJ%*Hd#A1YLu~PMdYlFHQ~SB`gKo(j=+BFxTxpb7iNz| zHL6%o8mw&mA!6j&uPIQbmD5;#)7jbHF*p9eoWJb)*Ra*^P2^v5@k2$M4emG5>wNb_ucKo@ z&>=5X8z(zRVJMnQ)J!~@zjs=TL@va|Y3TC|uXua*BV? z{gO-hXG`9^cxW6blBRMB)ggKe@cxIvjmLg4YadWg z2}ibAo`8xOJ8QIJP|Ip)t9jlWSmj)&R%|Xhv_n8Q0H)jt>qWa(embSo!fc)+KCK8? z8uw&ekX`08J#=yfiAr{Tg5Ia40+s{&1=hVD+v_SF+gip@tAU?*E z87zC{K6mT%=4-ucSSnv<44{+;W6El^`zOCfn9aSZ7T%XyP~a~p_d0+@?_3e8P{Po` zh+>^ZD!YU2#)IB>y-i+dK0@y;FqwVo-hO6=dc8-%&u_-C`X8gH`uxX{e#_W*Pqp6v zCY%?r;o1H5LTl335f(*gJ~6S|rUHn#?y58Bsh}ru!qyq_?tB%eES9t7#wRuR_Fh3W zaieo@fC~A5dYdOZ)8)y5&FYP=3w(yF!dhN$v<30!F`DSGq@n|d-W$|rw@UnMtJyqp z`9vx{2)00NtDVU~8}sq82B^S+g`f$7S`L@qfd5!nF2LP!F1Xu)lBE4Ax@@*HXu+3V z!se3rjLZwAyw7m#hsQ3qefN)gs`&=o(K}&0-Zbz-=x*IuN)%6Jqjs*^vnx49t?`Ai=qa!$|FrIxFm`fxy-^nW=lE)^| z3vBmuO@is1M|$*4XyLBAMp@h!dRc_hu3IG;j%MYn(|-Z3^h#mc z<&(O$`x*Rk_s+YIL5;u2sl&&%&soEu)nxZ z3f<-ch%$@Gvp86#Dx#_Cuw!ZIy!!Vz|KhODSMP{V2B%^**4}^7%763b?a_R19lGX0 zfd~akub;hdgebUkg7&6gkxbR+^c$J9BU@8Eu5t%uOIQPL}lpP({ zn=H6M^Hr@^x4W2E2j^?&8hs?ihFA_ARHR>decJHHOOY&3o>039|8Anb@QWwB6RQlu zIk22>w%liWPK~1EZ;U=e@xNxejp*X9K0L^F=WwwRPj2-f*D_e56ntCrJ2P*~?&_^m z0npBl!SMfl@G0-#nr0r(t7@VK8$K$`gMSbmiYwtfN3!%# zDM7GSS~&{9-;yqLI+Xqa_SY<)zGPsK$aeyU344e5@+wS+8O-Ja+mGOtd@6Ej8Tb4rSr0GTew9^{}fu~l0C>q)OpG-UQ4uF2)E{&4XP<3 zEKq|P_rnZ&IX)v(wjy;&R7@8?>x)qPS9vrYy4)T_S>$)swvdXLMrpQwP1`m_i~11Q z$@J*N`IyGO?iS-VxI`}7*KotR#*dPO<2Q49USQ4;Tit4+OFK_HhF^2mw{cha3g+{D zNKc|PdXEp7Qavp&mM?s{H`#Own9n)J&V0tV4U8YeROnEL1?=x5ljJy}W&K_JF7meT z3J`GPEdl-(6Gz}84z=ws@W~q~We?VI_vLyXS>O~C@1VyIe6Vf*gVu#j1h0>_ee}hl z_Eoc8<=P_|UM>5+&HH2%%}vtll-E{9b-nB894opNNyVkk!DS5DBy2nq> z-wzH(_%3_cuKK;|d&RPkrhn`ZVwCIE1#TOE*aEV`%C{5jsnA-2My&Z20mD4MHfpnh z851zs8g6|=O$NdF0{+?C9^E_=pM`hJ(2K+V`qS#29S2=`=@kQe&Y@N3o_wrCTv@zX z+%##Re~|M--W6e&S4IcyUQT_;d@FNRNpIG%tW5W8c!g5ojgyv{HeQI6f_I}evc5bS zojcg*&ia8r^8ob)T1B|yehq)BKj0$IH4Qu=R+`AY|81A^_4pOe4;5Fe5UX!AhCHR{ zr(ofoI=2#P_U>$|c>x16L_4|Bhz)|Iv)4jJmg;n341e4iLTFG?6&lrapogrPW!aj{ z*$T^iT2gU6i8J%CC|DjTx!2K-@=mD3edNP;2ku1js1%_Pq>^*_c^0Feg5@xJ!NSMP zF;c8&7vJ=tBE&WLS|`Z|9Nk^)y8NSCF7jin9Xjdv!BD(eGqHQaa$*Pa^mGl+HW402Ds`u2hv~o4yv*Byq7<)b)&uQv6SqRmTthsPF)%#?yFMD2q zFKc*14e3+ea%blUp*psuMVxOg;Q4qv6?q->vC9~n8q$k`jhg(pgL zXpW{Yu~m51`IA5RuATnXX$|e>3C(D!yy`#VFFK%kbVH;}`E2Nn{$aC*?oU7KewGUc zj9xq%GD~Ty%f3JEm~(v>6Wu*Co16L@+>EOdw6%U@s@G>UGmSrAY-VLSAA6n%kL!eD+IeIU@qmLbNtRp^*m5I(8PG3bkO?Hw~oq@F?0 z1qW}NyS$f=Sy=_v@=ponzvLM7ZQGyI6jPf%*p<+e^Fg6%@Z|^G$%uUt$&J;R<_ZD0 zMULp7!#`Fp{fA~8$|Bz65}k#bX6X8_;Rv*hCkZ~U1l9$Fj(49GEyMXFQs?JWYj*L2 zf`PTwOths^1Zl4c`rvF851XH9V$^e3zyalJ-Y{Fv*2&kpbDPYi zzxp0L@Kn|jZB;^WvD_6S@o+kdLyhC^WbMZ(!*#e?*H1@{YTu}9KNYv?(>Lf5@f|U! z)~?JRxjVIf;_Q*#$L7)~Aex{`lkl$5{kaWkJLhD-N)2@{m&PQS8~^8Sf~{Hl&6(dW zbLVMSiA81R?#(n3g$4{pT_1Wx)NCHiXP$`obrQXOkHeGK5KRdjs>~iHaTC->sl=nq zWxjM%iWX=*;j}$>R5YM&$~`aIFu^^hFt#v=FGtnGq)7^CpS)NXn##df4&GW!tOo54 zmKDqg!a7=CuW>gmHj8!7-Hb)8%!VBi)qEVsb8h;& zw>YrEz{tmtoS-Lyxof8LpBh=2+tOacl{_nee%fURGwhzvt&@k8FT{z$dz5G`Lalfv zbs3rQcm(xP(bl|<#uyz$WO?%AJQUJ(W#RVg$FA7iH}Ty2!Xg;LqaH8!LO!|V^HzA~ zIc#zjuWx!xGL=6hiyZm=iO}vqeplGeK>lW#yLP~h-}0cxuBORb9t5h`kiP=GLRtk5 zJhy%g5yy*&%VX?#$)M{oAHvu%^pZ_8ytgMi0alLYD;S}N{Q2+pn}rTgJ^SG)uO9N- z*I}S8nbq6bI@HW_jzwsBheJp7@Ysf)yjgoVH-V40F_`voKvsEq!;6H{$9aoWG_VbW zj=SG9mA)Zg7jBDSK5x&FNfa{MsFYSrN=rcS2(JS$5oFh@m1QChojaRVb<#{tb8F+l zT<^)9&g{Sf)VZCZ{5~>hTP+yh-&D2Z+P``FESD12k^`@DR^< zjhWoWub1BuFs4*0b@VCO$0$g85DtJXc=r)5`3J%Rg04_2iL@$RyTx?ZYjwIDtt1;x zU4|YxDk}cOyzy4aNwGy6bcJLOFo1ShKSPSUKe}POlh(Xjw!I2@6|HrEC zX#MZ5unk&_fY)9{k2FdF6PFd8N9M6T}p1GyQ0Slji3^X6BWPczsqom~Dn7yIi>`@NtAzQ>Rv)7B*MPTUbIK z3{;Ex6ZifwRTDbcsl61Cc?_z7i?G!@aFz1D`0VsTWud#_pgJ6rFP`P>$?l7!(&F{NCaPJ3(icU3kfD?>K_~&TYr-ZT15g z?VqvxS78Da47NkJ=sEoS)leknVFe6xMoHBTCrx z@30_+qN|YW%J5Z3Y8Yt{@!OU^Alt73Sw7l2=1Ef~QUR8c3|xe>)kDtv66qnpb)JGo z9aT=G>XW~lGDN9B9Q~77{e{#!M2r3c9@@bPHjD4T65@sq7c^ZBxEc*MpVwswxdm!Wzy&8Wr3_&{;$qFB|ry+TQFJQ&`!X*EE1PL7u50dwI7t>N$a)deKfojwGCK$NE%fZS8K^ zjfH<5x1v4-cTi;BVcNSK;lCZX9{|8~i_8J^2tWhs&6j24D8nmEqJ?3Q&taZZ&PP$r znw|plrX}Q7z;|<;1>l^bAIf^yS440AY66ZMZL8A^D}Ekt@@>@?kG*OC9+bc- zgtanXyu4O(GtQe|D*>4XTP4Y>|K4V)4uSpo`c9j_VSs^tHNW0s|Mz)Fs>=$3ljwub z)n_kCAgt81@szq*{!)BO=wxYk4Gny=dYz(j9511VHg0J)#k=V02e^_FT9PT3GqClX{a-7$<=u8ClK=8Q=v9%Z>b7;w4aq<B$fM>K*_ zPu&Iw69j;bac|IhX2cX!0bchGn0O>tWyr`9sQ0CHnGZ<(H~YImDP( zW6&|YcXS|^PYz!co>uiUzh$gAD#I-f$5G3&J3$|J@)wgzQUqz7C8Zxi#MFmDg7pK2|RN2smKU3t! zb0&Kp`v_+G^h#R1rR_9Vgm!k_gVQ4eAxam1a8KHDERVz+!RiB!NBxTBKWAc-c+-Al z(G{Osz8QHdbi^(>n2sk#=1Fl@Npu)YysSFiXPps{$CL*#g->oU#ZNg3F7G+Px+3L0 zkQaNc<~T-eypFnzR9w)JoO%dRjU*aEIv9J}p*N;1n=tqKF-Q(4*B843oY8V!tZ3

h_M5>6b2J5I}WA_xL(3UH>FUf>_LX;aijKZKsP+aiv-m7 zFncg{*_fO_kPi-Uny6nvr#QHxZwD{N!H#DO9GOCK@qE86Ul%9Qu8`|cy?n!I;u8;| zAVmgd*`5+Bre<^gql53v=Sx93FH0Zur}8qL|ERdR!tVPy?uSv0CprInf!t=Km*X_{ zab4q@Yj9Xt?6qq*ls;EUb1qGG$G>s+fTyLsgwdLf`f}%vS#)cm1uIKp#OC<P+qNNvC!e`L^V?@9>mgN*r zuNw!T#wrUCB5AzJZh4G?<475u`I#SvXsQmQs6y3IU%B<6Z_#MM^Q^H`)|E!{-2okF z13MC8gLcT~^D}1^Q|n}>zA+*tzu7#2mMM-!7|7dHNdzSF;M9+DiR+<%2f@F;`%~3_ zDsq>e{`cc{R=w@TyLXeh;|{c%2DFMmk8G;pmo?ENcUb{zvOJG>Dmf1D4lLlgWDpn% zmQN{($VAXtbY{iJ|1bjGi_QINoDnrr6@$7IxznT;OsYLe6E zrtTqLB2V0vChuhuy{x3$j);Nb*v@Cm6sDzHhfq@k)B2-uwr11NX`ILzp8nJkunKjVW{NMtmIF&JNAFam&QCKlfa98Y7!Tj1&y9-Lo_bee5a{aqM#S+)ex-`Mi>N8yoM(0`RqF>yB>x81A4W zyaFcIok%___A96G5ewnU8*5*3zXvX7ao(3m_T{@To)YaPI$qfDi5y+AV%l^dp#rt- z*-jUl7AGT_`VDJt9?{kg;+c%wRl~m?8~!~S{%c#{{X0MZaaGYdz;e!kMb*vix6rr; z=wlHa5~hThVYz)uSla^$!hoZzT-x9OjAIX*qJ2XGPqS}>I}aX<^d!(Kb=`~nkr8i* zW{NgZ+r!zr|EJ_m^Y>sS?jGsmxDO%$Jp1^vKzd@=N1wef>;lOit&1>0Ka`h4lpmai zm3piouBNe_lxWn`$a|CslkQccmXo?h{SmLN!S!BEqvTELHQ%;V8v*;6V@ysobYes; zVRGw>(boiV;p~?$j1LtQ049^zl?USrXOC}a?211v>jnR2(02o!p(oP^eVDnIQAzRN-?!PH__eaP+)X@IKZQZyV{4)E!a114iKV#H@43$|+o%~F znT!!-63@M2htFFKA@j&|g;%NHho6|i*f@_W|MF?sSYK>g z8%E>C4*_#Z@qDQ20{EWJd`Bf0Y{ky4te0!qx(R{*Xz0l)!NrEIx;vY-HZK=c^M`a0 zs3P&N>zaBnL6NacwwH35;P=m%{?9X8bts6^7JCRZQ2uxP_VRRq&Q^Vl=F zB+@2F?wh70SaK5h;OG(f!;u><>vMNXxtbD$JpEwE@B=#b*R(a9>Y)=L$f)Vk<{b+| zr3Rx%HNU|S1y5v+HRup^EOao(jw+a|5jpZxxB6JDJOb4|kDkC?fKz+#=WJ{ok_uy3 z5Auna8F*e~ydfQAXz#tg)J?rU$$Gg20GpiiO3AgFrUah>k#*r3P$0FPbLKtg{&CV} zGLZebNibsvWu80bP}~o)!JA*K_@!Xs@_7Uah;J%%uQ^Jz zuBFf6*0L!Lajh-Ozmr3!Ym-;mmDZ(uJ@4}Khy5pV?})31RPH-^F6WDuguEwa^X8W- z_gNvXOVJp_c|2LVot!Ok{Of2nEOc4S*~#0>EuXF?ZgJ2oN4E0J8~h;zfv5caOihF( zcA6=(R%OV(zA*m$AZ@~+r^?XSr@(?Zw$(|84Ez4l7;A4;HTF%1c2vKp?I2YLr}yf7 zzok-`Pa8+=IIdYMB9XJLVi9TekxmoVJw^k+k&zVJ%4Y1*E)=H6j28>&eMbneJi`R1 zK>MNo4~MSJl!>0Am+_hMB9Xc1sHsmz8$`;9$28$bkHPli!O4F+(ErQb?>t2j9mn32 zj%IvqajR4osOfSGr6O3Ht6>k;q>j@m)yGb|#q0azO*672bhf0R?uSb@^2P-4_D>5K zICly0ObN?+<cJpGYrk{`E6&lmxr{ zBT}L2s?{6pzzlk@oDaHwKi*VF!Glav8CoWV6J^IrW2>E!YUA%hF2g;Odv8d6Ckha+ z#D8ZNcS;cZF;$i)}*SYGK8O}g8A<@J)pO)>j%)yqo^L{K-#C?E3CBQ{Us zTC{R?&hqP(sd#wI!^WT1K1#no1m8UiLRshw8)o}h@t$+%4U0XK+*AJ zDa4`E_Z}WVA1s*TI!01qZnE7&>X~~4<9)i$CmCiEMn21OQinXP3on(;x^&|B5@?Zm z!hvJFpJ9FBmeo?Q(WuAjWBmo+b9pP@KkFaG8?uzfr$^b2bZnU)kSz1t_dPWQsD5B{ z`Q!_pCTn46s-{I*6ph1M__Lq!lo=_kdP?Bt?!G>?qE#}b9j zoL<^jjQ72JFEyEK8%QN3$?`A8vty`yzgrviRS}lZ0q1rZVu)b1PTV5dc)#fHpZ^!z z{JqIxnDSoqlg1*nDY7pzSKpYvdjnPKj~dxm{D#B@jIG1dJC>S|Hl?N&0`D4M)Y9Oo zjW)gR+zLF%0wjB+<&|{=sAnf%v6aI3#Izu`}6>Mr0Y;? zyyy8a6U|I%-tm4eIT-}@Y;Qu58Gr}h&DEjhHIX={IUkMTKa!Lt(_*SLR$GF`z*k_I189)^1V9s>j(ck>{0#`(6H8u z*z{Vyd^52GplVJk3rE(MwYl_Bd{#msSS}0|;kCQ!Tc2*1V!ETiOeE+@j8qmEgHhKX zA!F8?>e#m19!9dlP_4o66JiW(@j){Wfdc9(~pBy5mt9MYE;ER^3Vy~!Z6br_d54|*yw?RVrZi+8Izo*}Lwx7jBNOtb{mLXvuQt`B=RwvBoo1jBe^bTmKM|PIeeT z>MoDYQ9HNfb&w~AF=7GZxQ0w=m)OOr{l0Nx_BU5nXPpPcHHgNUCR1N|-$(frsX17! zg)zXB6u);>YPbDVVXoJ*B+-lVe9*l}k|TCIsKi!dwiK{@;-VijKnQ zjJ-_($5}7*zWXm1{nvmAZk2NqCOQ_n(JJ(;{|Z*b57~ojlgG|K+&y9_Lp!Cw0|@%z zhw!>@W4IG4PM<(eBMZ}Ofw;xt`|;n9%_W~ZMcceKc*fI01S4;R{d(y_I;E&|xzw7& zyI@3>c!ZxESuJ7uTzdF=jAq(o=J?!BIHtS6RheBapYdbzWX&k=#A2>` zwN|dgyAg-$t9h*$JDewaGLgolb-dHhOp{!r**wnC7(M2Cv4?GpgEu@@2WiqfjRplP z2v(hovr5IOGV0bPT)oACN(7zvawRNI7^cfUF~D@?EAcz?&4TBG3x{&Ml6AUrGqv* z0b)^B^4N68e?f@CF9R->$0`)e)h@%3Sh&3yI{vn1Tlk})OvTh$EJdO`N zp0;P}*!#{_x8C<&f|$qkBd`!l)?QP~c9(@b3HaFBsO|V?8Xtj+RyaQw4N!%tZlAU7 z8W3gmoc>CTTp-6%uy?6R<&tNJ`H3ab4|wIu!#BS=ri`~q48@3r;#OxW(LTd1ZmE~2 z-&6n~v(YjxsM&a%K9}p+9LAZc$%wa2peD)M36)uQDjgBxtr80`aKc*=r+q80JzMR< zp?w~GG*{if0z%JtagLa=yf0#yNtDu%xxOh$NQcOn>5S1#6-a!Lc~DM9%fq(kE4tcX z7+F58>t$vtRk74$M4neE7`k{{cd5~vI3YL_U-#Z>L^M#Twdpi0Z*5V6=wGuGm!SDm z%51YbWxQH$O~Yd`UnJ-t#;#$crz7q86R?%`s)EJxFU&x;pK z_=ef=t}7-^RJ0?Ee3wgTNy{Zu0olYu5^{px9?dFN8+7eTjnb*|A{Q9YcP7m4At~@{ zj274ddq84}v5moI|JNAxL;>orONp}wULLU40-PBK-*?8L@Bm00|V2w#r zLEyiYErS1#5y^)Xy8PD06OhM`t>zA*(EE-L84eDWv!iI)rm`^*5&05&?Yn2k1QuzdJbTn^{O6DruugvFd3&q@-x* z^w6aTzp6ac=d?dtFEEY;W4(Se`jYeLgUtJb?uyuH8fwzU<#LBn0O9;d>@|1XGI2? z!nxOhZ)$zgsQ-Z)_Oqcm_-ElU%ob^8s*`bpfwoHw!x^yA#l@vfHQlAb_VoMqm#>+s z)+)~`h<_DiP;yLG{9%+MtTOw=t-Ub|AyYP_W1Y}mt^!Ex9WzV!%uWy<&<|v{21c5Cx;zXr3G6tYFlqf~!>hLMkHvbTSmapPnLNzOl_j7kn^4`;sd+WP?9^MTy4Z)f=aI2F4|MJSF#FZVt{Tj(f^}oUXAEuCV2DV_< zdxYyWosQMUWO`lH)UZVL{5N=N14t1;>TTa$Ll2{zSt=+Xe}ix4+IhzI+84xLx_$*a zLOBT9ZRTD~u@WjCFywU(CbmU~FKxAYU{Q(F8l^pmuZr#Va7_2@G9e4)Y+cnD!r%#= zU~ZzHE>8s<@1-u!XhU$dSZK1#z%9g(PfJ)cqFzAe#y-4K`CI$`I#Cc`JtNu@CDfSR zri7^->|+n%?blbQ(be8x>kblgE7)cpP=dfo`VhStEKE4VJdX^*_jrR%GQ$ zb_D-ZDbr3O^4Y8K<#l23;Z&bhbDK;@F0weCE7ePbxEj4QRgx`se?~t@qFm{afEl~{ zHD)=Js@s;m`zLYaQROen?#8(`@vNNvNyk|~7@4Z#^JNXh=YT_kubMv(1i!|j+0I(_ zQ>B1aSbm?8l0;UlXpJDmgs^Q^ovn(>1fJef&+?lx(E&vcC!}vrytND2#JJ6TZV|ot ztMv)>*r^OpS$>>-@Z#cJJG^YH!*}+H{@`YDtjLho$#{W)PIMJVp5>6UpqL(h*R=NoBC&=6%%xKu#*)F9Y6R z>i;^w?p$7*F#RAh_m@}z>p&?k{e|*SzVD1UV+Z*N`qEj9vcHK5nX#C4$Ze>N6SQKh zG~qy)>C|_l0|dM)H!Av#r#3<+V3B+&Vgarb#Op*T%q@ChPZ!M6zV5e)Me*WXk-09;x*{Iwi!)E;Vr`xG;afzr+8H9y zTE4OfCrrod>_CtL6O*bl`UvS`;2D;eBlh517Q9w|;B&dJ@1s3hqh<|?>PRL$!COVv~*<3IIB|R1P zdbQFuXWfg_LRKH!aIn#s{Dr6EW(Bc})#gcx>66PP9-sLTvwEW|po;LCx#X&A=N)YZ z!a>5HIP(t=*b-#uL9{Rz1R)u0_(fkeK(Kj=Yjr;O?)L{oUf6Z~^8n@1|BE;Lr4{TV z2N>9R|4WdvaZ;MCkhlTK!q+8K%e2g$L8doroorg1RtJe4`iZc&XUy;wEDGf7Y$L!c zuya!}{SnbVtL^NFcRI`QKE>9x{s)PdXRA2o0<_-#v|XBrCF(g;Uzo0Fo}re@rzMQH za%Cts8}RHk8!-waeOVqZa2mL%u}`0PF{^!aQ(bp`1Qm2CV^Z3*FTnWo;}b}9v@neH=KO|LZ;aLdTNVpoVh3RfM}Qo3=$@d&g_O@x8JF1#K}aC}*~w49 zn~42(1j`OcJbkTCYi|o5jGB^!e&HuIT*ET%>6Gfj74)5`?o_9t=MypGj0+ zLnY9JEF4ZzJA-VA%Kaj1BJe!cSu5By0fl)2Cq-}ix@gG>#2A#M!v_bVdt z`o^I$c=YwZy&zBkaxV=ZMNibV={Pn~gZ8knd zQPitSji=wBIxQ)rhbYR`9pY2{MrCdgRMY+}p&4-1(>gJi##c>LrBki<;FAnV9JjGG ziq<2`E}Pd~{jOu8tJzc3s%C&4K?B+9(Lr=9H9l>RP7fHt$meJXq8@0nLw{wroDsYj z0Jiq$1v%D3g55$El&~CoSaEGnthOOjf}PbO(KEKxYCzYix16uV^LTchFwo-C6_L?~ z1;|w8ML>T3rvUiqclm|JdXt3Qq0wuEpi8Hm&qap*DdQq}Y{^+9k#SVx8JTN8{wN0q zzvkI3FEaQT_HV6qq6W!_@E7*u!5jgup{_2$&KH|L|HV#Y$A+3ge$!_*GQlR~hUH$&nPhMQk0l1bMV@}LEULN~-AKu!wbO4)e>GADN zRz{w<&ABeds$B=!&VmndD;~${d&`q4Fb zVC@%+{&gzDMwOW$W!Is<@3%)$pilkP&(e!opoXGa*0@e_u_fX*&K`p#_N96u#Upd) zqqgT1?0iM~=fvS5`vAY+)~Koyq)1a$s2I~Q$+j(o1ez_Lat3({K3YRao5qIRI|#wh z(`XH7jNoH1#<{o6P^ZDQF(NQg?$RkgXDn06h0bn@?Oe|6&S$_VG~2~&noP3?wAx@k zN+U*gx)+yJ23z#f?aJ-X`=OFzsKNDXrxR!(hnah;xNTP3r6FX}b~QWWEPs2!&C_^& z$DO)Qs3Z^Pmc;x}2Ot4JJ%T#Ua~jC;#0B*7;q$rPJo|%OOM&=B|u<~$k1*X zQ`K$7fcQpBxn^$jIwk6=9uCs424QknIId(ajDTrCMu9w5-so_ls z9`KBYcE2q>{U-KKiL8~Cm3M1!iNtOG3rNIF5AA`J`=* zNP%|LP}=dGV=xXw^0t*UB<(l$H=hh%i~o zxshv!OfJuSvEo{h)+?%=+Vkq;*a^HveGxE%PV?6nVaofO7QLHXHz9O(((f;|`ijX^ zN#L69GZzg?)mYXJ3|LO6*13l%(d)j+sp_^XS;!WD8)In@%0??Mx+eE4#x zVZ4XU%I@$8WwO6K4hsoD@x{3=_2(5^GK-o%`;@clMlF{yU$EEHei-w9-QlU&nxN_QK5Dk3PNEA6bk#g`7c7pG9WKTI%fh`i}km zhmeHBm62=BKQfRIco*T}Pdak(mr zm@*&{E7|)sJtlz$k{-}E&;jI~5VYKpK%0}#hCD<)kyYQ7^sdV0oQ**n$h@9*%WS*1 zbK0UeTE)OZ_%MDgURi8&(954tBZ1j`uu!@Qbp=G1f8ZDZk1eeQT~fagY3mjR&y+)e z>Iy}1I++c6lopfr7%B0=c^p@-o1H}LFP>Om)@ldP`9$`m7}7)qrU8GPD`VF+fi<~C zY>$MH{Wg+dtVN{r?7jZ~eD*GeLQwKgL;B0{FSw~v6aZ8H*_F$N!4c9bKBcWK5I+y1 zM%@-lttQ?`IkfbY##<0#mMxw2GK21Sxzo&)m?2-Z3dHTfAFWH^XQ!ppH#w^`HZSu= znjX?j>;t=+o67BrQ)UYpq&z+NT1-n#bw>ftQdfL9BR5BDs_9ur{5p3Dk+?Yf0VPFKfNx*lS;I!U6;`G7xQR*1ov-{fR~*e4m*8=XA;%>GOEgbLzxJ;1mvT+rl~NtNf0421{>A$x_H=)>DC zmZoqBF*JB98Sf_NY49M>Qp)=CIfLY&OLRkQdSc|;u9>iK6IFn)SuL236?hp58xnSl zP!7t1@q{nk9;s(o%FBYR)v$z`)(F&PX!KC<2Ab!(oD?Ghf*4RgB*i%|;AG$x^!X$k z^!%!LyH z7|Q&eTbxylL2MabzP&O=bNC=SV{W-*GvH>G>F{h8v28q(lvia6CRFMjU7vvBxE15- zNR&bPAL z8hTZ(Z7WdVN!8p*7kr5>rDJADwv|^r{?f(M+S;!`;T{q+&{)N1Q(Rq<$Iu8pi-Dn| z{>+=~VSh*4*TX3&igsZ8xoE-iH=`AChM8%bIF2;HW}y0nqsCG6T&AqURqA%G$j}|; zATRQsW9Q|$a~LmwzSSV?sY!2BAluRT5GPEdG+f-#WhUA}95(rOCK^(@R`6SwnVExs zpXcAM>^AKr0b78(`vk160@gKNPk*?QV2V4@ycTan@JX}vINmf0+_xIF(aQgWr~K=y9wqy)#?!8S`CKwXE4JaE)Qr55+Z{iMDpEv&{f=Ljw&z1V(M&}vVm-EAAh zBTRr8sItl6Q{D_~jK4nxz9XkR@QD~p%AD6xRfX>Eh-yw5&u^5!-{~InTQ+ykQkS_r z2PJ{>%cjqo3gx9kPT+6k2BMf7EkIHDT5r-rwU}|W=n(~xN2Uh5N)Z`71iTSwb33j> zybUrDw!OcdMdG6fZWWDQsb@4LGs)bdt1)231HrMZm7l`EN4Vpg&!Y(ua1W&Hb~(a1 z!jM09A0FlXStPOIb6Q1i+A=eU`3dZQJ!9*MAPImsxgYrbEawf8|tUUN}#-dt18ErY_tCkva8Ha+|XR=Za2SlN4=c8{zRW6WzeNq%hG=A zczotYW$Cez(pbnN!u&BMBo9nq>&US#OB&97R`rwg(HK_#|-!IyE+L z5fW-rT22)P!x#!tDl=vM)r>1bnJ;Y1>Vy|cYen~4FmWeJN(G`oE{17)p0H^H z{3+)GOfS&PVtF^4*-2`4a~q)7(1pXl`yGf;wVvqCL;7w-gJ*gdzo^BSN7?F|aWBjQ zg5`&5H16S|WT425r%Vsq=xFDJ3Kj1de!sz=rm}T%H@RbSek}{5aky~($vuKs@)`8_ zzFHoEr`X!W8-nruFQ=XW0H(ly7YQy9d+%G(xOC5UpnIvj><^1@@=!#DZ%@v(9J54= zlm)hz&%pAdzmf z{J)FsAMbKUBMGUsJdf^VypMpU%SeMZ1qkL6TPta2zakAo`;P%;?gHg+mfvo1Wrza* zcb#l~7IFx$6sl6TPPjT9qLnYV0Pd-^#F$xmziyQ%L*u-@kj6v&g`ZV66Yx;=aB9-9 z`X;KJo_SL|<`=&Ckx44fIa_QwN;88+;Ih7l<&-e-=GYWJZuy-$F?Y@qp6~qH9CjJ? z!(Jn%-;O+sl|$yG#soA9^-p^|X)KmDZt|7RZ9P~XN97PTDIQxtEdbi2YCJiMw?9V8KuE+u_|Gr^t;iSGF&79tACE=_Y?VF{yh|QJHpS6P3gg$Iv z{lT=T7MJ&i2MuCqW5gfF>0Q~nKDJkMly)BgTHN}?do(Bnvso|`m|u(fp(^z6*?d}- zL$g(XbwLctZp1C@UXY?T=~SDy8u0&d_U7?We(&RdmXwf^U0KJz581MnFqTLmlbxYt zkVJM#RCZ$>SyG{eWQ6Qn(J&DbV=c08W#4_z4E3tsz23j~=l6$)nS1Uz_kEx1oa%G{|BKHP8l&IgN-y` zt_>?hB`ra8)L3iGob{bJ(d)yE!_crvv9hGmYhgJM$*FrxDsk+o`KMSc}kRMjM)d_7d_@nLbiGl{T~w_S|v42t4|pjOS*+prvsmpRKod-=P88mU{2M2}xLAS1T?oIH(%VOi<= zVq*-9*oE(6K1r+9;q!NoqO`z~VGpL;?N;?EtX$~0H)#m#WuczeZs#c>j<*pgDsS}? zr6~(L#dPo~JyS@qI@GV6LTcpWe(Ylb<`G~5)B>K+?Z-sjov;7>pW8Ee$n!W=%!CHC zf&lK;nOWhARN(mm{Rc!(=DC;Hpg9_l%XPaOMnjM3iP4OI7r6<;D==Ska+sIKI4q*; zQ#Ml;K0Z%}>2GZwAI}fVh8@Wgk4=-CWf}iskVklv$nHJiH_kD{sNGZO%r`#kmB)VH z$%?e@8ncYuphKH%x|uls!z9NA8q~rxkKTaV+Eiam8(Q;53?VM_G5?I4;6REhX3<4u z{UcB2>u=!R0g`eBGIVj;P8FJ1(TH3Z1gxpqQgvI26`+Rqb^sLa2_gEq;&S6OCb=sX z7h)~5#|>MQmqZnUpK13fuYc)ctRJh2LB!~C%-#hSx*-MF5->{GhfO*k17qx*21pk8 zBhE3fzduD3(mWSSOE#3S`YJVH(Q%c(RmzYv3cf2VoPkP zQ|Gy7ZNLO6$19m_rZuZIg1Nxr1K5{f;FpmK@uKj~;>&5jS&_fnlz(|= zd>r;^>uVF~JdaVHseQt$kXbI%6j+hga)na6b)A3CbD*HV^Poe7Q|l%C%4sSVb^_0b zm%sPOoKI_l$e3UFY{zzACU+7|x%hu!h(Ii9Yq9!`dYZ$=o4sva<4upZ@0S{~N^SW$ zZ1lN3UeEFq==WYHH;+*5-_^@rr=8c(Iu+N7NO>9Obyq7yafzJo*H7qN?aWA&k2B<`^3uOT*TWHha z1$tT>HQodI*&ot=cNi$9qH(__Uzl;b4?s_`3O8m%jONo2-n04VxZ!jSlV(-`oj8U% zv(~NxA%6EvnZMdSCG3Gt0L!Ndig=4w40{Oz7;%x{T5_aGW}Yf0_8s_z<=+!nUYMWUX~40GyEX-NkSkIZYvBCiuN0oozLJiJRYi-NNdEgu=_8M%d zk7Fd13JdnH8`4X15o*vEBo_YQw@eWC;s{y{4FGC2qCUGTeb(Vk+gSX*SEOLq6C48F zNDV8R-P#B9iq3hq1Uv`qV7IOAb~n6i0ae=%xCS86hOZ}~TmC{)D1t2_NDItKQkci5 z=}lfXXQCmQNFlWZd5(nmtc0{@D#GgLpWcS$)=ug`6&XbW$-y?AxK z`>Mk#uHdne@J!=k&0^c7Su-KOl$v~ZzlBCGV7uG3-_hZOTSMB5@2Ep*Y=f4%E!v6; z2;*C~Aq@c_UEEU`gsMzGDNhyTlb{?VsKTyt!Z(VS`~Ow_kj9i#PBJYkflb zaf9S8dmStw6fmI#Q9T1;SiO@jPY(72ati{21T^;6%iu~by(Lcx(V7We+*3uE$K>4% zIvSwIa4>7|SQ-I16dsb#MTfxf#kib8eF2Jw+ zy<6ku2XFG%#K%X$_2Xr#PWg>IY6d=+BsK<@63 zJpatPUaOfl9&8p**0WEL6)FKD=E`Ap?0C25e{Z!JX znU^9yJ&;}aRUsP~wjgP2q0+`WW-7eqgw7iCrqWfKZ=XScO`zmKVtuXvEXx|OH8?>z zKg`T6i}RJmfxu5jx_p(&{s3d-WqZ8$7J4Y{(s95oZWT=3vS2NK$O%tr$-Z(5Bj6JS zY!*GWM72WU<3#nMw?lQh$J01y!3A({?m&Kmp76^*&UwxBpD@prJl{L0BsjJZ^@V6 z)g;O)UC^gb2aN1Hy0fxbQQuEqG(XFVZ7#iW&59GgSe}miZ`ZK>VdE0X5is1sL zlf2T#BXurm&yVWIS8mM-YLO*PE3aS2zmbYyuTu3!eiU)2o#%kb4lGB0bSh&UNJ`Rk z?prO}RBErqs>CKJ`aFBJ5gZ0kzM}Qsik_^r@zneV@}kf8j_^iYQc@_|sQLcf3T|1AT=UK4!B?(xd8iJJ&zoEe`Y-$2$L9%Gz!oUovec_5P zNaZZLT(t?VIX>j{WQ1sw?!h8`el`xk1j9X8>6>^7T+tmgP2Ixgm~A9$G3>&nZs0uJ zlOJwVUHnFqO~~t7^QcHnqDtFzSZPJ^fFQTfO+=AW9kRIWGkV&8j+4a=93bBJ5*^SJ z;Kom4Y?dHRgPmGT+xQdHn8{8xXaL&w28&|O<)gx*!;H^Kz;Kjt_`C+c-2gh=_HYKCYNGu%Y*TVBh&vBu%@ z(!wMEFp8#;i?A29Cf+ip((iGCcv8;8&Z&}irS`;hJrr_1BDf-8(~+ZO3_gBu-w5o6 z(n8wDTs56OjjW65?QP?uo>{c^Y5e#lHefZbbzT7)yrStb?Y3LwN89xX-cI?BTC85| z7MnK6fwge}I@FQ5eUH5wU|+^%Aty1lu&l$tm9FJRHz^}F8q^eFSpiwrF*v&cfR&Y2 zyjQ$)U?#vgtc5?qMC>(i|4`n_Mqr04&fE4Y3;^Qew66k$rZAr6cH%wwvA=Tpi;kFY9+o*PXe5rJ4+lHJE4hOY*B zo7TEPXbq^JBC!vD>wU9d-~C)mM+}`{_9*?hR1f@Gx`0%!I>t8D0J*TLhI#9Uf9nfo z`UPAF0?4=$rBL`}ukqsMK50LZW@ zOOROK2MkFx9Y~)fd^EtTYAYzwqY*XLwKLWu7#BJ>?Y*erZWn+Bg4mhAGXCuQqC4<& zE!oO13Td@PEH|4?*zIxC{8c|DmOSJ+i~z?jAXJ3vq{LJs)J;)g@6# zv84^;>vWl3r-R5glzHj1dl1H4x?Ep|4aIVFzT6MPP%Hh5k@YWAmN<6QH8C125XsAS zbr_pgk?|(cTL|z=61)U^u;Z0NvEvMSOJ>6Tsm~q8#jNf1PVScLb;t8yobYkuPA5~( ztPg0yHnAy^y0k_~UG{b6fP1saKZuo$GNL*T*>VU^N9c5hd1)ZlR6fghq=ThPY{c zpn?d4B?2-=*grA=9xE+c6flx7V>(Cy*i{gZ^y2WiOmFhf4A_Cxeig(&@i7dP3r*8x zUoI(opW{6;(ddxZLF^_){ zJ{SYS(}Qg7&8eU#XzjPOfz#o@JU^`kydDsXXd3oc#hi{7IfR=t{N}$Bb?0>dN7};C zb6x8Qy^bR-*^OSxMn_O38yBN<)<77{Ckadfe6+|%Fd>%fQfdobN3-^_fxX_;n`kFm zsR$>a-!!dKG$FST8A}pwC4l+6#|eL;gfpADBhR~gFdg{uG)>6Mj7xK&!D3}5)3B`8 zCJL(^eO7WbPZCVbg*@V>_ix?=;D(e59R@ZF2}bfi{g`kH(tb>4ssRPYJt`RRwCP14 zp)y$mqk2>-)gPIN41P(yx`(iFOjMBNl$5B$e>Z)@Sg;B%75-u-BURyp9|DkV0v8}` zep}JVk*!_%XjfizX+`jK@MYPbky+5aq4=#xIshN=c)D9UcERroq--mslQ&wVNZtwA z=GG>WHQ7~E@_4r3FbcTg-~kGfb`USE7D|ZlL2UP{?l)3}E1^hTSJh8d2;sFX|<|lw%j+fGJre2lY0rD%8hKb`r)8a@EH&I+H0&4{D%|PhU*d4}r*N&p0 zoc4rtV~8zrX^N&Lz;=iEGj~utqHp^PO%!MD+?rm4In_`y(m=@NY`SAA_8~K>RfQuu z-00&Ka@zt%tuSR@Ezz4r!TEz`K z4kUxnT-@{+;|YE%(U-UGo&#cocIp@nlaSLd!8H5_&iP#y+Map`vK!BF9n~Sl3XRK? zfvzQx$bX-+JJ6ePFpQCrP=}*vXi;)^8%9`)Kr%=FLJ$Fs_A8+{bS-RHNCfwh8)crq z+#gu7;pIfPY2%CiT=Q;n0-AQIIC{je{fcVBxLcfl# zZ4y;D7)LsTk{IIc0AiRhh7F9k;U>MIbB-qo&>I1(-XU}t3s?N1fGi>~tcW|r<2xzS z|B$!j5(M)SK-j-hlY}+#pN95BY>now;mLGoS+ZP-X1R|@{YdQ0V*Z=fh~-d{gOxs zz|QmeJ*|{sKC~Ar^j)&c3fr4al77z5$qJALMuWCb9;@gOhEB@VPKW;B?REwgfpY($ zRl?=))%&vCP3hQyNcIT-CozwGo%Q2kGb211Z2eWm9 zw~;_8|3!Ml4&5EN-xCFX2=tfBy-8V-H|$E8n4Bb=e{>VOl$F2z`ZaXmPEDx5OM@3? z>}r{~n(7Nc-!31mRSeNyDI048?O8l*P`c-%txP}6KTKRverC&iY3kn&&o#XPPi=3l0h(mQYX407+nIYm#J)4o2|Z^Nf$)#R`d$9~BRv_fuBwMrV-NmS zW>37Mk%&i4Ywwpb!Z{p3`Gx2lMUYBC99cYi9>kGGB-ei%6IbZ4IR6V2HXDiaQg&zg zelpJ`DnaBF%G}&=MoZ^o^PZ20Rylk`ZaZ^2v;t?DLg@Lcr!j^t8O#u=5}PEBmMxDK z%Yujid!)>^tJVg*_7dp*kp}S~n=>-cd-wsU;*@9^zPD*bC-mRM4iI18Q`o(ifN_N7 zgg+hK1C;O1q>%t!jk}jl_G7B}Obuh+Ym`rMsH2G(6^AVUOg^~ZoEXlp}tSVG^n5NR|0 z8U)09SpJ8upNvIJ%=w>`8>&)FSzR89)!9)nGcB4QVe<3!bOeBbUz4~B!-5Ks^aZVHW8^Rv3o$lfaEWbsxe5p?$^}xv93qR zbRbo>(Y|bo8&?0+H#LnYT_WMV?Hx?kMnOXg+9~7T*K{L<%jzHkzPUg z|5GDz3pYQJy>Ucz!fwP@&K9K)CB{S$(A>9Z+f+&WA2AW_6g3ja^Sko5PeN4^R)rUQ zl8111jGM58H|fCIuWS-cbNx_UuFF4DEaP?}(G;>fScyp{|6B{C6adgU^2F@2qyN!w zT6_^wAUsZnrPIK?Y9=A33}6rba`r!pIw^K*bn1R7nAe^le*tIF-(ohXzvboNtS{nL zw%RT2Zk8%*H`T2}3UW6JN<`b=oR}=9ywM#~zX)|>OmVm^U1Szh&)_oXNgWT}rwP$n zJUXWo61H_cviQZ6(^lSlKl(LxFo@=clH|pjRdj%Zzrl~)=o+q?8Z=l(ezn7x+qZ6b z?`XT7RcvSd*&RgZ)p_u+8WtKUM_=YguJ%i>dbe4_B`qD3u+k(Dx6_#G;OU4SE^%e} zTLVViXjR-qjHAl?uWBswi^xrE=6HlkiS(spQnah%cIrtMx3bCMt7&p(Yq_kg`!Cs#*0BumCoT+cW!*x&^+Snd{y7=`6s=(@5eElz z*Dnv?;G1aN@cJjflV)yFSFTjanAn!#(!J&fj6pJxS~80Z?)^vuYft7k27Z3$slBO}?U~4iG?CcOqd_bsPo7p^C9GaH>2E||1 zHGgsV=WI3c7gq`my6UY{MovYL;$JodfjP;`m~&*tbo6F^fr*0goTAaU)!oOhqyR(*;2 zNK4dV|D$)wE+JvIb;&m3v!ip&LQV%=x#1-vhZdMOT4I=}7cpHu&NnCvnpz>Iq zHE00q<%RwW*YH)Yj*|67ObI0^DbsD9Vget$CKugKvTkS2hHTl!YLpD>9*xRq8#zR- z8!vN50yd&4$ri~%K0lOHBe6Zqv}t+2!OEYnNJsY;BA*A|?{2+JQ=#jgX|}=pmZ0p2 zxkH3>NJ6y2Q~A5^j?HM`5Tk3^-$E;OG9A&#jayh$!G3K>KF0$;)MiMB%Je(<0+ui$ z|C$pW2n8dC85D(CuMXW?kbD}mG~Ls_=5>&voI}s!%IxE~Q9S@C9qh_DmYY6$wq~6K zdxaM38DUzrDT^Fr5Q-Q*T;aX6AK7s126axKOwa8`k8K5-qrC6jo_yZ?xVCB`xNUp3 z#XgT}Hriiy+E!2`2~dj_qlY*A3|U5pagV+GqdqG>RA}K@>U!wa=9OTxU7_t&@^;PL z#~R(V(IFi7blH3)uhnj|emL^Jf*7-1*2SgTIkk|QTTftFG4&oLD)T zzKP4vmovh*F#&je=$V#4+|k9te%hu0v##%|>6g)C1G;?mHu)Xujh4(E9C>pbvrW$eu|BZ34oBYY# z$FgUN`tu!G=M1e~ijN&(h8dTRwp;kjn!h}!l78iD#FMjqtR`uD zoC&2Qdm{~OUs){gdL440%mVS{PE1_;I)M^5J16jHxgNT1J6`b;=Nc?Ff2r;!=-06p z&NCT9W^TZpg6<>J(K2(+H*|Zq%#4KDOW>mH_}D^L-73m$;)w1JNS6fO@pld{U_}_a zrT-eJ{h6mgyf{WKT30#umG{>*xuG5^M~^gpF|@wus;m9PYZNCwD?Sq8kg5hF#CV>c zf%trRn^^t;WD8FxodVhAMxU{x7i>%E`!GEh>BUHs0RN&+^32595)@YxoSH7ORmEIw z$xh#z8daCQ{NeL0b&>*BKR4lT5l>_Tu3vAhxORFQ93x<5&u@1s(HL&8kKBJ~s^H^M z^YA7-v$Dj_=g3%J_Ohr6{Rd3P)vCQ=V6QR~u{PQUV&wTB0k{*mC^w@xUSyC9>K(GS zsbW6Q-_7Qzd^*Nn;&&Rf0j6__a`Ni+RzHa^i-aqU#5p#Hu08abdmm3|tiyx-@ZP#D zC^Gh8*}{WT;{Bb%+>-EEHNLL{L_H2tldg&4o+!QC@UebxoQ`Nyl`_o;U+sPn5tvN< zLHB5C9XccSqMg+K&)W*r601@TYX(^#W?7ay)%{R8Z`{5qo8Mne+q7gN)8%u^2{;(+ z-}1WZ;5@VcTEP(v{v|Gfrf!3GmFODpjtB4$aLpozici@jwfkiuK{oPlvE(1`a-@kw z`b=K`XB;5Xp?0qJX0{K$&jy2h8ry@v`kA}#=D+*{p~<;VNWp7w1Lb0JKFS&6Yy}pN znq>&;L4=P-s(VrUixgyB=w)UcZaK#qt;_Ri`i2T8zFK64fH%@xHuO-wLcIu$5jk*{ zm&CkCqtE+3q9>Lx6OUpKD5smL3XiK|YS(>U-!yImHvk7^^ePm*Fkf$(Y9Yu9wAC>> zw9lyGo3_}Ar48U;q|JB(t;IlDp=q^G-`rE5GPdySwh^FGLTn>=^egE_tqV@Tfc3gf zb(!}VjPYQ-*}s*4c#ip}TYv3F%*07(fWL{5J8%($5FHE5)C+M6Y$CxYF;~2)2x9f$#P#o+Nq-fA2*{sF5N0O>5w5enwgQapxRPa2)jCZUc8-0m}0h0Dr@FO5ZRP@xujvym9D_)ddv@t&(VVn^L2h z)?&QE0ftxCXUT8Oe-2v!FaEW2o^u1T+LKz5f}M;Q`2I2R;S5~xWO4m}K@S5F)f9wI zD+A>j>ydyk@HC!DR}K=$YL}UcSschDCv*3~d1H&gFq8*W{2!ix^P52@+}$e=k?0 z+LieD@Dxdix&GMn`uRk-^UQ=z7KUJ4=7sTfip#YLI!8YpQhBXJ)yU4*I( z040-P#!Iqu!gc|ei(c)I_e%)BzJRW5fmDmAgAYc}XHMxGkH{7`9<+_e{OFA|8Z2th zOHgh({s^{h&&~(NET9jD6(5>O7z+|!m6z|nveO&Bm9LFY&%ynum?5+Wdy2$_ek}Bw z@-(3supHr$ZX)h+p_zVrf-CtiA22W_y-!~b-Hq)1DQey*gpWnBoz<4?@EyG?Ms2K> zcaUN)B`3ON0#`YLmL1Hs99P&eC5jyNrgoc3Q}{zdCO-U;)IyOOQwD~4E#Wlox}+TK`J79F9u-w|M)l_lvaIy zV)@&F%oazd;V&un#~>((-E1>cOM};BtOn6JbfZ>v zl5Xh~x!Q|`HTeQCd;j-iA;1xaCr(WI1|X+)RP5X8V z3%=d`zdM^cK&}}TZm>K3r<&1W#Nseq=-iu4lAx5-FCsr)|7S7b-S2=JCpl*5**afK z(5t;s8J|Y1)G*gnDCYebf)FE>)W-O4Z-B$|-Jq&@`TxAYlKa(D*bm>#zpHZR;Y(bv zb0pBszCc^zzTC!xS}O3>7!fB>5y*%FbO0DcJn(LyF`rX23m-?R%U6ka680gze>o?f z1boS^+N6*RPA}nO)Aar^fWiv`;TNN)(9iX8GHV-L_|V`3ZYseuEm2TcPIw8sT5fYq z2x8cqdSsmvJWyxyhRM0AD{s0JI=mcNpIIhu%gtBNd*clLbIFs;3IzN*!0&rO7Qa7p z`@g6#VlM-=0jeU5cjU36_d<`sj3o(Sg^kN24N#Rx^w6&(PG?Nq{f9O9Q*65f2&5pC zSg__D(xnGc@HomKDF-iV9M_8E!j#gx$-<`3C?yJ&NFbKASX&lmDQ1~V-D~eHE{vpS zhv&{`MVfLnZ3x)2+X6>o_#2B#Qao9`DrP@_W>ZLUv=Ci=h$$%17=rNn(6kpDN>L!q zjjn&DeH*|Fu6+TlF}k)!yr5cG%j+4HX_#?yr&)AP`RHM-x~^^TD^A|-a|pr4&Buqa z(#A&I;p6M!lUq8QBMD8DXj+h#A5Utl)qCl;HbA}W{AYBa!q_FV<#Y{5yNS^~iL6SYT+eC|q zC+|g^AXHEJ(T|wNTj%t_o}h$C%a%2&X*1}zq5e}&nQEB6Qm5&0R> zJyL`ZKmw&yx+>vn?<>yVcrmZ%IJR$}`?tuAfMcgDN5u8I_OCOC&^mp3B1Y42-L?9a z4ToBiJVN={C*gU)eE0n$0o3>&w#X8Y%utZ4wJSFoe*cV1YhHRf5fZ(&-$%1lYaZTbm^I zV)T586>iD*nFNIDwq##ha4hL_dgk^h1HwM7Yt@Xyiq;xojRVzS#2Po_SOKz`wQk?B zP%X5#e!=!52tkYw^ib%4U3JimO!fgaj4E^c`}?bBt$^5z=I`ztzyp$;p1B}3@d7j9 zsx$ZF+sVLX3f@1h1zu78^&0~p`Cn=WO7;wZvS@MuWF)n>7T$|ud6O(-jgXmrTY|?; z`%gg|(Ci!Q4)~4z_K~v7mrlHhOI&5^^>0U7>Yl)_`*v&f-zom&CCpkn8Yk1QRCRfX zraRBA$Rb)6{{!L^#510AduNEp6w3w*MP_xNh zeE{m(aySQfr;E^*{~T(+s#Tj1?{4hg&yChNhrodZ;6H-e1zU){%R)^BE^s4IWm9X4 zW`xw%8l=01BgA;~Wx=k#L-c&bQ-+nUlVI6(CeW$vXTP3nVxQQ=5nt# zi9&whepH}Oub~vGByOiB(8*?C%_Ya+el{jHI{fRsfa(>;d4AkyLs9qHYpd%|KgEbp zN&uF30W)n%f#SW1=s_k2W7hU|+Bb3IgB)X!tLc;Fc{5IlXQHRBMvFwsCn;fBx$t5K zVV}yN#DYEvv&5ZkjjmH~^NGA{iLY0EPV{fV~g-j?CxlcH76#CjTJ zWfl?@X1C6F+n3FTH`BO(_rH3UfA&cT^$5frufE9)ii+3e@0(O^2Af_FIhqE&so&yi ze4#ncj>`ZdfeBj3r&>Yw{%pBrF>sfHCjl zugwDEF3Xx4LgkD{Ek9OVzw?&V-a8MJ6?_h=m;}Pxe4;?Q+|8Z;AX34J&OKM#6-=Wd z7E6V~Tjc<8@5Q0|eZVhjAcvaIWM&pCiaSWOPflOBkznD!YWFnm*9Lwx^ymM#Jm^tB zP!>fauc^4(43m*eg9G_wG9qs8t&&tan}iulab}XhkN^bgfXn5M(IXhY^3MlnW0z+a zDhI4%Lu3}WPI&Xowzsl*YdQ^p^umUZUd6Pz^E1jB85|v6m)P+g>vn3vV(Uzc=e*E% zbuTBkySVO8&xKMn=~7^IK@>K@<{-se*6A}v2P>iJbSI`43M$&08IZ67ezp7;)rWbe2~7iKd28K*&%e|K+VCJIwg5!&c>o=e$F!4M!?c z77*Kh0r~HA^!Ha?ok6!m22bz>-%y6@>ucnO@Sx@1~3^)T*T+dbz5eb-N^;Yx+s zu#|RDZAdKkjxC=^Y`e&Wq%}OQ;pD?k@Vf|&&RY|?VxjUq5Dn~T^rJ?`-h3G1ZD@Mv zyXlkL^#a+ZahEeybNnk$Jkza{EUAo$to%MoFtw5fK@U@^YiVF&7TC5-uC2~71!&PIy* zjZ<-!)KWDjXwIGMRV@#@S|w4|Hs4KsmeC?Hgk2%5!7 zIDLlu9o@o4-JWq!3~MWTYF=-v@?rM7OS9r0nA@CiQxZEo%;WMAh^a>g8z-6_diUGKKgR^-vNi6u z1)s#D^`2^z$Yg{d-FcXsk%i#xTap208h3-ujZ%mxG=h!1^0XQS>ueu1D5g1&d`5* zl)ugk`IxD}i3BlJU%Cc{{V4X+-K(6AE1gG>E#NtnCfi^mUkz>{6zQ;X(Ii-3PW&Dk z?EY|UDS{N+)(*9g&T*r_>QUZ7NL$B*M{>rtI!2Gf>a)!r^E4JY@HEbKK#p)GXjBS; zi79drBIO?1-enap=pQ4};5O`Z!BE1!HgLouR)jhU|4~A!hS`t(NK!ldP)2br+CEw=514{tkZX+qg@ zpl`)d=(L8c%8TF{S6o2#EsE#5-%WkMbu+2+@dKR$wm*-CAzTMESAt8=0b-f>#kYQz zGfWfOlvw1;M*1ok4hDHV*F#3!m^7U>ip)SEX5%ZLgBA4_KzuY)yZS zn=rJJXvIlCGyT@O>xoA`FgO^l)T(iMvIX<1yf;fFoH zDo{szn8wL>x2%<@S2hCif&q@;i&onbR`g z-+j{SekpmU*xok9FN>q6S<7c>NQai*>&nQzDSkbQbh&D5en!tB3hZ_I*cg?7EvBIY zn=pg{`yIXqr?N~8&xv}kEYe4p*Oi!l^F?ANV+By`;AE;&{)%M{s*R=O3gR}9Mt z|HOH|9h4R`5V1T1^=pL5cK~)BX76)8x%gA`84;I*6(^f-TFV@-774h}UzPD?K-6$- z7V0FsNs}3h$m%xtcl2J}TX3|nL6M;N!P+2ah3hOi3hF9*DNV!kPW9MzTioqDPhurv z7xN@qgn@g)YwY`jX<`ein2`vCOktUBhLwGgk<4NXm~~0ZN^6SSUaNUR^mV#DX9CpX z^7zkZ>k+2zGYJ8^Q_%YZcJs0ymSwxe+C9$m!_8)WT&ugn@5Kw|DdMxE4)g`QtLP9S z*}2lz)_~>n4<6_ura?J|J@vlkb!y!3ugiyDnP*K0zm5{v6TVu?fAAh6W+1VhcvfZk z9*L330Rad~-9P1WnQjl2Yl`k{t3gfDzyKQ^mb*l`b?7AFk;c~V-sVJ$!^#S5PcR%X zzkWhp{787~C^>5NG)Bw`6CF;54nyRsPaJd+eGtyg(n21H5Q{E>eX{tfnOM{oJ!QfP z-vc>c_-=pn_gMxMC|wCX{!oMzGQ{(;mXjsb-e4az@30+dos7&egRDnrQPj9QR(lUt zQ4)D+lRbWMkw(b++i#W!TuKdk!0>v;9{X=^7uE)M1&PW6>3)` zm!;2hiHiN?Rr_;ML0ym8_hF(K%i0ARyAda5ug?dxL@}=R*;-zMrbf*4hD2P0W=&>r z50rQdtT!8};T!W(zkJp8z`n_t3L8yjg|i_egD9JHVOfuoBJJqR4|pB@SO|?UfEsl_ zdBSIspghWy)*jO*Uukr zH%^1p)|C@|M(O2#7j35z%H`t&p7P-RpeOi-hU8SutWu86tzzaF5jtjJGKkK?q@WPt zMZr_q!ROAD(K)MJaAsyHUTTzqFSJ92itL*IHv4`0K|gVDnmahn)E?d`^U<1N zD9rwur2Q?i^SPL#>(2*_1R$|bcw?Gw+g|&A;{C(Zd;G2_9wVvMoxfmeTIb1LF$jx^ zV#@5&q>{of;zqk(bfnv^Z^;c{ou)G2qjK_zN)ki5F&r)OsY)Y=x4DnHhV+^@dMT%< z`3(wdM&w{0QSahun-&}x&cKY{4OxXD6s}?2gYAY+gia-1i_N21qYsV`d3s<|xA?XG zefxZaI!V7Zb;*ZFTZ;OkSoe#GIO)EJJb6^Bh;wxUI1A7oFZPMl#yQ+!tIujlKu&aosA6OVZ z;R25yVXtwaBYi3JwEp*nV4MdHrbUyfi&JALWy38EO%@e7so|9K%q??GJtrI*luJ*% zRW22NyS0fs%byJ`T7Awf?95W1_S#O)XFImL};J#MU0C5Uo3jx8EyKFOyUH4ZEC zH^OqG`NIvy9!l0Ey|+DM^_xs{yue+Phvbv;pf6oh0@jfS3mRk1reYCY$)EXL+I5-br%L zxmcxVoxb(r&PnL8rWdKc5AH@s}s_rp%G^w(4%ox_l1sPtW|x+6n^+jw&``5d+*$|UJ!rY;YcXlJEernS*eoAT{L3_M9(`pEa#(^iWb#Rt#aBzJAVmLhEUQwMIWi7b z)ar8@t%@L#Nl|-edolQ#;vx!QVSF-+ z!P%D$u$rr4#!eI?i+Vnek)qy)fp6A*RLxjl56+j177>yNoAei_Ts{o44_}Lebb@+U zQWV3eGWU!E0vuAsJh2kFcN~jAWS5^la;y$f%N_2ZbO3VxWi-W|nC!(%Jo+5fVmT)a zzAkWW;$NPpxLq&N?oAKN;@AgHMtL5+4w(-#HOO5j^!#9Cq;=LJgd0s+C$>5_Da5x& zDp*-Y4|t}`C-ZWWiW+W#N?n{RKP=IV8r?ufa{=%CdJTpn;T3aBK0>e9O$=cpOt?7` z)@bTf)4zK~2}iJc9G|)INwHmf-T5)rq}a9pIhGKzz8?rNrvwZ*XmBK@-NxteNZVjN z3UHw`Zei8(3Ycq=c`n_xlH_r)d`+YR%6vfsJ@Qx?Ixd z0+lV@cFs7}+GOwZ?&4)tCJ?m}Nd z5b!DC;`IthujX08DHz6!Pr2c(vrU5DcqA>HD%UV3XRP7g!^HPX(S;uxCzNYv7qPSR z?EyCkmpN1T;U)qLjK8jG6FSEqt^oWXsq1G}PRaC^8%0=w| z95fet05J0hY%Sd1HLm)BT+P4D;$$h*VR}LBxr}g{=69o>-zuBc?}=WPP8|9$joGWN zrF^vDB<7qVutad)$?84h^t_>1q_#U_YYdL8hhUz7(yf1M_V1QU&RMWfdNyX8%C3|6IYRoexVuj)QbI*oy45hFo|I9ZgxX1Gzo z7#;ecjNE4?Qon$X3uzX;?o2QzUq|gheW^U!0Olp(B?)#9+hwxuJ-Z#SUm@%}7x8RP zmj30Nh_T*NqoB4sY-jLN?|e(O$^gyHnn{bLZ9W^!#(0n%<^;M1p zdzGUV7fro9ET{K6s@jh{ntD?M+}cf5JxFNIsEy;^iG_a8S=XNGC;Tc2rfNuph zixSi$P&Clq#GT$FfvAP{XGHF$N|?LYcntgQ`dxUtaPn2$?5ptiPNnL-aol4f^^=r@ zZXuubQH5T2CbUKYga9rJ3mLk-zQ0%V^Zb!$YAQw%Fc#s`u#mmrrO8Vx@MB8<@gnnO z7zad(gt@OlJmhTzg1wzPG%4%C!U@qwzFQ`=q$A?$Y(RV&N4w8< zBPcJm@MYujtX2x&vkm*)Iaw^)iGB5e<}%q#;?D+Nk>rh!gdwiQv~y3G@Wtqe2{t*f z@TA%wPJJ-;?k#9*52TJp@&&EunMX9QpAs8V(A~X@4-~!PU;K&pk)i_{kCuZxvJUZ_ zaqB4TeA+dY=d=&yUWN?3w4euDa(MQ@3(PxinbZeiQO9`azC`E4TYEkug)gU6X^|f* z8SV)oYwk^tc??r59+G?0PO+wtz8b$Mv{SnKChf$^0}De)7){H! zWQ~8igMMJHcW)T_!qwFJlG{EmglebOXkU`Jfhs# zP=yl2K@=dN>%e#h#@bb}3>wcfn&Ai;gR$e=ODBww5+Zsa^V?1#dAuE3e+xuguYVn+ z5jSB&KG5mW3?2+0w)H!C(M>5)_xMwRaC4c73qpJnVFy}}i9OEA^@zdO??U|i!Q?b; zrRe<#et{$sA@hNO0!w<5^|ui)7?dxue%kA3$cyMpy_KTcDF)Kivl&{5Me$S^QL3Qsj zn^OfQ24KoWnw5X~;jZr7T%r_eb3S%iNlg+5U&b|Rau`yQfxzisI_X>GZJE!<&2=F@ z-irl7fO0r-lGm8Q+kETT-0Ylp#&H=LZw;5A@K2Ht9?miueI4NXE*2>pCRJ)Ak=ONE zH_pYg!RUsKMZT9%HRuVo`ppD2zr`as&MR3`4P z?G;YDWZ_W!S&#vgz=LHKZO`B}4ab)FXDUzeGPw3(o-{W}m0gg?JKFi>Q2gVn;x(@` z{NMfVWzTX6j>NM+)m@rQ(8iiw!}3<18#@@wxL#}}v>p>Z{aSBm@oaO$m&(aJdkge@ zVcv7d_X_vmT+dtTYS8+Z`Lyl^!4+7wYX8Kt+`~eUM`cyEx0PXNNuC^ zi}8?3c8x{F6tCg`N7$FgL;1Gtw~L~$eUBJR)-hxaAq>WrB~!8v5-NLkk$oFu9YP4% zGeU?-vJ8VHTSQs2XW#i<(|38F=lMLp_x-EJ%-r`q_kEq)aUREcJzHisn()?E_qyO1 zZ<1Wh>mDgU6wF@;9v{lo;$CRQ`4U!5=g0RavfGMMV^yBvrIPpa%n@D@hZD+D_{_Se z4Lc#5Tqcp^GpUKWN8X!Ne?FmE)^TMHKSDgHuP zdds;RZ`k-T1jWnb^TQ|0+jjNM5-@u(m76!Q?)!6bY_vT`q-Fha9nz+MvC?qHT*9Qq z%|I&Wcxb%$lRnpkv&l>BI z7w`JE{E<|cD_{hWb8`E5k4F8A=?d>U#Ah$fmnb(8HMqv^&N*!K=_+m#yEEiS%V}F4 zB1q)Q+)vlDuxOJMpd;?Acs6!Gqh1|O&O@`Aw#M?S1U!j%7Mbe;OlLor2S}Sw&#&vO zUp(CQ*jkUS%%T7eut&q-=U{W&@gY%M_i(B}z!^-5VO1EZRJR#&(k=cp`FLV$SfKe> zzrf{l{q3Spk{9ulbBV-)de36IJ--O6A^aA!=V0#Ju|BvbL2sJQSi8x|r!4y&mxCiA z6QNsM3%Q0viV6j|O+m84XD2K9%lqDpftC~ko=j=mSnjzWe{;+G1yi--SW1KVF$Fz^ zE&06O?QNS!|{m}g3 zWUAw%{;a&AJtM5t#}`g$>A$nu=|EK86F*X}_uo@m5=`Sz2Ctr(7>Av-VXTK&dv1GE zlasnOH(HyWqg9wMfu!ghq)(P_e~W|fRvKJV(ZRdLi6wT<8Dew#RMCIn>( zdMhaZrhmhK{@uWowe8GH66rCk$A{x7ai`#(F7p{Ltm-R!Mr&PFM-SW1X6k5pM0V(W zR_4T($nh!>B~nD z{jLvD6yv_T1*K2FW20~OM2&4Yp0Mb(axy6{i!Fa`Oy0>z!Rr8(#PQ9ou7h!YulQyJ zD&PAY-5W;>Vj}B8a#8B{t_`UnGd6_DX56K*TiN@1OyXCVzv(nZD_rvPXMHX>yP%Q? zHHJek`1nDaQm@i>%X2=mHTC)a1E$Y~E!+a@=tCfOT|7zp;e6Hjf&;LnoM#osUyjxD zK3NPP(8Svfpx|)}sQU(5C1{40FlkZ;);8qm#lzFqA!jNjFjs%pd48zyvOaC?-?XSz zut8*HAIbs7^T~br^}3O*ng2DyH34;x%dcRo(N_g5A0(Ysg0V71Q4lFsy_v*4p4(B7 z+g3a647cK60b3au|9AEgy&JDO8V^6$A`23s9aH@eT7`DD81DpvWOs6JEf$`86$ zk0YNHyPrq;PmyTJRMK;F+YcJYXDc@$e0()|%l3IF?$I?z*E%RBy?swy3CLf+U@cl^ zR;pa1TT*P`Y(`57lR1h#4W~;zyL%D!Of1Nf7rH-FMQfB(WCTW%EHm|Xh=-gL?_{xV z1a}&FIBvLl!B%N31!3XQ^)tZ;-2OKim&x!@hUvr4qM~M<{xa?{@aE;)Mq9tiH|Nkx zlvy^P9NTh)9os%=b0vitS9VlCq;U3o!8B03+0^)XE-+a@|0$@mWv(E-(}ZO1CEM}c z5BN;}74H_0h^4Dkg8dy490uUV=LX6CiVy!0kp6m!0~=h3n&M*R16J=)pR~d;AP>E( z?Y0HEe7669a_4q<2#uE9>aR}Q)FdO>&^d0xny1T}&aKRA4g=xgZItK7m|14sqA1$t zzu7a?IB>Tk@aa~}Sr6mmcsoOELed`o-I0ov!j!xzzx3ZxhktDB&o1*!IqjERSA*pO zStXTNCjK*+2wBt>w9q&J%C2C^TYt8Ev)%8X)X|G5GRBUb!&`ua_QU(sOOW*v6#)LN z8)YFLomRb?R^mFRD2iI!@)O89*l-{9eL(4$A2yz0#LM@({zy5JX`qS+3;pRQV51hkA`b$RO@5e{JPT>@- z2(c^3LS>`6k-naWpa z&|Q;+8-FV>&if{ji5$7-g`Zo7_vmf-zs6k#dYWng25nu#8Aqh8l(T+7z(X|#cA5u*jxjW!~Fmej{!*NbP{j{{)T68U?ggu?_Xz{jzfXg9wlZt@ZUnq-&Bl0P1r>9 z55mS=)h#&%x?VXMRlBQL?y(0Wl^9*G+vwH zT6G1n1mHOFmUFExRZWIrMA1+{f2sTS$PacGjC3>f;Wap(a}_u~SdTNDkDYt9Rx^Ft zsC9e(#oE$UcB97QmWKkjbr*|i7zq5DA%!Zo!KkPLr`r{~tN@-3H#i4nrVSka!j}tI zqumO9`@HPZbU4a#9!;rG7-Hx608{E$=pKTQ;Y1orZSs;9HC?MCT4?^#JRRZS0~6S> z2ZIGm2r5MMh(r}oUN>zyw-m%RjN#E*3;i&3Q0pr1+VoOGU^*J5KLDv5Zp-IoRxP*U zVpc8bI~QYIyPltJ$k4q-6;e^eeXf`eP=pyMtljK)HiUZ;fqKNLx!`!XR5`AB{;Q?H z?buqR&D1pBkQq9AHp#U>p$O(Y&n<9?L%&#gvIlwty9Iy z*AG_8*N^_Pr)QOZwnRYSUp+2qwD%aR#V(n?F`CYGdmy)A?bjD^fiUK`PTAu)eo)tu zTwq-E$$@mCvZ1sNy2^V#k zLXgk}!e{CoFgArODjc$*(llU1`-2tt5|CJx=83*kquDs(Lck;8y=FTMXDNQbWIhr5(igo2Yf1vM& z=Qc=3JWN@DE!@rt&t?3Ak5mgstDZokW(^#gktQ2>J> zp0Tx5Fa^9a5Oqeh;mH?>jw9b|sF=0=CPnD`6t<7PGbB+p@D|-9x1Ekey-8*|D@^Ns z=re0f&k7XQKkgbiqN9)G{n5mHD$?1(+IGN%6zV1yQ*KmkmgC0?Xvi>!&dsNiILqxx z=6nUP)@U(+Pw^VA23G%rIDH5hD75)FX&#uFP-?A-%>=Un=!z1zWSx{n@loKiHVHYt z@;f17fH!oG=or^rrb1=Bdq`a+pvdwhHnMBM(z(1zAkJ+e7dI2gAfSJlj28T2Y?lHZ z0pylr=V+s`pTJG}slW@#9k~V-yrT^RM`9on%fqM;td9LpBT%ygY%M7NC*3!a zgm%*Qc~(-QmSo!bJB-%@l%4w9syv>-_Sq-#tuR(NsR~P``R53A#yeTuCu&bmaEgT^o-KFQoUZA8sx%K@2kG8VE(Q*w{l|vx%j;yaBD1 zligj5k31czcklNyY=II-RpG6l`2OQ2hWH|tV&rCagR0}mW8YaF0lL=vOBIR-zJ-yM z=0|J;sv%s%bv`S`*%mW33P>7@#h4m}jig)WDjN>ZiDw2K-5M_RTJNgCrQ^MeN9N`n z6Eb4fy3}J?Odhv!#nTJ=3vkttWrzrN%)Oa1b9>+g+{~syWpD#OT3I4h0k}DSouxqW5Ag=*Vut`y= zLG@%eDH|>2dMJ&-G)uIusN@@l7OfUTv@4Bc79Y6Q}jItdJ1Ufx^Ch?F{sgy54`R(>W zUm$;JW93g=ltyMRNMsBI$t}~cXSj6;T?>G zZQ+%5RmXy^2VaMgokb5uz1|6GoQi=IdluZC+H89=^(G4D!qVc0%FwChr$(5k+_z18 z*qhrYAXp8zH3%cprSB#ma$&nhJEwzC`LZZWsL(}B@6sD_?2%J7+(SuQj(2_R^gW|_ zZ{t@Ii+558;{iEqSyd^;2TSpd0^{`?@T5n&vL@3_7e;G312%IjIjTo(zU9It1-kRU^5EL4JwarJ=`wK-xD!4iY~sWgrhT!4U%iA=6sX zZ`GbY5)_4}FX6@`MNmHp4rAxhhd%qY zUe;faGp;pUw=U9U*m7m_6QQ8@w*-f_J-F`o>#4x8Q=eMM@_Imh+%KX7elFX^w4fTS zdXy+a(8y#&$PkFYK(!fA~cLN5};Y|X0zAmd&?jfgS6N62DNBbskoFY-nZ=#U= z`TdYwcLK!!8naS$M_1)bUgl$@DV%{Uy4O^G`3yZP^nwd5E!l5Mw?(oVtVb55aRZiq z>D`_)lQZgU!tm;bbdu+?jG@h!-X)`MX*_O9ovp&+Cy7}I~E5)e=k zBkTA0K76?nb(#PU0jgoB?bkNCNK5}FFyOBD$%oXdl7PL4O14PGR4i>|-`E^WcQ8Z% zwmYaNFXv+phmZoa(&2gH6I0{@{pwW4bG=?KC_?H?on9BJsPz_`4;l+d!qv8cNtWs| zOu0TMZ_SCM#9&Ev;D+>~-0M6$0BAfz)r^lS@d!ZsMs7TPkX5PFdhWfWRgf6Vo{!Ue z#G_#HR>u=WUQmq|k_p~DyuymPaX^_EbcGj2O?qVB5h8Gd;+^&q$qWJv`6=-wr_fW7OI z04k^i8}I^p&Ixg0&&C~H`Z`7JpeCZBc>mVy>J{w83}r3t(&?KXnxFMg9{60d;_TPF znPuGhWw_RN>rA|6DFYI%^T`yD3ztFARx7ysQcukRT{y>hyOtj?Rd`1%(ro_*PdZK# zFpvEnJH*%iZ^01YGqLbi=f&EqqESy`|0o_rJ8lBa|E5Odsi#mGXdQp6@%ux9;SXf2Zl)mzmdXdh5X%0R~U2PCS93R}pFEmS}z!i|3}1P{8s>vF79P5U?i zarNJZW8b2q&C&roH-gj}*=#-tVs3dI7ArF5x4ZyK_Uf4rqF;1+NvtYkhs!H_>yn@K^{ zCSZ029YI5wc%`8hFVsFO4GwIO70r8U|P5MPmD&xbpN{ktM?kWf=a+Rv5>=))f*uCgFB$F7MZ9mva+# zCrqfQ#n30Z71Xk5&?VDBHltV|K}-Hsgg24crG?7sl|o&0&O3c`%*P&Wwn;>ZsJpm) zlb0jKU)R2({BDxUoaUb%>3*Qqd_RVSKah}2Kx~|>p*7@?0YKiD^DscZG)thimQ5qf zG4@MD$)Evk3>*JHij=71z3)Z|B{Y8po8Kud(l$}*KNndE^2)F{V`ICq`HmoUgz4T- z=u?!7+tB6DS&P1SsSNpg@$tT+nM)5xJ}SKPtAyvD1FW^``&t9{D>F-dDl(k}R7r=f zTU?S#?fuCu>wYm|e{NxKQofq;GGRjpQClXf(&kAwEIO?=t_zTScR!X==rzKWTh#s= zdj{5PY<&ghble@1wib?Z$O3l&(lCfS@%PLz074B3!Vw}L3?mLUGX2HWbOG>%5FRwT z{uC(vSCjeEf@I{Bb`$Sh-!G6wx(9uhMfHAxqz|4U@JBk0e3#F0tZxg4$)=UufU6~d zB|xGx_Tt9@{B_2wFP4Hh5hw&qPd+7htbKZCRQ7-$BfxF!>LK#gF?21uYm$a%d|+B*5Yaw}Dv9VGe^vl(*AEi>*8 zq%?P8Fg5y>BJVBsLV}n}#cYdoLCD!^+te*~doZkN^R2#kzQQG3y$A- z9o7B~;$(1sYK4WBPj@MWXI#iU8-BSNOqMZhcOrS3?{)4S@=}giih0cu9>x+Gf;Y)k|b8~M--PR6w zZg3Nx$it{mFB!0A368AH`fAyZtW9FIBCCiX<}jRHik;4@OJULU@$5Bw-k|BPF!gHZ zY$epa{R0r*|MjFvoV8T&u8DL7ZG4)3rXeU@Ym_JxzrcLb~*RmvK2eUch zHm7YRpBTeQm=@Tngz(e)YAAO8xoQlGuGqhJvz+7{&b0=jjubfkY&!JlX#<5Dt-|eo zT5Chqwj)jv_>D6ZUsXm@ruKfd1|YFa#6<$X6+mcWD5L4YyH-5ez}|E?z_m2v=Fttc zYSW{Zv&F=B2P{dKGswJnFA(AEI6xlJBlj8Cq%Jm$kLEIFViJ`2``WgNOF1<8R3KQZ z{q-);>llOyfgPZODmX#iS=KjPzIlh&`m}AMWr?Rby`4O%ozpFgJDq^7ejC<-^D@7M z^d51eH(itnU#M0FNy}9SpYx%;{%pI4!eIHQc?qFMW4x%K0xQ7zo?u-*ww|Uv<#xUVLj! zJK{WR8(-vMR8n`Z3J!e8W^<#M$4d%es2{}ZF8K-hb>C8lfi5Kxc(FsLQxHKGjJu(M z!)kem5^N<66?3^yL{bwn@>4zs0h&*a{C)p$QbE``kFhcTyDIzt_OE}S9UsY30=SLJ zilXH+y88CURV~O!iCTnTN$~K!hY<9tVNE%MUh8RKH>x2vXIsSZ887^u1&iFX-~&@1 zBgL!P>;8gljJtvFt;xAVB3h9GDy<&y|DYD6X~tO)XRZM;_Fr%OccWU5K!J9rHXD>- zN_*sttt3gOu!dk_s&oq0E``;_0nKe!FFOyW*LCiLtBwt9JbgfG`kT+!BHx1zlcYVV z*+L<_&C8B${OH~3dIP!Q=khN#Rg1~Lvw{|ucp8XG-&+hCvVf7jwJ50t4G9ZSLx%Xt zf26eS_P&69?R4LFfi?+h0m8@8TT7*mVP2Z8=A_#(WO#tvpowm!pA=t*`k4d6FhGFa z=3CRQV5ZNC{{|HkUks9`SMLGyh0c_$j~CGN`#CT{0djNTM_6!Yucowo530lqpF@S9 z+>cckVj2MO(vczc?WN1t_q_l*;Sbej^3RS0j($Y)U@vT))IxMf!YXe|srnoxB8DVW zWpCO8=*ytZZfG#LkP-xoi_xkPDA&d(E=<^sLpRqXG}M+B%0Ex0j3qb{@_ zm=(;9!_-4E>znJ1U+HbYno{RBwJn8&Fj$4tF=cZ^6n`ofcG#F}*a$84<4vjQeCD`) z!5T!RgCL|S)D}q60)bSBL=0dj1AV;d*h7E?WV2a6 z5V5!$x9)l58QdN#C++VvDSvLtd!QcU+;D3Wt{K z{R=B@_lE^|5tu7VFm>X z|Cy8iX>duj7^KDRZu~U{A#pz@UlN~Nqp@2zT}Q9q*?;j>&a;Cf!?GQ}=3GV_@Awx| zDq_9$zlZP7bMg{2Of2e-?A!$x_iKMCrqjC|0G5`v~ zzGJVT=0uK3o_P;M{E3J}r%!>J!t40Quk9S6#0`_Wq4%jvwJ%kh=s|}h6Pwe|YJmK; zFLev{`<~xFure`Wy5O22nOCPPoKUf=+o;b2&w4-HAPY^(l`9(co8EaRvztmKpyKUe zsG*s4FjS_M)3R`uBcXR-Zw+@^Mc-H6#@s^r5QU+wh3Nafq* z17yWJEzmR%;X5x;63?|*fYYg@DU<)NsJcVk0s zfTiX8=Dx!@!tz&v0k1vd4O+ccSGR@CMzuK7lDAC$-K6+%q_uy*Y4YmksB_-ua3+4PUey4ACovu%~H zpe3A6&;y_Dw_EScV1TUj9f}Et`!<=BNnVt(eGxe)=Eji*EuW*D^*3l)8Mg+0lkYjB z?equn-(}0#s$uvx4{e}ogyjt!%A7A}ZyS2j#E`ocBwl`{_hdFKdWPQRYsd9xwynE6 zjhu_Tl~0rv&vW_m6q|Y?I#gkRu5U&7<28{BJ@9*CEEM)F?{xSMU7vR?T!Ccn&OfGH z^FDvVwxaqu|YOlp${h?Wg0x(QjQQ?L2ON}jKz z_1HXzgXx0|9?6vDNO=VPxD_07(QkLU(5C#dHj)D zolZXZZk3f;Q|&Vc(?Zk9|MhsG-<{r*L>$@TEPqkw4XY$QYwOKa8hehARZp_Y^nWb$ zPaVotn&l2u)@*luHJe^Fkzd>XZcFn&^!xvE3KBE%l;!lg^fE(lLwSsU3rw#zrd!Z-f$Pz zjvvzBO+Prl_jB;NSI{|U>Jn>itT|`;Vc`mMd6rX~w! zYzdZK2IarTR>|KI2DZoJq$NahJJKYTH4ctOWYWpq?xu?u9aAptlp_DfmGCeUe%^;h zTGQaCB(WDG@1>qKo1dMk(7w!Zn~N)u&3!kJ!76UW}3*kkQvCsa2aQ6@oxnIH4AwtDz}FuZ&))|{EW4{#Kv z5%g)>%5E9v=UO*#C9x%wUtjymETdI!!Sek=zYUtTY?v5)$kf8kq%N{0x2q1yST!6^ z2`_uIygPd55*z+ZWOu#m!Lf{0`a!L)LIlbsBaSm6xBW>`I-v_9OOl8DOOBLEdO2D{ z!g2#C|GKwe$H&M+UQ3pT4<7u}w%t`-C!KYB8L}57nd+b^`yz5B|PU?@3bbAC2_z zP*?KTCx0tN1T`SymL@8P-bvqj%Js;D?W!|+V`5@5dv$LyQ}P;a9OgtG$W5i205xF6 zsY>PVXVJt#2h|MSg|R1X;w(jZs0qhl*X38AT<>qkhIr*>L|s+f9b=y~3`9+bp?FUe z*SRUu_iaI;3}-P$_Hf?b$Q;HDHGOp`W7nI*9x>hGRIkRx!+T)ZPx}MqZ{hTUYb)r0 z+o_krTOI}<)CZkN@Q>4{Xlr@~hA#PwbIeHpJQmTop0W|ttszH6vjXDBNVStH;&^N= zZE}&@s*W*5QS@VjXRWV4f5;@~kK)8Ql`_f14(ImGM#I?hu$N;FJGHGz&4-vm?%P1q zIS3rXnk!q!kM-a=Qvnaw$0A8%e=T+C6pE2QJ;d(_U|{e5(LWl(qr$;Tiz%d{rnobX z^^1t4yx-gQIrXwumbp)6BEnS-*3aAk>yr^G&`uohEB960UEj_k`Xgq1T-#g|W@$5e zuQkdcem@N)sMdsw%L>o_NBQUo6mudcf4dOyIoy+Y1m5e>sC-XXH{HnWOELD5^n+{L zrkJGZO1|(^9UUaRw*0a}mHMJq@^!v3RL;GJP{mvj$Su1>j zs(GiOb*|+>v+oTdW7Z9>ulk(WgA64(ste9+2>8}PL*LEIjb0eIo@0DZ!pbg!JS>5p+Eqf>8_#xXMY=5rZm~~GY&IWYL8)?mb<>vV3@l2e9 z?vU`wv3fQ|MaYsg+=K#%m=vfao#1=Uuf5C#3Ir}{$)qQ)zQ40aeT8M%S_LcZb;vi9x0z&3Gom5ugL!O?7TR!zQi3? zMH;>Q<`iba=wVu{a`7fx#5o9SzoUYp1Q|qd!}$azX>8#sH^mGbgov5Kz0y>gBcG=%>=>s1+7)Jl}K6@vz7+Ayx$6%sQP;(mI}lL{SkC;WiPd zOfmTa@_wOsgnQ@#z8tX?Z4MQ;nUza7L_pOXQuW^hiGXp&<));$n;y=c7pl^H@i3n| zYPBMAxdC215?j8qY@7@{~>R}=W~a_*;$vu^Qmt5)z6tJ zKh*u;{JKSiw(N{*l`ngk|JN7l6GJw+{yaI>NJ9=(j&us-;c73+#SqJwMksg5dTMPK zF~#aNc4KWtFNh^1|c zYcR^MoVw*&S>j3x=5r|2Ph6U6=OEDasuxg40D8nP@AnJYqp`u-tdUhh{A3mz6!fQ&bN@hw~Opka_oCJvzfjR0UNR*8vgco0QWsU@<16wbQ+GD6}SfPJPc+1_HmV>o_Y0_wzq8~>7=UuL@v@~UDYrUp~TkN$uMR<519fJbI4ll~CzvbTm?B1yy{_zbNNV3VHeikQ zP|#tYp1M!dXr`IP@c<0;@Y8j6`7Bnl^4;2GTYiyYO04t?dbylD;>nV}BeK`c zcM`j6-&juzZSA2`h8X1>b21h;)#m~vd%HItwDXJ>)uG=P;!jOG9; z$a=JW!0*o5Iif;_gc7N>;}*cO_xuhSqEZFM-hw(>9ezmNnrxTn+y+AkGcb5I{xe?z2Q7t!NveP+HC~;LBda)JE@;s}Ig9P~B*8z>-$rBL4Z#{ix z+#Np~o-v}0NSW)&<-0P`wR*;1;k=fMuV_?ZCsEgf<1DLm>XNxIRo4SKk%9w-s&kyf z$0hHu4Gn7!r`pe8GU^mA5d4pagdO)IhaJ^Vp%EVj+9eVJaMQO=8h`5+Pg?4~f0Gq^(s|S;WexZ`4zLYaa^j># zU+C}u8h2zBOv^WuarkoP#PRnr)|#7cvj8I8nLMABVcn^3HJ4~=XVb57DP>>naJ^dS z6prJ*Ju$TCxnGe!R%%l@?uys(sY+=_&emEazuY%&prmO_9=jGM+~7lX*JE3l%ad5E z1L04mUt3o>HYi3OXZJg9Fz(9~+T@BXcdE(C_#?>p*pW2~>^CvKBJ9KSC#1;U>cgw? za%s`l%(}OFcoc#h`|R5{oJ6{FCEfE0!1nYBDWtV6WfLb%STQ0lHV>qeWV7z`Pgno^8_f-esSb zB=>jDigO}CFxQSDF$nU(NA*Y|uie<-SnFLqBHiwj2pjJh*t+*t4=EXY$<53m1LSiF zu?sX$Zi2amd|*dPW(Uv3JfwkOEMK3<*I3Ten6oM*5BG@;T{6^m5^T){9GeR#GrMIz zH#6BA((pv$M5B>TMSDC^yYxI6ZtfJ}PSQn;2HWHh6Jr;+hk!cn=v0R$1A&SY?hz>W zjf+Dy|Api6nM|!~RS(WEli{=roDAW{qVbLl>bR~`?R)HolRh_*U*M8lF{p!v%vhn$ zFx2sr1r5muAEA=oQ;(fGTAfR@YL-hX6R1Pu?Qg(9kE2-?7GWLN)i8RP$vymT?x}hr zJsaWqc{c7JH#|nz7zstBpKAq%r7?V1$T#CC;|~xyZcA54scXcW4%Ymhse%JP;I^3aFGViI4i4B1>L7(M85lmDdC3+A{-5+~ z)}Fl+#*_?(@0dwJ78}&)fCJPB!bA&8(69NEEH#JwwoY;C(~67e-pz~2HoAC|Xp(^< zx!XVTlG`AW2ZBs<}uC)EtPk^Rqvgle^_egPT@7J|OPrDMEO`ic7WT(f< zy-ts7C6=2p3(r>u$@yuqRRltMNzc>%65ZklVA`D{D@eJv!g0!PHhWh)4q zfz3mTV|Rv8H?i-M!EoB#ULZw;Ucnxu1KA&73!5ZA8w_Sr8fb12ixVx2j|ETt}XlQ$T3VTf%X^+brfJ1(U`-H#VQW7;U5&fJB7O zV4N;IG0;7pW&NFAk$@1*3xHk{Asec(AzPJSY&X?&ygw3;oa@gHIlp3YG%epZv<`dO zhqY$PdZ6#FCa0L9XWU=Fi;EX0p|dFXz&D>W*vkrO(PVH2-8Om-d5>2&V2sW84=WH( zIAkle4H$M5FI1Stk;aOlp9BDu=JC2)9yQ^CYb<|4AJ{A~5VhxiN4s+8a+BDeygJNI z9wbJ^Q(V|*6%vWUok2jyK3II1u(7K&z}XiMDBo>ju{LaAtYwVrQ=vjo8xYUpg6R?o z5rzfRy**m|*YcJ3lfxCkVH6NB^q4d(llc-EOsohrFL@aW&)cbOL>W&K zrX-@)WZyrHR>9h*wJcKGoFUY4D5;@iKm#%e#V!tS1#dBDxHykVoJ6s8gt`^~p$8^H zO;)uXVKU9)Pcg?me;Cire4K_iKMhajhvZ&REeYM*6sXTK&$cR(2l*iHh9^iE44!^? zMeDX>|66M(`HGr8bsEeryuQJV`eyBiDgp5F97w4sNpb~pA}ER^0lnE6 zyX6Bmybnd?2XFu(!?RN+hs7L)5EcSInwo!qUGpom{5?zTVsxw(1S=dn2hX?03Y}uz z{N$f531{;u*`8HxHQ$$y|KfSs*>$`A;pH)i%c|=7&4;p^#awIoup-H+y}(y^(OF>I zW$FG$Nmo@=5q(gGHR;Dp>qSb*J`g0Mhlh~^b%f2O@#`9pGJD23GNlNs{5krS+Kt-h z@uU+Tz4I@yd?S;BSaK-t=bYSZo1%+y$?)%r=1nk}(V+$j=lLMiuIxI`xpv$1d~9Eq z9g*3bo?Lb*hZ2BdvthSnsQl4Fg>6^PV|bRNca(50d)K=PPET%A=*n*H$z8`rezqstaC|7pjAKO}apF=rkZ`qzfvb2mX6vXf@6wG5*S9mbm=H^(V z+!Az~Qk+?^kT}LMRj?j0Ca8P%=}t`sE|B z?!k&h@7V?SZ8>xNH8n?8>7pP?mU~UH9DN!H{7@Ax7%tYs6JZr+>Q>m^Y*6r;!}NNH zu*@Th`r~&|LlcDAh|z!}RjWhRf-K<=*(I(~3=j5NtTMFB114^VJ=_~rYi(AKsznOw zIo=MALitFem+rRBvp$zre3V*+y+$1U=+W~eVaeszx1yAG6)D}=mz4f!#7t;B-;;?{ z`)qS4+DuK|a@&hRjhElmZ8kh6KC_`AxCsf)ELcuimJajt#dUQd zO5y4lea^{o(Z-xu;aV$%tox$jtm|T7_v~=awu7{j9(UW9^SDxqHtFbwH{REL=CF2j z$r)1K5^@1<8GCUu+FyTtag<7~I&l1|Cm_AG+;+^zHOR5ID;sZ8@prFZlbT?Q*P9gN z{oWg>-QpEZ@lA(=l^z2W2w)b%jO5RqYc?j2sAVW~V=Z87vxjvx)bY~1>%SH?xBz?I zFHLoIJ!veXw?CPkm*iEzz^zo?;ilDn5Z~eG2!-Z7$@V%!#P<4AX9-&jtAHPV(Xb$Hu|ieRlNyYv1e$AU(`5h9m9d=dJ!FV8hB?e$igUZ z{n5}n1SL?6(GXyI#(aT7V(@Ak2VPX2YJ`=jd}Z3<9@g$-VwCUUL7gnMHJ&tBj)9JF z-|~mw=6cJUKU^N1-#m;Zn$Ek_o2L9|3Gs^m;WRe~K{Z((CCv`oT&?!T?#?KT|1$Us zi#?4=;=o{E5HEDW01l?A4(q#yz$Z`Fw}K4?Hw!0K%M{mbpF|` z8c$6>+N1OU8cQy=_H*&}jO*G91;^LVV>CYEGHiRFk+hzSV)EtoUt6HFx60yyzy0|o zl7`418{s51&rPtE#CnJ=k}B8!dx~vOP_qq-M2;`pr>KXq;T4`p|`2>TO=?-MLse7@x(VGmKtRF%A=x5nyf+3 zwTAnIKEJh)#KuZ+gYoa_juR)(%2Vy4iS?HOuZmczRsFqK+aZB|a{Y9cRfWfWQAw)@ zr3sDpY^;2*iCsfN60G96DebG)a*=MOWfu2*Zqsd=Vm4ck4vvrBj|Hq#t|rZ92$$D; zS%oCD_4i1YdTZXaVh)34l3c^xRpm~pemL&-C>RZH+PNjs_+e|&CuC_Nw{z+~Y}TQf z*fr-!!#q@%)lQ~MjC@I{gpS^*_tGhUCHep|-1czoUh~8g`BXF_%UFyf4V!;YXlmig zwW7T9Syo~WX{sk}QUoP@Oj$KrEgcC-*m%@C5#~~D<3#_MW(daVie9xU(5}RC-Yr$b z)K(E1=edS|_LkJ?mL~Wse1!@={x_fp(DbBv!O$`{NJ0-^7T)3-fjvCQ_SH zoWju=Z1y8HreC3i>n~n5$c~EvqtLHBFwE{*A+vpoBB;VBKi-11vAdj45bM93`^n}M zbk798Nmyei1LGd&7&q;iHr?5~rq*kJP4_d)%NI)`^VP8Xm5`X<-JTsdOyL^{HX59h z=6qQJjmo&mKvaA*WO9||n&gM1ykOvQ(y8{y7?gTmyr6gxkzK4>=w6B=);O2w02%I8 zRRbTt&MjJIW2alH&+pNEdU7~sy!*0IYcG+OP!tt~>Yv*KRKbRZ-D^Q8!7n{hhl^LY zW_mJF-P(|>7oB)ELNZ*gC_SrXD!9WqvGLPYg^WH%CD@v_5m@>kXyRDw0 zmMR}4&{vwP#9Y?m6AxO%3h^gAX^mgJ9)hYOdEpIs{=@GXtUjyw?{Ct*z%~j;aWyf+ z$CFO`=Ulk)9f#O=*c7mlLd2gVQhGtkQo|qEJ9L7ntFv5Z4ZtI`srCR8>!2kbv_6s- z?^~DN&qD>Gn&P9CQ*Yb3(cY}LyTcErQG&?=V6vAp00b8D@FkxtOi*nh!8F5cGh$J|rro}Y;`c=xs3l<`Wn}C2~n^G)iUjZ^v z+BVHYKb9BFSkB~mkWeLNg(Phy!IrQC)N%4OZ5yDR0plfTS~l*W-YhfqWZ;Juuoux? z#J$GI=+s}zm0$yjFLJw^0FPDd2|LMa8ooAGpQxw^8WzC$XvkAZ!jm^9zmpL_gThVW zqPXzg=LHj98d`?iEDC_Cl#DQ-pI00#5FnCi=i{&C%rQiL4(0F3)1bqDmi9*NKnN`eQUpX=LX!@mDhw$7 z00L4~ER@hPfj~e&ks=U50)#Fq0trn841{{5inJqDgy0>{``+((zWclW<1d+=J&T#W z*Iw({dp*xm|0OAZP2ChF$JDTzF8bHo-}>u|l4(gz1Ilh@AJ_BnU5n?Kh4JrXEE18b zonv07>D|IGwuMgzDVE%&`Ypm92CdA;xkgoI+381=hn+A90KrT{@zTmleuQmFM#%i# z`cG~`jQ^n@B_D?9^EPn9P_Dfd0dZFEkDKmK1da&>;1K? zsU(Dj(Nup}QSirQK6!|wlMJ6!;HR&+vCmw@UBZWePK^?%G$IUf9|BZLE#Bgh9{ZT~ z3l*`vZ+^lv$OFOvwgP`3Rp*`XNo3dPC$44E8kE=W_JT~7w_;MKHHsKtQ^IvFR5Q=t z(c)#2=f|%n$f43FTpnps-ttM9^3r~o<0uE1u90BKJ}7`D!2Mxy!A5K;zUKqyOvSI= z`?XOfIL`?m?rY&q5<>L{u&Ug$2*$c=lEFRhUmIk}J#?PK(0r}8T(QzC8*nf-1D62i zto3JG7V&@xU~}2$^}G;MbRYf6F8WqOmBSl6xlNlQs=Eq2Z&rl)-`A?iCOY|c9lMyf zsQvV=6B!Zm?4X(fgV1MuR+VFJfCAQte8qOhO0u}f%mR^aVU+!enrE8}T%MIrwfFY4WB(-5D-f4)ep^()pQ2w?| zCELHz;r8O--12(EOT)UfMjg_;FYOT+a^}G zL0Doig!gJZAkpo6(oC4qt58#NaO1K)ynA&TE^%M|tT3l7DpBGlv2;h-;?%ImJ`Ge) z??c2b=xMH|nc8(!riuxyZ8Mm}_7grOPSz%Fx9KLBTy7`V33n*$bQ>5?krN$= z2XJE!nz}F*i6=L=EcN2-rl%M|Ch(vJN?n+qQ}4q!5jHhe11DIXEs(6|`{r=w3*4s5 zGj4ttg{kfca#xJX@p0?Gu8t8Q*r^3Rn|mxi?UCX;pxx;8mmEY63Q2Fi8}WlmyMF%$ zUoVZ)*szsM5ar0hAZOuR+=PJ`e-3+sO2sR22#MaB_a1nbZ~v_4>`ZCdPgRfptjCp3 z6V?ci&otEf=IwY&gOT^pL;{e}{LJ&HN3~lydj5VxGaA*P0P~5%QRA#`)*J6Xme$oL z;jSI`C9u|1#Cbb^u*Xr~us6@b8{cz5g=Cm{&ZPeQ`-?c?o=RH6xA*5W-~u4EyUL+U zao*mK`MWGn%tG9TQ(Ja(gcg*NpQpp>wx`1)i(Y}_$m)@587%!o{Qd&(JY(Y=#*#LQ zfC<}#{A#r~8lHHYL`<4V`2ochTWsk!29nGH+x0c0Q}77-sj2QLx(7IV4_vuhKeIo| z@X~5n?4h6Pe^T9k?Ufqk{RS`Gu!9QO;r1zXO*`)W>0)oG6?}wpaItQCABWdP3kTrb zP!R`#4vT!3BIo@pVCIyJPowSpXPRqZ68*s8{h}zI7{?GGfi+$}3}fx|uZpfkhC0H! zaW#FBJgW`+P}{62e`y0R<>@^;xAH|#abPoGTn6^O_d7n^#=*5`XgDl!xT26{RQ|*6 zKntf@>RZZ((M{?-ln>zVk!;b|y9buJ!4`%`h*<&pW4^Z%(3TbBCr`l=s9F>*dBE0J zQkSB@5XRjHJ9ohvILIY0Lhr@6+qK$3{0TR1>H)Tng*wDci+zBKo>A3{ZwZ}r9usOZ zTww{*i2w=qqx9*G6S^Om8-9yGP@r3oTwPKS%W<_Ba{#>fvd z4rf0G0>?)`;ZdBAP5w)yulW$=Hju_jH>g5;5Hi(6Zi6hIop_C))H^&QI92@XJ2if& zqgd(lCD+$&8G*|7?~B3M=$okp1yR;6msB6lh?CkRx%VAh8O3|J0NnxU?EDSDq zcC);VghRLKn%q}NI94#K?BCa#H8^|N)d_&vAGn)QN}ruR-o1N<$b5QFa?jUKCT{|H zYQz>A^f#6@3;7PJ%hO@E1_P>Pup)qYFh=b}O7K9^th_xbf~}&t>}TFf<^unDA=Ac{ zlhnC_3)EgZ6xFj4p**vym)-N%fKlCx-DV8-M+h`!KSC983nl2A z@3c}3J7>Z>3eg=P#c9c^`VDBu-0~gq;7yV5baMTvrBpdqeX6Pd2fnFy_UfK=yOwxG z`?+#~cxM*HJ=varqXOsPbykra%NL|$5!hL_*&02=zR`zwV#gZt(B1bZ*W8-&R=MG+ z8~85GXirPaNwM+gZFdrizK!Mje^r1@01_};kT4yH{Qk+Aw-q!*#oP)TjVds@k!edk zX@VwZd4E4wkSJiy3FB}B7o_FRE4dpu^?R$Oliz0gm1V_=7k{`~UP-<&g7y1=uj{cwjI!EAYGib6pi~3z*98 zq(Li66{aT(8ECdEjgqw=cvTDQ`aIU|YPf&qK6u$*+Bxw<-4oS_*}S>$rH*Y`FN#5l zR`aryo40RXTROG~P$ptO39UQIn!e}5D5N*Ys5EB

i{ZqbV}28ofgPV+jK72Bm1- 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 b-|}m345NN{d+li0PCJaYkygpo>yiHllP57R literal 297048 zcmce;dpy(q|398wQc1W-S8^)nQ(a{WIkzz9oHyj^WSd2Vgpfo!NKI(NoHmD?V$MQ` zODeY6#uQ0XW`xR+Ienk2uHM&my|2&b^ZtB(x7+WJlAYT2cs%cq`{Vw&KVDay9IQk) z%4}S-W{oJ!8tS}e%{n^x)4qNkxW)XJvF&SqN>YG9&0Wv+e;C|w&h35U$L0Ie^M-L< zIK>^;cO1oT`+4VE&&$$9hHIXyqa1r#j7a~0sGipH+M;sTn+UH`eOH0@;q~f!`21-;Sy)WI zOzn!N_6k=UeJWrKNtQo-z401M-ss#%t9=rg2JZ>-t3_c|y?tROE*W5l4L1GuF|x0u|At3P!V%=)3^Jm$`k8rngL% z2Z|U&t1IE0)3J+QER$s}vn=}aGc|_qDzC3Bdg)UK!%87qdGbZoL0+ft%FgI7!UUuh1etbQY zN?O(i!J16Zw6rvAYoW$i8YHXG=9O=^S?25;v&T}-g8Ldtm zvoi~6IV~c06W$%3%#wwZSbOhXpFJ^AvZ9-6wA#8nwO~cM zywYIbv??a1$Ndn`U?F89KZ*?y17>L{{oDEo>4b<$F~-d4R`r(8Q>EB-wc#e`JR)N| zgc-gjE7w5ktZ1U0Fjs=+UPn#Cq_bLPSzcHns}|B)d0ec~(q?<#gKcw_Nzr29O9v+)4#fvdK9n5$83gYf|KAg^uLtyNTm(V< z-bNi8;z#!DoqXH^@u|A=6S(IqLPheQ_~XIi$zKRB=HJlO&w)_ESPSmbXW+ShBwvrA z6v@$ZHCTHw+I6y%6SHG>^umhZ>Wc4qC5#OH?Sq#$VoxZyTsbO`Gb>+iHO4+pGF8Cn zLOV=dIaH`f_Q`#ip=0RrBR)jjGX))@(oGpvn{_rZ19e;YJRBq{i_rBc51#K3_e(XjZ>MLIsc&i_^Okp zuSAP|3&xKiTJi%4Fg5APKL+KNe11Fbo(*;C90*>m|v1!e0*#T7Jlj68|}^3+5Hunp$z$KqDE9$y5E$SvWifE z%fJ>JT|+6`;0n{)<5zC?Y^wEME88X#5WZ*IMx-I_zOGn$MD>(_6ZjB%j^IPo_^d_` zaC=%9O&$AfO6w~#rXo++v!+T`hYpp;f^fW_pNDywW){p<)-8XM;8bN0ZpW;S>w=SW z_xR-#`H2qORFZLf&HK0YoVAwefn>pxM&oqF^4sJ1F%d*AtUDTfuPfw6#n96gMnL_b zd$(u(>&Vh~27$BNvJMT#EKcfdq5s^mHThT`+PWXfFlPkbgE4YWs~0Cw?V0lhND_R$ zaL6|6rGH+KG;e|ZbI<=z=LHe*=G5!^5e%WN-iq+t!jy@b*)VL={?@nY^_-&*;aL$# z!b>EptCr{o850#~p%3G{Zo%svnuv=(X(T6K>+U;Qcc|%_xCqrRO#PN$xOzHutjEV@ zW8c|}b#sRpSE9~hRi_6J;j0}zG^8sGa)bOj7>P~Xn-04tbvSUe%YihT)Pb!w3yGMe zT{Y$HCuH@>;yTF&sJwZ>Vu0d|?09}o4GgxO#>fvSmPfvm=TI+uz`~7cESi=dCa#pO z#BL4qSPseO>g_j-w+eY zH{pFz(vYHJ@$u3dk&&DGOFBN}TrvFZxw-RXa2VqbY=n5r>Uw&j++J^)$Tu;JHyaKT zJL1OW(ff|_5lx(<0c(}@gzZ=J$}2gz=)%R` zM+&fszRC=)abHi5)Ct?R4!PB*Cn){g?&+2l1f&1?CoFrWidtnNNt-~>dNbvDExH3c zw?Stfqy~N_WN+^;&C`eqPvc%E!xyD(KjoG?F>=q^EU(ILxf;m}`BE<*P1L$84AXj& zd@JTvr4625(hO<#Y7DhluFZFtaMiY-_SS6274ZR{`{-q|>C#21jc(GHNO33v4s}5$ zMY%6^c4D2!p1y(zuR6Vutl4*E)dz{*6>8;uCyjBt^3nRu%eu;o9`&zcn`=%@lrtEM zGKjj_eVjVs>D%t7n!OO@=oSFEQ7WST#vSv|pzt8S*%q%|x1KoS)T0|lb_*TmAL@wF z+=NFsJpi&~oSN*ND~zDJ4^dV&m|cs2cLD576HIAT5+^jK8r@c!553PPFo`(ZL8Ch_&smr(ZWZWG^VOZ6Bjo*cfN zsFN-H6+{Xv(dGonm5bs!8rqm$PJ+l6Z9C`q+yvD8A9UnjRqw}5--*iC{)3+n-*V?G zPLUFfpd1%Ok8 zstuia)Bn0-NkK!ZhiiEmnV3-f?6-i>F{`mcD-^Gpd`ytYxpuQL!C3dO{JD*ki}xC{ zZAjZd;3HKLpe)i$p!k7WyJ|wI?|kCX$3(Z|Rd+bt8XpcH) zpheim^aInEmqLgpx=ff}E3RP|eroPE+?onNCoeZ$Dm{TP-MD|EWsQHC+RO&}XVD%D z7OhNefEBq{iVn%rX`bH4d7{%voY=n`U8Bz?-j3G5+p4f!n*rqb5f65e%SY~e*0%!izM!*o_3uoT+y^GhZE~M2{ zH)N-bM)>k%&5~!|P2Kg?ZL}FTmAqe>QF=YUSIPP^M0Th4Vs!E5^M=x@-FB#EtlK5Bk@T`lCzsnL?lNMgz5eeW)i3&ggm z$QcCR2_5^g13UZ5*Vm|#4vC45>KA}-kFriCnmUU5XT(l4STZX~du)|Frt@B26Aa)P z^1}4eMa%ErEDH&h8LVjKjbAKozqYmh9MpC@)k5HDJc?zIm+N;Fhjhm$pjgJ2K-a5p zE4V*(tzJ+eN3O5HOLaV-V5|~-zxASeh-lQxTf3eO^=)^xlK7N2{*QzIo$~U#+kfn5 zKpyak*EdM`j~fryie4^RG7Bl&T6R=z%H2vlCMRb z%rC>848{*MUWSzWdn~Z#0#xm<|*`@+lrVcjeV}icg^^Uu4iOJ_kFkZrt_@o!Vu;`|eE(i2w>- zuUvI`S=H10_H5Ha!yP;S&Hd>Ic45m#q%WzY3DYfxlydou0kafcML1!M6M@czE*GjW zsuu&(rp^VCdZN!osA_8HGn4VoN-e9yi{F5s^Y#HQQ773Ya_Wu{Qu-!3t3Nh0jTqO3 z4=&8X9L1%rWQ{1|!)_#MCc|42x#>nhAV(Fu{84U>&xHKlwlLg0eB6F*s9uVFt^~C` zFYoRgBrV+YRuLiVeJvg(y>@(r*K#S{_SH{xys0SC*SNJUG$gFHr*((`XL`rVRkdU$%?>D+rmre*UVHQ|r?|26*qRd)QT zSbXoczTNVzGJsogXg3 zWWSk?0F)W~Zrpy&gxCqOVmSGi&>6Man{Q7^d3yTErI6`Wb!YB}a>Occ$w!RApBb8# zlu<;pTlZrGf46X^t+V!eQcII(ibVP4zg^=F*k#k#x=~7NPy8pbNc8zXn6K|X!vr(;nImAuO3?II-6^*yf4%oxAK~E zZ4!*zH%N>g2hj*Kot(p6O|c^PD`X+cw=B$ce8nmFXz8mb-80>N`cZtczU%}QN!SMv z=7fucj#NKFDXWTbDtqD?Q9OVaJ>LaM7w!psRc(ieS(Rg44G6c5G{GmFl-DO$j~09* z3V~08g{?bV!-=6EjuxnhWuae{$Q{;zC;8>2A?I+jxS}|K1iWSUO`J5nWYm^}k+!@> z>8o(|Cb@2xjY75SnP-LlbvDqk7C3!tB0CAL+de1jt7E*nG=q&%PC&Wy&PPWtch8rY z+ap_iCt8Y3F0nK2bLrGvK>a2bmr}s4RNI!{@Lv+KJh{E?@7q;Kz;Qx0~XU zM+Nl$pa9ACe_{jw*wy{3kLDMu?^?>A2!y9&HG$uG|M807@6z#o(-m1>)}eQb6V)wV zn6mmgqwTg4dwLz!XnsTSdyRZ}?D`39)J&JnBEJS%AWI`wx>@t1u%Wis-0qzC*Jk$^ znewdQ<&U!S?;3@(h3#-xr0dqxXXu5XlW2Wa>y2kt)#N#d+GG0c`#D)RR2UkQO)&Y7 zS5uKwy&8+su+{qMHR$Cp!3^Wt_e0fuG-}l{MJs+SE;;MPOZGM^j6Ga zW2dHYi}IR#v9&eD)p^NbBgEAYJ{o`+W6$wiG>a)0jqtxIN7U0~-8& zzkBLZEy&`O$XE9}7rPDub;JKAJ5V`Kqa~c3VD;K3|zK zVJ{})JZo?PF?Z-GH}E}9U2KPomKH4zw2s3Q2G7D*HPHyR zJe(PnP6O3)@k3Sf^vzv{W9vDjaxW5l`pAximGh$q@)M{gGZzrD@1CC|%wwgB;p%fO zyZeNMD&kO$cY^D-{{jm$HYoid{tU4QXJ3-9uRxcp%^L7lW`kL&_Et4c`s5%@sh-f% zy7F|EX&p>cfvF`)n$DXSSSEEZ%9@_cKk*QBER#TwOYPBL>@Sn={kC(Fk-*A)W&k^&|FrX@u$ z<4%lEI4+-e)R3Zc4HiB|tLQ0+LPcyJrwez=M{^HrNcC|s_@UJN8X;>H09olo=c&1-%N zF7FMD95el`e1sq>AeIl;l`u-Uh*tyENB8j~3oViR+EOAqQlm!Qemn5&#kD=vyDg~N zrCm_co+9lbLlPh^EiPAT0+;k*2Ya7?ss&m_Fr$aZwz9|gDwmg+-s0+Qb#;fCmddIF zNJ-^Ump8p9G!Va%r6ZL&(=LH!>o=^+Zx7gy z$%L9w$b_2i=WBP1v|ETI7>L!tlUS_2)L?NPDI(SaGB`d|p8DL<{X{-@*|ldbjb~SO z+i2c&F`ubotnf`TRD-&ZX@0Wd7ivIDyAQvvcG=1U2BVmCs+@7VU6b+rnvAB_sbZ`8 z#HON~>=w@uinceiL)atTRyv;(;ZM1MiZ~#wBkDw^)|*jmaw;nekU1Pk)?}#ZxFJhV z2TFeJVRC+VUM?s>VxS$5;#KXyn#dy)!GFZtnEzRh?l8e^lc5Nnm}Q@wyl)*cLD$3*vg*>&VO}Z`Y**1zp00=^egwO2pz&Ed}Y3G!8hMmC2l{I(n{-| z9~0=P+l^?%sCxwVJYZ&1BwqG zM22ddF7|Td*d~ZZlWI~RB%l3gH;O)Nr0*`i3Ts(2JzsJgl0RrtVTm}saZ-C zN)uE8M0vuWNLlQ&X#><4y{TH9vbI)L5z&20Ph`+y_`~oFHFc$7G>|i|3UsfPL~-h%!8AewW&%H*3`le0u*3@(hCmvB5Yi*3j<0L-6YCr>x> z^wXQWDWjp`G_mJCQq?BD|6tpYyLfMD-Dy?L(85eY%4i(9ekjJP`b`%;zHo82Jyivx znvnqKefb#ILOj|l8Zla!Kwy#^T8MEUa)(J2LIQ>r^{LCADLU?}iq^-JB&E~Tj|<@R zc6VLW6zL0JDT4lrh+p$z=q@1Ir?CLBb~f3Gx7;a~ z4mXo7I=@CQS}Y(~^+!?r4-)79@R0VOYMr#nXYw6c5Le3K1gaGilm%h?wyD53+Qs@g z1A(m>`&u}8t9NSmiGwoNZVtem$?5CugN>Vj1GKTdk_e7-j}gd3fL`Jl!Mn9P`airq zf>SpZ)(j1F-E~b@a|`wn+LDb3EK&*EF|Ri|cGI))S!PHzAv>+Cm82r?Vc`j+OV_ zBDsY_;2+*QTJEFcU7N!_IAyRFl3I-Jk;w7!J&aGswTS7LP{9|T#iMv%UeF&aFMDU3 zbcEPX7{VUH`C_(}NcI(J;gJWmbqV~@-39Fue&)c{W5D1v542``w?UbxEkOLrR}u0L zHw{Xu84k}REy_3h-u;ECCb{J*O6i827FwJ9=0kb?Yplb{`2f}D@u<$Nlv|K|j*;$D zuAbhSNe`bL{5Bo1CHKfDaCm*9#nFlDO3(4ev`u;>$ytLP&Y2 zNVG2tc`~|=x`0A28P%MFl0WmaTvS4sUS^tqaR2_Y#ZRx;N@B)ZDtc|&CG7DiX6ANY zL(Fc%e8ql4BeF$kdDzM!7jYd0Mo0(5f2|Txeu{~;KDtanH4?JUd|KPwRNtr$iV*7V#T+1RhFegK9Y>J zD4LtII28sweGWad0xR#*azCt;+x6UwB%<~~ZT1DZpyuST07KFi38TG^xE^Zec3Pt^ zNgBBsA?tL2$IT?q_2!hNm4!^HYx+Yb40#Q1yVwO0e)$RMV5H*&+8u;)zTU?D?vz@B zoqr76nFjRU1JGvR;{X(g8!T4CPoviZ-Ns^Nsrv#3b`G{J3zQrS&9S>J0S@Q&@c~aa{LsFlYt`-pA<N#?tba~h&3vxz>EY`MFcWcRSz%RgT=c@M=p0S z)_+#ShXf*=O$Bw%Fdvf-zlkGv8%X2@jXvBf_PYJ$nyRSbz^z%@Vf)4(^>Xh;RgLrzCD`Tb&jCaz(cqgs!L+>H{KpruVPl`@^ zs8djt{q@#1#}oUzfQ(~ETA9r+>}|VT+>Gh4iFEI`?7jG06Kx4({=zVIY{hmHi+)cJ zr2uF2dM_fQvQ%QyrgzKT>G`SM?|-Ii>oap26Hc-=wN4JL61Zct9yT(a=`oW(p`nY> zq$9IKzOM5Z&oU0SWoftE8(z<8PZpIk&POo%&<;>$L#k9(#G1~vm0MFxi|CO2ghyFZ zw@>Ryfkaa;hU?NTsp@*iH2Ki)xT0HA8b0(ztr90yzub|uGk5>J?McVr`N+*=P3(Q*`g{J{j}Kbw&gBS zf6$SL<%=rxczu3=GYO;sda--=sok~i+SdgAWvE6W%yn}UJ47qAcPFi-%UlMtNkB?> zZO)1?1I7|zsu4VCU2&}sbRya>!_HOxYD@5YL1*=RPRHVDH{0enGD+!0fSX(Wpw|D? zpZ|lH^CRp2(2@V>>435fw6VWx$=?CV=niA7titK0rj}MGUN|ptvm9Pe3`WYUmhwZb1dk zWRG#fxD$xX?JP?x=gjSdJGR&_P|lG4{@B~Z-=LO(*D2_kvtCmIQTwYfSa1}J&pYS2l>p$P26 zBUTEhcR4XDw<9pnng>yxO@TERu7e);*?Fs)oaMePLWeV6$28VhEgJ7V400*aZT}Xi zj!*Qp(1HE~#I)_x*vL&lVn>5Pr+v|M`E-K~=joNlM4AG`!f^b-FmPc+&f(IY$cQvT zx;K?p+%yXE^Jy!)*K*rdpXrq5$|Iy~^SEuz2XeMw{S!I zmk?|s*<jZcynsm!ZE|>Ry9$tJep`r&J{^D8;Vdy zxd0WgWS-cwL0MW8S{$j$$9VSdareDn7uDhduTz zWqbIgELVoEWZ1F3y_{DBL6UCs>}`h$Pfg0y?H2MzEh1dbMmgC&c(fpz)Q}Qt$(F|o z(+lggZbNQzaHx#A(__NX>%KO1t-S>)M1ct~mJPr24wvk<|E>*>-=>LBjgtJbry~L*4o7JO=!+!$nr&&8 z9oEV=gvH@oZ&=#-On=@9TQzc>)kp(qZgeMYE~I>M)1exOUp^X2zF?<>kI{*46{d5e zxQckTuH8tZBI5N9{5$g+P-S@yCg8Iaq?jPY!uV*~bzH?7A>0 zdF8=%vc#>P^AKPuqQd-je-9C3vC%ENX$l;|j4}J%zJpl&`&&;`VYawT>2QfNXX-zL zDt@N@S-d_F0c?uGHBv1qBS6mvJKmkxgy3{6ud`teY=zZdm(?iW?yD5wM49$b;TWJq zw1jPBsM5<~#O2iiR}Uq$qL+T|4zc`*dtGa*yH}#$UN7TBor|F$p3RUCBkrYyKF&g* zhqpAqOil(6-;2dIC zWC)w@Zrhb`5utuHfgowPUnkTZ;T*Kj|J`~GZr0!+GGr&BZZ$vSh~T*uTi@U}U&~uM zL^5Ew+{4<`=M3(|4n*)tG>iKf49J*4bIb!}jY!k*#nQaOHKmkT+eA_KG|LoU9~-u|{vX#Vg7EWvR8l1N2(FZ?NJM ziz+ah$&U9ENEZT$5+)~lBGMh-Dl8gASfta0=w>yAiuiSVMLNs2`AesBCdNV>8kXIB z%v~2_qHHMFQSf2A@W=VXaEwM!;ZVWmKYS2>IBfpNF5tNV*!x!-;a@tuea3&!r~l#! zHNOJ|!WfwqY{}vps1eTbHQ8O8PbK81r3pm@KcF`4UtYKrQz!3)(Ms}*!><$^S>3^T zdD}Y54=S^LEA0KtF>6iCab<<8(v)VK7qqX%r36xhX{>3iumrSPi-Pi1UmgOwfbqzalL3?l8T4Z?8F{qg zCJONWs>QX1qDBl8llrEa<>*-qs<6BjO_ZcGR-Z+&3VSBeJi$Y*u6gf5kQ zpP0@q49C;nifE|TUG228%U}$6Uw2SWto(LNVmDg&4Y*Gn}QMXAvqC2@wI+%b7 zD1@`eK1(6-XnHyGy+qAq6Y%c zU3(Z;Lq=hJ@_P38b6qEaw0S7i&sK&`osPW;S)FX#3boh1`xNRkH$6k#O*8b7c~J%R zF9C`^lCXek>Y50Li4E4~J~KZ1SQ$0~D!YBL+^%+JN~427C;%ABj^IVv(XI z6u_6jsbxkp{rCt~#LkeYnWK}>BloWMr4qsl!WJ56cPB7^ zQS{08>=gP!x9VON?w?=EOel<$%4^Q9^sFsnKS#~b1h0Lm^mC@tAN^$pO1Fv z&t=u$x3*bBzc`u;93fhp^CO(PLRy8~WQw@K(XC-L0tWHrWJ`GTjf7}JTJ+R9di6?H z&Fz;!oYaP~qcyEUaHXDcdr~WnKqs5!a;`GO7D+(L?LWjr6iU!vdFZ zn1XM28-nqrHh0wwefZ?TjOfH8t2U2ugz&VDE$5?Io*5f{}Zvq$4 z$}2D@%aPCCm=BPK^b<09CqBWQH9H$Db}nHj9u(%jvlqxe-VDW{!ZHo4RS6v zz|*-QyETik+R#!tZo6I2M_#N#e&M{_>jTdPk+ZAK*EQGoV}Yy{$+(kJNd7osD=aJT zYNCh$xsT!J0-TM4is*#a@y!*0ZyXIa)7f=XPc5I+C~dzzjcW zPgo}-$?xIIs%wt7En|<`u`J93KJ3tgGjob8A+#>NY(k$Og}`h8MtLWv&aF_gxZ314 z&%C73OXM;j|47WdLXH6rnw9W6-eFo4F+CWc0^5PuWljk(RYZ)`PS+y~fVV8u!Hj~> zE8G+KVc{B?m9^*rq|SJ_dtOpqCT7bo5md1zg^A@KV$olGL0>WG?|%Qk&Ts$DhXm>W zg)@W#g&3c7 z5oRK3bYXRMsQ-7AMnd3)OOJeIGIun-a)ljFJhTf)+1nLav=Haqbk?!pK-hGd%4L*~ z?qPJ-4Bx8AH=Y3=G!u8m3 zH|Li=j5jBNoXGY*KgXAbP7e0lO7nm>NNMzXIyeKMN9(J)*L6{M@TP#Vipc2OE~`!} zsO?58i@3rKW-Jsd#z$Q$l3I0>8j|hw8<2&CcN&|TqF1vZedlZHZlH8Xh36fmh##5Vc&#K7Ibxal_wkEs5$_hRC z1p&*6MGt9mdZL?PMPZt0+=Xkvo;o|y0X)k7VEYER#Om%v7N6LWAxwYUr3d_bT2!1G z2#gTYBN~6pa@ywXT*4;uvXY!kBNqMA^d{U;zD5>yMXZ%U`gQc3v(MBZ;s0~U8@hlY~**xGO z0^2Oo8@+(;0i-0_g1s4pMuQcYdZ20Uf^_vAP}`rV2mwI&3xz1Ye<~hd7w-PlF#X+8 z_-!xqy8!f0NCW?i1@T)7;lxLPYd7zX5G>k_z(zQ2+33l#tI@CABK*i`I{N9&9ej!G zLfs*t$5I~ELKL+_U3PL)-gnQxe;M?#tNf{os%n@R`qbXvUtYf&ayoBD`&%A33>IO) z98o`yZ{&zBAX-#^9GggZ&v*tFRQSd)gzL-h_r39?LvHazfEJ|MT0O?niW^qA4r>A6 z*jA7}YOilo4K@*3hlCZ>%6Y>i#teq#7r;qwqx3HfEv1F_7U7m%*PaD7CG-wF*dfqW z0K;6!Z|J3shaeNOBK&3u^h|KJvJk)y6q;rQtm2@}2^Q(JK$6~bBC8MUlUX^S@FeR2 z!xSqOR<72PH1}~XfKgbUNlKdl;Bnel)n_)dl=QXMB%f@G5c5?e2_R(}E#kDLMz{vP zSyA~XK!17OPu2y@P(^eCpv)k@El3k!7zFbPzU?qUcMy2mu`9bEOSP_0OnFGR5%~e& za7{?DO$jjs*78`$F(4nYCrF=kZ?1a-Dh0v?83$Cz*`~apbU~=C@(*(HPfo8tcc8FO9h9SNeTkWrN9P_s#E=cr{S;Yyuk z@no-o#QhfPQB4OQ*0n;alg<^yiISHM+tS^`Q!d&An&wV~uUU4lp4`3p_e)K6V{ zWa|6{e9Y9Rt&U(aIe<1nv^i@~&)0bGL}VH8Eu>l)Rgh+zW%pal=n+$h!(Aacus8%Z zDP8LPyT`JS7O}bCdH$S{dFpL|8JlmmgIOW%<;8u=F|)}0fs^~ZcLEb$e*1y}uz%K^ zN*Xl?Oq-MalMnQtM3w(#Y51$Ll)rh1*#iueb_HEm)ClSTl0C|Q$efo%$e0zH zN;-lxrTND0<}FsYDv9fy3d9QqpqGwj+5#D1%FB7?0OQ50dnR5j25$_eUUIERS}p}r z!D<+)$z^^DY&N{EaI*0?>CdV-$tZIeBt<>xvzbu)ush&I?ETQA@Y86^#hn~NlOg(` zPq`FQ9^Rijx=>cO=pFv5is2J73KUbaA6OO2Mk_KY2ax6k@E}$e7zyN#;!>oA`yf|a zqWd=R$9HT*lC&^YTYKgKZpF@mjc>BmP~7QW1(cNKHwaxqNmkm{xe1E#|)*!@Q$%Yn^^! z6`g1*Ji&fwC~wH#8Csx`yiFGD7o}V~L_4M7SoEP+MRJR9e3{=_&9u?8IvFhc;?#Yt zUYks}t+(fIIlL^oRs$%(JAo3X^)nYdoZp}Z<1Hb^l9PSDD-|amzI@MIwXYVRe9NTX z(w1Vfe_vYY;*tBafXl%hFH8)@^-W_zcLWAIv{@j86O1F?XoC?Bh`&{aRB^&Z4_WOQ zqy1ZRSyIqzD?_{?7T6Oj(YqiVUKX~QpAWzgs_=3pspfsF4&!cbDN&_fJIyFb`l`1~ z=Nzj3GT7<`6Ox4{<7O~@D1)O}QFK}_82JE@Ikaq}EYbrE3V>vY+xs#>FO-)%`mkDG zx8Ep-EaVT{iLeC=testJvwcRhPuk}U1@z}(TQB?krjB}@4wl=0*&1oIIUfjPL$^%S zM$gw+wuLW;WcARHV_{}Bb(c~07wU-54IRrjguLAN`JesfUkSs%HKObPpqBi*zpq98 zpceey{lE4hUqQhu$!~|Ps%7Txsq|TbYGHidty%nd~F87b8 zQ0G=|I{HgnhEP(jk@HSa9*Hb(TQ9`Y&q0n^US=EDt$|{sl#H_LP6w-w_p~%w=Y{-k zv>w>1cB1_{env+RoWOkl>51V%)?w>q5d^=na8*V@|rRu$|z zMNuRr0aeiUt156AD zHnG8VQoy-2CR?LV6HU-oG^z>Wcl2PM{Rf) zr$c*^0Avy0&Kcom;SmO5Xc+hK)?DFbWXJ>EiHGrb^Ky=kA>RW3ON;~Jgfg!aekl|eC|p=$ot(!0pdRUHbcQv;DXH9SHu zquMJl-6&}k7yNbS94wylWW|~$e+mLc>}%cEW|A4)V9_Ay;z@`CMZh1e*pKgQRgV}g z01M00lqN?X=X+a(`z$aHP+R|WxbwYZJMgbZKCi*)Y1tzIS(T)?0A(!C6IAyd_^I)Q zc-_M+-PT4je)@2^04I`r+v)bO-S7xq47JphIzlTagBc2 z3dk8N-wwkmTQ1h$YgmtaS@Emn7DFX%;IAg6?W?yH z19rDA-l~D-X4iIHN;)uCa16lufp*sCF6|Ji9&O2>Q?k80 zl=Xp@6P}0_GSy@K01}Eg-%s3BFB0Z`61%k*{I`{Mq^gqWRD6 z>l){1ZRVo%nSG9ek1TQ~aKT?akD$v>Dm0CqXto|<@6d1(Idmdu-S^AlfJzI>_dfyg zj{ttJJ1Iwk5P4iV+H7zBTi5p3mwrs?EFc)@&eZyX%E_qsWZ77#6Q z=DB>f?%@!_bep^e>HZIa@131dq{<=h+`{25zCKzMJ3PQ3KJ2hzo!>+Ipeu5}ECiBH ziCvtA0|!rNy06J2oR9+R6-4Bo_IeQt9^=TzK8H`(_#IlpPK5Iefc`IOQlO#H2RomYILscy&THJSumv0CH|Mgvca9E$n7z^7M+!2Wf`y<6bBN55Ak~Ta@S-Y2p;rp+2 zr^~iUlplU)?{6Z*zbwulr0YMMCj0>8|C!5w1LXI=xg)Z$7x(!%lb>)ygg49n;wlSm zCgxiAi(aiY0D?63P)&E4=*@1*iP8D2P2wwNO5A3Pd9=6UJFY0KtE zc0G+M(L~~JofZ|Wt0=Iu6r1znW+Hh8>8lA}|NdD#94MPfh0Px>9w!mOx&RRvrga~M z%C84lI*JEuo6fj%1uVLOOK{**(o^^*Ia~kDc=k~E^nJ22f3vR>;h0FZ@0^YNj6)J` z)ddWw2K3j!nr5g6NE4N9M>MB=@mF{&^KHK3Vc=RsHbprxO(!0v6O_P!h^gBSYGZ_7 zY1q_DH^v=OCBLoL0>vBy_5e{}8PE*`4^qrA0?k0-{VDC+~H*Uai^q7I458A31yYijG}1%>n9 z|I|}v+Eq@8>xTafOrZqR@sX}j>*EEFdAqn1F`tJoI|IdeCI$}TH`S_wD`xg@wGAH4 z1q!14`c4(FSLPK=HA2&gBY+t->851v3TQfdCG_=6)6~MOAi}$cdSZSNAd=+4u45Q( zE!YbI>pQ@#rZ&;YYtUN+Cs?fb{TWq!S}P4)t%ugzq4UCj7m68Fm@|L{4NVrj!&tY7 zVF?JL4#v17MJ=HbR-v}~@v^Bk*q6W2D4zl{iZ?v<#}z zj2(Y|;_QxAM6f?6Ho1nTaxq)xG}5Wf?=7Bz-2@4Hg+6`;O|Y7+d5m}Q${1C|*!db@ z2bQit*HlyMkL=fg0w+0hr~q8h!^u{JuknKV5Xx(8N-_0Tf#*sac6RlPagF-ok4eWw zeE#5N=v$hrvXwWj`KJK=yEyt+2>troA9CvN->>uWBW{13?7zemSjpFbcQkXMr?7xr z|L*2H@)0dA;h?E7Xr()aidAak#GB|6k;}x-r|d|d7AE?PqdG(2wj1dd`*X|YsneOq z-4U0uc0C?Vw_$k~(w>~EC5ls;3QbzaF2s|I?%5UPU?}EMbGJH3rzs{ryk{yIFy8~Q zF)Zo|&yl1*RB5><`6M8sNi3{%DxkQ)QN-U~67rW<1SXEc=^=}4V*Jej+wDDXyKHO) zpE54gc|U+>&7R1LEjYM3Sr?=X)3=rB@pK&ZGu~#XrQ^#=rJ1hY=l&>{XD1OHme=Vt z>D@=PDLJOTO=J1jh}4rld!0Aw1A&%x3Jg!I3RAYu>hPBY`aXAAG1*?=aJ~ROq_42? z0$gBAdgEE?Ux7sL2kK^}T%2@!Ed_a;lULg0X@miP5PM!<`kdt zWJ4=WYU~lKU#3aDj>c+(K^-=nIj_?hf%nlRp)Yr*!({rn^I=a(yZx%)3)Jcd6$(`7 z(ZKgns}EGe+~Y^>Wpe$pbJl_38MwqjzuBn4YrKU0>VXN|Y8pmgL_m*~N;x2@6H3W> za=xapOwBivqr%}7<)nXwVQ@V-aIv=pZsyREWz*ZMTEzSqFhCP&`)a2Tvv&l&D{*8u zK^r)w!=jThz#8!XIQ#NIsMqiRQM%IxnF?KPQ5PoN*ZgubH^SR&OKi7@%HuF5sIp=j==XK6QtNmBp zTQ9wj^(6ORlezaL&ofA3AhjkXr|md(*oue-VdT50Erl_t;qJ@76IJaIFv$dZm!PHQ zS(;^yv+@<7Z5k)kEhWb$coKG$36}|fTl0TGUBGC>Z^lOptKTg2A7Kwn{a8=Z1LW{B zmO;<3Wu7(!4_Vds+<^@Jagze_Mfbx8M2Q2(czDI;Z=vfge4?lO^FS<}Y=dd3h|RMq zt!lZ2E~(Ak8}znc5%^-zI6w{iVSZO=AUgc=LN>wsHTO`Zz!&4y&{14emRJ7LjV3+LI{BOx zjAE9P3ka9CV5Lmn+Jbx_RyoLKNzA0WIbr0Dk)`(;4+xD9$2y*Bnm&ao3bc`ZH0+8E zH+)|%#g%$DLo@dXbd^E9{b0>rqW$K%eNkYA(=+8>&B)b~wY;JLrt)Bc2{lA_aik@k?6CiDc%3A19Ye;|}c5&|c z7M4XMb>M?nxggIk8}gG!1aA2kD(N%!{{VAfu*z?^-G`Vk8E=o^9vrFl>wYOkJ+ahcA%a~=OmU`R;-RpQXA4eD$;=MYj$NZ8d_?kk~A2ntJO?CA>;vT)?_)mZ-LI> z#&rc$hoaMsK?CmQVQ8A*FVi$>Cu}%$F45Ys%Ar#m#`w^b>MZI~tQOoMtGw5UIa!s~ z1R)F~G3&PA5A0+8t*Mn@0o{$=_nilM>O3T5k9z5N{rAN2o85l(upc~ke*4D1G~|KU z`eu<$L(XgpqX$j~L@Fs=Y}|D+DJ6Ua_88PCi<8fD6W5Xa(Y4TYLEZ_$tGc>>Ba8j) zjX+4ry0YI}HsTwRcwMCP8D!G$K`mc^O;1P-Ltp6%Wg=NTn^0P90UsMVtZ7=d>bm@ps=acug;pdY*3(M3#4M@l zLC;UgrzA9)o1~ml4}wTj3Di&(vEJMi`HTK2o{R^RI}tTmeSWAB3!Oaxzz7)P%*T$) z<>IANZ}&Cg>y`Ea!feSg-xp;#Q>`|3Lu;u-6KGw?yYTj4vTO;^)TB74^*WX)DFFX1 zD?3J@5k|+P*kMQoA1y*Umj$jCD3i>Wv-|SX{KY`=^hAtRZRA}_MAN-xANw3RpyA>@ z;}7g#S zS~W8?`u`aOxZE%$91O+;`N?&c$L$$Z+@3Mm~ujnfVAa zA%r2d$8;2Gk)ONco4n7u?|J)2dZj854O%JpTz$wvHl_Q){1b+xa-g*dV&I+gbS-U1 z?eYP{Zh^eexvVmm&|6D?9>{Y0;npcZl=?}}Or3CT23}Vxa$T@%-J}@=KZYb|UiHM^RB1meQotG&oppS&;ugVMVieyt@7*I}t0|={0@u z9@aa}7T4+W`7Y=YdBYVfHy86HLV>$FhqbMW$wx1Cuv3D>GD3q2N|SIoZwFLA!y!m_nEzi(ivx* zGFvI5jo7^h{U1dXYy`jWmn%arnFJy|HV(%IH@-(~#Lc)`lxPWF`IiqW38^0qP5)aYr&Gco*8Zrf7+v}F0p&97or zW3lgAq5o{f{cNPlZ9~pk+s=U~rbVtujUYRBF_CLw+jaC8p{|6W=$f!l!eRM)apyQ~ zl>(!H*0FsZR$)a2x#%6{3Q-+?F-->IIBK^d-YsmpEkq&pF?)tR+AGXujB-2DL2g`j zXy9bk3C(a>4V2S#-aTxT+pyocm0QJDm9epxphbLNP}<9Vtf#4hG&R&-;!H$bR5Zx- z>utNt@Qp_fN8UU=*<^N{gFAPgB#yCBN1fi)hFe@Zr7EJwt*21YmZw!3f!Up zdE36{oo`?xpg}r7pW6V+DpxEH6F^yQs1oM;=ewG?p--+9Bh;o7LZYyc2o-zSp-Yq}1R6 z&wrL2U0KcxT@PBul^*%7S;Fy7mmdVpPF4xPEG=@<^0e>}t;gTOy?^r%%}IKM561*I zis&m5j=-q!qpD5A&5uEm1;i(4elEehO4)kx?7b0EIKVS^V6sw6mXx>I>sYycQ1bA| ziB;;RapcsVIM*_!8W_=*SF53Xn8+vPy$s^G)R$5HwCJbJnOy<=sdwkYGRy71gslHf zmu9O+Ge(0J3>)5Z3&SPGr&0wae{DkojLKix;Be>hU^nB`LWFk%;I zT}_VznE-=ZX|u~GNO2FiL)5ry1787da(5KZj71y5($UqzR!h9t4Qb09SlLGbA^l$Ga z>Tnj5V6XmIKH4Rp(;aG?@)|G4Me$C}qjPFv4cEkmaeCJaqQ-5K`sowIw=3wr?SEc) zT9)dyY${@wC@oKvRqfq|XIwZ@vk|2y zr}77qFRD?WJBVb!z@sYZgdu0($P&Y${>4)?wTQn6HYySA8ljFeC#DH=tI~Ng7#hP=-#C$ zFxD5Y9If=_(@_{fyPD9!yo-BGO@)`cC1&2|8tu^#GSW*<^M3&jVam3%p2edrm5O)E-Jd0q?ku6=TRwHG| zCrdT+J-lWsc|$LMrLt+#mLDJb9gaW;AJ*LhbnDmmno9sx#5Cd>0jz=-Bo9h83)b)P zMV!S$oC{SxJTMf_{hZ;I0T)^mIW&b`y}kWTu-)#U=mM_*N%mbAIcj%blWrZw%Lcir zy1MQee*bp4G2HWvhUNaD-6nI?P8draz23Gi7=1-W>*y)yibruJ0N)70-yW2R18Qy? zQ`6K1B_8`@EfLul6=NrYX1iCGy6-Vc!wx~~>OG~;J@l*hXPoyZYwj=Ir8xKAxpBgG z#WSo6;ZrtF3RPSoNBFBqQrjMkEKFG~t#^33L(32k;? zJLs;cUTmP!eOMTTu46cL5$YsE_(n090(gM1d)e#9c=8V+=g(hwuWZ&GaS~9h1XXJG z`8_BO(Dyx2z%CQsxT~z;&VMNT!p$^4pMbCbLcQyS{9o%3bKnk^Y?x^Q`t$k`59kD1 zG70nv7v!Oe#ZBGx)ttBPCa(*YEEkI56}(7qVya5m^P*ijvR*0%`3#^71)7PTGwdR2 z0GJS_coDb{GyR0p|K1oX^tZE&!tWp#N#4GP>NrKnq$ovP6k|1QxuOG_W{NSez^kYI zUu@vo-_4%q6n9xR61ihSD;B$3)Eps5DG6|d)!jP^D-Va_l<{k(Vvg{${ zTl6e)XLF@&3vIWO{h{P-qEpDHwpgXSyaN~|sf^WoWo~a**u`oG^DD6r zRmZ{(cimaVae-Sb?kqI3Rjc*$(i$TGdq-#WEfn#zBB?Zi>|1)f1wNKmWJ7c%Qd$RK zelw_%8}l#JfR~hB{F2|W?c7N^LoO9@;*yldW$f@oiJ+{Oy!4J3x*pn3le7=BeQ9QzMYEgDIq^|&_;?a{%C!HAly*1Fw?P~GaL zbkZ`CAL_55S$MZ!889IP);}x|=XU;c?d@4Ku&<+lpMU~o*mh9@QQ>chj`Uiuzm1+8#NQFeZgJXaKZa#~)dm72gC z=i7mXp!>%+O5sdQ<}UZfVbjwlv;>~!J7#BGh8r`G1*Zk^jTEEVz0r&_`y*&mMWK3| zfBvLuQ^@9hps|)_R=ilhKlFD~tv{ZhHq#vTF`iq!v*kY@-YhO!LiV$s*Pc;rv0q*I zWHz*C(ssdFD&s%Lgue%t)jx(BhYE8P!%?0mF4*}C4_13R+2?0X{Lvp1ZAmRJPWZBc z(qBcYCrgw{oT4MzOb>Q6l&Lo+STsY+*u?Ch5Wpm7&9{eiq05l`^lD0-=)RaZPEvkL zLg<^7m{L#wn1@;Q$S#R}eGI=D2p*uXzVO@mXB%H{+@ktB<7rL?H9i9+krC;gTchqX zsH>(_vY6c4TJiq*IE_D=m#MrmK(Bz-v<5{M7?`Y0qSu-I1R!IW*5x0NH?IH~pFY&%NFgg8R9 z%l+Q2O52?qH~EoYFJJoZBl~>rE&hC)4*@`GMSxX|(gYzAavjG)xzauOmS7pt)1hvU z*UrOaL%^?(3R^rmE?kuK^NCu8fS9QuxAPA~YNs9=2&1M8G`1@QRUQqOkrx}?KV>&B zrnqhrIsGZ&vHQ9LiRH!UcUsYSA5q!R*|h!_vFdrY6VFUT)bqBC&aqKMC1L?(()vnvCLz~G(l0}$3|mGn;4`$vCUH=9-#1KYEqG+y|DzW z^Coqv#d>BE&irEe=ZgJ$$mGJ=Eb9d=Hiem>$N+(-^6IWQcRl8&S-Ig}QGOS^dt02h zzFD%{;A=f6K{0YTcNoPO*=hGEMr(|+gT2EYEmmvtq{F>iya;Nd9$H-sEl<;)Qxhjn z&A(t$x|82QSF@<41YvG2&I^u2Li^2#jg0j>{$d)!*!S~$d}{dNJ_zugtIPQPHH`FX zBQ9cRy+6%av|YB!tCe_aZo_Zp`8pJP?}Hj4c1$gFCyFgYoqjDw9cBO918E=qqtBYh z4SRX^6W{9p6An2VGLt;EpU@UY#b;i@+OjrXyMK2j*&2JdnqM>XCO_UIr+8|@bPKJ6 zLAr2elp;Fkefq8b)6N|gxVFHWIjy4exi`a4WzLN!jJb^;&ZG4=9Udz#I8RQA;uDqi#JRh(%P5(OB#hK)vFpRHYILMm6gL*Vuk&bMU&hdU$_^vot$idyR}` zP#V^@|B^yf_wzq{T!QKn~Z=}uiD|6nk9Qp zQ*2sH866(kDR9mSR-ffq;Yv;AY;yCs33WQxeW3}t)i(Uf-0`dvx|H`=3iR1K!@^hg z$o+s?CwnVs=k~{V#fZD2{AOwGHDtaWC=W*0H#fCUPA0d-+Rtjq^)uXLW2HUjs{cZ|9}UJ=_(&i)nz|Ce<@JN~z$g4%c*y71(S zv|azCx8uel0in!K-F*Wscgr+yFV1YsOhPW9>KW$&yF-oDp0D(v5rZ4 zHWh>@Sk7`Y&Tu}Y$?jWx8_mA~FL$-7^RVlFTAP|a8c8r#*`iW$!d_(Jr9`Jzuv_6 zoql=tmuJDqb6`Ynn8fL7r zQI{(hNCUku1HC_=P2qR&S`N5;B9C3(V08ZsHbbtqQEo1QNvE4B$dI2ThD<$OEh}bII-1K+?9?PSvVRw{N|AhBhKgx#84RwSW=-7Ia`t&Igo~?vv9EhL4=H z8BtLl*?iTH4<_ji@gR=%Udnwvw~dE^Mw0zv6Mv7daLL6gEw)~uRvv>RdTyKJaceJ{G*9KqyYHq7W|Nbxj zb0g!|ERy^g?es7xov99g`#OK9R_SgIv;oY=AbK@jarbDzQOruO3X-Rn);kCQh($+w zH1y6_`0~`_z5P;%I@5gD(GmW5zE6+xv_-}A9o_zT6Uc6Yohn>1Vy&A{x^&^~gZ<@i z(pq=fI#AyGru#&2xp|=h&s2?nx`XHlm>nG}d|2sDPqH$~s7`Tbgm{bIrckI!^dliWIlhgn|N?@L0y4dlBP5LD`49?Y%E$v15>InG_Dmz^%R1)bkyL+ntvp3^?xCWEhS&O+*y956d}^MR zOKziw-ItHXq-XFGR)Y#R$v~KgW=n_!bk(( z_}Dv$n?X9z{%WVI(cA4;-`{mw9DADn<-?Zp6S~jb9tAUL0I1S^uZ!+~nZ-A)Yi`aR zKpu^vUMl&}qo^O9OWsuCH8L9Y0o)Vc!jlK>)B{}3K9{(kWW6yen~I7AUi)j^1$%VU zx3&A9z-rGe^ik5EuR?~!@FF(O0|4_z3uWZarmqXqCVa7f;jFaTL1Iz~8d}t9PUyHK zh;Gm~zo?m&Wbv)s`YZNbv(53pE?}ft(Bv0ps0h6U?{{4OgFhaoA-oIb4wM9y6j3LW z%8t{T+dnWIgD%h>JLP#fFvKJI<51E4W+t_QWp91Q+1l+_buw9P`Jcri#@qM_)WYuF z6|W-36!6BgP3E(w@)pGc(IXR2t;^rkxOGZaxFu~b9rYLGCe60lbVN$ed7sr7vnX;` zlL}AQx?EJ_;Fvos)SpXN!Qbd_s z`JHF`^lvLKK8&;``nYHyULoJm<$K@uxc0Vd#1QVxThilpOn+lkzR-+dQDJVKMb_G| z8~_80SF~rYDf<+-e>%=L*Clj^ zSuK7S4lYBIs5cQk5Y2M01E! zkRMgzDW&%fPxrm{|A?G^Qo4DVE^v4%NP>xUObCUM^=u5dsv}2HEx+e9SaUgl8*h|G z|E<-iGCNB%C#x+J`8z9e^m(zwj&rTF)Wv2;{Z0sf47M`$ZR0xtqKz$6=$f3Mby<`3 z{#GW@tqV(UhdA*nhL03#sl~dokLfVOaler^%tcVc(B||79S$Yp{kF=BCT~|W&WV-f z3DK%V?<3`J?%E2(q0{Dj=+E06UJ6TOVz3h_IicuQB6tI@tfVoJ?I zj=zXpx-BPof#U6^H9v5;yxo2(?6Q~81-h$o#?zBMae=#do42Gj)E#?iC}p?e#-=NV z_qK)YU7pEJZ=-(ES(*N({rc@&?X&N7=-*Nr$8XrWMF!`tqL!VKyo=cu?l9EJ>&q8@ zE?Z$0YwiH|=4zqwtuOwVC|AA^rxLa($dSD-$dH*hkjM2kSv~Tr6F&Crr))Efa%a^e z!@Z?Kk{^D1(2)1>Y^(-?WPp1X?ac4w2vT@|^!Y9UzlOPkvGI2(N-p(25pap+$nm?W z2FHy>Pa!GG3aQ3-y64+b^0zM0`=vb;Un+wur7~m9dlL*v87EU8&N)2!Za6e z<~`Ue%H5=~y)SDADn3!)52Y01`anOc3F&-5J6oV4|4Po$@nG08d@%Y(?c&k%U*qpj z$%rh+ugDibkE+y>>T7cmL+!UKa(8ps4KD2E)&dCHyD*|JcBkEI>|Quq-KI}fgBx&} z{G}I%HTozlK`oorsDIMix_kvw$2RPlHt_a9upS0qh7w=qHL!+#*=uk>ayLY zL@L$IoOIc#Yx(BGj|of+c6Q!@0hpbr_)d3>5DKA$aIEFUwf8CZ5roqshd0!?TRL?q zNHe+Kl3pO$o!d4SapTkJP!3oZYd`e+!%pSjT>|HkA9+N0`yBjs@b1GH_}u36s`w&s z9Cfp%pz(hZyWM^cxvOF}xxF9q+393g{-)7_$r4*dCv@=!yDwh7KGw{)%at9{l1sQ& zOKxY+&2hYQ_UJb7fpnp^1gfs9)W=lY4MsbRh?=b+AI6swxP^ZjV&4LHst$gX z)bcGpCU}E)Fh7` z`I8=Fmw3``Y*J2V1AD>hn`R8E^cI@qT>~D1JUhOd9SxI@3;*8xfO`ixh#_~P^p=D; zE6(A-%L+R#?EAFI7@iF`)TgVJ)EoHWhmJ^jVvJ?=TaAsd1yj_(MONCufX5#DX>Gzd zUL6&uZ3MXLE8W#gjIrhbXHRTdLAo^^+}mcyMM^Ngn5cLY@0j@M=*>Gr&wiJ4ieH?G zPgoFms_9-ye^q<3C+5|m`a))sMq&y+W~|||Q`l1B6=A4m^$EJI4D27w6|F(tz|Ab-jt#0kmZxPh0ZuuRmd_=A!-}-j z)}*w>NC3jC3GgiC!4-#C&d8ADIxv`+)qF%fQGn%CAy?hF^O(259o#^2@>?v^W?>v_ zSlm%W)2LF%bM0a$yvZZ$ear``8||1$h^;mwz4n>=kc;10q` z;dS?^IJlN<{e!7(;5<~1-hLe~**Y43kKVR$aa8=Whki)KA>iCNSY_iCW$_a!rF+gq zs}Kxx>3qr@%U}NU(}Zq(QVIIOzF!`!Wx}~9Kiq`t|DwWM5}qY^EzmJr&j^LNA8G;T zddK@4r=+BdYjN}Mn*~k+4&}e2*VAue2^rNk#S>c+sd80(NgwE>?dKp3 z+|RCz?2p{S7x%$kd>oxN;$Fby+$uCx>=cXRG2)i6l17}o{f9InvWMdbol)7rhdyefJ|dLXn*+T zts8!5_*#E>&B`a`WSoQC-cc-~to%o3C(6&AA6;-$#y;{jqhJh5xHvn+c9~z+8T&NHoc`-)``mwv1Cpw(tSB6ojR8sYuj77K|wuWRRZ!0c=M#P^T5dj4pST=V-v*6Sj# z2yda9&$o<~ zfi2XYxk4?)r1KHXBdI|>oG%~-vwI^r>c^| z`~4(;U9qs$qX63kA8$Rb|5ZD*aR!*_Zotl=c~X~|nOSh7l8 zXf?<+alvAp*W)->kK=}*%2g|1-}d`g=Q;VqPQ}6$zLD*@3epw0`{v^&!pwGXjRzAE z0ci|AXtqD;iz)NkuLr#Bfi+*Va<8&gq(l3dtA~DHAi8`oB8T$DlsIu%u8+Efot2Wg z7#o;J;RlZXbCici)ojP*)+DuSgR0eEyqErbuI!ok$F;8gUGK(`&zMNV5UERA}#*>XgYAzg>}8tC<;;$~Ams7kqZA$2*@2C||%Fe%%;ylzh}N`Z+$ z&l+q7ERWFF)4ayVz!CU%+}^-^yJ+>2Mn;RPHXPa2hkg9Odwx6LUw8N$?fH#IrIC7H zmpqM)m~&{w_NIA`iWVIoWvd!5Q^n{436paF&`Gb@s3Lc#M(f*`#W5+ohppjRnqkfM z;rlkt-06K#GA_M&5_zEgxy9u;dYsnb40TKu^>xJ_akT zM4>Pd-P$jfAkOAECngPH)l~0S9D?k(xVa_QQdx@omDnAV(!q3!>W6vdHHV^y=L)3e=UKg zV4T*5CMT;oWk0)%N+jF$snAphthP*g>vso1`z;6V-@n@x4d~nq|Ndhds-~?Wp4HY) zbFJ!r+}avP`i6!#?J4v8;?4dwRpL z)@*;P;rC?Yk0CqFGa;KcFGFNpk%WgwhoUi7?E|g63M#xom0RJ8W48V5D0RBWZaC-J z+H%GWEqf!LBr5(U+HI-cce*?^^3^lF#otJk$3by8!)Jbxxd{=})+MD-=Jw{e{^)5% z2mZFHb^3@uJa7Wph$B(gl|%LUPwP{Jnf9GI9?!H>bv5|>rUqwab$4$#zGMH2eZPL- z@1IYFFl>@H=Do0|bdPk)#l^XyKoK>3^_i4?S~Dr`w;#wPEOj4#e-A2+e+5-~68MjA z7P|U$q}Si-p@jB7Bm*xz;?kN)&RqJWM2J`%b2(9da!YR(0+;AEY^{%6)IIU=Ma8QS zRJjaJ^x2z}hzjKP^Kw*Ozo-aWoj7ek?waDK8j&4>WZ57?dZLqGmO=4G41Atw1*D20 zeLDHie2Ld)1X~QG(e>_t*G7KBg|XVXZ7(RJtZ<%q5TAZntIkBV9#U;zG_v#jUN>d8 zqPX<+B_cASAM>e`)1knyu zcU>~nUdAqiO zVpyV6hxeI&hMXuM3qyJnBqM=~x{l}jC_nr$Lf=j-i5rR%DugRfF6oJ+6=t(yFzdUY z2f%^rR6AIr?XqMttQv~FX>wGGHiHx-Mj=eCqe_aM#{5CSJL8yoJYMn>21h*nPWeY3 zkt(@;L~79@qg9V`JdvrympwZ38^Wf!^VReAEi8RAuD=#B+d~Y>C-WbRk~Am9u3E)c z267gS3SvL_)iqEN5NX31t*p0!DVMu5{1Z3I2AF*Z#z+tyaE1{`2a> zzv9Pyq!`XamF#6KS%!&pTXA9lQS$x+>3L~5^R^Ng5ALFkC|5&ax2N^39e|>y!sGdEk0J_-_q#kV`}l7a)_P=}*aR zw}TsfOQCtQXi z^669A6Lh(Q_Dl1ozR(6I8*e~C9p@DP%}yHkslqC3SSxfkS+rqt+Bxb7oNJQW74bN< zaGdZXE^%^hs~h`wo&4GHTkpid6B4}-o;-qawI?gu|^u{HID_Yd& z)vpIMs4r`!S{ETw)`mG_6n8x;!Fh9q0z1n6be5a1$@CXW!clgRGQCSox$S9Cu7-7~ z)JG0U_r4E614{)G2Py|6wBl4k2cW48zZnqZyv^QeKrzwVVP-?MScbu42JN5D>@ei!}e}39pgr~E^8lxX~ zGJu#vqA$)vTzUlv(%g>CYN6NT!pyrP`fi(U(a^3k45JRvi3QrHF4Q291;JLSDs}W; z%tU0zR6~j)!udmM_4C13-6w*}##Nd4<)!F5rsMps2*zz8IrU^A&H^nDoR=BsK4ezq zIfO}7Ond$XQRXHK52C23oidhRsyWy&B|M9z)3$(=T{(ih}2|wV=zYh3+`{o%a`A^ZQv0TL+Ogrmm@Yg#3;IjVln`1l4 z&yfQ6hjTYGW=>htRa9C|Gp~H?c;?Qyp5#^XX8yn&+ z&YqAh@7Q0Rf$2crOf9ZO8XMAAjJkU+I1JQ?ecRQh5 ztc5Yl$jrm!?I!PY>K3iDahd&l-5lJA*fvb_dt{ZxUIt71;RXyhM>b=E7IO`9vR0Xi zRLlqk_HCy zl~rnAD4HYz)xG{pru0OEgZRB@SWirH*oy=3+Wxi#q?pNaqR=>GR5ty9FcyKs;Oa-d4{4O9Q)%74$R z;PewRVQ%$AAiU0yp~Aq&%hVU$<=v@#6DKifzWnZi=wkW&6;Zb$b4!17%;_3BQT7b= z?ifCp=u`JDU_hO~qiD%YM4Z3IF;+V9eD7{%yS>a!qxS9n(hIN1NJ^?l9sf+Vt0*@f zIT15fME!G|V*7M?m-^-$3PF^Au&CIPB)WQG(!YXKXc#ExOs44X=mb5olSk*n~44XiryECh;FGz=(=HCPlThRN3b`8lf`T3PdtQ&GhGdxF0m} zA#m-O6k{98z>vLu;uY+?XV*KuiAAYSTksC#tQ}B&@4$N5Xh?h$;{G?I`2I77=<=aw zD~XL2+JARv|IO9WlNf!fmR^KCP5VM_ewsQBw@-^_uCo&)^PP)5k|%N}k@{BP;qxhs zvUXk#My%x{a%*1h1%2LDjhVasXQLH!^qi~x@mDyq5j#a1>17L8%zQv zbz1MI2Z+fFfa+=koP4D7r*+Y`4kRV%%`?l<@<#8j+nXPks^7o$9h+@yT+=#f9b2=Q zMN;v`5_4542D`$(+7D2i4Lx#ZtTj8#p5(pv#?&qd17dgF9W}%+bCH@7R0)&4r&RBg zWtdO#qxES>w7!s}RFJ?{fW90D2x9%Q2a<8O-EgdM=p3U1&@`ht zR0z9$sydm87H*xk9x~0mQot@Di#6* zIH)JMB{Vk)mz*|`JV1|&8af0`2~mv!VOCfXB!xV-&Z_V@;=}yvJ#t*6qMJCW`*dxGen(=SZe zuBpS!37y5V4(o{zbykS`c0zGsfxDv6$Bm^@7l=4#eaeGlkUD0xtCch&*FF6+!9xqq zD(>)Wk~Sgd9ON@)7ih_n!!sUG>%go+*5|FIIccFmHpg3gvb4TDBB^pKMuV~x5Juny zsPH1147S9lzr%1e4Q#7%5`>InXlyDg_kakwh#SgeWs(ezsXbzGBC}dwdM{`ur+uox zYp0H9KWK-7M0$q9p#ltab7!S+Xw`u}vyLYkoEI6nlRV?=>|Gu_rbhiST>N)0hDoE| z$FONnCf~2frRmjeC=G5PP5SR)>$|U9vqJjv8lRp@Q~gWbvFe1yFD-vWX7|+2rfgm> z&4FPC>JYO3$8@%G4K^vKPtoDtK2#BBYPGxiIFr3WuWBTaH`I*V9!m4BqfWdqF5II5 zK@gS7gMkoCqc8Qu()2?F)XXaw%SL7URp3bZ@3Ckg%k_~x@xJ0W0 z&boKu1O8726Et7OYPhUmy&o;LUJJ*SUSuZX@)nb%)%JZx32|oE(3IPkkcnuwqTyh? zUqS2J%|a<%EcMGNTbXdKEX3lL3()}=$PFjyA3J{&;fF11Xmc05E9v-likvB5Yd@4&KywuUoXaB z>&6DgR9i)FYrU@Dj7BKN?@oUuE|Ul<&;WZo zWlcfU%>OxJB{&75G#>ISBxkfoXVx%RY*eHO{es~uN7$^Kq3ABJ1%o3MJ3Hb^MPF} zr#SP;R28kUDlTZve6}sc+J1ETQ2m#b&zr+MvUwEkv0ZS1P}C6u+&NZBzmg!qIulw& z5Q5AOmVXIIXM~f9V98+fe*SGGaK|RCETcSE9G3&`Og8ibT%1&ognLvHPx_-WeQ57U zz5;Q=y+LCpvEL6M8P9%Psf`@RfNFw>4CJ$Lb@m9EjDmLB zykQ;XIPut}X1RJvhXfF>;-@ZdiUa)y%WNfm`tfVytvZ<&W!lbh@#2RV?M2qN0_| z%^bO%2zEk0ILwctU-T4RbT{ZHgE>K{iEq`6-J|CG>IOsPgC|zHmDhD+IJ053Ie=HJ z!xQBk(Os@H0%04ITg27uxrk1V*)j?03PpB3psCg!X`^0>*{7!Om{#0~I05XnOh%m$ z+&Gtp2oXK8x1dZNmykoyb4Fnxjkt!cM}5qbLSAWyQ--5W`T&SC73^jcHRw{Tgks=LSM#3 z+B8rTLF%p$X@0dDTDY=?G#@L#p5RJHYVtx(XtLc=r)B$q5A2p?IO^UV=Vo9D6oeQ- zCIQ~F3Qx6a*F}g0&)pD2o6RJRF@5Njl4h|L#(H>( zNUMm4pl3bekE?~gG9f&D%$U%xVu~OlKD1HO^imFIBP!K7wR(-%aGBo6IP0iN4z7vu zMgHi6&@vNqBvx;6dipj@Q2-(T6QIC{kxjR4v7{n(h0n+i;(DC;mGI2~lwh7+kE&F` z`*-C7LoPL{Qj|PNSj}u}8nM$gds|t1{vB@)UtEpy=Xg=O?e#uBpZ0g3%!BI0zg_cq zBQ@%-2WtfPKbg%xY_C6hJ6!yiP*hy|+cnG#_2qO5CeUFU^r=QM1?~iX;JmJw{+l5s ze4eui!+8uK;Dr1jw^8@Q=_0ZtJ8>q{d6R;1VGM^t?S&NwK1V_-2-T~Id%!nz+R7=S z&7nv|>E>cT(!Va2slsW2ggbHgBrxA65X7b_@Mopwx~Cx$VyT%B0?Brx${{Ypr9szG ztZn92Nu_%p31Fzt^~ov(wCypNGZlIAOEGJ{Zz`qV5^VSzk@-5YEfHOy&?y$OG;R{k z#}fzxNA$1RQc`B+7|m9>7nvPup2Vn;o5y-%%pF>lryJ@2s0_IBk1PJq-$ZdVMORlZ z{rhBj)p!j#;R5E=;`)SKMZ!MJV{eej!+s(~1w(hntv^(P3_$}$l^n&?WY>p+Yz7%5 zqXlTV+JHT77|;zQAy|iek&Mi19xpF7Ehv}w{}HLMJQga->pPhk>AMoMa<_30uD#Wx zNvJhd?chOXJ-K9=Ol>J00m}|RcA0!M{V#8G$L2&l;q-`oU}NPP=kyUpO?jS$b;0XZb?jcW-{8aymflg1 zjqc5o{nV@?6?&o7%+W5yeB56Kc1o5yoxeI=rv(E?&W5@55)vwXtF_S z-?nDkGIhT>zGQ_Iu3va$-#g1RpFb`Ttv~yWjXaVl^!KHx5UwktxgEr`GY^ZU_d&f5 zFg&x)Eb>}UEf>Riw-q_#fnfE=0H71YJtp=9L*y4T$OI|bNaQe$Kow?YRZO0>FZa0g zRb(XCNYNT`Z~P^juci%j9}|Y*4~PQ=zr#2P*cf5y z;x_|ykPi(ERoD0%=~N{#HOX9#&|@Lt*bSS)%9u2qIK;`}Hq`91#f-Kx{IdES11DKe z*F2X=<0s)n3`>{*XuCq!N^+V@psfN=p z!gPmpt{S^+XFuESiXhNZ71h+@H%`fpU58G}>WlQLrpiz*<-G~^5GrZB#00^0444de z&@(6;ZVsUJkul=5Xk?!jU2LFCbNs+d$S?o>zI~Sm{fl4+FCkCwByW;E^IJ_wFAz%X zM5OmQW^fjUdzmefrDs#EC=u_%(>~=u`U4o)5nT7^VrrwSn*QVq6Ho_lIiqb97ed*J zK?b!1YD+QLQWCsvG}vxL4R6PDaBFENp@;{yH<5t0hI+tmX<#8}8UVYcGmi~ZPbtu} z{H&+(i?`e!yWiH_G4w)SXpuyQOCn$25V?J=L2bs)V;A?l)EWjaj0e*y9!E_DI~(HIF+88@W2G3@v3OLg6PdONqG5c!(?% z!7`hO27B6`O1lsr1=PNZ2xDr3-sQ|9Xv)bO@$A~NiV!gJoMVN;NwHQ6H3qruG<*nc zA&a_-=*02=12+7x1mNF%=&>(BGCRcf{b4UizUF9Cb!K2?ws`m>f~F9urv)CzG>OQ^ zd@Jd<6#+CmYJmso8!a3*(|W{IXOmkxt8tO5>A6sd7WX5!4oplWPa+zdBehwc=3=oe6u_Wpr+Qcwq{oU$+!al7MxqRM z%tzB1nl#|z!S(DuQNyYLfZ*%|?q%7{+~fq99h$<#nQEK{Qa)+EG@Mq{TZCLn5Vetr ztcEHO;CQG`RO8Gq>3#|sPd^4hZT%y6k55D(RZ1u9=^q-*#WKaC*2O)Ly+9d-DZ4&U zw%gnskmk{DARL#j69%FSfQ#B{@dmGwj{a?x|6?Bf?=x�qTS;2Sy{D4x~avu}$ib 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

8Tl*8XpAX3_JKhNHGV% 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⁡V?$4E)U4^7Ni# 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 G_5T6bA8Jtm diff --git a/common/dist/sprites/spritesmith-main-7.css b/common/dist/sprites/spritesmith-main-7.css index f3f3d823b4..ac0c141b87 100644 --- a/common/dist/sprites/spritesmith-main-7.css +++ b/common/dist/sprites/spritesmith-main-7.css @@ -1,285 +1,309 @@ -.Mount_Body_Dragon-Zombie { - background-image: url(spritesmith-main-7.png); - background-position: 0px -744px; - width: 105px; - height: 105px; -} -.Mount_Body_Egg-Base { - background-image: url(spritesmith-main-7.png); - background-position: -1180px -848px; - width: 105px; - height: 105px; -} -.Mount_Body_Egg-CottonCandyBlue { - background-image: url(spritesmith-main-7.png); - background-position: -106px -744px; - width: 105px; - height: 105px; -} -.Mount_Body_Egg-CottonCandyPink { - background-image: url(spritesmith-main-7.png); - background-position: -212px -744px; - width: 105px; - height: 105px; -} -.Mount_Body_Egg-Desert { - background-image: url(spritesmith-main-7.png); - background-position: -318px -744px; - width: 105px; - height: 105px; -} -.Mount_Body_Egg-Golden { +.Mount_Body_Dragon-Shade { background-image: url(spritesmith-main-7.png); background-position: -424px -744px; width: 105px; height: 105px; } -.Mount_Body_Egg-Red { +.Mount_Body_Dragon-Skeleton { + background-image: url(spritesmith-main-7.png); + background-position: -1180px -848px; + width: 105px; + height: 105px; +} +.Mount_Body_Dragon-Spooky { background-image: url(spritesmith-main-7.png); background-position: -530px -744px; width: 105px; height: 105px; } -.Mount_Body_Egg-Shade { +.Mount_Body_Dragon-White { background-image: url(spritesmith-main-7.png); background-position: -636px -744px; width: 105px; height: 105px; } -.Mount_Body_Egg-Skeleton { +.Mount_Body_Dragon-Zombie { background-image: url(spritesmith-main-7.png); background-position: -742px -744px; width: 105px; height: 105px; } -.Mount_Body_Egg-White { +.Mount_Body_Egg-Base { background-image: url(spritesmith-main-7.png); background-position: -862px 0px; width: 105px; height: 105px; } -.Mount_Body_Egg-Zombie { +.Mount_Body_Egg-CottonCandyBlue { background-image: url(spritesmith-main-7.png); background-position: -862px -106px; width: 105px; height: 105px; } -.Mount_Body_Falcon-Base { +.Mount_Body_Egg-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1074px -848px; + background-position: -862px -212px; width: 105px; height: 105px; } -.Mount_Body_Falcon-CottonCandyBlue { +.Mount_Body_Egg-Desert { + background-image: url(spritesmith-main-7.png); + background-position: -862px -318px; + width: 105px; + height: 105px; +} +.Mount_Body_Egg-Golden { + background-image: url(spritesmith-main-7.png); + background-position: -862px -424px; + width: 105px; + height: 105px; +} +.Mount_Body_Egg-Red { + background-image: url(spritesmith-main-7.png); + background-position: -862px -530px; + width: 105px; + height: 105px; +} +.Mount_Body_Egg-Shade { background-image: url(spritesmith-main-7.png); background-position: -212px -1062px; width: 105px; height: 105px; } -.Mount_Body_Falcon-CottonCandyPink { +.Mount_Body_Egg-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -318px -1062px; + background-position: -636px -1062px; width: 105px; height: 105px; } -.Mount_Body_Falcon-Desert { +.Mount_Body_Egg-White { background-image: url(spritesmith-main-7.png); - background-position: -424px -1062px; + background-position: -742px -1062px; width: 105px; height: 105px; } -.Mount_Body_Falcon-Golden { +.Mount_Body_Egg-Zombie { background-image: url(spritesmith-main-7.png); - background-position: 0px -1486px; + background-position: -848px -1062px; width: 105px; height: 105px; } -.Mount_Body_Falcon-Red { - background-image: url(spritesmith-main-7.png); - background-position: -106px -1486px; - width: 105px; - height: 105px; -} -.Mount_Body_Falcon-Shade { - background-image: url(spritesmith-main-7.png); - background-position: -212px -1486px; - width: 105px; - height: 105px; -} -.Mount_Body_Falcon-Skeleton { - background-image: url(spritesmith-main-7.png); - background-position: -318px -1486px; - width: 105px; - height: 105px; -} -.Mount_Body_Falcon-White { +.Mount_Body_Falcon-Base { background-image: url(spritesmith-main-7.png); background-position: -424px -1486px; width: 105px; height: 105px; } -.Mount_Body_Falcon-Zombie { +.Mount_Body_Falcon-CottonCandyBlue { background-image: url(spritesmith-main-7.png); background-position: -530px -1486px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Base { +.Mount_Body_Falcon-CottonCandyPink { background-image: url(spritesmith-main-7.png); background-position: -636px -1486px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-CottonCandyBlue { +.Mount_Body_Falcon-Desert { background-image: url(spritesmith-main-7.png); background-position: -742px -1486px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-CottonCandyPink { +.Mount_Body_Falcon-Golden { background-image: url(spritesmith-main-7.png); background-position: -848px -1486px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Desert { +.Mount_Body_Falcon-Red { background-image: url(spritesmith-main-7.png); background-position: -954px -1486px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Floral { +.Mount_Body_Falcon-Shade { + background-image: url(spritesmith-main-7.png); + background-position: -1060px -1486px; + width: 105px; + height: 105px; +} +.Mount_Body_Falcon-Skeleton { + background-image: url(spritesmith-main-7.png); + background-position: -1166px -1486px; + width: 105px; + height: 105px; +} +.Mount_Body_Falcon-White { + background-image: url(spritesmith-main-7.png); + background-position: -1272px -1486px; + width: 105px; + height: 105px; +} +.Mount_Body_Falcon-Zombie { + background-image: url(spritesmith-main-7.png); + background-position: -1378px -1486px; + width: 105px; + height: 105px; +} +.Mount_Body_FlyingPig-Base { background-image: url(spritesmith-main-7.png); background-position: -433px -523px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Golden { +.Mount_Body_FlyingPig-CottonCandyBlue { background-image: url(spritesmith-main-7.png); background-position: -539px -523px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Peppermint { +.Mount_Body_FlyingPig-CottonCandyPink { background-image: url(spritesmith-main-7.png); background-position: -650px 0px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Red { +.Mount_Body_FlyingPig-Desert { background-image: url(spritesmith-main-7.png); background-position: -650px -106px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Shade { +.Mount_Body_FlyingPig-Floral { background-image: url(spritesmith-main-7.png); background-position: -650px -212px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Skeleton { +.Mount_Body_FlyingPig-Golden { background-image: url(spritesmith-main-7.png); background-position: -650px -318px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Spooky { +.Mount_Body_FlyingPig-Peppermint { background-image: url(spritesmith-main-7.png); background-position: -650px -424px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-White { +.Mount_Body_FlyingPig-Red { background-image: url(spritesmith-main-7.png); background-position: -650px -530px; width: 105px; height: 105px; } -.Mount_Body_FlyingPig-Zombie { +.Mount_Body_FlyingPig-Shade { background-image: url(spritesmith-main-7.png); background-position: 0px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-Base { +.Mount_Body_FlyingPig-Skeleton { background-image: url(spritesmith-main-7.png); background-position: -106px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-CottonCandyBlue { +.Mount_Body_FlyingPig-Spooky { background-image: url(spritesmith-main-7.png); background-position: -212px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-CottonCandyPink { +.Mount_Body_FlyingPig-White { background-image: url(spritesmith-main-7.png); background-position: -318px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-Desert { +.Mount_Body_FlyingPig-Zombie { background-image: url(spritesmith-main-7.png); background-position: -424px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-Floral { +.Mount_Body_Fox-Base { background-image: url(spritesmith-main-7.png); background-position: -530px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-Golden { +.Mount_Body_Fox-CottonCandyBlue { background-image: url(spritesmith-main-7.png); background-position: -636px -638px; width: 105px; height: 105px; } -.Mount_Body_Fox-Peppermint { +.Mount_Body_Fox-CottonCandyPink { background-image: url(spritesmith-main-7.png); background-position: -756px 0px; width: 105px; height: 105px; } -.Mount_Body_Fox-Red { +.Mount_Body_Fox-Desert { background-image: url(spritesmith-main-7.png); background-position: -756px -106px; width: 105px; height: 105px; } -.Mount_Body_Fox-Shade { +.Mount_Body_Fox-Floral { background-image: url(spritesmith-main-7.png); background-position: -756px -212px; width: 105px; height: 105px; } -.Mount_Body_Fox-Skeleton { +.Mount_Body_Fox-Golden { background-image: url(spritesmith-main-7.png); background-position: -756px -318px; width: 105px; height: 105px; } -.Mount_Body_Fox-Spooky { +.Mount_Body_Fox-Peppermint { background-image: url(spritesmith-main-7.png); background-position: -756px -424px; width: 105px; height: 105px; } -.Mount_Body_Fox-White { +.Mount_Body_Fox-Red { background-image: url(spritesmith-main-7.png); background-position: -756px -530px; width: 105px; height: 105px; } -.Mount_Body_Fox-Zombie { +.Mount_Body_Fox-Shade { background-image: url(spritesmith-main-7.png); background-position: -756px -636px; width: 105px; height: 105px; } +.Mount_Body_Fox-Skeleton { + background-image: url(spritesmith-main-7.png); + background-position: 0px -744px; + width: 105px; + height: 105px; +} +.Mount_Body_Fox-Spooky { + background-image: url(spritesmith-main-7.png); + background-position: -106px -744px; + width: 105px; + height: 105px; +} +.Mount_Body_Fox-White { + background-image: url(spritesmith-main-7.png); + background-position: -212px -744px; + width: 105px; + height: 105px; +} +.Mount_Body_Fox-Zombie { + background-image: url(spritesmith-main-7.png); + background-position: -318px -744px; + width: 105px; + height: 105px; +} .Mount_Body_Frog-Base { background-image: url(spritesmith-main-7.png); background-position: 0px -408px; @@ -342,187 +366,187 @@ } .Mount_Body_Gryphon-Base { background-image: url(spritesmith-main-7.png); - background-position: -862px -212px; + background-position: -862px -636px; width: 105px; height: 105px; } .Mount_Body_Gryphon-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -862px -318px; + background-position: -862px -742px; width: 105px; height: 105px; } .Mount_Body_Gryphon-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -862px -424px; + background-position: 0px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Desert { background-image: url(spritesmith-main-7.png); - background-position: -862px -530px; + background-position: -106px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Golden { background-image: url(spritesmith-main-7.png); - background-position: -862px -636px; + background-position: -212px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Red { background-image: url(spritesmith-main-7.png); - background-position: -862px -742px; + background-position: -318px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-RoyalPurple { background-image: url(spritesmith-main-7.png); - background-position: 0px -850px; + background-position: -424px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Shade { background-image: url(spritesmith-main-7.png); - background-position: -106px -850px; + background-position: -530px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -212px -850px; + background-position: -636px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-White { background-image: url(spritesmith-main-7.png); - background-position: -318px -850px; + background-position: -742px -850px; width: 105px; height: 105px; } .Mount_Body_Gryphon-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -424px -850px; + background-position: -848px -850px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Base { background-image: url(spritesmith-main-7.png); - background-position: -530px -850px; + background-position: -968px 0px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -636px -850px; + background-position: -968px -106px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -742px -850px; + background-position: -968px -212px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Desert { background-image: url(spritesmith-main-7.png); - background-position: -848px -850px; + background-position: -968px -318px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Golden { background-image: url(spritesmith-main-7.png); - background-position: -968px 0px; + background-position: -968px -424px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Red { background-image: url(spritesmith-main-7.png); - background-position: -968px -106px; + background-position: -968px -530px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Shade { background-image: url(spritesmith-main-7.png); - background-position: -968px -212px; + background-position: -968px -636px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -968px -318px; + background-position: -968px -742px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-White { background-image: url(spritesmith-main-7.png); - background-position: -968px -424px; + background-position: -968px -848px; width: 105px; height: 105px; } .Mount_Body_Hedgehog-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -968px -530px; + background-position: 0px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Base { background-image: url(spritesmith-main-7.png); - background-position: -968px -636px; + background-position: -106px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -968px -742px; + background-position: -212px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -968px -848px; + background-position: -318px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Desert { background-image: url(spritesmith-main-7.png); - background-position: 0px -956px; + background-position: -424px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Golden { background-image: url(spritesmith-main-7.png); - background-position: -106px -956px; + background-position: -530px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Red { background-image: url(spritesmith-main-7.png); - background-position: -212px -956px; + background-position: -636px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Shade { background-image: url(spritesmith-main-7.png); - background-position: -318px -956px; + background-position: -742px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -424px -956px; + background-position: -848px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-White { background-image: url(spritesmith-main-7.png); - background-position: -530px -956px; + background-position: -954px -956px; width: 105px; height: 105px; } .Mount_Body_Horse-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -636px -956px; + background-position: -1074px 0px; width: 105px; height: 105px; } @@ -534,61 +558,61 @@ } .Mount_Body_LionCub-Base { background-image: url(spritesmith-main-7.png); - background-position: -848px -956px; + background-position: -1074px -212px; width: 105px; height: 105px; } .Mount_Body_LionCub-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -954px -956px; + background-position: -1074px -318px; width: 105px; height: 105px; } .Mount_Body_LionCub-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1074px 0px; + background-position: -1074px -424px; width: 105px; height: 105px; } .Mount_Body_LionCub-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1074px -106px; + background-position: -1074px -530px; width: 105px; height: 105px; } .Mount_Body_LionCub-Ethereal { background-image: url(spritesmith-main-7.png); - background-position: -1074px -212px; + background-position: -1074px -636px; width: 105px; height: 105px; } .Mount_Body_LionCub-Floral { background-image: url(spritesmith-main-7.png); - background-position: -1074px -318px; + background-position: -1074px -742px; width: 105px; height: 105px; } .Mount_Body_LionCub-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1074px -424px; + background-position: -1074px -848px; width: 105px; height: 105px; } .Mount_Body_LionCub-Peppermint { background-image: url(spritesmith-main-7.png); - background-position: -1074px -530px; + background-position: -1074px -954px; width: 105px; height: 105px; } .Mount_Body_LionCub-Red { background-image: url(spritesmith-main-7.png); - background-position: -1074px -636px; + background-position: 0px -1062px; width: 105px; height: 105px; } .Mount_Body_LionCub-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1074px -742px; + background-position: -106px -1062px; width: 105px; height: 105px; } @@ -600,19 +624,19 @@ } .Mount_Body_LionCub-Spooky { background-image: url(spritesmith-main-7.png); - background-position: -1074px -954px; + background-position: -318px -1062px; width: 105px; height: 105px; } .Mount_Body_LionCub-White { background-image: url(spritesmith-main-7.png); - background-position: 0px -1062px; + background-position: -424px -1062px; width: 105px; height: 105px; } .Mount_Body_LionCub-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -106px -1062px; + background-position: -530px -1062px; width: 105px; height: 105px; } @@ -636,571 +660,571 @@ } .Mount_Body_Monkey-Base { background-image: url(spritesmith-main-7.png); - background-position: -530px -1062px; + background-position: -954px -1062px; width: 105px; height: 105px; } .Mount_Body_Monkey-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -636px -1062px; + background-position: -1060px -1062px; width: 105px; height: 105px; } .Mount_Body_Monkey-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -742px -1062px; + background-position: -1180px 0px; width: 105px; height: 105px; } .Mount_Body_Monkey-Desert { background-image: url(spritesmith-main-7.png); - background-position: -848px -1062px; + background-position: -1180px -106px; width: 105px; height: 105px; } .Mount_Body_Monkey-Golden { background-image: url(spritesmith-main-7.png); - background-position: -954px -1062px; + background-position: -1180px -212px; width: 105px; height: 105px; } .Mount_Body_Monkey-Red { background-image: url(spritesmith-main-7.png); - background-position: -1060px -1062px; + background-position: -1180px -318px; width: 105px; height: 105px; } .Mount_Body_Monkey-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1180px 0px; + background-position: -1180px -424px; width: 105px; height: 105px; } .Mount_Body_Monkey-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1180px -106px; + background-position: -1180px -530px; width: 105px; height: 105px; } .Mount_Body_Monkey-White { background-image: url(spritesmith-main-7.png); - background-position: -1180px -212px; + background-position: -1180px -636px; width: 105px; height: 105px; } .Mount_Body_Monkey-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1180px -318px; + background-position: -1180px -742px; width: 105px; height: 105px; } .Mount_Body_Octopus-Base { background-image: url(spritesmith-main-7.png); - background-position: -1180px -424px; + background-position: -327px -523px; width: 105px; height: 105px; } .Mount_Body_Octopus-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1180px -530px; + background-position: -1180px -954px; width: 105px; height: 105px; } .Mount_Body_Octopus-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1180px -636px; + background-position: -1180px -1060px; width: 105px; height: 105px; } .Mount_Body_Octopus-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1180px -742px; + background-position: 0px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-Golden { background-image: url(spritesmith-main-7.png); - background-position: -327px -523px; + background-position: -106px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-Red { background-image: url(spritesmith-main-7.png); - background-position: -1180px -954px; + background-position: -212px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1180px -1060px; + background-position: -318px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: 0px -1168px; + background-position: -424px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-White { background-image: url(spritesmith-main-7.png); - background-position: -106px -1168px; + background-position: -530px -1168px; width: 105px; height: 105px; } .Mount_Body_Octopus-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -212px -1168px; + background-position: -636px -1168px; width: 105px; height: 105px; } .Mount_Body_Orca-Base { background-image: url(spritesmith-main-7.png); - background-position: -318px -1168px; + background-position: -742px -1168px; width: 105px; height: 105px; } .Mount_Body_Owl-Base { background-image: url(spritesmith-main-7.png); - background-position: -424px -1168px; + background-position: -848px -1168px; width: 105px; height: 105px; } .Mount_Body_Owl-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -530px -1168px; + background-position: -954px -1168px; width: 105px; height: 105px; } .Mount_Body_Owl-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -636px -1168px; + background-position: -1060px -1168px; width: 105px; height: 105px; } .Mount_Body_Owl-Desert { background-image: url(spritesmith-main-7.png); - background-position: -742px -1168px; + background-position: -1166px -1168px; width: 105px; height: 105px; } .Mount_Body_Owl-Golden { background-image: url(spritesmith-main-7.png); - background-position: -848px -1168px; + background-position: -1286px 0px; width: 105px; height: 105px; } .Mount_Body_Owl-Red { background-image: url(spritesmith-main-7.png); - background-position: -954px -1168px; + background-position: -1286px -106px; width: 105px; height: 105px; } .Mount_Body_Owl-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1060px -1168px; + background-position: -1286px -212px; width: 105px; height: 105px; } .Mount_Body_Owl-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1166px -1168px; + background-position: -1286px -318px; width: 105px; height: 105px; } .Mount_Body_Owl-White { background-image: url(spritesmith-main-7.png); - background-position: -1286px 0px; + background-position: -1286px -424px; width: 105px; height: 105px; } .Mount_Body_Owl-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1286px -106px; + background-position: -1286px -530px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Base { background-image: url(spritesmith-main-7.png); - background-position: -1286px -212px; + background-position: -1286px -636px; width: 105px; height: 105px; } .Mount_Body_PandaCub-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1286px -318px; + background-position: -1286px -742px; width: 105px; height: 105px; } .Mount_Body_PandaCub-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1286px -424px; + background-position: -1286px -848px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1286px -530px; + background-position: -1286px -954px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Floral { background-image: url(spritesmith-main-7.png); - background-position: -1286px -636px; + background-position: -1286px -1060px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1286px -742px; + background-position: -1286px -1166px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Peppermint { background-image: url(spritesmith-main-7.png); - background-position: -1286px -848px; + background-position: 0px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Red { background-image: url(spritesmith-main-7.png); - background-position: -1286px -954px; + background-position: -106px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1286px -1060px; + background-position: -212px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1286px -1166px; + background-position: -318px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Spooky { background-image: url(spritesmith-main-7.png); - background-position: 0px -1274px; + background-position: -424px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-White { background-image: url(spritesmith-main-7.png); - background-position: -106px -1274px; + background-position: -530px -1274px; width: 105px; height: 105px; } .Mount_Body_PandaCub-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -212px -1274px; + background-position: -636px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-Base { background-image: url(spritesmith-main-7.png); - background-position: -318px -1274px; + background-position: -742px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -424px -1274px; + background-position: -848px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -530px -1274px; + background-position: -954px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-Desert { background-image: url(spritesmith-main-7.png); - background-position: -636px -1274px; + background-position: -1060px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-Golden { background-image: url(spritesmith-main-7.png); - background-position: -742px -1274px; + background-position: -1166px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-Red { background-image: url(spritesmith-main-7.png); - background-position: -848px -1274px; + background-position: -1272px -1274px; width: 105px; height: 105px; } .Mount_Body_Parrot-Shade { background-image: url(spritesmith-main-7.png); - background-position: -954px -1274px; + background-position: -1392px 0px; width: 105px; height: 105px; } .Mount_Body_Parrot-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1060px -1274px; + background-position: -1392px -106px; width: 105px; height: 105px; } .Mount_Body_Parrot-White { background-image: url(spritesmith-main-7.png); - background-position: -1166px -1274px; + background-position: -1392px -212px; width: 105px; height: 105px; } .Mount_Body_Parrot-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1272px -1274px; + background-position: -1392px -318px; width: 105px; height: 105px; } .Mount_Body_Penguin-Base { background-image: url(spritesmith-main-7.png); - background-position: -1392px 0px; + background-position: -1392px -424px; width: 105px; height: 105px; } .Mount_Body_Penguin-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1392px -106px; + background-position: -1392px -530px; width: 105px; height: 105px; } .Mount_Body_Penguin-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1392px -212px; + background-position: -1392px -636px; width: 105px; height: 105px; } .Mount_Body_Penguin-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1392px -318px; + background-position: -1392px -742px; width: 105px; height: 105px; } .Mount_Body_Penguin-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1392px -424px; + background-position: -1392px -848px; width: 105px; height: 105px; } .Mount_Body_Penguin-Red { background-image: url(spritesmith-main-7.png); - background-position: -1392px -530px; + background-position: -1392px -954px; width: 105px; height: 105px; } .Mount_Body_Penguin-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1392px -636px; + background-position: -1392px -1060px; width: 105px; height: 105px; } .Mount_Body_Penguin-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1392px -742px; + background-position: -1392px -1166px; width: 105px; height: 105px; } .Mount_Body_Penguin-White { background-image: url(spritesmith-main-7.png); - background-position: -1392px -848px; + background-position: -1392px -1272px; width: 105px; height: 105px; } .Mount_Body_Penguin-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1392px -954px; + background-position: 0px -1380px; width: 105px; height: 105px; } .Mount_Body_Phoenix-Base { background-image: url(spritesmith-main-7.png); - background-position: -1392px -1060px; + background-position: -106px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Base { background-image: url(spritesmith-main-7.png); - background-position: -1392px -1166px; + background-position: -212px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1392px -1272px; + background-position: -318px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: 0px -1380px; + background-position: -424px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Desert { background-image: url(spritesmith-main-7.png); - background-position: -106px -1380px; + background-position: -530px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Golden { background-image: url(spritesmith-main-7.png); - background-position: -212px -1380px; + background-position: -636px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Red { background-image: url(spritesmith-main-7.png); - background-position: -318px -1380px; + background-position: -742px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Shade { background-image: url(spritesmith-main-7.png); - background-position: -424px -1380px; + background-position: -848px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -530px -1380px; + background-position: -954px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-White { background-image: url(spritesmith-main-7.png); - background-position: -636px -1380px; + background-position: -1060px -1380px; width: 105px; height: 105px; } .Mount_Body_Rat-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -742px -1380px; + background-position: -1166px -1380px; width: 105px; height: 105px; } .Mount_Body_Rock-Base { background-image: url(spritesmith-main-7.png); - background-position: -848px -1380px; + background-position: -1272px -1380px; width: 105px; height: 105px; } .Mount_Body_Rock-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -954px -1380px; + background-position: -1378px -1380px; width: 105px; height: 105px; } .Mount_Body_Rock-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1060px -1380px; + background-position: -1498px 0px; width: 105px; height: 105px; } .Mount_Body_Rock-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1166px -1380px; + background-position: -1498px -106px; width: 105px; height: 105px; } .Mount_Body_Rock-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1272px -1380px; + background-position: -1498px -212px; width: 105px; height: 105px; } .Mount_Body_Rock-Red { background-image: url(spritesmith-main-7.png); - background-position: -1378px -1380px; + background-position: -1498px -318px; width: 105px; height: 105px; } .Mount_Body_Rock-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1498px 0px; + background-position: -1498px -424px; width: 105px; height: 105px; } .Mount_Body_Rock-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1498px -106px; + background-position: -1498px -530px; width: 105px; height: 105px; } .Mount_Body_Rock-White { background-image: url(spritesmith-main-7.png); - background-position: -1498px -212px; + background-position: -1498px -636px; width: 105px; height: 105px; } .Mount_Body_Rock-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1498px -318px; + background-position: -1498px -742px; width: 105px; height: 105px; } .Mount_Body_Rooster-Base { background-image: url(spritesmith-main-7.png); - background-position: -1498px -424px; + background-position: -1498px -848px; width: 105px; height: 105px; } .Mount_Body_Rooster-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1498px -530px; + background-position: -1498px -954px; width: 105px; height: 105px; } .Mount_Body_Rooster-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1498px -636px; + background-position: -1498px -1060px; width: 105px; height: 105px; } .Mount_Body_Rooster-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1498px -742px; + background-position: -1498px -1166px; width: 105px; height: 105px; } .Mount_Body_Rooster-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1498px -848px; + background-position: -1498px -1272px; width: 105px; height: 105px; } .Mount_Body_Rooster-Red { background-image: url(spritesmith-main-7.png); - background-position: -1498px -954px; + background-position: -1498px -1378px; width: 105px; height: 105px; } .Mount_Body_Rooster-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1498px -1060px; + background-position: 0px -1486px; width: 105px; height: 105px; } .Mount_Body_Rooster-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1498px -1166px; + background-position: -106px -1486px; width: 105px; height: 105px; } .Mount_Body_Rooster-White { background-image: url(spritesmith-main-7.png); - background-position: -1498px -1272px; + background-position: -212px -1486px; width: 105px; height: 105px; } .Mount_Body_Rooster-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1498px -1378px; + background-position: -318px -1486px; width: 105px; height: 105px; } @@ -1266,173 +1290,149 @@ } .Mount_Body_Seahorse-Base { background-image: url(spritesmith-main-7.png); - background-position: -1060px -1486px; + background-position: -1484px -1486px; width: 105px; height: 105px; } .Mount_Body_Seahorse-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1166px -1486px; + background-position: -1604px 0px; width: 105px; height: 105px; } .Mount_Body_Seahorse-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1272px -1486px; + background-position: -1604px -106px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1378px -1486px; + background-position: -1604px -212px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1484px -1486px; + background-position: -1604px -318px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Red { background-image: url(spritesmith-main-7.png); - background-position: -1604px 0px; + background-position: -1604px -424px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1604px -106px; + background-position: -1604px -530px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1604px -212px; + background-position: -1604px -636px; width: 105px; height: 105px; } .Mount_Body_Seahorse-White { background-image: url(spritesmith-main-7.png); - background-position: -1604px -318px; + background-position: -1604px -742px; width: 105px; height: 105px; } .Mount_Body_Seahorse-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1604px -424px; + background-position: -1604px -848px; width: 105px; height: 105px; } .Mount_Body_Sheep-Base { background-image: url(spritesmith-main-7.png); - background-position: -1604px -530px; + background-position: -1604px -954px; width: 105px; height: 105px; } .Mount_Body_Sheep-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -1604px -636px; + background-position: -1604px -1060px; width: 105px; height: 105px; } .Mount_Body_Sheep-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -1604px -742px; + background-position: -1604px -1166px; width: 105px; height: 105px; } .Mount_Body_Sheep-Desert { background-image: url(spritesmith-main-7.png); - background-position: -1604px -848px; + background-position: -1604px -1272px; width: 105px; height: 105px; } .Mount_Body_Sheep-Golden { background-image: url(spritesmith-main-7.png); - background-position: -1604px -954px; + background-position: -1604px -1378px; width: 105px; height: 105px; } .Mount_Body_Sheep-Red { background-image: url(spritesmith-main-7.png); - background-position: -1604px -1060px; + background-position: -1604px -1484px; width: 105px; height: 105px; } .Mount_Body_Sheep-Shade { background-image: url(spritesmith-main-7.png); - background-position: -1604px -1166px; + background-position: 0px -1592px; width: 105px; height: 105px; } .Mount_Body_Sheep-Skeleton { background-image: url(spritesmith-main-7.png); - background-position: -1604px -1272px; + background-position: -106px -1592px; width: 105px; height: 105px; } .Mount_Body_Sheep-White { background-image: url(spritesmith-main-7.png); - background-position: -1604px -1378px; + background-position: -212px -1592px; width: 105px; height: 105px; } .Mount_Body_Sheep-Zombie { background-image: url(spritesmith-main-7.png); - background-position: -1604px -1484px; + background-position: -318px -1592px; width: 105px; height: 105px; } .Mount_Body_Slime-Base { background-image: url(spritesmith-main-7.png); - background-position: 0px -1592px; + background-position: -424px -1592px; width: 105px; height: 105px; } .Mount_Body_Slime-CottonCandyBlue { background-image: url(spritesmith-main-7.png); - background-position: -106px -1592px; + background-position: -530px -1592px; width: 105px; height: 105px; } .Mount_Body_Slime-CottonCandyPink { background-image: url(spritesmith-main-7.png); - background-position: -212px -1592px; + background-position: -636px -1592px; width: 105px; height: 105px; } .Mount_Body_Slime-Desert { background-image: url(spritesmith-main-7.png); - background-position: -318px -1592px; + background-position: -1074px -106px; width: 105px; height: 105px; } .Mount_Body_Slime-Golden { - background-image: url(spritesmith-main-7.png); - background-position: -424px -1592px; - width: 105px; - height: 105px; -} -.Mount_Body_Slime-Red { - background-image: url(spritesmith-main-7.png); - background-position: -530px -1592px; - width: 105px; - height: 105px; -} -.Mount_Body_Slime-Shade { - background-image: url(spritesmith-main-7.png); - background-position: -636px -1592px; - width: 105px; - height: 105px; -} -.Mount_Body_Slime-Skeleton { - background-image: url(spritesmith-main-7.png); - background-position: -742px -956px; - width: 105px; - height: 105px; -} -.Mount_Body_Slime-White { background-image: url(spritesmith-main-7.png); background-position: -742px -1592px; width: 105px; diff --git a/common/dist/sprites/spritesmith-main-7.png b/common/dist/sprites/spritesmith-main-7.png index 625f3cc2a5f153024acd39674372c67d798a938d..dcbbb639a90c7dafc9fc98db29ab8c01571c3415 100644 GIT binary patch literal 151921 zcmbTedpwl+`#`WQ+@pJq`RC?{b=!l=h3}-Sd@k%l*twS6L}uuUXdbTIAQW+JWo2-L{3h%lgO ze_l-Yymn8%Ih1&@c!TF1S(#M>>T2PWgAQ3Y9O}j`ZEvVn#g0sq&}Evjw@}T+>Rpz$ zr5)J`OcFXF==TqUU(na)ewy84`}Jz;jTc$X7LNu8@ETKJ=@CIyg$`}68HJzG4s{Wg zv+>g!!>U2_GYV+AbC;i7)ANO|#jLB54IIT~c=6-46dny~DO@O%L(3)HJ)m$w_&Rb| zl|5@|B7Ia>BP|7gT(ibXYQ=0k{RrvUf8WDUoP5@IsLU!lfFs<;wM_2QG}>ybMF;6v zrkp>nQ~KxGEmJy9Hrog?%@%#}mvIgH8D0(i_<{~mf=s3yL1veYF#C!F3Xj@@U4`(g z;Q5u zHE>&*o%j>$y)p%olxXBh=A(w*0wZ@fhq=C?Q}|G=+JyP4OZ3MwhpgxW*H80_b=-ZR zX(A#OVvfQOC|P5*u?}w{rsuXc=%1{(uL+_t6ad+KI4R>18q zl;wW{X%>&goMZ4(2MfEN)zi3|Wo?=pd#2L$lwxbz@9r4(p2+JfJNNkS{Vj+PNxuJ9 z8hB5ch8oy&)=uh^d@tjqm+95N-k0!U`rkHjjzpU(Nxes!dOTP>ZCbD3L5~Ts+eVWl zW;Sb`M|22Y*5EDbXT09|AeU(A=F=lFIZPuHo{Wh%#6)>7qroQwUizJ?A#*cM!UU9M z+GX62gtD;JCd+pEg0Pp=GR5QqiG#tG{V&Blbx5PXAxTuc`X@PUqd*fBAuL%eyBTHL z_t6W3d>`4Fr-|5#Xs2T)D&z8h8P>g_Y(Ib{#e3VEG7=e$^F^oS``TqMHyIndPJK#^ zIH1sx09tz8?CaH!LXU!y62r{SQ4vF4MayBCB=XuF35b=I2>rJn{6MY)$FjVbwJE&n zZ#hT8-^|aAD^A`CDNTGYSmvweaB9pXVyB@ZjTlQArJVCTdW5u-${cAAKLGnVIR03S ziXMHoUO!Nma!paPrZD;%$MrRJ9Ze@py> z4?y0MGV3lW{D=LI+xfx#0SRW8z~V=;*y!4uo?0*NG2&r`Rq}oLGUS6pJpPNO@8~C) zj=6j(6A|H_FsYgF`r^-{5pLZxQpGu2rJM9Z)C-h#>KMUv)za_@$u7r`p(CU$rE&x( z-wX|K;VY|zuHp8qJV~@ywad+v<}l>U#wU?wCG4F=@NqsKTRl8F5zW8)$77R2HpE* zm%h)?PVsv)A(|fg)edJh!{>*q<-%)wBJSx{6m+&~$`|wxmD!zQF+8KeiY=)21{_7@VI#XUE6X~`5qL1XeqBfwRTt@wyc=a0j$4m`ALX{X(Nv{@w3eo z#fgOLGrdLiF^U(bQ%eiqXU@;v3+u&=9-QBX}_BEFOyELV_C-gmUh>( z)oN!+Hr4dDnaiP4a~~hgGz_WdzhB=zIv2{fzr&Lm60Ym+Sv9_qs6WHpaUmQ>S~FZw z*|WjQfOKQwNxED&9N=!`U$$b`i zE=&vCw}FDIZMCOK@@Ffh)s%`me8$|ZQN$A+0!mh8w ze-a`jYbJ&v4vX|jgzZ|9m;!-mmXBDli&;+US6hqY zy&BZ_(;f4%p^ok2mNN5i6Xp+Aug;u%0D{%gVmR#b9(z;_Mn6>WHb`c8Vq!z5%XKz# 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

PT^^O>u2R^_C~^47a|XV(XS(GCeN}F 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

@J&<3 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 diff --git a/common/dist/sprites/spritesmith-main-8.css b/common/dist/sprites/spritesmith-main-8.css index 1b304feff8..8c8def6459 100644 --- a/common/dist/sprites/spritesmith-main-8.css +++ b/common/dist/sprites/spritesmith-main-8.css @@ -1,192 +1,216 @@ -.Mount_Body_Slime-Zombie { - background-image: url(spritesmith-main-8.png); - background-position: -424px -795px; - width: 105px; - height: 105px; -} -.Mount_Body_Snail-Base { - background-image: url(spritesmith-main-8.png); - background-position: -954px -1113px; - width: 105px; - height: 105px; -} -.Mount_Body_Snail-CottonCandyBlue { - background-image: url(spritesmith-main-8.png); - background-position: -530px -795px; - width: 105px; - height: 105px; -} -.Mount_Body_Snail-CottonCandyPink { - background-image: url(spritesmith-main-8.png); - background-position: -636px -795px; - width: 105px; - height: 105px; -} -.Mount_Body_Snail-Desert { - background-image: url(spritesmith-main-8.png); - background-position: -742px -795px; - width: 105px; - height: 105px; -} -.Mount_Body_Snail-Golden { +.Mount_Body_Slime-Red { background-image: url(spritesmith-main-8.png); background-position: -848px -795px; width: 105px; height: 105px; } -.Mount_Body_Snail-Red { +.Mount_Body_Slime-Shade { + background-image: url(spritesmith-main-8.png); + background-position: -954px -1113px; + width: 105px; + height: 105px; +} +.Mount_Body_Slime-Skeleton { background-image: url(spritesmith-main-8.png); background-position: -998px 0px; width: 105px; height: 105px; } -.Mount_Body_Snail-Shade { +.Mount_Body_Slime-White { background-image: url(spritesmith-main-8.png); background-position: -998px -106px; width: 105px; height: 105px; } -.Mount_Body_Snail-Skeleton { +.Mount_Body_Slime-Zombie { background-image: url(spritesmith-main-8.png); background-position: -998px -212px; width: 105px; height: 105px; } -.Mount_Body_Snail-White { +.Mount_Body_Snail-Base { background-image: url(spritesmith-main-8.png); background-position: -998px -318px; width: 105px; height: 105px; } -.Mount_Body_Snail-Zombie { +.Mount_Body_Snail-CottonCandyBlue { background-image: url(spritesmith-main-8.png); background-position: -998px -424px; width: 105px; height: 105px; } -.Mount_Body_Snake-Base { +.Mount_Body_Snail-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1104px -848px; + background-position: -998px -530px; width: 105px; height: 105px; } -.Mount_Body_Snake-CottonCandyBlue { +.Mount_Body_Snail-Desert { background-image: url(spritesmith-main-8.png); - background-position: 0px -1007px; + background-position: -998px -636px; width: 105px; height: 105px; } -.Mount_Body_Snake-CottonCandyPink { +.Mount_Body_Snail-Golden { background-image: url(spritesmith-main-8.png); - background-position: -106px -1007px; + background-position: -998px -742px; width: 105px; height: 105px; } -.Mount_Body_Snake-Desert { +.Mount_Body_Snail-Red { background-image: url(spritesmith-main-8.png); - background-position: -212px -1007px; + background-position: 0px -901px; width: 105px; height: 105px; } -.Mount_Body_Snake-Golden { +.Mount_Body_Snail-Shade { background-image: url(spritesmith-main-8.png); background-position: -318px -1007px; width: 105px; height: 105px; } -.Mount_Body_Snake-Red { +.Mount_Body_Snail-Skeleton { background-image: url(spritesmith-main-8.png); background-position: -424px -1007px; width: 105px; height: 105px; } -.Mount_Body_Snake-Shade { +.Mount_Body_Snail-White { background-image: url(spritesmith-main-8.png); background-position: -530px -1007px; width: 105px; height: 105px; } -.Mount_Body_Snake-Skeleton { +.Mount_Body_Snail-Zombie { background-image: url(spritesmith-main-8.png); background-position: -636px -1007px; width: 105px; height: 105px; } -.Mount_Body_Snake-White { +.Mount_Body_Snake-Base { background-image: url(spritesmith-main-8.png); background-position: -742px -1007px; width: 105px; height: 105px; } -.Mount_Body_Snake-Zombie { +.Mount_Body_Snake-CottonCandyBlue { background-image: url(spritesmith-main-8.png); background-position: -848px -1007px; width: 105px; height: 105px; } -.Mount_Body_Spider-Base { +.Mount_Body_Snake-CottonCandyPink { background-image: url(spritesmith-main-8.png); background-position: -954px -1007px; width: 105px; height: 105px; } -.Mount_Body_Spider-CottonCandyBlue { +.Mount_Body_Snake-Desert { background-image: url(spritesmith-main-8.png); background-position: -1060px -1007px; width: 105px; height: 105px; } -.Mount_Body_Spider-CottonCandyPink { +.Mount_Body_Snake-Golden { background-image: url(spritesmith-main-8.png); background-position: -1210px 0px; width: 105px; height: 105px; } -.Mount_Body_Spider-Desert { +.Mount_Body_Snake-Red { background-image: url(spritesmith-main-8.png); - background-position: -1422px -212px; + background-position: -1210px -106px; width: 105px; height: 105px; } -.Mount_Body_Spider-Golden { +.Mount_Body_Snake-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1422px -318px; + background-position: -1210px -212px; width: 105px; height: 105px; } -.Mount_Body_Spider-Red { +.Mount_Body_Snake-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1422px -424px; + background-position: -1210px -318px; width: 105px; height: 105px; } -.Mount_Body_Spider-Shade { +.Mount_Body_Snake-White { background-image: url(spritesmith-main-8.png); - background-position: -1422px -530px; + background-position: -1210px -424px; width: 105px; height: 105px; } -.Mount_Body_Spider-Skeleton { +.Mount_Body_Snake-Zombie { background-image: url(spritesmith-main-8.png); background-position: -1422px -636px; width: 105px; height: 105px; } -.Mount_Body_Spider-White { +.Mount_Body_Spider-Base { background-image: url(spritesmith-main-8.png); background-position: -1422px -742px; width: 105px; height: 105px; } -.Mount_Body_Spider-Zombie { +.Mount_Body_Spider-CottonCandyBlue { background-image: url(spritesmith-main-8.png); background-position: -1422px -848px; width: 105px; height: 105px; } +.Mount_Body_Spider-CottonCandyPink { + background-image: url(spritesmith-main-8.png); + background-position: -1422px -954px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Desert { + background-image: url(spritesmith-main-8.png); + background-position: -1422px -1060px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Golden { + background-image: url(spritesmith-main-8.png); + background-position: -1422px -1166px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Red { + background-image: url(spritesmith-main-8.png); + background-position: 0px -1325px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Shade { + background-image: url(spritesmith-main-8.png); + background-position: -106px -1325px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Skeleton { + background-image: url(spritesmith-main-8.png); + background-position: -212px -1325px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-White { + background-image: url(spritesmith-main-8.png); + background-position: -318px -1325px; + width: 105px; + height: 105px; +} +.Mount_Body_Spider-Zombie { + background-image: url(spritesmith-main-8.png); + background-position: -1740px -106px; + width: 105px; + height: 105px; +} .Mount_Body_TRex-Base { background-image: url(spritesmith-main-8.png); - background-position: -544px -408px; + background-position: -544px -136px; width: 135px; height: 135px; } @@ -246,265 +270,265 @@ } .Mount_Body_TigerCub-Base { background-image: url(spritesmith-main-8.png); - background-position: -1422px -954px; + background-position: -1740px -212px; width: 105px; height: 105px; } .Mount_Body_TigerCub-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1422px -1060px; + background-position: -1740px -318px; width: 105px; height: 105px; } .Mount_Body_TigerCub-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1422px -1166px; + background-position: -212px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1378px -1537px; + background-position: -318px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Floral { background-image: url(spritesmith-main-8.png); - background-position: -1484px -1537px; + background-position: -424px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1590px -1537px; + background-position: -530px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -1740px 0px; + background-position: -636px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Red { background-image: url(spritesmith-main-8.png); - background-position: -1740px -106px; + background-position: -742px -680px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1740px -212px; + background-position: -892px 0px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1740px -318px; + background-position: -892px -106px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -636px -680px; + background-position: -892px -212px; width: 105px; height: 105px; } .Mount_Body_TigerCub-White { background-image: url(spritesmith-main-8.png); - background-position: -742px -680px; + background-position: -892px -318px; width: 105px; height: 105px; } .Mount_Body_TigerCub-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -892px 0px; + background-position: -892px -424px; width: 105px; height: 105px; } .Mount_Body_Treeling-Base { background-image: url(spritesmith-main-8.png); - background-position: -892px -106px; + background-position: -892px -530px; width: 105px; height: 105px; } .Mount_Body_Treeling-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -892px -212px; + background-position: -892px -636px; width: 105px; height: 105px; } .Mount_Body_Treeling-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -892px -318px; + background-position: 0px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Desert { background-image: url(spritesmith-main-8.png); - background-position: -892px -424px; + background-position: -106px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Golden { background-image: url(spritesmith-main-8.png); - background-position: -892px -530px; + background-position: -212px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Red { background-image: url(spritesmith-main-8.png); - background-position: -892px -636px; + background-position: -318px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Shade { background-image: url(spritesmith-main-8.png); - background-position: 0px -795px; + background-position: -424px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -106px -795px; + background-position: -530px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-White { background-image: url(spritesmith-main-8.png); - background-position: -212px -795px; + background-position: -636px -795px; width: 105px; height: 105px; } .Mount_Body_Treeling-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -318px -795px; + background-position: -742px -795px; width: 105px; height: 105px; } .Mount_Body_Turkey-Base { background-image: url(spritesmith-main-8.png); - background-position: -998px -530px; + background-position: -106px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Base { background-image: url(spritesmith-main-8.png); - background-position: -998px -636px; + background-position: -212px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -998px -742px; + background-position: -318px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: 0px -901px; + background-position: -424px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Desert { background-image: url(spritesmith-main-8.png); - background-position: -106px -901px; + background-position: -530px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Golden { background-image: url(spritesmith-main-8.png); - background-position: -212px -901px; + background-position: -636px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Red { background-image: url(spritesmith-main-8.png); - background-position: -318px -901px; + background-position: -742px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Shade { background-image: url(spritesmith-main-8.png); - background-position: -424px -901px; + background-position: -848px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -530px -901px; + background-position: -954px -901px; width: 105px; height: 105px; } .Mount_Body_Unicorn-White { background-image: url(spritesmith-main-8.png); - background-position: -636px -901px; + background-position: -1104px 0px; width: 105px; height: 105px; } .Mount_Body_Unicorn-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -742px -901px; + background-position: -1104px -106px; width: 105px; height: 105px; } .Mount_Body_Whale-Base { background-image: url(spritesmith-main-8.png); - background-position: -848px -901px; + background-position: -1104px -212px; width: 105px; height: 105px; } .Mount_Body_Whale-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -954px -901px; + background-position: -1104px -318px; width: 105px; height: 105px; } .Mount_Body_Whale-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1104px 0px; + background-position: -1104px -424px; width: 105px; height: 105px; } .Mount_Body_Whale-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1104px -106px; + background-position: -1104px -530px; width: 105px; height: 105px; } .Mount_Body_Whale-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1104px -212px; + background-position: -1104px -636px; width: 105px; height: 105px; } .Mount_Body_Whale-Red { background-image: url(spritesmith-main-8.png); - background-position: -1104px -318px; + background-position: -1104px -742px; width: 105px; height: 105px; } .Mount_Body_Whale-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1104px -424px; + background-position: -1104px -848px; width: 105px; height: 105px; } .Mount_Body_Whale-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1104px -530px; + background-position: 0px -1007px; width: 105px; height: 105px; } .Mount_Body_Whale-White { background-image: url(spritesmith-main-8.png); - background-position: -1104px -636px; + background-position: -106px -1007px; width: 105px; height: 105px; } .Mount_Body_Whale-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1104px -742px; + background-position: -212px -1007px; width: 105px; height: 105px; } @@ -552,7 +576,7 @@ } .Mount_Body_Wolf-Red { background-image: url(spritesmith-main-8.png); - background-position: -544px -136px; + background-position: -136px 0px; width: 135px; height: 135px; } @@ -564,7 +588,7 @@ } .Mount_Body_Wolf-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -136px 0px; + background-position: -544px -408px; width: 135px; height: 135px; } @@ -588,283 +612,283 @@ } .Mount_Head_BearCub-Base { background-image: url(spritesmith-main-8.png); - background-position: -1210px -106px; + background-position: -1210px -530px; width: 105px; height: 105px; } .Mount_Head_BearCub-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1210px -212px; + background-position: -1210px -636px; width: 105px; height: 105px; } .Mount_Head_BearCub-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1210px -318px; + background-position: -1210px -742px; width: 105px; height: 105px; } .Mount_Head_BearCub-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1210px -424px; + background-position: -1210px -848px; width: 105px; height: 105px; } .Mount_Head_BearCub-Floral { background-image: url(spritesmith-main-8.png); - background-position: -1210px -530px; + background-position: -1210px -954px; width: 105px; height: 105px; } .Mount_Head_BearCub-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1210px -636px; + background-position: 0px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -1210px -742px; + background-position: -106px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Polar { background-image: url(spritesmith-main-8.png); - background-position: -1210px -848px; + background-position: -212px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Red { background-image: url(spritesmith-main-8.png); - background-position: -1210px -954px; + background-position: -318px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Shade { background-image: url(spritesmith-main-8.png); - background-position: 0px -1113px; + background-position: -424px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -106px -1113px; + background-position: -530px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -212px -1113px; + background-position: -636px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-White { background-image: url(spritesmith-main-8.png); - background-position: -318px -1113px; + background-position: -742px -1113px; width: 105px; height: 105px; } .Mount_Head_BearCub-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -424px -1113px; + background-position: -848px -1113px; width: 105px; height: 105px; } .Mount_Head_Bunny-Base { background-image: url(spritesmith-main-8.png); - background-position: -530px -1113px; + background-position: -1740px -424px; width: 105px; height: 105px; } .Mount_Head_Bunny-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -636px -1113px; + background-position: -1060px -1113px; width: 105px; height: 105px; } .Mount_Head_Bunny-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -742px -1113px; + background-position: -1166px -1113px; width: 105px; height: 105px; } .Mount_Head_Bunny-Desert { background-image: url(spritesmith-main-8.png); - background-position: -848px -1113px; + background-position: -1316px 0px; width: 105px; height: 105px; } .Mount_Head_Bunny-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1740px -424px; + background-position: -1316px -106px; width: 105px; height: 105px; } .Mount_Head_Bunny-Red { background-image: url(spritesmith-main-8.png); - background-position: -1060px -1113px; + background-position: -1316px -212px; width: 105px; height: 105px; } .Mount_Head_Bunny-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1166px -1113px; + background-position: -1316px -318px; width: 105px; height: 105px; } .Mount_Head_Bunny-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1316px 0px; + background-position: -1316px -424px; width: 105px; height: 105px; } .Mount_Head_Bunny-White { background-image: url(spritesmith-main-8.png); - background-position: -1316px -106px; + background-position: -1316px -530px; width: 105px; height: 105px; } .Mount_Head_Bunny-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1316px -212px; + background-position: -1316px -636px; width: 105px; height: 105px; } .Mount_Head_Cactus-Base { background-image: url(spritesmith-main-8.png); - background-position: -1316px -318px; + background-position: -1316px -742px; width: 105px; height: 105px; } .Mount_Head_Cactus-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1316px -424px; + background-position: -1316px -848px; width: 105px; height: 105px; } .Mount_Head_Cactus-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1316px -530px; + background-position: -1316px -954px; width: 105px; height: 105px; } .Mount_Head_Cactus-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1316px -636px; + background-position: -1316px -1060px; width: 105px; height: 105px; } .Mount_Head_Cactus-Floral { background-image: url(spritesmith-main-8.png); - background-position: -1316px -742px; + background-position: 0px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1316px -848px; + background-position: -106px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -1316px -954px; + background-position: -212px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Red { background-image: url(spritesmith-main-8.png); - background-position: -1316px -1060px; + background-position: -318px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Shade { background-image: url(spritesmith-main-8.png); - background-position: 0px -1219px; + background-position: -424px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -106px -1219px; + background-position: -530px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -212px -1219px; + background-position: -636px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-White { background-image: url(spritesmith-main-8.png); - background-position: -318px -1219px; + background-position: -742px -1219px; width: 105px; height: 105px; } .Mount_Head_Cactus-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -424px -1219px; + background-position: -848px -1219px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Base { background-image: url(spritesmith-main-8.png); - background-position: -530px -1219px; + background-position: -954px -1219px; width: 105px; height: 105px; } .Mount_Head_Cheetah-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -636px -1219px; + background-position: -1060px -1219px; width: 105px; height: 105px; } .Mount_Head_Cheetah-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -742px -1219px; + background-position: -1166px -1219px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Desert { background-image: url(spritesmith-main-8.png); - background-position: -848px -1219px; + background-position: -1272px -1219px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Golden { background-image: url(spritesmith-main-8.png); - background-position: -954px -1219px; + background-position: -1422px 0px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Red { background-image: url(spritesmith-main-8.png); - background-position: -1060px -1219px; + background-position: -1422px -106px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1166px -1219px; + background-position: -1422px -212px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1272px -1219px; + background-position: -1422px -318px; width: 105px; height: 105px; } .Mount_Head_Cheetah-White { background-image: url(spritesmith-main-8.png); - background-position: -1422px 0px; + background-position: -1422px -424px; width: 105px; height: 105px; } .Mount_Head_Cheetah-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1422px -106px; + background-position: -1422px -530px; width: 105px; height: 105px; } @@ -930,415 +954,415 @@ } .Mount_Head_Deer-Base { background-image: url(spritesmith-main-8.png); - background-position: 0px -1325px; + background-position: -424px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -106px -1325px; + background-position: -530px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -212px -1325px; + background-position: -636px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Desert { background-image: url(spritesmith-main-8.png); - background-position: -318px -1325px; + background-position: -742px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Golden { background-image: url(spritesmith-main-8.png); - background-position: -424px -1325px; + background-position: -848px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Red { background-image: url(spritesmith-main-8.png); - background-position: -530px -1325px; + background-position: -954px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Shade { background-image: url(spritesmith-main-8.png); - background-position: -636px -1325px; + background-position: -1060px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -742px -1325px; + background-position: -1166px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-White { background-image: url(spritesmith-main-8.png); - background-position: -848px -1325px; + background-position: -1272px -1325px; width: 105px; height: 105px; } .Mount_Head_Deer-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -954px -1325px; + background-position: -1378px -1325px; width: 105px; height: 105px; } .Mount_Head_Dragon-Base { background-image: url(spritesmith-main-8.png); - background-position: -1060px -1325px; + background-position: -1528px 0px; width: 105px; height: 105px; } .Mount_Head_Dragon-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1166px -1325px; + background-position: -1528px -106px; width: 105px; height: 105px; } .Mount_Head_Dragon-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1272px -1325px; + background-position: -1528px -212px; width: 105px; height: 105px; } .Mount_Head_Dragon-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1378px -1325px; + background-position: -1528px -318px; width: 105px; height: 105px; } .Mount_Head_Dragon-Floral { background-image: url(spritesmith-main-8.png); - background-position: -1528px 0px; + background-position: -1528px -424px; width: 105px; height: 105px; } .Mount_Head_Dragon-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1528px -106px; + background-position: -1528px -530px; width: 105px; height: 105px; } .Mount_Head_Dragon-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -1528px -212px; + background-position: -1528px -636px; width: 105px; height: 105px; } .Mount_Head_Dragon-Red { background-image: url(spritesmith-main-8.png); - background-position: -1528px -318px; + background-position: -1528px -742px; width: 105px; height: 105px; } .Mount_Head_Dragon-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1528px -424px; + background-position: -1528px -848px; width: 105px; height: 105px; } .Mount_Head_Dragon-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1528px -530px; + background-position: -1528px -954px; width: 105px; height: 105px; } .Mount_Head_Dragon-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -1528px -636px; + background-position: -1528px -1060px; width: 105px; height: 105px; } .Mount_Head_Dragon-White { background-image: url(spritesmith-main-8.png); - background-position: -1528px -742px; + background-position: -1528px -1166px; width: 105px; height: 105px; } .Mount_Head_Dragon-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1528px -848px; + background-position: -1528px -1272px; width: 105px; height: 105px; } .Mount_Head_Egg-Base { background-image: url(spritesmith-main-8.png); - background-position: -1528px -954px; + background-position: 0px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1528px -1060px; + background-position: -106px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1528px -1166px; + background-position: -212px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1528px -1272px; + background-position: -318px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Golden { background-image: url(spritesmith-main-8.png); - background-position: 0px -1431px; + background-position: -424px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Red { background-image: url(spritesmith-main-8.png); - background-position: -106px -1431px; + background-position: -530px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Shade { background-image: url(spritesmith-main-8.png); - background-position: -212px -1431px; + background-position: -636px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -318px -1431px; + background-position: -742px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-White { background-image: url(spritesmith-main-8.png); - background-position: -424px -1431px; + background-position: -848px -1431px; width: 105px; height: 105px; } .Mount_Head_Egg-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -530px -1431px; + background-position: -954px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-Base { background-image: url(spritesmith-main-8.png); - background-position: -636px -1431px; + background-position: -1060px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -742px -1431px; + background-position: -1166px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -848px -1431px; + background-position: -1272px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-Desert { background-image: url(spritesmith-main-8.png); - background-position: -954px -1431px; + background-position: -1378px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1060px -1431px; + background-position: -1484px -1431px; width: 105px; height: 105px; } .Mount_Head_Falcon-Red { background-image: url(spritesmith-main-8.png); - background-position: -1166px -1431px; + background-position: -1634px 0px; width: 105px; height: 105px; } .Mount_Head_Falcon-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1272px -1431px; + background-position: -1634px -106px; width: 105px; height: 105px; } .Mount_Head_Falcon-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1378px -1431px; + background-position: -1634px -212px; width: 105px; height: 105px; } .Mount_Head_Falcon-White { background-image: url(spritesmith-main-8.png); - background-position: -1484px -1431px; + background-position: -1634px -318px; width: 105px; height: 105px; } .Mount_Head_Falcon-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1634px 0px; + background-position: -1634px -424px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Base { background-image: url(spritesmith-main-8.png); - background-position: -1634px -106px; + background-position: -1634px -530px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -1634px -212px; + background-position: -1634px -636px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -1634px -318px; + background-position: -1634px -742px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Desert { background-image: url(spritesmith-main-8.png); - background-position: -1634px -424px; + background-position: -1634px -848px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Floral { background-image: url(spritesmith-main-8.png); - background-position: -1634px -530px; + background-position: -1634px -954px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Golden { background-image: url(spritesmith-main-8.png); - background-position: -1634px -636px; + background-position: -1634px -1060px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -1634px -742px; + background-position: -1634px -1166px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Red { background-image: url(spritesmith-main-8.png); - background-position: -1634px -848px; + background-position: -1634px -1272px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Shade { background-image: url(spritesmith-main-8.png); - background-position: -1634px -954px; + background-position: -1634px -1378px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -1634px -1060px; + background-position: 0px -1537px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -1634px -1166px; + background-position: -106px -1537px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-White { background-image: url(spritesmith-main-8.png); - background-position: -1634px -1272px; + background-position: -212px -1537px; width: 105px; height: 105px; } .Mount_Head_FlyingPig-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1634px -1378px; + background-position: -318px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Base { background-image: url(spritesmith-main-8.png); - background-position: 0px -1537px; + background-position: -424px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-CottonCandyBlue { background-image: url(spritesmith-main-8.png); - background-position: -106px -1537px; + background-position: -530px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-CottonCandyPink { background-image: url(spritesmith-main-8.png); - background-position: -212px -1537px; + background-position: -636px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Desert { background-image: url(spritesmith-main-8.png); - background-position: -318px -1537px; + background-position: -742px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Floral { background-image: url(spritesmith-main-8.png); - background-position: -424px -1537px; + background-position: -848px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Golden { background-image: url(spritesmith-main-8.png); - background-position: -530px -1537px; + background-position: -954px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Peppermint { background-image: url(spritesmith-main-8.png); - background-position: -636px -1537px; + background-position: -1060px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Red { background-image: url(spritesmith-main-8.png); - background-position: -742px -1537px; + background-position: -1166px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Shade { background-image: url(spritesmith-main-8.png); - background-position: -848px -1537px; + background-position: -1272px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Skeleton { background-image: url(spritesmith-main-8.png); - background-position: -954px -1537px; + background-position: -1378px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Spooky { background-image: url(spritesmith-main-8.png); - background-position: -1060px -1537px; + background-position: -1484px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-White { background-image: url(spritesmith-main-8.png); - background-position: -1166px -1537px; + background-position: -1590px -1537px; width: 105px; height: 105px; } .Mount_Head_Fox-Zombie { background-image: url(spritesmith-main-8.png); - background-position: -1272px -1537px; + background-position: -1740px 0px; width: 105px; height: 105px; } @@ -1361,30 +1385,6 @@ height: 114px; } .Mount_Head_Frog-Desert { - background-image: url(spritesmith-main-8.png); - background-position: -212px -680px; - width: 105px; - height: 114px; -} -.Mount_Head_Frog-Golden { - background-image: url(spritesmith-main-8.png); - background-position: -318px -680px; - width: 105px; - height: 114px; -} -.Mount_Head_Frog-Red { - background-image: url(spritesmith-main-8.png); - background-position: -424px -680px; - width: 105px; - height: 114px; -} -.Mount_Head_Frog-Shade { - background-image: url(spritesmith-main-8.png); - background-position: -530px -680px; - width: 105px; - height: 114px; -} -.Mount_Head_Frog-Skeleton { background-image: url(spritesmith-main-8.png); background-position: -408px -544px; width: 105px; diff --git a/common/dist/sprites/spritesmith-main-8.png b/common/dist/sprites/spritesmith-main-8.png index 478b78dbe19e2b56358786e60838f471893e3e13..d76361afa3ec6df5e92c976e90affcb3c4235a9f 100644 GIT binary patch literal 149583 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

W`xnxVxkg*__Dm;AVO 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 TJ-yq&GN4Rs2RD+=96( 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

$GrL2Vx?lbglj%LS(XsGg7_#Q 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%0DS%Hr){2AK=qW{6VNEauy- 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

`XVfF|1Q;8gb+?0%a*IR`7q%`z5jyb6E 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 z⪚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

%z_@T^P5AxL}-H~Ym=vf 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�Bn@+(*14N6#$32QD9@INcIxLNt5-{Wn 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 z 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 PpBgaIGuN%vae4AT@k=vx literal 0 HcmV?d00001 diff --git a/common/img/sprites/spritesmith/gear/events/mystery_201605/head_mystery_201605.png b/common/img/sprites/spritesmith/gear/events/mystery_201605/head_mystery_201605.png new file mode 100644 index 0000000000000000000000000000000000000000..742bf1fbe7c55c65039085fda662caac252ef125 GIT binary patch literal 3174 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 literal 0 HcmV?d00001 diff --git a/common/img/sprites/spritesmith/gear/events/mystery_201605/shop_armor_mystery_201605.png b/common/img/sprites/spritesmith/gear/events/mystery_201605/shop_armor_mystery_201605.png new file mode 100644 index 0000000000000000000000000000000000000000..1d1525547b5959d1eb7d63918d0dc1c929a5f6ff GIT binary patch literal 3214 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 AMgRZ+ literal 0 HcmV?d00001 diff --git a/common/img/sprites/spritesmith/gear/events/mystery_201605/shop_head_mystery_201605.png b/common/img/sprites/spritesmith/gear/events/mystery_201605/shop_head_mystery_201605.png new file mode 100644 index 0000000000000000000000000000000000000000..19193c8566ebd065dfd955b8267f254b9992241e GIT binary patch literal 3058 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 Aw*UYD literal 0 HcmV?d00001 diff --git a/common/img/sprites/spritesmith/gear/events/mystery_201605/slim_armor_mystery_201605.png b/common/img/sprites/spritesmith/gear/events/mystery_201605/slim_armor_mystery_201605.png new file mode 100644 index 0000000000000000000000000000000000000000..c41d68b60d7cdbe198417ec696ee270668d7c8be GIT binary patch literal 3327 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 literal 0 HcmV?d00001 diff --git a/common/img/sprites/spritesmith_large/promo/promo_mystery_201605.png b/common/img/sprites/spritesmith_large/promo/promo_mystery_201605.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7fc4463d577eafdb40291173e1929952501f8c GIT binary patch literal 3712 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 e8i9tG{tf_A#yKN#8)UNp0000` literal 0 HcmV?d00001 diff --git a/common/locales/en/gear.json b/common/locales/en/gear.json index 471b6b6362..8a532fa35c 100644 --- a/common/locales/en/gear.json +++ b/common/locales/en/gear.json @@ -406,6 +406,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. April 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", @@ -631,6 +633,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 8c0d453727..9561c42a4b 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -107,6 +107,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", diff --git a/common/script/content/gear/sets/mystery.js b/common/script/content/gear/sets/mystery.js index dbea14a391..de7a0a322f 100644 --- a/common/script/content/gear/sets/mystery.js +++ b/common/script/content/gear/sets/mystery.js @@ -115,6 +115,12 @@ let armor = { mystery: '201604', value: 0, }, + 201605: { + text: t('armorMystery201605Text'), + notes: t('armorMystery201605Notes'), + mystery: '201605', + value: 0, + }, 301404: { text: t('armorMystery301404Text'), notes: t('armorMystery301404Notes'), @@ -304,6 +310,12 @@ let head = { mystery: '201604', value: 0, }, + 201605: { + text: t('headMystery201605Text'), + notes: t('headMystery201605Notes'), + mystery: '201605', + value: 0, + }, 301404: { text: t('headMystery301404Text'), notes: t('headMystery301404Notes'), diff --git a/common/script/content/mystery-sets.js b/common/script/content/mystery-sets.js index 95e45bf71d..8eae5a9392 100644 --- a/common/script/content/mystery-sets.js +++ b/common/script/content/mystery-sets.js @@ -110,6 +110,10 @@ let mysterySets = { start: '2016-04-25', end: '2016-05-02', }, + 201605: { + start: '2016-05-25', + end: '2016-06-02', + }, 301404: { start: '3014-03-24', end: '3014-04-02', diff --git a/migrations/mystery_items.js b/migrations/mystery_items.js index 50634749fe..c858c277e3 100644 --- a/migrations/mystery_items.js +++ b/migrations/mystery_items.js @@ -2,7 +2,7 @@ var _id = ''; var update = { $addToSet: { 'purchased.plan.mysteryItems':{ - $each:['head_mystery_201604','armor_mystery_201604'] + $each:['head_mystery_201605','armor_mystery_201605'] } } }; diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index c59174b701..1d1c7604cd 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,28 +1,46 @@ -h2 5/21/2016 - WELCOME BACK, HABITICA! +h2 5/25/2016 - ANDROID UPDATE AND MAY SUBSCRIBER ITEMS hr tr td - h3 Welcome Back, Everyone! - p Hurrah! After many hours of toil, our valiant blacksmiths were able to complete our planned maintenance ahead of schedule. The site should be working normally again! If you notice any issues or have any questions, please feel free to email us at admin@habitica.com and we will be happy to help. + h3 Android Update: Bug Fixes + p We've released a new Android update with fixes for lots of bugs and crashes. Download it now for a more stable experience! + br + p Thank you for your patience as we worked out some of the hiccups that came with our major code overhaul this weekend. If you like the direction that we're taking the app, we'd love it if you could take the time to leave us a review :) It really helps us out! + br + p (Hoping for an iOS update with similar fixes? We've submitted it to Apple, so we're just waiting for them to approve it, which could take several days. Stay tuned!) + p.small.muted by viirus tr td - h3 Important Mobile App Updates - p We’ve released an iOS update and an Android update that contain the new code. It’s very important to download these updates immediately, or you may encounter significant bugs! - tr - td - .Pet-Wolf-Veteran.pull-right - h3 Veteran Pets - p To thank you for your patience during the maintenance, we have awarded everyone a special Veteran Pet! You can see it under Inventory > Pets, at the bottom of the screen. If it hasn’t appeared yet, never fear: because there are so many Habiticans, it can sometimes take an hour or two for everyone to receive their pet. You will have it soon! Thanks again for bearing with us during the downtime. - tr - td - h3 Daily Safe Mode - p To protect the accounts of Habiticans in different time zones across the world, we enabled Cron Daily Safe Mode during the maintenance, which will prevent you from taking any damage or losing any streaks for the rest of the weekend. Let us know at admin@habitica.com if you have any questions or concerns! + .promo_mystery_201605.pull-right + h3 May Subscriber Items + p The May Subscriber Items have been revealed: the Marching Bard Item Set! You still have six days to subscribe and receive the item set. + br + p Subscribers also receive the ability to buy Gems for Gold -- the longer you subscribe, the more Gems you can buy per month! There are other perks as well, such as longer access to uncompressed data. Best of all, your support directly keeps Habitica running. Thank you very much -- it means a lot to us! + p.small.muted by Lemoness if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 5/21/2016 - WELCOME BACK, HABITICA! + tr + td + h3 Welcome Back, Everyone! + p Hurrah! After many hours of toil, our valiant blacksmiths were able to complete our planned maintenance ahead of schedule. The site should be working normally again! If you notice any issues or have any questions, please feel free to email us at admin@habitica.com and we will be happy to help. + tr + td + h3 Important Mobile App Updates + p We’ve released an iOS update and an Android update that contain the new code. It’s very important to download these updates immediately, or you may encounter significant bugs! + tr + td + .Pet-Wolf-Veteran.pull-right + h3 Veteran Pets + p To thank you for your patience during the maintenance, we have awarded everyone a special Veteran Pet! You can see it under Inventory > Pets, at the bottom of the screen. If it hasn’t appeared yet, never fear: because there are so many Habiticans, it can sometimes take an hour or two for everyone to receive their pet. You will have it soon! Thanks again for bearing with us during the downtime. + tr + td + h3 Daily Safe Mode + p To protect the accounts of Habiticans in different time zones across the world, we enabled Cron Daily Safe Mode during the maintenance, which will prevent you from taking any damage or losing any streaks for the rest of the weekend. Let us know at admin@habitica.com if you have any questions or concerns! h2 5/19/2016 - IMPORTANT: UPCOMING MAINTENANCE! tr td From fb035c414cfada1f20bd1a30c632f981156c712a Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 11:52:15 +0200 Subject: [PATCH 056/152] fix sending gift messages --- website/server/libs/api-v3/payments.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js index b896031afb..144b4fe568 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/api-v3/payments.js @@ -90,7 +90,10 @@ api.createSubscription = async function createSubscription (data) { data.user.purchased.txnCount++; if (data.gift) { - data.user.sendMessage(data.user, data.gift.member, data.gift); + let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${shared.content.subscriptionBlocks[data.gift.subscription.key].months} months of subscription!\``; + if (data.gift.message) message += ` ${data.gift.message}`; + + data.user.sendMessage(data.gift.member, message); let byUserName = getUserInfo(data.user, ['name']).name; @@ -163,7 +166,10 @@ api.buyGems = async function buyGems (data) { let byUsername = getUserInfo(data.user, ['name']).name; let gemAmount = data.gift.gems.amount || 20; - data.user.sendMessage(data.user, data.gift.member, data.gift); + let message = `\`Hello ${data.gift.member.profile.name}, ${data.user.profile.name} has sent you ${gemAmount} gems!\``; + if (data.gift.message) message += ` ${data.gift.message}`; + data.user.sendMessage(data.gift.member, message); + if (data.gift.member.preferences.emailNotifications.giftedGems !== false) { txnEmail(data.gift.member, 'gifted-gems', [ {name: 'GIFTER', content: byUsername}, From db7a4f1911fa39aba67efe774d3e600eb426605f Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 26 May 2016 07:24:05 -0500 Subject: [PATCH 057/152] chore: Update package.json version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d8ec90e3f5..55c837b0d6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.2.0", + "version": "3.3.1", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 2c8910257646b891db8f618bdec89ef9210d1eab Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 26 May 2016 07:41:42 -0500 Subject: [PATCH 058/152] fix(locales): Correct mystery armor date --- common/locales/en/gear.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/locales/en/gear.json b/common/locales/en/gear.json index 8a532fa35c..a32a3cb3df 100644 --- a/common/locales/en/gear.json +++ b/common/locales/en/gear.json @@ -407,7 +407,7 @@ "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", "armorMystery201605Text": "Marching Bard Uniform", - "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", From 4b65deac0b95bffcb69a39eb1604a8ffd98d5e94 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 26 May 2016 07:52:51 -0500 Subject: [PATCH 059/152] chore(i18n): update locales --- common/locales/cs/contrib.json | 4 +- common/locales/cs/gear.json | 4 ++ common/locales/cs/maintenance.json | 62 ++++++++++++------------ common/locales/cs/subscriber.json | 1 + common/locales/da/contrib.json | 4 +- common/locales/da/gear.json | 4 ++ common/locales/da/subscriber.json | 1 + common/locales/de/challenge.json | 5 +- common/locales/de/contrib.json | 4 +- common/locales/de/gear.json | 4 ++ common/locales/de/groups.json | 4 +- common/locales/de/limited.json | 2 +- common/locales/de/pets.json | 2 +- common/locales/de/settings.json | 2 +- common/locales/de/spells.json | 6 +-- common/locales/de/subscriber.json | 3 +- common/locales/de/tasks.json | 2 +- common/locales/en@pirate/contrib.json | 4 +- common/locales/en@pirate/gear.json | 4 ++ common/locales/en@pirate/subscriber.json | 1 + common/locales/en_GB/contrib.json | 4 +- common/locales/en_GB/gear.json | 4 ++ common/locales/en_GB/subscriber.json | 1 + common/locales/es/character.json | 2 +- common/locales/es/contrib.json | 4 +- common/locales/es/gear.json | 4 ++ common/locales/es/limited.json | 2 +- common/locales/es/pets.json | 2 +- common/locales/es/quests.json | 38 +++++++-------- common/locales/es/questscontent.json | 12 ++--- common/locales/es/subscriber.json | 1 + common/locales/es_419/contrib.json | 4 +- common/locales/es_419/gear.json | 8 ++- common/locales/es_419/subscriber.json | 1 + common/locales/fr/contrib.json | 4 +- common/locales/fr/gear.json | 4 ++ common/locales/fr/groups.json | 4 +- common/locales/fr/limited.json | 2 +- common/locales/fr/pets.json | 2 +- common/locales/fr/settings.json | 2 +- common/locales/fr/spells.json | 6 +-- common/locales/fr/subscriber.json | 3 +- common/locales/fr/tasks.json | 2 +- common/locales/he/contrib.json | 14 +++--- common/locales/he/death.json | 2 +- common/locales/he/gear.json | 4 ++ common/locales/he/limited.json | 4 +- common/locales/he/questscontent.json | 4 +- common/locales/he/subscriber.json | 1 + common/locales/hu/contrib.json | 4 +- common/locales/hu/gear.json | 4 ++ common/locales/hu/subscriber.json | 1 + common/locales/it/backgrounds.json | 2 +- common/locales/it/contrib.json | 4 +- common/locales/it/death.json | 2 +- common/locales/it/gear.json | 4 ++ common/locales/it/groups.json | 28 +++++------ common/locales/it/limited.json | 2 +- common/locales/it/npc.json | 38 +++++++-------- common/locales/it/pets.json | 2 +- common/locales/it/quests.json | 18 +++---- common/locales/it/questscontent.json | 12 ++--- common/locales/it/settings.json | 10 ++-- common/locales/it/spells.json | 6 +-- common/locales/it/subscriber.json | 11 +++-- common/locales/it/tasks.json | 2 +- common/locales/ja/contrib.json | 4 +- common/locales/ja/gear.json | 4 ++ common/locales/ja/maintenance.json | 4 +- common/locales/ja/subscriber.json | 1 + common/locales/nl/contrib.json | 4 +- common/locales/nl/gear.json | 4 ++ common/locales/nl/subscriber.json | 1 + common/locales/pl/contrib.json | 4 +- common/locales/pl/gear.json | 4 ++ common/locales/pl/subscriber.json | 1 + common/locales/pt/backgrounds.json | 8 +-- common/locales/pt/challenge.json | 29 +++++------ common/locales/pt/character.json | 6 +-- common/locales/pt/contrib.json | 14 +++--- common/locales/pt/death.json | 2 +- common/locales/pt/front.json | 52 ++++++++++---------- common/locales/pt/gear.json | 30 +++++++----- common/locales/pt/groups.json | 62 ++++++++++++------------ common/locales/pt/limited.json | 2 +- common/locales/pt/maintenance.json | 54 ++++++++++----------- common/locales/pt/npc.json | 34 ++++++------- common/locales/pt/pets.json | 10 ++-- common/locales/pt/quests.json | 38 +++++++-------- common/locales/pt/questscontent.json | 12 ++--- common/locales/pt/rebirth.json | 4 +- common/locales/pt/settings.json | 10 ++-- common/locales/pt/spells.json | 16 +++--- common/locales/pt/subscriber.json | 39 +++++++-------- common/locales/pt/tasks.json | 30 ++++++------ common/locales/ro/contrib.json | 4 +- common/locales/ro/gear.json | 4 ++ common/locales/ro/subscriber.json | 1 + common/locales/ru/contrib.json | 4 +- common/locales/ru/gear.json | 4 ++ common/locales/ru/maintenance.json | 4 +- common/locales/ru/subscriber.json | 1 + common/locales/sk/contrib.json | 4 +- common/locales/sk/gear.json | 4 ++ common/locales/sk/generic.json | 4 +- common/locales/sk/maintenance.json | 4 +- common/locales/sk/messages.json | 6 +-- common/locales/sk/subscriber.json | 1 + common/locales/sr/contrib.json | 4 +- common/locales/sr/gear.json | 4 ++ common/locales/sr/subscriber.json | 1 + common/locales/sv/contrib.json | 4 +- common/locales/sv/gear.json | 4 ++ common/locales/sv/subscriber.json | 1 + common/locales/uk/contrib.json | 4 +- common/locales/uk/gear.json | 4 ++ common/locales/uk/subscriber.json | 1 + common/locales/zh/backgrounds.json | 8 +-- common/locales/zh/character.json | 6 +-- common/locales/zh/content.json | 8 +-- common/locales/zh/contrib.json | 4 +- common/locales/zh/front.json | 22 ++++----- common/locales/zh/gear.json | 38 ++++++++------- common/locales/zh/groups.json | 22 ++++----- common/locales/zh/limited.json | 2 +- common/locales/zh/npc.json | 2 +- common/locales/zh/pets.json | 2 +- common/locales/zh/quests.json | 6 +-- common/locales/zh/settings.json | 18 +++---- common/locales/zh/spells.json | 6 +-- common/locales/zh/subscriber.json | 15 +++--- common/locales/zh/tasks.json | 2 +- common/locales/zh_TW/contrib.json | 4 +- common/locales/zh_TW/gear.json | 4 ++ common/locales/zh_TW/subscriber.json | 1 + 135 files changed, 630 insertions(+), 513 deletions(-) diff --git a/common/locales/cs/contrib.json b/common/locales/cs/contrib.json index 6d3c1312f0..cd1155bfee 100644 --- a/common/locales/cs/contrib.json +++ b/common/locales/cs/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Navštiv Sál hrdinů (pomocníci a přispěvatelé)", "conLearn": "Dozvi se více o odměnách přispěvatelům.", "conLearnHow": "Jak přispět programu Habitica", - "surveysSingle": "Pomohl programu Habitica vyplněním dotazníku. Nejsou žádné aktivní dotazníky.", - "surveysMultiple": "Pomohl zemi Habitica růst vyplněním <%= surveys %> dotazníků. Nejsou žádné aktivní dotazníky.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Aktuální dotazník", "surveyWhen": "Odznaky budou rozdány všem účastníkům poté, co budou všechny dotazníky zpracovány ke konci března.", "blurbInbox": "Tady jsou uloženy tvé soukromé zprávy! Zprávu můžeš někomu poslat kliknutím na ikonu obálky vedle jeho jména v Krčmě, Družině nebo chatu Cechu. Pokud jsi obdržel nevhodnou zprávu, měl bys nám poslat její screenshot na Lemoness (leslie@habitica.com)", diff --git a/common/locales/cs/gear.json b/common/locales/cs/gear.json index 0a52259b20..9afc42691c 100644 --- a/common/locales/cs/gear.json +++ b/common/locales/cs/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk oblek", "armorMystery301404Notes": "Elegantní a fešácký, joj! Nepřináší žádný benefit. Předmět pro předplatitele únor 3015.", "armorArmoireLunarArmorText": "Uklidňující měsíční brnění", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fešný cylindr", "headMystery301404Notes": "Fešný cylindr pro ty největší džentlmeny. Předmět pro předplatitele leden 2015. Nepřináší žádný benefit.", "headMystery301405Text": "Obyčejný cylindr", diff --git a/common/locales/cs/maintenance.json b/common/locales/cs/maintenance.json index efdb524cd2..b085037351 100644 --- a/common/locales/cs/maintenance.json +++ b/common/locales/cs/maintenance.json @@ -1,34 +1,34 @@ { - "habiticaBackSoon": "Don't worry, Habitica will be back soon!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", - "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", - "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "habiticaBackSoon": "Nebojte se, Habitica bude brzy zpět!", + "importantMaintenance": "Provádíme důležitou údržbu, která dle našich odhadů bude trvat do <%= localDate %> ve vašem časovém pásmu.", + "maintenance": "Údržba", + "maintenanceMoreInfo": "Chcete více informací ohledně údržby? <%= linkStart %>Podívejte se na naši stránku<%= linkEnd %>.", + "noDamageKeepStreaks": "Neutrpíte ŽÁDNÉ poškození a neztratíte vaše série úspěšnosti!", + "thanksForPatience": "Děkujeme za vaší trpělivost.", + "twitterMaintenanceUpdates": "Pro nejnovější novinky sledujte náš Twitter, kde budeme přidávat více informací.", + "veteranPetAward": "Po údržbě obdržíte Veteránského mazlíčka!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", - "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", - "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", - "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", - "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", - "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", - "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", - "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", - "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", - "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", - "maintenanceInfoSeeTasks": "What if I need to see my task list?", - "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", - "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", - "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", - "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." + "maintenanceInfoTitle": "Informace o nacházející údržbě Habiticy", + "maintenanceInfoWhat": "Co se děje?", + "maintenanceInfoWhatText": "21. května bude Habitica většinu dne mimo provoz, z důvodu údržby. Během víkendu neutrpíte žádné poškození a to, ani když se včas nepřihlásíte a neodškrtnete si své denní úkoly. Budeme se snažit zprovoznit Habiticu co nejdříve, novinky ohledně údržby budeme přidávat na náš Twitter účet. Po údržbě obdržíte Veteránského mazlíčka!", + "maintenanceInfoWhy": "Proč se to děje?", + "maintenanceInfoWhyText": "Pár posledních měsíců jsme Habiticu v pozadí důkladně rekonstruovali. Specificky, jsme přepsali API. I když to na povrchu možná nevypadá o tolik jinak, pod povrchem je to naprosto nový svět. V budoucnosti nám toto povolí vytvořit mnohem snadněji mnoho funkcí a zlepšit celkovou hratelnost!", + "maintenanceInfoTechDetails": "Chcete více detailů ohledně technické stránky procesu? Navštivte Kovárnu, náš blog o vývoji.", + "maintenanceInfoMore": "Více informací", + "maintenanceInfoAccountChanges": "Jaké změn, ve svém účtu, si budu moci všimnout, až bude přepis dokončen? ", + "maintenanceInfoAccountChangesText": "Ze začátku nenarazíte na žádné znatelné změny, kromě vylepšení výkonu funkcí jako jsou například Výzvy. Všimnete-li si nějakých změn, které tu nemají být, pošlete nám email admin@habitica.com a my je pro vás vyřešíme!", + "maintenanceInfoAddFeatures": "Jaké vylepšení se přidají do Habiticy?", + "maintenanceInfoAddFeaturesText": "Dokončení tohoto přepisu nám dovolí začít budovat vylepšený chat a Cechy, plány pro organizace a rodiny a další produktivní funkce, například Měsíčky a možnost si nahrát včerejší aktivitu! To jsou všechno funkce, které jsou jednotlivé, takže bude trvat déle je zavést,pokud bychom však nezačali s tímto přepisem, nebylo by možné je začít provádět.", + "maintenanceInfoHowLong": "Jako dlouho bude údržba trvat?", + "maintenanceInfoHowLongText": "Musíme převést úkoly a data pro 1.3 milióny uživatelů Habiticy - nejednoduchý úkol! Očekáváme, že se údržba bude konat od (20:00 UTC) do (05:00 UTC). Buďte si jistí, že děláme co je v našich silách, abychom vše stihli co nejrychleji. Můžete sledovat aktualizace na našem Twitteru.", + "maintenanceInfoStatsAffected": "Co se stane s mými Denními úkoly, Sériemi úspěšnosti, Bonusy a Výpravami?", + "maintenanceInfoStatsAffectedText1": "Během víkendu neutrpíte ŽÁDNÉ poškození a neztratíte vaše série úspěšnosti, váš sen se však resetuje normálně! Denní úkoly, které jste odškrtli budou odškrtnuté, bonusy se resetují, atd. Jste-li na sběratelské výpravě, stále budete nalézat předměty, jste-li v bitvě s Bossem, stále budete působit poškození, ovšem Boss nebude působit poškození vám. (I příšery potřebují pauzu!)", + "maintenanceInfoStatsAffectedText2": "Po dlouhém přemýšlení se náš tým rozhodl, že toto je nejférovější cesta jak si poradit se skutečností, že mnoho uživatelů nebude schopno si odškrtnout své Denní úkoly během údržby. Omlouváme se za veškeré nepříjemnosti!", + "maintenanceInfoSeeTasks": "Co když potřebuji vidět svůj list úkolů?", + "maintenanceInfoSeeTasksText": "Budete-li potřebovat vidět váš seznam úkolů v sobotu, abyste věděli co dělat, doporučujeme vám si ho před začátkem údržby vyfotit.", + "maintenanceInfoRarePet": "Jakého vzácného mazlíčka dostanu?", + "maintenanceInfoRarePetText": "Abychom vám poděkovali za vaší trpělivost během prostoje, každý dostane vzácného Veteránského mazlíčka. Pokud jste žádného nikdy předtím nedostali, obdržíte Veteránského vlka. Máte-li už Veteránského vlka, obdržíte Veteránského tygra. Máte-li už oba tyto mazlíčky, obdržíte Veteránského mazlíčka, kterého ještě nikdo nikdy neviděl! Může trvat i několik hodin, po dokončení migrace serveru, než se váš mazlíček objeví ale nebojte se, každý jednoho dostane.", + "maintenanceInfoWho": "Kdo pracoval na tomto obrovském projektu?", + "maintenanceInfoWhoText": "Děkujeme za optání! Bylo to vedeno naším úžasným přispěvatelem jménem paglias, s hodně pomocí od hráčů, kteří si říkají Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, a Alys.", + "maintenanceInfoTesting": "Tato nová verze byla také neúnavně testovaná skupinou úžasných dobrovolníků. Děkujeme vám - bez vás bychom to nezvládli." } diff --git a/common/locales/cs/subscriber.json b/common/locales/cs/subscriber.json index 9fdbe4a3f0..1e50cf7ab2 100644 --- a/common/locales/cs/subscriber.json +++ b/common/locales/cs/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Set Lamače srdcí", "mysterySet201603": "Set Šťastného trojlístku", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Standardní steampunkový set", "mysterySet301405": "Set steampunkových doplňků", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/da/contrib.json b/common/locales/da/contrib.json index 129bce58a9..6d37d19d13 100644 --- a/common/locales/da/contrib.json +++ b/common/locales/da/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Besøg Heltenes Sal (bidragsydere og støtter)", "conLearn": "Lær mere om bidragsyderbelønninger", "conLearnHow": "Lær hvordan du kan bidrage til Habitica", - "surveysSingle": "Hjalp Habitica med at vokse ved at udfylde et spørgeskema. Der er ingen aktive spørgeskemaer.", - "surveysMultiple": "Hjalp Habitica med at vokse ved at udfylde <%= surveys %> spørgeskemaer. Der er ingen aktive spørgeskemaer.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Nuværende Spørgeskema", "surveyWhen": "Emblemet gives til alle medvirkende, når spørgeskemaerne er blevet gennemgået ultimo marts.", "blurbInbox": "Her bliver dine private beskeder gemt! Du kan sende en besked til nogen ved at klikke på det lille brev-ikon ved siden af deres navn i Værtshuset, gruppe-, eller klanchat. Hvis du har modtaget en upassende privatbesked, skal du sende en e-mail med et skærmbillede til Lemoness (leslie@habitica.com)", diff --git a/common/locales/da/gear.json b/common/locales/da/gear.json index 52f290e1cf..5e85b380a1 100644 --- a/common/locales/da/gear.json +++ b/common/locales/da/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk-dragt", "armorMystery301404Notes": "Nydelig og elegant, selvfølgelig! Giver ingen bonusser. Februar 3015 Abonnentting.", "armorArmoireLunarArmorText": "Beroligende Måne-rustning", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Smart Tophat", "headMystery301404Notes": "En smart tophat for de fineste folk! Giver ingen bonusser. Januar 3015 Abonnentting.", "headMystery301405Text": "Simpel Tophat", diff --git a/common/locales/da/subscriber.json b/common/locales/da/subscriber.json index 488d7173df..a0357a9f0c 100644 --- a/common/locales/da/subscriber.json +++ b/common/locales/da/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standardsæt", "mysterySet301405": "Steampunk Tilbehørssæt", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/de/challenge.json b/common/locales/de/challenge.json index 4eed96519b..ed7c319ecc 100644 --- a/common/locales/de/challenge.json +++ b/common/locales/de/challenge.json @@ -74,9 +74,10 @@ "onlyLeaderDeleteChal": "Nur der Wettbewerbsleiter kann es löschen.", "onlyLeaderUpdateChal": "Nur der Wettbewerbsleiter kann es aktualisieren.", "winnerNotFound": "Gewinner mit ID \"<%= userId %>\" nicht gefunden, oder nimmt nicht am Wettbewerb teil.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" wird nicht unterstützt, wenn Wettbewerbsaufgaben heruntergeladen werden.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" wird beim Herunterladen von Wettbewerbsaufgaben nicht unterstützt.", "userTasksNoChallengeId": "Wenn \"tasksOwner\" \"user\" ist, kann \"challengeId\" nicht angegeben werden.", "onlyChalLeaderEditTasks": "Aufgaben, die zu einem Wettbewerb gehören, können nur vom Leiter bearbeitet werden.", "userAlreadyInChallenge": "Der Benutzer nimmt bereits an diesem Wettbewerb teil.", - "cantOnlyUnlinkChalTask": "Nur ungültige Wettbewerbsaufgaben können entfernt werden." + "cantOnlyUnlinkChalTask": "Nur ungültige Wettbewerbsaufgaben können entfernt werden.", + "shortNameTooShort": "Tag-Namen müssen mindestens 3 Zeichen lang sein." } \ No newline at end of file diff --git a/common/locales/de/contrib.json b/common/locales/de/contrib.json index 1aaa554f9d..474aa825af 100644 --- a/common/locales/de/contrib.json +++ b/common/locales/de/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Besuche die Halle der Helden (Mitwirkende und Träger)", "conLearn": "Erfahre mehr über Belohnungen für Mitwirkende", "conLearnHow": "Erfahre mehr, wie Du bei Habitica mitwirken kannst.", - "surveysSingle": "Hat Habitica durch die Teilnahme an einer Umfrage geholfen zu wachsen. Es gibt keine aktuellen Umfragen.", - "surveysMultiple": "Hat Habitica durch Teilnahme an <%= surveys %> Umfragen geholfen zu wachsen. Es gibt keine aktiven Umfragen.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Aktuelle Umfrage", "surveyWhen": "Das Abzeichen wird gegen Ende März verliehen, wenn alle Umfragen bearbeitet wurden.", "blurbInbox": "Hier werden Deine privaten Nachrichten (PN) gespeichert! Du kannst jemandem eine Nachricht schreiben, indem Du auf den Umschlag neben seinem Namen im Gasthaus-, Gruppen- oder Gildenchat klickst. Falls Du eine unangemessene PN erhältst, schicke eine E-Mail mit einem Screenshot davon an Lemoness\n(leslie@habitica.com)", diff --git a/common/locales/de/gear.json b/common/locales/de/gear.json index 7fc625a9d1..e3c71e3fdb 100644 --- a/common/locales/de/gear.json +++ b/common/locales/de/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Dieser Anzug wurde aus tausenden vierblättrigen Kleeblättern zusammengenäht! Gewährt keinen Attributbonus. Abonnentengegenstand, März 2016.", "armorMystery201604Text": "Blätter-Rüstung", "armorMystery201604Notes": "Auch Du kannst ein kleiner, aber furchteinflössender Blätterhaufen sein. Gewährt keinen Attributbonus. April 2006 Abonnentengegenstand.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunkanzug", "armorMystery301404Notes": "Adrett und schneidig, hoho! Gewährt keinen Attributbonus. Februar 3015 Abonnentengegenstand.", "armorArmoireLunarArmorText": "Beruhigende Mondrüstung", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Dieser Zylinder ist ein magischer Glücksbringer. Gewährt keinen Attributbonus. Abonnentengegenstand, März 2016.", "headMystery201604Text": "Blumenkrone", "headMystery201604Notes": "Diese geflochtenen Blumen bilden einen erstaunlich robusten Helm! Gewährt keinen Attributbonus. April 2006 Abonnentengegenstand.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Schicker Zylinder", "headMystery301404Notes": "Ein schicker Zylinder für die feinsten Gentlemänner und -frauen! Januar 3015 Abonnentengegenstand. Gewährt keinen Attributbonus.", "headMystery301405Text": "Einfacher Zylinder", diff --git a/common/locales/de/groups.json b/common/locales/de/groups.json index 2baca7563d..3f7d51a212 100644 --- a/common/locales/de/groups.json +++ b/common/locales/de/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "Benutzer bereits in einer Gruppe.", "userWithIDNotFound": "Benutzer mit ID \"<%= userId %>\" nicht gefunden", "userHasNoLocalRegistration": "Benutzer ist lokal nicht registriert (Benutzername, E-Mail, Passwort).", - "uuidsMustBeAnArray": "User ID invites must be an array.", - "emailsMustBeAnArray": "Email address invites must be an array.", + "uuidsMustBeAnArray": "Benutzer-ID-Einladungen müssen ein Array sein.", + "emailsMustBeAnArray": "E-Mail-Adress-Einladungen müssen ein Array sein.", "canOnlyInviteMaxInvites": "Du kannst nur \"<%= maxInvites %>\" Benutzer gleichzeitig einladen", "onlyCreatorOrAdminCanDeleteChat": "Löschen der Nachricht nicht erlaubt!" } \ No newline at end of file diff --git a/common/locales/de/limited.json b/common/locales/de/limited.json index 76d0f4e8d1..5138b4eee0 100644 --- a/common/locales/de/limited.json +++ b/common/locales/de/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Nervige Freunde", "annoyingFriendsText": "Hat sich <%= snowballs %> Schneebälle von Gruppenmitgliedern eingefangen.", "alarmingFriends": "Unheimliche Freunde", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "Wurde <%= spookySparkles %> mal von Gruppenmitgliedern erschreckt.", "agriculturalFriends": "Gartenbegeisterte Freunde", "agriculturalFriendsText": "Wurde <%= seeds %> mal von Gruppenmitgliedern in eine Blume verwandelt.", "aquaticFriends": "Wasserliebende Freunde", diff --git a/common/locales/de/pets.json b/common/locales/de/pets.json index e502467914..ca77a54390 100644 --- a/common/locales/de/pets.json +++ b/common/locales/de/pets.json @@ -12,7 +12,7 @@ "etherealLion": "Ätherischer Löwe", "veteranWolf": "Veteranwolf", "veteranTiger": "Veterantiger", - "veteranLion": "Veteran Lion", + "veteranLion": "Veteranlöwe", "cerberusPup": "Zerberuswelpe", "hydra": "Hydra", "mantisShrimp": "Fangschreckenkrebs", diff --git a/common/locales/de/settings.json b/common/locales/de/settings.json index 9bc58f3684..32515261b3 100644 --- a/common/locales/de/settings.json +++ b/common/locales/de/settings.json @@ -47,7 +47,7 @@ "customDayStart": "Tageswechsel einstellen", "changeCustomDayStart": "Tageswechsel ändern?", "sureChangeCustomDayStart": "Willst Du Deinen Tageswechsel wirklich ändern?", - "customDayStartHasChanged": "Your custom day start has changed.", + "customDayStartHasChanged": "Dein persönlicher Tagesstart wurde geändert.", "nextCron": "Deine täglichen Aufgaben werden das nächste Mal überprüft und zurückgesetzt, wenn Du Habitica das erste Mal nach <%= time %> nutzt. Stelle sicher, dass Du Deine täglichen Aufgaben vor diesem Zeitpunkt erledigt hast.", "customDayStartInfo1": "Normalerweise werden Deine täglichen Aufgaben jeden Tag um Mitternacht Deiner Ortszeit von Habitica überprüft und zurückgesetzt. Du kannst diesen Zeitpunkt hier ändern.", "misc": "Verschiedenes", diff --git a/common/locales/de/spells.json b/common/locales/de/spells.json index 59a3ac5301..99c107fddf 100644 --- a/common/locales/de/spells.json +++ b/common/locales/de/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Wirf einen Schneeball auf ein Gruppenmitglied! Was kann dabei schon schief gehen? Dauert bis zum Tageswechsel des Gruppenmitglieds an.", "spellSpecialSaltText": "Salz", "spellSpecialSaltNotes": "Jemand hat Dich mit Schnee eingeseift. Haha, sehr lustig. Jetzt ist aber Schluss!", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Spukglitzer", + "spellSpecialSpookySparklesNotes": "Verwandle einen Freund in ein schwebendes Bettlaken mit Augen!", "spellSpecialOpaquePotionText": "Trank der Entgeisterung", "spellSpecialOpaquePotionNotes": "Beendet den Effekt von Spukglitzer.", "spellSpecialShinySeedText": "Schimmernde Saat", @@ -50,7 +50,7 @@ "spellNotFound": "Fähigkeit \"<%= spellId %>\" nicht gefunden.", "partyNotFound": "Gruppe nicht gefunden", "targetIdUUID": "\"targetId\" muss eine gültige Benutzer-ID sein.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "challengeTasksNoCast": "Fähigkeiten auf Wettbewerbsaufgaben anzuwenden ist nicht erlaubt.", "spellNotOwned": "Du besitzt diese Fähigkeit nicht.", "spellLevelTooHigh": "Du musst Level <%= level %> sein, um diese Fähigkeit zu benutzen." } \ No newline at end of file diff --git a/common/locales/de/subscriber.json b/common/locales/de/subscriber.json index 9efa500bbd..6ba2cef264 100644 --- a/common/locales/de/subscriber.json +++ b/common/locales/de/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Herzensbrecher-Set", "mysterySet201603": "Glücksklee-Set", "mysterySet201604": "Blattkrieger-Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk-Standard-Set", "mysterySet301405": "Steampunk-Zubehör-Set", "mysterySetwondercon": "Wondercon", @@ -131,7 +132,7 @@ "notAllowedHourglass": "Haustier/Reittier nicht zum Kauf mit mystischen Sanduhren verfügbar.", "readCard": "<%= cardType %> wurde gelesen", "cardTypeRequired": "Kartentyp erforderlich", - "cardTypeNotAllowed": "Unknown card type.", + "cardTypeNotAllowed": "Unbekannter Kartentyp.", "invalidCoupon": "Ungültiger Gutschein-Code", "couponUsed": "Gutschein wurde bereits eingelöst.", "noSudoAccess": "Du hast keine sudo-Rechte.", diff --git a/common/locales/de/tasks.json b/common/locales/de/tasks.json index a431a2bd17..a2507cdbab 100644 --- a/common/locales/de/tasks.json +++ b/common/locales/de/tasks.json @@ -73,7 +73,7 @@ "clearTags": "Aufräumen", "hideTags": "Verbergen", "showTags": "Anzeigen", - "toRequired": "You must supply a to value", + "toRequired": "Du musst einen gültigen Endwert eingeben", "startDate": "Starttermin", "startDateHelpTitle": "Wann soll diese Aufgabe beginnen?", "startDateHelp": "Setze das Datum fest, ab dem diese Aufgabe in Kraft tritt. Davor wird sie nicht fällig werden.", diff --git a/common/locales/en@pirate/contrib.json b/common/locales/en@pirate/contrib.json index d348b6b326..9e25a32d49 100644 --- a/common/locales/en@pirate/contrib.json +++ b/common/locales/en@pirate/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visit th' Hall 'o Captains (contributors 'n backers)", "conLearn": "Learn more 'bout contributor rewards", "conLearnHow": "Learn how t' contribute t' Habitica", - "surveysSingle": "Helped Habitica grow by fillin' out a survey. There be no active surveys.", - "surveysMultiple": "Helped Habitica grow by fillin' out <%= surveys %> surveys. There be no active surveys.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Current Survey", "surveyWhen": "The badge will be awarded t' all participants when surveys have been processed, in late March.", "blurbInbox": "This be where yer private messages be stored! Ye c'n send someone a message by clickin' on th' envelope icon next t' their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/en@pirate/gear.json b/common/locales/en@pirate/gear.json index 364b16cc1b..e3587155c4 100644 --- a/common/locales/en@pirate/gear.json +++ b/common/locales/en@pirate/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper an' dashing, wot! Don't benefit ye. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothin' Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat fer th' finest o' gentlefolk! January 3015 Subscriber Item. Don't benefit ye.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/en@pirate/subscriber.json b/common/locales/en@pirate/subscriber.json index 66cf091577..95166c4f72 100644 --- a/common/locales/en@pirate/subscriber.json +++ b/common/locales/en@pirate/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/en_GB/contrib.json b/common/locales/en_GB/contrib.json index 402cef1557..450ebf46a0 100644 --- a/common/locales/en_GB/contrib.json +++ b/common/locales/en_GB/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visit the Hall of Heroes (contributors and backers)", "conLearn": "Learn more about contributor rewards", "conLearnHow": "Learn how to contribute to Habitica", - "surveysSingle": "Helped Habitica grow by filling in a survey. There are no active surveys.", - "surveysMultiple": "Helped Habitica grow by filling in <%= surveys %> surveys. There are no active surveys.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Current Survey", "surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/en_GB/gear.json b/common/locales/en_GB/gear.json index cc8c1b4f35..2656c124d3 100644 --- a/common/locales/en_GB/gear.json +++ b/common/locales/en_GB/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armour o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothing Lunar Armour", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/en_GB/subscriber.json b/common/locales/en_GB/subscriber.json index fa4b5ae867..4beb0c8c48 100644 --- a/common/locales/en_GB/subscriber.json +++ b/common/locales/en_GB/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/es/character.json b/common/locales/es/character.json index 3a3c6c6c5e..c399625c9b 100644 --- a/common/locales/es/character.json +++ b/common/locales/es/character.json @@ -1,6 +1,6 @@ { "communityGuidelinesWarning": "Ten en cuenta que tu Nombre de Usuario, foto de perfil y sobre mí deben ajustarse a Normas de la Comunidad (p.ej. nada de obscenidades, temas adultos, insultos, etc.) Si tienes alguna pregunta sobre si algo es apropiado o no, ¡puedes mandar un correo electrónico a leslie@habitica.com!", - "statsAch": "Estadísticas y Logros", + "statsAch": "Estadísticas y logros", "profile": "Perfil", "avatar": "Personalizar avatar", "other": "Otro", diff --git a/common/locales/es/contrib.json b/common/locales/es/contrib.json index 17f01b1b1f..1a7398feea 100644 --- a/common/locales/es/contrib.json +++ b/common/locales/es/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visite el Salón de Héroes (colaboradores y patrocinadores)", "conLearn": "Aprenda más acerca de las recompensas de colaborador", "conLearnHow": "Aprenda cómo contribuir a Habitica", - "surveysSingle": "Ayudó al crecimiento de Habitica rellenando una encuesta. No hay encuestas activas.", - "surveysMultiple": "Ayudó al crecimiento de Habitica rellenando <%= surveys %> encuestas. No hay encuestas activas.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Encuesta Actual", "surveyWhen": "Este logro será concedido a todos los participantes cuando la encuesta haya sido procesada a finales de marzo.", "blurbInbox": "Aquí se guardan tus mensajes privados. Para enviar un mensaje a alguien, haz clic en el icono del sobre que aparece junto a su nombre en el chat de la Taberna, un grupo o un gremio. Si has recibido un mensaje privado inapropiado, envíale una captura de pantalla del mensaje a Lemoness (leslie@habitica.com).", diff --git a/common/locales/es/gear.json b/common/locales/es/gear.json index 7cd6a32b57..2e0953745b 100644 --- a/common/locales/es/gear.json +++ b/common/locales/es/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "¡Este traje está cosido de miles de tréboles de cuatro hojas! No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Traje Steampunk", "armorMystery301404Notes": "¡Sofisticado y elegante! No otorga ningún beneficio. Artículo de suscriptor de febrero 3015.", "armorArmoireLunarArmorText": "Armadura lunar reconfortante", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Esta galera es un amuleto mágico para la buena suerte. No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Sombrero de copa sofisticado", "headMystery301404Notes": "¡Un sofisticado sombrero de copa solo para los más refinados caballeros! No otorga ningún beneficio. Artículo de Suscriptor de Enero del 3015", "headMystery301405Text": "Sombrero de copa básico", diff --git a/common/locales/es/limited.json b/common/locales/es/limited.json index f031e52134..85611b5873 100644 --- a/common/locales/es/limited.json +++ b/common/locales/es/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amigos Molestos", "annoyingFriendsText": "Tus compañeros te han lanzado <%= snowballs %> bolas de nieve.", "alarmingFriends": "Amigos Alarmantes", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "Tus compañeros de grupo te han asustado <%= spookySparkles %> veces.", "agriculturalFriends": "Amigos Agrícolas", "agriculturalFriendsText": "Tus compañeros te han transformado en flor <%= seeds %> veces.", "aquaticFriends": "Amigos Acuáticos", diff --git a/common/locales/es/pets.json b/common/locales/es/pets.json index c9a4ae19bf..6777aa57b9 100644 --- a/common/locales/es/pets.json +++ b/common/locales/es/pets.json @@ -12,7 +12,7 @@ "etherealLion": "León Etéreo", "veteranWolf": "Lobo Veterano", "veteranTiger": "Tigre veterano", - "veteranLion": "Veteran Lion", + "veteranLion": "León veterano", "cerberusPup": "Cachorro de Cerbero", "hydra": "Hidra", "mantisShrimp": "Mantis marina", diff --git a/common/locales/es/quests.json b/common/locales/es/quests.json index 8f933d1f67..f555a731cb 100644 --- a/common/locales/es/quests.json +++ b/common/locales/es/quests.json @@ -79,23 +79,23 @@ "getMoreQuests": "Conseguir más misiones", "unlockedAQuest": "¡Has desbloqueado una misión!", "leveledUpReceivedQuest": "¡Has subido a Nivel <%= level %> y has recibido un pergamino de misión!", - "questInvitationDoesNotExist": "No quest invitation has been sent out yet.", - "questInviteNotFound": "No quest invitation found.", - "guildQuestsNotSupported": "Guilds cannot be invited on quests.", - "questNotFound": "Quest \"<%= key %>\" not found.", - "questNotOwned": "You don't own that quest scroll.", - "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.", - "questLevelTooHigh": "You must be level <%= level %> to begin this quest.", - "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.", - "questAlreadyAccepted": "You already accepted the quest invitation.", - "noActiveQuestToLeave": "No active quest to leave", - "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", - "notPartOfQuest": "You are not part of the quest", - "noActiveQuestToAbort": "There is no active quest to abort.", - "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.", - "questAlreadyRejected": "You already rejected the quest invitation.", - "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.", - "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.", - "questNotPending": "There is no quest to start.", - "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest" + "questInvitationDoesNotExist": "No se ha enviado invitación de misión todavía.", + "questInviteNotFound": "Invitación a misión no encontrada.", + "guildQuestsNotSupported": "Los gremios no pueden ser invitados a las misiones.", + "questNotFound": "Misión \"<%= key %>\" no encontrada.", + "questNotOwned": "No posees ese pergamino de misión.", + "questNotGoldPurchasable": "La misión \"<%= key %>\" no es una misión por oro.", + "questLevelTooHigh": "Debes ser nivel <%= level %> para empezar esta misión", + "questAlreadyUnderway": "Tu grupo ya está en una misión. Inténtalo de nuevo cuando la misión haya terminado.", + "questAlreadyAccepted": "Ya has aceptado la invitación de misión", + "noActiveQuestToLeave": "No hay misión activa de la que salir.", + "questLeaderCannotLeaveQuest": "El líder de la misión no puede salir de la misión.", + "notPartOfQuest": "No formas parte de la misión.", + "noActiveQuestToAbort": "No hay misión activa que abortar.", + "onlyLeaderAbortQuest": "Sólo el líder de grupo o misión puede abortar una misión.", + "questAlreadyRejected": "Ya has declinado la invitación de misión.", + "cantCancelActiveQuest": "No puedes cancelar una misión activa, usa la función de abortar.", + "onlyLeaderCancelQuest": "Sólo el líder de grupo o misión puede cancelar la misión.", + "questNotPending": "No hay misión que empezar.", + "questOrGroupLeaderOnlyStartQuest": "Sólo el líder de grupo o misión puede forzar empezar la misión." } \ No newline at end of file diff --git a/common/locales/es/questscontent.json b/common/locales/es/questscontent.json index 4a7ed0639f..37789c944a 100644 --- a/common/locales/es/questscontent.json +++ b/common/locales/es/questscontent.json @@ -315,10 +315,10 @@ "questFalconBoss": "Aves de la Procrastinación", "questFalconDropFalconEgg": "Halcón (Huevo)", "questFalconUnlockText": "Desbloquea huevos de Halcón adquiribles en el Mercado", - "questTreelingText": "The Tangle Tree", - "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.", - "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"", - "questTreelingBoss": "Tangle Tree", - "questTreelingDropTreelingEgg": "Treeling (Egg)", - "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market" + "questTreelingText": "La Enredadera", + "questTreelingNotes": "Es la Competición Anual de Jardines, y todo el mundo está hablando del misterioso proyecto que @aurakami ha prometido revelar. Te unes a la muchedumbre el día del gran anuncio, y te maravillas con la presentación de un árbol viviente. @fuzzytrees explica que el árbol ayudará con el cuidado del jardín, mostrando cómo puede cortar el césped, recortar los setos y podar las rosas al mismo tiempo... Hasta que el árbol se vuelve loco, dirigiendo sus podaderas a su creador. El público entra en pánico y todo el mundo intenta escapar, pero tú no tienes miedo... Te acercas de un salto, preparado para luchar.", + "questTreelingCompletion": "Te sacudes el polvo mientras las últimas hojas caen al suelo. A pesar del revuelo, la Competición de Jardines está a salvo ahora... ¡aunque el árbol que acabas de dejar hecho astillas no parece que vaya a ganar muchos premios! \"Aun quedan detalles por ultimar,\" dice @PainterProphet. \"Quizás otra persona entrenaría mejor a los pimpollos. ¿Quieres probar?\"", + "questTreelingBoss": "Enredadera", + "questTreelingDropTreelingEgg": "Brote (Huevo)", + "questTreelingUnlockText": "Desbloquea la compra de huevos de Brote en el Mercado" } \ No newline at end of file diff --git a/common/locales/es/subscriber.json b/common/locales/es/subscriber.json index 4c07e05c64..31ba696a9f 100644 --- a/common/locales/es/subscriber.json +++ b/common/locales/es/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Conjunto de Rompecorazones", "mysterySet201603": "Juego del Trébol afortunado", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "El Conjunto Steampunk", "mysterySet301405": "Accesorios Steampunk", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/es_419/contrib.json b/common/locales/es_419/contrib.json index 1821b6e02e..26a34553c7 100644 --- a/common/locales/es_419/contrib.json +++ b/common/locales/es_419/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visita el Salón de héroes (colaboradores y patrocinadores)", "conLearn": "Aprende más acerca de las recompensas de colaborador", "conLearnHow": "Aprende cómo contribuir a Habitica", - "surveysSingle": "Ayudó a mejorar Habitica llenando una encuesta. No hay encuestas activas.", - "surveysMultiple": "Ayudó a mejorar Habitica llenando <%= surveys %> encuestas. No hay encuestas activas.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Encuesta actual", "surveyWhen": "La medalla será otorgada a todos los participantes cuando las encuestas sean procesadas, a finales de Marzo.", "blurbInbox": "¡Aquí es donde se guardan tus mensajes privados! Puedes enviarle un mensaje a alguien simplemente haciendo clic en el sobre al lado de su nombre en el chat de la Taberna, Equipo o Gremio. Si has recibido un MP inapropiado, deberías enviar una captura de pantalla a Lemoness (leslie@habitica.com)", diff --git a/common/locales/es_419/gear.json b/common/locales/es_419/gear.json index 80c4c0de6f..69bd990d1a 100644 --- a/common/locales/es_419/gear.json +++ b/common/locales/es_419/gear.json @@ -366,8 +366,10 @@ "armorMystery201512Notes": "¡Invoca a las heladas llamas invernales! No otorga ningún beneficio. Artículo de Suscriptor de Diciembre 2015.", "armorMystery201603Text": "Traje de la Suerte", "armorMystery201603Notes": "¡Este traje está hecho de miles de tréboles de cuatro hojas! No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.", - "armorMystery201604Text": "Armor o' Leaves", + "armorMystery201604Text": "Armadura de Hojas", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Traje Steampunk", "armorMystery301404Notes": "¡Sofisticado y elegante! No otorga ningún beneficio. Artículo de Suscriptor de Febrero 3015.", "armorArmoireLunarArmorText": "Armadura Lunar Relajante", @@ -398,7 +400,7 @@ "armorArmoireMinerOverallsNotes": "Puede que parezcan usados, pero están encantados para repeler mugre. Incrementan la Constitución por <%= con %>. Armario Encantado: Conjunto de Minero (Artículo 2 de 3).", "armorArmoireBasicArcherArmorText": "Armadura Básica de Arquero", "armorArmoireBasicArcherArmorNotes": "Este chaleco de camuflaje te permite pasar desapercibido en los bosques. Incrementa la Percepción por <%= per %>. Armario Encantado: Conjunto Básico de Arquero (Artículo 2 de 3).", - "armorArmoireGraduateRobeText": "Graduate Robe", + "armorArmoireGraduateRobeText": "Túnica de graduado", "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).", "headgear": "equipamiento para la cabeza", "headBase0Text": "Sin yelmo", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Esta galera es un amuleto mágico para la buena suerte. No otorga ningún beneficio. Artículo de Suscriptor de Marzo 2016.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Galera Elegante", "headMystery301404Notes": "¡Una galera elegante para los señores más sofisticados! Artículo de Suscriptor de Enero 3015. No otorga ningún beneficio.", "headMystery301405Text": "Galera Básica", diff --git a/common/locales/es_419/subscriber.json b/common/locales/es_419/subscriber.json index db8b583c47..4952211ebe 100644 --- a/common/locales/es_419/subscriber.json +++ b/common/locales/es_419/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Conjunto de Rompecorazones", "mysterySet201603": "Conjunto de Trébol de la Suerte", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Conjunto Steampunk Estándar ", "mysterySet301405": "Conjunto de Accesorios Steampunk", "mysterySetwondercon": "WonderCon", diff --git a/common/locales/fr/contrib.json b/common/locales/fr/contrib.json index 8248057861..dfffda3394 100644 --- a/common/locales/fr/contrib.json +++ b/common/locales/fr/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visitez le Panthéon des Héros (contributeurs et soutiens)", "conLearn": "En savoir plus à propos des récompenses des contributeurs", "conLearnHow": "En savoir plus sur les façons de contribuer à Habitica", - "surveysSingle": "A aidé Habitica à croître en participant à une enquête. Il n'y a actuellement pas d'enquête en cours.", - "surveysMultiple": "A aidé Habitica à croître en participant à <%= surveys %> enquêtes. Il n'y a actuellement pas d'enquête en cours.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Enquête Actuelle", "surveyWhen": "Le badge sera attribué à tou·te·s les participant·e·s de l'enquête lorsque celle-ci aura été traitée, fin Mars.", "blurbInbox": "Voici où sont stockés vos messages privés ! Vous pouvez envoyer un message à quelqu'un en cliquant sur l'icône en forme d'enveloppe à côté de leur nom dans l'espace de discution de la Taverne, de l'Équipe ou de la Guilde. Si vous avez reçu un message inapproprié, vous pouvez en envoyer une capture d'écran par mail à Lemoness (leslie@habitica.com)", diff --git a/common/locales/fr/gear.json b/common/locales/fr/gear.json index 790a9e9374..444d3dc980 100644 --- a/common/locales/fr/gear.json +++ b/common/locales/fr/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Ce costume est cousu avec des milliers de trèfles à quatre feuilles ! N'apporte aucun bonus. Équipement d'abonné de mars 2016.", "armorMystery201604Text": "Armure de feuilles", "armorMystery201604Notes": "Vous aussi, vous pouvez être un petit mais effroyable bouquet de feuilles. Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Tenue Steampunk", "armorMystery301404Notes": "Pimpant et fringuant ! N'apporte aucun bonus. Équipement d'abonné·e de février 3015.", "armorArmoireLunarArmorText": "Armure Lunaire Apaisante", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Ce haut-de-forme est un porte-bonheur magique. N'apporte aucun bonus. Équipement d'abonné de mars 2016.", "headMystery201604Text": "Couronne de fleurs", "headMystery201604Notes": "Ces fleurs tressées font un casque étonnamment résistant ! Ne confère aucun bonus. Équipement d'abonné·e d'Avril 2016.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Haut-de-forme Fantaisiste", "headMystery301404Notes": "Un couvre-chef fantaisiste pour les gens de bonne famille les plus élégants ! N'apporte aucun bonus. Équipement d'abonné·e de janvier 3015.", "headMystery301405Text": "Haut-de-forme Classique", diff --git a/common/locales/fr/groups.json b/common/locales/fr/groups.json index 9c9a31031c..f00d472438 100644 --- a/common/locales/fr/groups.json +++ b/common/locales/fr/groups.json @@ -182,8 +182,8 @@ "userAlreadyInAParty": "Utilisateur déjà dans une équipe.", "userWithIDNotFound": "Utilisateur avec l'ID \"<%= userId %>\" non trouvé.", "userHasNoLocalRegistration": "L'utilisateur n'a pas un enregistrement local (nom d'utilisateur, courriel, mot de passe).", - "uuidsMustBeAnArray": "User ID invites must be an array.", - "emailsMustBeAnArray": "Email address invites must be an array.", + "uuidsMustBeAnArray": "Les ID d'utilisateurs des invitations doivent être un tableau.", + "emailsMustBeAnArray": "Les emails des invitations doivent être un tableau.", "canOnlyInviteMaxInvites": "Vous ne pouvez envoyer que \"<%= maxInvites %>\" invitations à la fois", "onlyCreatorOrAdminCanDeleteChat": "Vous n'êtes pas autorisé à supprimer ce message !" } \ No newline at end of file diff --git a/common/locales/fr/limited.json b/common/locales/fr/limited.json index 3df9d2a08f..e7712af012 100644 --- a/common/locales/fr/limited.json +++ b/common/locales/fr/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amis Pénibles", "annoyingFriendsText": "A été touché·e <%= snowballs %> fois par des boules de neige lancées par un membre de l'équipe.", "alarmingFriends": "Amis Inquiétants", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "A été effrayé·e <%= spookySparkles %> fois par des membres de l'équipe.", "agriculturalFriends": "Amis Agricoles", "agriculturalFriendsText": "A été transformé·e en fleur <%= seeds %> fois par un membre de son équipe.", "aquaticFriends": "Amis Aquatiques", diff --git a/common/locales/fr/pets.json b/common/locales/fr/pets.json index 23bc3865d5..42e3de96e1 100644 --- a/common/locales/fr/pets.json +++ b/common/locales/fr/pets.json @@ -12,7 +12,7 @@ "etherealLion": "Lion Éthéré", "veteranWolf": "Loup de Vétéran", "veteranTiger": "Tigre de Vétéran", - "veteranLion": "Veteran Lion", + "veteranLion": "Lion vétéran", "cerberusPup": "Chiot Cerbère", "hydra": "Hydre", "mantisShrimp": "Crevette-mante", diff --git a/common/locales/fr/settings.json b/common/locales/fr/settings.json index 67fe35a8dc..2b15a37a27 100644 --- a/common/locales/fr/settings.json +++ b/common/locales/fr/settings.json @@ -47,7 +47,7 @@ "customDayStart": "Heure personnalisée de début de journée", "changeCustomDayStart": "Changer l'Heure personnalisée de début de journée ?", "sureChangeCustomDayStart": "Êtes-vous sûr•e de vouloir changer votre heure personnalisée de début de journée ?", - "customDayStartHasChanged": "Your custom day start has changed.", + "customDayStartHasChanged": "L'heure personnalisée du début de votre journée a changé.", "nextCron": "Vos tâches Quotidiennes seront réinitialisées la prochaine fois que vous utiliserez Habitica après <%= time %>. Faites attention à compléter vos tâches Quotidiennes avant cette heure !", "customDayStartInfo1": "Par défaut, Habitica vérifie et réinitialise vos tâches Quotidiennes à minuit dans votre fuseau horaire. Vous pouvez personnaliser cette heure ici.", "misc": "Divers", diff --git a/common/locales/fr/spells.json b/common/locales/fr/spells.json index 20a82d3b5a..757c86f89a 100644 --- a/common/locales/fr/spells.json +++ b/common/locales/fr/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Jetez une boule de neige à un membre de votre équipe ! Qu'est-ce qui pourrait mal tourner ? L'effet dure jusqu'à la fin du jour pour ce membre.", "spellSpecialSaltText": "Sel", "spellSpecialSaltNotes": "Quelqu'un vous a envoyé une boule de neige. Ha, ha, ha, très drôle. Enlevez-moi cette neige de là, maintenant !", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Etincelles effrayantes", + "spellSpecialSpookySparklesNotes": "Changez un·e ami·e en couverture flottante avec des yeux !", "spellSpecialOpaquePotionText": "Potion Opaque", "spellSpecialOpaquePotionNotes": "Annule les effets de la Poudre Poltergeist.", "spellSpecialShinySeedText": "Graine Brillante", @@ -50,7 +50,7 @@ "spellNotFound": "Compétence \"<%= spellId %>\" introuvable.", "partyNotFound": "Équipe non trouvée.", "targetIdUUID": "\"targetId\" doit être un nom d'utilisateur valide.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "challengeTasksNoCast": "Utiliser une compétence sur des tâches de défis est interdit.", "spellNotOwned": "Vous ne possédez pas cette compétence.", "spellLevelTooHigh": "Vous devez être au niveau <%= level %> pour utiliser cette compétence." } \ No newline at end of file diff --git a/common/locales/fr/subscriber.json b/common/locales/fr/subscriber.json index 1b9b8f7b6b..5109a67816 100644 --- a/common/locales/fr/subscriber.json +++ b/common/locales/fr/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Ensemble du Bourreau des Cœurs ", "mysterySet201603": "Ensemble du Trèfle à Quatre Feuilles", "mysterySet201604": "Ensemble du Guerrier Feuille", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Ensemble Steampunk de Base", "mysterySet301405": "Ensemble D'accessoire Steampunk", "mysterySetwondercon": "Wondercon", @@ -131,7 +132,7 @@ "notAllowedHourglass": "Familier/monture non disponible pour l'achat avec un Sablier Mystique.", "readCard": "<%= cardType %> a été lue", "cardTypeRequired": "Type de carte requis", - "cardTypeNotAllowed": "Unknown card type.", + "cardTypeNotAllowed": "Type de carte inconnu.", "invalidCoupon": "Code du coupon invalide.", "couponUsed": "Code du coupon déjà utilisé.", "noSudoAccess": "Vous n'avez pas d'accès sudo.", diff --git a/common/locales/fr/tasks.json b/common/locales/fr/tasks.json index 95777dcd2e..48230d7179 100644 --- a/common/locales/fr/tasks.json +++ b/common/locales/fr/tasks.json @@ -73,7 +73,7 @@ "clearTags": "Nettoyer", "hideTags": "Masquer", "showTags": "Afficher", - "toRequired": "You must supply a to value", + "toRequired": "Vous devez fournir une valeur to", "startDate": "Date de début", "startDateHelpTitle": "Quand cette tâche devrait-elle démarrer ?", "startDateHelp": "Définissez la date à laquelle cette tâche prendra effet. Elle ne sera pas active avant cette date.", diff --git a/common/locales/he/contrib.json b/common/locales/he/contrib.json index fc3e75ba62..3ce26f2995 100644 --- a/common/locales/he/contrib.json +++ b/common/locales/he/contrib.json @@ -35,12 +35,12 @@ "hallContributors": "היכל התורמים", "hallPatrons": "היכל נותני החסות", "rewardUser": "הענק פרס למשתמש", - "UUID": "User ID", + "UUID": "מזהה משתמש", "loadUser": "טען משתמש", - "noAdminAccess": "You don't have admin access.", - "pageMustBeNumber": "req.query.page must be a number", - "userNotFound": "User not found.", - "invalidUUID": "UUID must be valid", + "noAdminAccess": "אין לכם הרשאות מנהל.", + "pageMustBeNumber": "req.query.page חייב להיות מספר", + "userNotFound": "משתמש לא נמצא.", + "invalidUUID": "UUID חייב להיות בתוקף", "title": "כותרת", "moreDetails": "פרטים נוספים (1-7)", "moreDetails2": "פרטים נוספים (8-9)", @@ -56,8 +56,8 @@ "visitHeroes": "בקר בהיכל הגיבורים (תורמים ונותני גיבוי)", "conLearn": "למד עוד אודות פרסי תורמים", "conLearnHow": "למד כיצד לתרום ל-Habitica", - "surveysSingle": "עזר ל- Habitica לגדול ע״י מילוי שאלון. אין שאלונים פעילים.", - "surveysMultiple": "עזר ל- Habitica לגדול ע״י מילוי <%= surveys %> שאלונים. אין שאלונים פעילים.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "שאלון נוכחי", "surveyWhen": "התג ינתן כפרס לכל ממלאי השאלון בסוף מרץ, לאחר שעיבוד נתוני השאלונים יסתיים.", "blurbInbox": "כאן נשמרות הודעותיכם הפרטיות! תוכלו לשלוח למישהו הודעה על ידי לחיצה על תמונת המעטפה ליד שמם בפונדק, בחבורה, או בשיחת גילדה. אם קיבלתם הודעה פרטית שהיא אינה ראוייה, עליכם לשלוח תמונת מסך שלה ללמונס (leslie@habitica.com)", diff --git a/common/locales/he/death.json b/common/locales/he/death.json index ad5082a730..82dbf1f482 100644 --- a/common/locales/he/death.json +++ b/common/locales/he/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "מטלות יומיות אשר לא הושלמו יפגעו בכם במשך הלילה, הזהרו שלא להתחיל עם יותר מידי!", "lowHealthTips4": "אם משימה יומית לא בתוקף ביום מסויים, תוכלו להשביתה על ידי לחיצה על העפרון.", "goodLuck": "בהצלחה!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "לא ניתן להחיות מבלי שיהיו מתים" } \ No newline at end of file diff --git a/common/locales/he/gear.json b/common/locales/he/gear.json index 3f0cfb637f..2c2e748282 100644 --- a/common/locales/he/gear.json +++ b/common/locales/he/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "חליפה זו ארוגה מאלפי תלתנים בעלי ארבע עלים! לא מקנה ייתרון. מרץ 2016, חפץ מנויים.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "חליפת סטימפאנק", "armorMystery301404Notes": "נאה ונמרץ, אה! לא מקנה ייתרון. פברואר 3015, חפץ מנויים.", "armorArmoireLunarArmorText": "שריון ירח מרגיע", @@ -573,6 +575,8 @@ "headMystery201603Notes": "כובע זה הוא קמע מזל קסום. לא מקנה ייתרון. מרץ 2016, חפץ מנויים.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "כובע ראש מפואר", "headMystery301404Notes": "כובע ראש מפואר למכובד שבג׳נטלמנים! ינואר 3015, חפץ מנויים. לא מקנה ייתרון.", "headMystery301405Text": "כובע ראש בסיסי", diff --git a/common/locales/he/limited.json b/common/locales/he/limited.json index c3532c1029..f8e0b66e0e 100644 --- a/common/locales/he/limited.json +++ b/common/locales/he/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "חברים מרגיזים", "annoyingFriendsText": "חטף <%= snowballs %> כדורי שלג לפרצוף משאר החבורה.", "alarmingFriends": "חברים מפחידים", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "חברי חבורה הבהילו אתכם <%= spookySparkles %> פעמים.", "agriculturalFriends": "חברים חקלאיים", "agriculturalFriendsText": "הוחלף לפרח <%= seeds %> פעמים על-ידי חברי חבורה.", "aquaticFriends": "חברים ימיים", @@ -73,5 +73,5 @@ "sneakySqueakerSet": "חורק מתגנב (נוכל)", "fallEventAvailability": "זמין עד ה 31 באוקטובר", "winterEventAvailability": "זמין עד ה 31 בדצמבר", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "זמין עד ה 31 במאי" } \ No newline at end of file diff --git a/common/locales/he/questscontent.json b/common/locales/he/questscontent.json index 3ad2af19ce..840037dfcc 100644 --- a/common/locales/he/questscontent.json +++ b/common/locales/he/questscontent.json @@ -315,8 +315,8 @@ "questFalconBoss": "ציפורי רצחיינות", "questFalconDropFalconEgg": "פאלקון (ביצה)", "questFalconUnlockText": "משחרר ביצי פאלקון לרכישה בשוק", - "questTreelingText": "The Tangle Tree", - "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.", + "questTreelingText": "עץ ההסתבכות", + "questTreelingNotes": "זוהי תחרות הגן השנתית, וכולם מדברים על הפרוייקט המסתורי ש @aurakami הבטיח לחשוף. אתם מצטרפים לקהל ביום ההכרזה, ומשתאים למראה עץ מתנועע. @fuzzytrees מסביר כיצד העץ יסייע בתחזוקת הגן, ומראה איך הוא יכול לכסח את הדשא, לקצץ את הגדר ולגזום את הורדים בעת ובעונה אחת - עד שלפתע העץ משתולל, ומפנה את מזמרותיו לעבר היוצר שלו! הקהל נכנס לפאניקה כשכולם בורחים מהמקום, אבל אתם לא מפחדים - מזנקים קדימה, ומכנים אלי קרב.", "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"", "questTreelingBoss": "Tangle Tree", "questTreelingDropTreelingEgg": "Treeling (Egg)", diff --git a/common/locales/he/subscriber.json b/common/locales/he/subscriber.json index 9616808714..9314b41574 100644 --- a/common/locales/he/subscriber.json +++ b/common/locales/he/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "סט שוברי לבבות", "mysterySet201603": "סט תלתן מזל", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "סט סטימפאנק רגיל", "mysterySet301405": "סט סטימפאנק אקססוריז", "mysterySetwondercon": "וונדרקון", diff --git a/common/locales/hu/contrib.json b/common/locales/hu/contrib.json index 0f6235b1d1..b7e63a83ff 100644 --- a/common/locales/hu/contrib.json +++ b/common/locales/hu/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Látogasd meg a Hősök Csarnokát (közreműködők és támogatók)", "conLearn": "Tudj meg többet a támogatói jutalmakról", "conLearnHow": "Ismerd meg hogyan tudod támogatni a Habitica-t", - "surveysSingle": "Egy kérdőív kitöltésével segítette a Habitica-t. Jelenleg nincs aktív kérdőív.", - "surveysMultiple": "A <%= surveys %> kérdőív kitöltésével segítette a Habitica-t. Jelenleg nincs aktív kérdőív.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Jelenlegi kérdőív", "surveyWhen": "A kitűzőt minden résztvevő megkapja, amint a kérdőívek feldolgozásra kerültek; késő márciusban.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/hu/gear.json b/common/locales/hu/gear.json index 09758ad291..f3a25506f1 100644 --- a/common/locales/hu/gear.json +++ b/common/locales/hu/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk öltözet", "armorMystery301404Notes": "Jól vasalt és lenyűgöző, mi! Nem ad semmi előnyt. 3015 februári előfizetői tárgy.", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Elegáns Cilinder", "headMystery301404Notes": "Egy elegáns cilinder a legnemesebb előkelőségeknek! 3015 januári előfizetői tárgy. Nem ad semmi előnyt.", "headMystery301405Text": "Egyszerű Cilinder", diff --git a/common/locales/hu/subscriber.json b/common/locales/hu/subscriber.json index 22116bf6c2..b8656c951a 100644 --- a/common/locales/hu/subscriber.json +++ b/common/locales/hu/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/it/backgrounds.json b/common/locales/it/backgrounds.json index 072b34ef23..3554fbb71d 100644 --- a/common/locales/it/backgrounds.json +++ b/common/locales/it/backgrounds.json @@ -163,7 +163,7 @@ "backgroundRainbowsEndNotes": "Trova l'oro alla fine dell'arcobaleno!", "backgrounds052016": "SERIE 24: Maggio 2016", "backgroundBeehiveText": "Alveare", - "backgroundBeehiveNotes": "Ronza e danza nell'Alveare.", + "backgroundBeehiveNotes": "Ronza e danza in un alveare.", "backgroundGazeboText": "Gazebo", "backgroundGazeboNotes": "Combatti in un Gazebo.", "backgroundTreeRootsText": "Radici dell'Albero", diff --git a/common/locales/it/contrib.json b/common/locales/it/contrib.json index 2257245545..bcc1ed8c48 100644 --- a/common/locales/it/contrib.json +++ b/common/locales/it/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Visita il Salone degli Eroi (sostenitori)", "conLearn": "Maggiori informazioni sulle ricompense per i sostenitori.", "conLearnHow": "Scopri come contribuire a migliorare Habitica", - "surveysSingle": "Ha aiutato Habitica a crescere, compilando un questionario. Al momento non ci sono questionari attivi.", - "surveysMultiple": "Ha aiutato Habitica a crescere, compilando <%= surveys %> questionari. Al momento non ci sono questionari attivi.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Questionario in corso", "surveyWhen": "Questa onoreficenza verrà conferita a tutti i partecipanti, non appena i risultati dei questionari saranno elaborati, verso fine marzo.", "blurbInbox": "Qui vengono conservati i tuoi messaggi privati! Puoi inviare un messaggio a qualcuno cliccando sull'icona a forma di \"busta da lettere\" accanto al suo nome nella Taverna, nella Squadra o nella chat di una Gilda. Se hai ricevuto un messaggio privato dai contenuti inappropriati, dovresti inviare un'email a Lemoness (leslie@habitica.com) allegando uno screenshot.", diff --git a/common/locales/it/death.json b/common/locales/it/death.json index 11121a58bd..366d1c2912 100644 --- a/common/locales/it/death.json +++ b/common/locales/it/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "Le Daily incomplete ti danneggiano a fine giornata, quindi fai attenzione a non aggiungerne troppe all'inizio!", "lowHealthTips4": "Se una Giornaliera non va completata in un certo giorno, puoi disattivarla cliccando sull'icona a forma di matita.", "goodLuck": "Buona fortuna!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "Impossibile rianimare se non sei morto" } \ No newline at end of file diff --git a/common/locales/it/gear.json b/common/locales/it/gear.json index 4241275894..76f379265f 100644 --- a/common/locales/it/gear.json +++ b/common/locales/it/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Questo completo è cucito con migliaia di quadrifogli! Non conferisce benefici. Oggetto per abbonati, marzo 2016.", "armorMystery201604Text": "Armatura di Foglie", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Completo Steampunk", "armorMystery301404Notes": "Raffinato, a dir poco impeccabile! Non conferisce alcun bonus. Oggetto per abbonati, febbraio 3015.", "armorArmoireLunarArmorText": "Armatura Lunare Lenitiva", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Questo cappello è un amuleto magico portafortuna. Non conferisce alcun bonus. Oggetto per abbonati, marzo 2016.", "headMystery201604Text": "Corona di Fiori", "headMystery201604Notes": "Questi fiori intrecciati formano un elmo sorprendentemente resistente! Non conferisce alcun bonus. Oggetto per abbonati, aprile 2016.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Cilindro Elegante", "headMystery301404Notes": "Un cilindro per i più fini gentiluomini! Oggetto per abbonati, gennaio 3015. Non conferisce alcun bonus.", "headMystery301405Text": "Cilindro Base", diff --git a/common/locales/it/groups.json b/common/locales/it/groups.json index 8609f0b3d6..0ca9f8aba1 100644 --- a/common/locales/it/groups.json +++ b/common/locales/it/groups.json @@ -102,7 +102,7 @@ "inbox": "Messaggi", "messageRequired": "Un messaggio è richiesto.", "toUserIDRequired": "Un ID Utente è richesto", - "gemAmountRequired": "A number of gems is required", + "gemAmountRequired": "Un numero di gemme è richiesto", "notAuthorizedToSendMessageToThisUser": "Non puoi inviare un messaggio a questo utente.", "privateMessageGiftIntro": "Ciao <%= receiverName %>, <%= senderName %> ti ha inviato", "privateMessageGiftGemsMessage": "<%= gemAmount %> gemme!", @@ -162,26 +162,26 @@ "partyOnName": "Party On", "partyUpAchievement": "Ti sei unito ad una Squadra con un'altra persona! Divertiti a combattere mostri e a sostenervi a vicenda.", "partyOnAchievement": "Ti sei unito ad una Squadra con almeno quattro persone! Goditi la tua maggiore responsabilità mentre ti unisci con i tuoi amici per sconfiggere i tuoi nemici!", - "groupIdRequired": "\"groupId\" must be a valid UUID", + "groupIdRequired": "\"groupId\" deve essere un UUID valido.", "groupNotFound": "Gruppo non trovato.", "groupTypesRequired": "You must supply a valid \"type\" query string.", "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", - "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", + "cannotLeaveWhileActiveQuest": "Non puoi lasciare la squadra durante una missione. Per favore prima abbandona la missione.", "onlyLeaderCanRemoveMember": "Only group leader can remove a member!", - "memberCannotRemoveYourself": "You cannot remove yourself!", - "groupMemberNotFound": "User not found among group's members", - "mustBeGroupMember": "Must be member of the group.", + "memberCannotRemoveYourself": "Non puoi rimuovere te stesso!", + "groupMemberNotFound": "Utente non trovato tra i membri del gruppo.", + "mustBeGroupMember": "Deve essere membro del gruppo.", "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", - "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", - "inviteMissingEmail": "Missing email address in invite.", - "partyMustbePrivate": "Parties must be private", - "userAlreadyInGroup": "User already in that group.", - "userAlreadyInvitedToGroup": "User already invited to that group.", + "canOnlyInviteEmailUuid": "E' possibile invitare solo con uuid o e-mail.", + "inviteMissingEmail": "Indirizzo e-mail mancante nell'invito.", + "partyMustbePrivate": "Le squadre devono essere private", + "userAlreadyInGroup": "Utente già parte di quel gruppo.", + "userAlreadyInvitedToGroup": "Utente già invitato in quel gruppo.", "userAlreadyPendingInvitation": "User already pending invitation.", - "userAlreadyInAParty": "User already in a party.", - "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", - "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", + "userAlreadyInAParty": "Utente già in una squadra.", + "userWithIDNotFound": "Utente con id \"<%= userId %>\" non trovato.", + "userHasNoLocalRegistration": "L'utente non ha una registrazione locale (nome utente, e-mail, password).", "uuidsMustBeAnArray": "User ID invites must be an array.", "emailsMustBeAnArray": "Email address invites must be an array.", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", diff --git a/common/locales/it/limited.json b/common/locales/it/limited.json index 0b75f92002..0e577356bb 100644 --- a/common/locales/it/limited.json +++ b/common/locales/it/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "Amici Fastidiosi", "annoyingFriendsText": "I compagni di squadra gli hanno tirato <%= snowballs %> palle di neve.", "alarmingFriends": "Amici Inquietanti", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "E' stato spaventato <%= spookySparkles %> volte dai compagni di squadra.", "agriculturalFriends": "Amici Floreali", "agriculturalFriendsText": "E' stato trasformato in un fiore <%= seeds %> volte dai compagni di squadra.", "aquaticFriends": "Amici acquatici", diff --git a/common/locales/it/npc.json b/common/locales/it/npc.json index dcdd626791..8898c6b5d0 100644 --- a/common/locales/it/npc.json +++ b/common/locales/it/npc.json @@ -21,26 +21,26 @@ "ian": "Ian", "ianText": "Benvenuto nel Negozio delle Missioni! Qui puoi utilizzare le Pergamene delle missioni per combattere i mostri con i tuoi amici. Assicurati di controllare la nostra raffinata scelta di Pergamene delle missioni per l'acquisto a destra!", "ianBrokenText": "Benvenuto nel Negozio delle Missioni... Qui puoi utilizzare le Pergamene delle missioni per combattere i mostri con i tuoi amici... Assicurati di controllare la nostra raffinata scelta di Pergamene delle missioni per l'acquisto a destra...", - "missingKeyParam": "\"req.params.key\" is required.", + "missingKeyParam": "\"req.params.key\" è richiesto.", "itemNotFound": "Oggetto \"<%= key %>\" non trovato.", "cannotBuyItem": "Non puoi comprare questo oggetto.", - "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.", - "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.", - "invalidPetName": "Invalid pet name supplied.", - "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.", - "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.", - "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.", - "typeRequired": "Type is required", - "keyRequired": "Key is required", - "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]", - "contentKeyNotFound": "Key not found for Content <%= type %>", - "plusOneGem": "+1 Gem", - "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", - "userItemsKeyNotFound": "Key not found for user.items <%= type %>", - "pathRequired": "Path string is required", - "unlocked": "Items have been unlocked", - "alreadyUnlocked": "Full set already unlocked.", - "alreadyUnlockedPart": "Full set already partially unlocked.", + "missingTypeKeyEquip": "\"key\" e \"type\" sono parametri richiesti.", + "missingPetFoodFeed": "\"pet\" e \"food\" sono parametri richiesti.", + "invalidPetName": "Nome dell'animale non valido", + "missingEggHatchingPotionHatch": "\"egg\" e \"hatchingPotion\" sono parametri richiesti.", + "invalidTypeEquip": "\"type\" deve essere uno tra 'equipped', 'pet', 'mount', 'costume'.", + "mustPurchaseToSet": "Devi acquistare <%= val %> per impostare <%= key %>.", + "typeRequired": "E necessario scrivere", + "keyRequired": "E' richiesta una chiave", + "notAccteptedType": "E' necessario scrivere [uova, Pozioni schiusura, cibo, missioni, equipaggiamento]", + "contentKeyNotFound": "Non trovata la chiave per il Contenuto <%= type %>", + "plusOneGem": "+1 Gemma", + "typeNotSellable": "L'oggetto non è in vendita. deve essere uno dei seguenti <%= acceptedTypes %>", + "userItemsKeyNotFound": "Non trovata la chiave per gli oggetti.utente <%= type %>", + "pathRequired": "E' richiesta la stringa guida", + "unlocked": "Sono disponibili nuovi oggetti", + "alreadyUnlocked": "Set completo già sbloccato.", + "alreadyUnlockedPart": "Set completo già parzialmente sbloccato.", "USD": "(USD)", "newStuff": "Novità", "cool": "Ricordamelo più tardi", @@ -84,7 +84,7 @@ "tourPetsPage": "Questa è la Scuderia! Dopo il livello 3, puoi far nascere degli animali utilizzando uova e pozioni. Quando fai schiudere un uovo nel Mercato, apparirà qui! Fai click sull'immagine di un animale per aggiungerlo al tuo avatar. Dagli da mangiare il cibo che troverai dopo il livello 3, e crescerà fino a diventare una potente cavalcatura.", "tourMountsPage": "Una volta che avrai dato abbastanza cibo al tuo animale da trasformarlo in una cavalcatura, apparirà qui. (Animali, cavalcature e cibo sono disponibili dopo il livello 3.) Fai click su una cavalcatura per montare in sella!", "tourEquipmentPage": "Qui è dove il tuo Equipaggiamento è immagazzinato! Il tuo Equipaggiamento da battaglia influenza le tue statistiche. Se desideri mostrare un Equipaggiamento diverso sul tuo avatar senza modificare le tue statistiche, clicca \"Abilita Costume.\"", - "equipmentAlreadyOwned": "You already own that piece of equipment", + "equipmentAlreadyOwned": "Possiedi già quel pezzo di equipaggiamento", "tourOkay": "Ok!", "tourAwesome": "Fantastico!", "tourSplendid": "Splendido!", diff --git a/common/locales/it/pets.json b/common/locales/it/pets.json index e5143bf1c4..159b4bf145 100644 --- a/common/locales/it/pets.json +++ b/common/locales/it/pets.json @@ -12,7 +12,7 @@ "etherealLion": "Leone Etereo", "veteranWolf": "Lupo Veterano", "veteranTiger": "Tigre Veterana", - "veteranLion": "Veteran Lion", + "veteranLion": "Leone Veterano", "cerberusPup": "Cucciolo di Cerbero", "hydra": "Idra", "mantisShrimp": "Canocchia", diff --git a/common/locales/it/quests.json b/common/locales/it/quests.json index 597135bc0c..81612ac3ce 100644 --- a/common/locales/it/quests.json +++ b/common/locales/it/quests.json @@ -86,16 +86,16 @@ "questNotOwned": "Non possiedi questa pergamena.", "questNotGoldPurchasable": "La Missione \"<%= key %>\" non può essere comprata con Oro.", "questLevelTooHigh": "Devi essere almeno di livello <%= level %> per iniziare questa missione.", - "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.", + "questAlreadyUnderway": "La tua squadra è già in missione. Riprova quando la missione corrente è terminata.", "questAlreadyAccepted": "Hai già accettato l'invito alla missione.", "noActiveQuestToLeave": "Nessuna missione attiva da abbandonare", - "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", - "notPartOfQuest": "You are not part of the quest", + "questLeaderCannotLeaveQuest": "Il capo della missione non la può abbandonare", + "notPartOfQuest": "Non fai parte della missione", "noActiveQuestToAbort": "Non c'è una missione attiva da annullare.", - "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.", - "questAlreadyRejected": "You already rejected the quest invitation.", - "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.", - "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.", - "questNotPending": "There is no quest to start.", - "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest" + "onlyLeaderAbortQuest": "Solo il capo del gruppo o della missione può annullare una missione", + "questAlreadyRejected": "Hai già rifiutato l'invito alla missione", + "cantCancelActiveQuest": "Non è possibile annullare una ricerca attiva, utilizzare la funzionalità di interruzione ", + "onlyLeaderCancelQuest": "Solo il capo del gruppo o della missione può annullare una missione", + "questNotPending": "Non ci sono missioni da cominciare", + "questOrGroupLeaderOnlyStartQuest": "Solo il capo del gruppo o della missione può forzare l'inizio di una missione" } \ No newline at end of file diff --git a/common/locales/it/questscontent.json b/common/locales/it/questscontent.json index 3bfa426841..b18d03e525 100644 --- a/common/locales/it/questscontent.json +++ b/common/locales/it/questscontent.json @@ -315,10 +315,10 @@ "questFalconBoss": "Uccelli della Procrastinazione", "questFalconDropFalconEgg": "Falco (uovo)", "questFalconUnlockText": "Sblocca l'acquisto delle uova di falco nel Mercato", - "questTreelingText": "The Tangle Tree", - "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.", - "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"", - "questTreelingBoss": "Tangle Tree", - "questTreelingDropTreelingEgg": "Treeling (Egg)", - "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market" + "questTreelingText": "L' Albero Groviglio", + "questTreelingNotes": "E' il concorso annuale dei giardini, e tutti parlano del progetto misterioso che @aurakami ha promesso di svelare. Vi unite alla folla il giorno del grande annuncio, e ammirate la presentazione di un albero che si muove. @fuzzytrees spiega che l'albero aiuterà con la manutenzione del giardino, mostrando come può tagliare il prato, regolare la siepe e potare le rose tutto allo stesso tempo - finché l'albero all'improvviso diventa selvaggio, rivolgendo le cesoie verso il suo creatore! La folla viene presa dal panico, mentre ognuno cerca di fuggire, ma voi non avete paura: balzate in avanti, pronti a dare battaglia.", + "questTreelingCompletion": "Vi date una spolverata, mentre le ultime foglie vanno alla deriva sul pavimento. Nonostante il trambusto, il Concorso dei Giardini è ora al sicuro - anche se l'albero che avete appena ridotto a un mucchio di trucioli non vincerà alcun premio! \"Ancora qualche dettaglio su cui lavorare\", dice @PainterProphet. \"Forse qualcun altro avrebbe educato meglio l'alberello. Volete provare voi?\"", + "questTreelingBoss": "Albero Groviglio", + "questTreelingDropTreelingEgg": "Alberello (Uovo)", + "questTreelingUnlockText": "Sblocca l'acquisto delle uova di Alberello nel Mercato" } \ No newline at end of file diff --git a/common/locales/it/settings.json b/common/locales/it/settings.json index b948362553..a745a31fae 100644 --- a/common/locales/it/settings.json +++ b/common/locales/it/settings.json @@ -47,7 +47,7 @@ "customDayStart": "Data di inizio giorno personalizzata", "changeCustomDayStart": "Cambiare l'ora di inizio della giornata?", "sureChangeCustomDayStart": "Sei sicuro di voler cambiare l'inizio della giornata personalizzata?", - "customDayStartHasChanged": "Your custom day start has changed.", + "customDayStartHasChanged": "La tua ora di inizio giorno è stata cambiata.", "nextCron": "Le tue Daily verranno ripristinate la prima volta che usi Habitica dopo <%= time %>. Assicurati di aver completato le tue Daily prima di allora!", "customDayStartInfo1": "Habitica è impostato per resettare le tue Daily a mezzanotte nel tuo fuso orario ogni giorno. Qui puoi modificare l'ora di \"reset\".", "misc": "Altro", @@ -149,10 +149,10 @@ "enabled": "Abilitato", "webhookURL": "URL Webhook", "invalidUrl": "url non valido", - "invalidEnabled": "the \"enabled\" parameter should be a boolean", + "invalidEnabled": "Il parametro \"enabled\" dovrebbe essere un booleano", "regIdRequired": "RegId è richiesto", - "pushDeviceAdded": "Push device added successfully", - "pushDeviceAlreadyAdded": "The user already has the push device", + "pushDeviceAdded": "Dispositivo push aggiunto con successo.", + "pushDeviceAlreadyAdded": "L'utente ha già il dispositivo push", "add": "Aggiungi", "buyGemsGoldCap": "Limite alzato a <%= amount %>", "mysticHourglass": "<%= amount %> Clessidra Mistica", @@ -167,5 +167,5 @@ "amazonPayments": "Pagamenti su Amazon", "timezone": "Fuso orario", "timezoneUTC": "Habitica usa il fuso orario impostato sul tuo PC, ovvero: <%= utc %>", - "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.

If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices." + "timezoneInfo": "Se il fuso orario è sbagliato, ricarica questa pagina tramite il bottone di ricarica o aggiornamento della pagina del browser, per assicurarti che Habitica contenga le informazioni più aggiornate. Se è ancora sbagliato, imposta il fuso orario corretto sul tuo PC e poi ricarica di nuovo questa pagina.

Se usi Habitica su altri PC o su altri dispositivi mobili, il fuso orario deve essere identico su ognuno di essi. Se le tue Daily sono state reimpostate ad un'ora sbagliata, ripeti questo controllo su tutti gli altri PC e su un browser sui tuoi dispositivi mobili." } \ No newline at end of file diff --git a/common/locales/it/spells.json b/common/locales/it/spells.json index 3b35e348e8..a81ae56d76 100644 --- a/common/locales/it/spells.json +++ b/common/locales/it/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Tira una palla di neve a un compagno di squadra! Cosa potrebbe mai andare storto? L'effetto scomparirà quando inizierà la nuova giornata del tuo \"bersaglio\".", "spellSpecialSaltText": "Sale", "spellSpecialSaltNotes": "Qualcuno ti ha tirato una palla di neve. Ah ah, divertente. Ora però toglimi questa neve di dosso!", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Luci Sinistre", + "spellSpecialSpookySparklesNotes": "Trasforma un amico in un lenzuolo fluttuante!", "spellSpecialOpaquePotionText": "Pozione Opaca", "spellSpecialOpaquePotionNotes": "Annulla l'effetto di Luci Sinistre", "spellSpecialShinySeedText": "Seme Brillante", @@ -50,7 +50,7 @@ "spellNotFound": "Abilità \"<%= spellId %>\" non trovata.", "partyNotFound": "Squadra non trovata.", "targetIdUUID": "\"targetId\" deve essere un ID Utente valido.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "challengeTasksNoCast": "Usare un'abilità sulle attività delle sfide non è permesso.", "spellNotOwned": "Non possiedi questa abilità.", "spellLevelTooHigh": "Devi aver raggiunto il livello <%= level %> per usare questa abilità." } \ No newline at end of file diff --git a/common/locales/it/subscriber.json b/common/locales/it/subscriber.json index db3640c226..7f2683cf90 100644 --- a/common/locales/it/subscriber.json +++ b/common/locales/it/subscriber.json @@ -78,7 +78,7 @@ "mysticHourglassPopover": "La Clessidra Mistica ti permette di acquistare alcuni oggetti a disponibilità limitata, come i set mensili di Oggetti Misteriosi le ricompense degli eventi mondiali, dal passato!", "mysterySetNotFound": "Mystery set not found, or set already owned.", "mysteryItemIsEmpty": "Mystery items are empty", - "mysteryItemOpened": "Mystery item opened.", + "mysteryItemOpened": "Oggetto misterioso aperto.", "mysterySet201402": "Set messaggero alato", "mysterySet201403": "Set proteggiforeste", "mysterySet201404": "Set farfalla d'alba", @@ -105,7 +105,8 @@ "mysterySet201601": "Set Campione della Risoluzione", "mysterySet201602": "Set del Rubacuori", "mysterySet201603": "Set Quadrifoglio", - "mysterySet201604": "Leaf Warrior Set", + "mysterySet201604": "Set guerriero foglia", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Set steampunk standard", "mysterySet301405": "Set accessori steampunk", "mysterySetwondercon": "Wondercon", @@ -125,13 +126,13 @@ "missingUnsubscriptionCode": "Missing unsubscription code.", "missingSubscription": "User does not have a plan subscription", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", - "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", + "cannotDeleteActiveAccount": "Hai un abbonamento attivo, termina il tuo piano prima di eliminare il tuo account.", "paymentNotSuccessful": "Il pagamento non è andato a buon fine", "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", - "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", + "notAllowedHourglass": "Animale/Cavalcatura non acquistabile con la Clessidra Mistica.", "readCard": "<%= cardType %> è stato letto", "cardTypeRequired": "Tipo di carta richiesto", - "cardTypeNotAllowed": "Unknown card type.", + "cardTypeNotAllowed": "Tipo di carta sconosciuto.", "invalidCoupon": "Codice coupon non valido.", "couponUsed": "Codice coupon già utilizzato.", "noSudoAccess": "Non hai accesso sudo.", diff --git a/common/locales/it/tasks.json b/common/locales/it/tasks.json index a837f3bddc..c4a18bcada 100644 --- a/common/locales/it/tasks.json +++ b/common/locales/it/tasks.json @@ -122,7 +122,7 @@ "checklistOnlyDailyTodo": "Le checklist sono supportate solo su Daily e To-Do.", "checklistItemNotFound": "Non è stato trovato alcun elemento checklist con l'id specificato.", "itemIdRequired": "\"itemId\" deve essere un UUID valido.", - "tagNotFound": "No tag item was found with given id.", + "tagNotFound": "Non è stata trovata alcuna etichetta con l'id specificato.", "tagIdRequired": "\"tagId\" deve essere un UUID valido e deve corrispondere a un'etichetta appartenente all'utente.", "positionRequired": "\"position\" è richiesto e deve essere un numero.", "cantMoveCompletedTodo": "Non è possibile spostare una To-Do completata.", diff --git a/common/locales/ja/contrib.json b/common/locales/ja/contrib.json index 2052a50a0b..813ddd6b51 100644 --- a/common/locales/ja/contrib.json +++ b/common/locales/ja/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "英雄(貢献者・資金提供者)記念館を訪ねる", "conLearn": "貢献者の報酬について学ぶ", "conLearnHow": "Habiticaの進展における参加", - "surveysSingle": "アンケート回答によるHabiticaの成長を支援しました。現在アンケートは開催されていません。", - "surveysMultiple": "<%= surveys %>個のアンケート回答によるHabiticaの成長を支援しました。現在アンケートは開催されていません。", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "開催中のアンケート", "surveyWhen": "バッジは3月下旬アンケートが処理されたときに参加者全員に授与されます。", "blurbInbox": "ここはあなたの個人的なメッセージが保存される場所です!あなたは酒場、パーティー、またはギルドチャットで名前の隣にある封筒のアイコンをクリックすることで、メッセージを送信することができます。もしあなたが不適切なメッセージを受け取った場合は、Lemoness (leslie@habitica.com)にそのスクリーンショットを電子メールで送信してください。", diff --git a/common/locales/ja/gear.json b/common/locales/ja/gear.json index a03725ef44..0c8fc8bc7f 100644 --- a/common/locales/ja/gear.json +++ b/common/locales/ja/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "何千もの四つ葉のクローバーで綴られたスーツ!効果なし。2016年3月購読者アイテム。", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "スチームパンクスーツ", "armorMystery301404Notes": "なんて小粋で最先端!効果なし。3015年2月購読者アイテム。", "armorArmoireLunarArmorText": "慰める月の鎧", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "ファンシートップハット", "headMystery301404Notes": "最上の良家の人々のためのファンシートップハット!3015年1月購読者アイテム。効果なし。", "headMystery301405Text": "ベーシックトップハット", diff --git a/common/locales/ja/maintenance.json b/common/locales/ja/maintenance.json index 6c1bfdae41..89354f6b66 100644 --- a/common/locales/ja/maintenance.json +++ b/common/locales/ja/maintenance.json @@ -1,10 +1,10 @@ { "habiticaBackSoon": "心配しないでください、ハビティカもすぐに換えてします!", "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", - "maintenance": "Maintenance", + "maintenance": "メンテナンス", "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", "noDamageKeepStreaks": "You will NOT take damage or lose streaks!", - "thanksForPatience": "Thanks for your patience!", + "thanksForPatience": "御辛抱いただいてありがとうございました。", "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", "veteranPetAward": "At the end, you will receive a Veteran pet!", diff --git a/common/locales/ja/subscriber.json b/common/locales/ja/subscriber.json index 75a0ec952f..53a3090d90 100644 --- a/common/locales/ja/subscriber.json +++ b/common/locales/ja/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "夢やぶれて セット", "mysterySet201603": "幸運のクローバー セット", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "スチームパンク標準 セット", "mysterySet301405": "スチームパンク アクセサリー セット", "mysterySetwondercon": "Wondercon イベント", diff --git a/common/locales/nl/contrib.json b/common/locales/nl/contrib.json index daeeb4c53c..8e7cbbda99 100644 --- a/common/locales/nl/contrib.json +++ b/common/locales/nl/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Bezoek de Hal der Helden (bijdragers en helpers)", "conLearn": "Lees meer over beloningen voor bijdragers", "conLearnHow": "Lees meer over hoe je kunt bijdragen aan Habitica", - "surveysSingle": "Heeft Habitica geholpen te groeien door een enquête in te vullen. Er zijn op dit moment geen enquêtes.", - "surveysMultiple": "Heeft Habitica geholpen te groeien door <%= surveys %> enquête(s) in te vullen. Er zijn op dit moment geen enquêtes.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Huidige enquête", "surveyWhen": "De badge zal eind maart worden uitgereikt, wanneer de enquêtes van alle deelnemers zijn verwerkt.", "blurbInbox": "Hier worden je privéberichten opgeslagen! Je kan iemand een bericht sturen door op het envelop-icoon naast hun naam te klikken in de herberg-, groep- of gilde-chat. Als je een ongepast bericht hebt gekregen, zou er een screenshot van moeten mailen naar Lemoness (leslie@habitica.com)", diff --git a/common/locales/nl/gear.json b/common/locales/nl/gear.json index 3bbc893f11..f17bf55009 100644 --- a/common/locales/nl/gear.json +++ b/common/locales/nl/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Dit pak is geweven uit duizenden klavertjes vier! Verleent geen voordelen. Abonnee-uitrusting maart 2016.", "armorMystery201604Text": "Harnas van Bladeren", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunkpak", "armorMystery301404Notes": "Net en zwierig, niet? Verleent geen voordelen. Abonnee-uitrusting februari 3015.", "armorArmoireLunarArmorText": "Kalmerend maanharnas", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Deze hoge hoed is een magische geluksbedel. Verleent geen voordelen. Abonnee-uitrusting maart 2016.", "headMystery201604Text": "Kroon van Bloemen", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Chique hoge hoed", "headMystery301404Notes": "Een chique hoge hoed voor lieden van deftigen huize! Abonnee-uitrusting januari 3015. Verleent geen voordelen.", "headMystery301405Text": "Standaard hoge hoed", diff --git a/common/locales/nl/subscriber.json b/common/locales/nl/subscriber.json index 59070b5ec0..ea2491c4c8 100644 --- a/common/locales/nl/subscriber.json +++ b/common/locales/nl/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Hartenbrekersset", "mysterySet201603": "Gelukigzalige Klaver Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Standaard Steampunkset", "mysterySet301405": "Opgesmukte Steampunkset", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/pl/contrib.json b/common/locales/pl/contrib.json index f223b61116..d5354d5eda 100644 --- a/common/locales/pl/contrib.json +++ b/common/locales/pl/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Odwiedź Salę bohaterów (osób wspierających projekt)", "conLearn": "Dowiedz się więcej o nagrodach za wspieranie", "conLearnHow": "Dowiedz się jak możesz dołożyć swój wkład do Habitica", - "surveysSingle": "Pomógł rozwinąć Habitica poprzez wypełnienie ankiety. Brak aktywnych ankiet.", - "surveysMultiple": "Pomógł rozwinąć Habitica poprzez wypełnienie <%= surveys %> ankiet. Brak aktywnych ankiet.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Bieżąca ankieta.", "surveyWhen": "Odznaki zostaną przyznane wszystkim biorącym udział, gdy zgłoszenia zostaną przetworzone pod koniec marca.", "blurbInbox": "Tutaj trzymane są twoje prywatne wiadomości! Możesz wysłać komuś wiadomość klikając na ikonę koperty obok ich nazwy w Karczmie, Drużynie lub na czacie Gildii. Jeśli otrzymałeś nieodpowiednią wiadomość prywatną, powinieneś wysłać email zawierający zrzut ekranu do Lemoness (leslie@habitica.com).", diff --git a/common/locales/pl/gear.json b/common/locales/pl/gear.json index b5dd1dba96..2d9fa1d1b8 100644 --- a/common/locales/pl/gear.json +++ b/common/locales/pl/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Ten garnitur został uszyty z tysięcy czterolistnych koniczyn! Brak dodatkowych korzyści. Przedmiot Abonencki, marzec 2016.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunkowy garnitur", "armorMystery301404Notes": "Elegancki i stylowy! Brak dodatkowych korzyści. Przedmiot Abonencki, luty 2015.", "armorArmoireLunarArmorText": "Księżycowa Kojąca Zbroja", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Ten cylinder to amulet przynoszący szczęście. Brak dodatkowych korzyści. Przedmiot Abonencki, marzec 2016.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Szykowny cylinder", "headMystery301404Notes": "Fantazyjny cylinder dla najwyżej urodzonych. Przedmiot Abonencki, styczeń 2015. Brak dodatkowych korzyści.", "headMystery301405Text": "Klasyczny cylinder", diff --git a/common/locales/pl/subscriber.json b/common/locales/pl/subscriber.json index 041bd248d5..c388402e12 100644 --- a/common/locales/pl/subscriber.json +++ b/common/locales/pl/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Zestaw Łamacza Serc", "mysterySet201603": "Zestaw Szczęśliwej Koniczyny", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Standardowy zestaw steampunkowy", "mysterySet301405": "Zestaw steampunkowych akcesoriów", "mysterySetwondercon": "Konwent Wspaniałości", diff --git a/common/locales/pt/backgrounds.json b/common/locales/pt/backgrounds.json index 446027ab2b..75757f890e 100644 --- a/common/locales/pt/backgrounds.json +++ b/common/locales/pt/backgrounds.json @@ -161,11 +161,11 @@ "backgroundGiantFlowersNotes": "Divirta-se em cima de Flores Gigantes.", "backgroundRainbowsEndText": "Final do arco-íris.", "backgroundRainbowsEndNotes": "Descubra ouro no final do arco-íris", - "backgrounds052016": "SET 24: Released May 2016", - "backgroundBeehiveText": "Beehive", + "backgrounds052016": "Conjunto 24: Lançado em Maio de 2016.", + "backgroundBeehiveText": "Colmeia", "backgroundBeehiveNotes": "Buzz and dance in a Beehive.", "backgroundGazeboText": "Gazebo", "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgroundTreeRootsText": "Raízes de árvore", + "backgroundTreeRootsNotes": "Explore as raízes de árvores." } \ No newline at end of file diff --git a/common/locales/pt/challenge.json b/common/locales/pt/challenge.json index a6a3238be3..5ad710ea47 100644 --- a/common/locales/pt/challenge.json +++ b/common/locales/pt/challenge.json @@ -64,19 +64,20 @@ "hurray": "Hurra!", "noChallengeOwner": "sem dono", "noChallengeOwnerPopover": "Esse desafio não possui um dono porque quem criou o desafio deletou sua conta.", - "challengeMemberNotFound": "User not found among challenge's members", - "onlyGroupLeaderChal": "Only the group leader can create challenges", - "tavChalsMinPrize": "Prize must be at least 1 Gem for Tavern challenges.", - "cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.", - "challengeIdRequired": "\"challengeId\" must be a valid UUID.", - "winnerIdRequired": "\"winnerId\" must be a valid UUID.", - "challengeNotFound": "Challenge not found.", - "onlyLeaderDeleteChal": "Only the challenge leader can delete it.", - "onlyLeaderUpdateChal": "Only the challenge leader can update it.", - "winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.", - "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.", + "challengeMemberNotFound": "Usuário não encontrado entre os membros do desafio.", + "onlyGroupLeaderChal": "Somente o líder do grupo pode criar desafios", + "tavChalsMinPrize": "O prêmio precisa ser de no mínimo 1 gema para desafios da Taverna.", + "cantAfford": "Você não consegue pagar esse prêmio. Compre mais gemas ou reduza o tamanho do prêmio.", + "challengeIdRequired": "\"challengeId\" precisa ser um UUID válido.", + "winnerIdRequired": "\"winnerId\" precisa ser um UUID válido.", + "challengeNotFound": "Desafio não encontrado.", + "onlyLeaderDeleteChal": "Apenas o líder desafio pode excluí-lo.", + "onlyLeaderUpdateChal": "Apenas o líder desafio pode atualizá-lo.", + "winnerNotFound": "Vencedor com id \"<%= userId %>\" não encontrado ou não faz parte do desafio.", + "noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.", "userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.", - "onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.", - "userAlreadyInChallenge": "User is already participating in this challenge.", - "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked." + "onlyChalLeaderEditTasks": "Tarefas pertencentes a um desafio só podem ser editadas pelo líder.", + "userAlreadyInChallenge": "Usuário já está participando desse desafio.", + "cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.", + "shortNameTooShort": "Nome da etiqueta deve ter pelo menos 3 caracteres." } \ No newline at end of file diff --git a/common/locales/pt/character.json b/common/locales/pt/character.json index 34cc1bf749..3cbf750480 100644 --- a/common/locales/pt/character.json +++ b/common/locales/pt/character.json @@ -110,7 +110,7 @@ "mage": "Mago", "mystery": "Mistério", "changeClass": "Alterar Classe, Restituir Pontos de Atributo", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "Para mudar de classe você precisa ser pelo menos nível 10.", "levelPopover": "A cada nível que alcançar você terá um ponto para distribuir para um atributo de sua escolha. Você pode fazer isso manualmente, ou deixar o jogo decidir por você usando uma das opções de Distribuição Automática.", "unallocated": "Pontos de Atributo não Distribuídos", "haveUnallocated": "Você tem <%= points %> Ponto(s) de Atributo não usados", @@ -167,6 +167,6 @@ "showQuickAllocation": "Mostrar distribuição de status", "hideQuickAllocation": "Esconder distribuição de status", "quickAllocationLevelPopover": "Cada nível concederá à você um ponto para distribuir em um atributo de sua escolha. Você pode fazer isso manualmente, ou deixar o jogo decidir por você usando uma das opções de Distribuição Automática encontradas em Usuário -> Status do Avatar.", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "invalidAttribute": "\"<%= attr %>\" não é um atributo válido.", + "notEnoughAttrPoints": "Você não tem pontos de atributo suficientes." } \ No newline at end of file diff --git a/common/locales/pt/contrib.json b/common/locales/pt/contrib.json index bae030f4db..e892c4e1ce 100644 --- a/common/locales/pt/contrib.json +++ b/common/locales/pt/contrib.json @@ -35,12 +35,12 @@ "hallContributors": "Salão dos Contribuidores", "hallPatrons": "Salão dos Patrões", "rewardUser": "Recompensar Usuário", - "UUID": "User ID", + "UUID": "ID do Usuário", "loadUser": "Carregar Usuário", - "noAdminAccess": "You don't have admin access.", - "pageMustBeNumber": "req.query.page must be a number", - "userNotFound": "User not found.", - "invalidUUID": "UUID must be valid", + "noAdminAccess": "Você não possui acesso de administrador.", + "pageMustBeNumber": "req.query.page precisa ser um número", + "userNotFound": "Usuário não encontrado.", + "invalidUUID": "UUID precisa ser válido", "title": "Título", "moreDetails": "Mais detalhes (1-7)", "moreDetails2": "mais detalhes (8-9)", @@ -56,8 +56,8 @@ "visitHeroes": "Visite o Salão de Heróis (colaboradores e apoiadores)", "conLearn": "Saiba mais sobre recompensas de colaboradores", "conLearnHow": "Saiba como contribuir ao Habitica", - "surveysSingle": "Ajudou Habitica a crescer participando de uma pesquisa. Não existem pesquisas ativas.", - "surveysMultiple": "Ajudou Habitica a crescer participando de <%= surveys %> pesquisas. Não existem pesquisas ativas.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Pesquisa Atual", "surveyWhen": "A insígnia será ofertada a todos os participantes quando as pesquisas tiverem sido processadas, no final de março.", "blurbInbox": "É aqui que suas mensagens particulares ficam guardadas! Você pode enviar uma mensagem à alguém clicando no ícone do envelope próximo a seu nome na Taverna, Equipe, ou nas Conversas da Guilda. Se você recebeu uma MP inapropriada, você deve enviar uma imagem da mensagem para Lemoness (leslie@habitica.com)", diff --git a/common/locales/pt/death.json b/common/locales/pt/death.json index 9a0c6d3ed0..23331708eb 100644 --- a/common/locales/pt/death.json +++ b/common/locales/pt/death.json @@ -13,5 +13,5 @@ "lowHealthTips3": "Tarefas diárias incompletas prejudicam você de um dia para o outro, tenha o cuidado de não adicionar muitas de uma só vez!", "lowHealthTips4": "Se a tarefa diária não for cumprida no dia, você pode desabilitá-la clicando no ícone do lápis.", "goodLuck": "Boa Sorte!", - "cannotRevive": "Cannot revive if not dead" + "cannotRevive": "Não pode reviver se não está morto" } \ No newline at end of file diff --git a/common/locales/pt/front.json b/common/locales/pt/front.json index e855195272..1046bc9ff6 100644 --- a/common/locales/pt/front.json +++ b/common/locales/pt/front.json @@ -28,7 +28,7 @@ "communityReddit": "Reddit", "companyAbout": "Como Funciona", "companyBlog": "Blog", - "devBlog": "Developer Blog", + "devBlog": "Blog do desenvolvedor", "companyDonate": "Doar", "companyExtensions": "Extensões", "companyPrivacy": "Privacidade", @@ -52,7 +52,7 @@ "featureSocialHeading": "Jogo social", "featuredIn": "Visto em", "featuresHeading": "Nós também temos...", - "footerDevs": "Developers", + "footerDevs": "Desenvolvedores", "footerCommunity": "Comunidade", "footerCompany": "Companhia", "footerMobile": "Celular", @@ -184,7 +184,7 @@ "zelahQuote": "Com o [Habitica], eu sou persuadido a ir para a cama na hora pelos pontos que ganho por dormir cedo ou pela vida que perco dormindo tarde!", "reportAccountProblems": "Reportar Problemas na Conta", "reportCommunityIssues": "Reportar Problemas com a Comunidade", - "subscriptionPaymentIssues": "Subscription and Payment Issues", + "subscriptionPaymentIssues": "Assinatura e questões sobre pagamento.", "generalQuestionsSite": "Perguntas Gerais sobre o Site", "businessInquiries": "Consultas de Negócios", "merchandiseInquiries": "Consultas de Merchandise", @@ -226,31 +226,31 @@ "altAttrGithub": "GitHub", "altAttrTrello": "Trello", "altAttrSlack": "Slack", - "missingAuthHeaders": "Missing authentication headers.", - "missingAuthParams": "Missing authentication parameters.", - "missingUsernameEmail": "Missing username or email.", - "missingEmail": "Missing email.", - "missingUsername": "Missing username.", - "missingPassword": "Missing password.", - "missingNewPassword": "Missing new password.", - "wrongPassword": "Wrong password.", - "notAnEmail": "Invalid email address.", - "emailTaken": "Email address is already used in an account.", - "newEmailRequired": "Missing new email address.", - "usernameTaken": "Username already taken.", - "passwordConfirmationMatch": "Password confirmation doesn't match password.", - "invalidLoginCredentials": "Incorrect username and/or email and/or password.", - "passwordReset": "If we have your email on file, your password reset link has been sent to your email.", - "passwordResetEmailSubject": "Password Reset for Habitica", - "passwordResetEmailText": "Password for <%= username %> has been reset to <%= newPassword %> . Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them. Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.", + "missingAuthHeaders": "Faltando cabeçalhos de autenticação.", + "missingAuthParams": "Faltando parâmetros de autenticação.", + "missingUsernameEmail": "Faltando nome de usuário ou email.", + "missingEmail": "Faltando email.", + "missingUsername": "Faltando nome de usuário.", + "missingPassword": "Faltando senha.", + "missingNewPassword": "Faltando nova senha.", + "wrongPassword": "Senha incorreta.", + "notAnEmail": "Endereço de e-mail inválido.", + "emailTaken": "Endereço de email já está sendo usado em uma conta.", + "newEmailRequired": "Faltando novo endereço de email.", + "usernameTaken": "Nome de usuário já está sendo utilizado.", + "passwordConfirmationMatch": "A confirmação de senha não corresponde à senha.", + "invalidLoginCredentials": "Nome de usuário e/ou email e/ou senha incorretos.", + "passwordReset": "Se nós tivermos seu email em arquivo, o link para restauração da sua senha será enviado ao seu email.", + "passwordResetEmailSubject": "Reiniciar senha para o Habitica", + "passwordResetEmailText": "A senha para <%= username %> foi reiniciada para <%= newPassword %>. Importante! Tanto o nome de usuário quanto a senha diferenciam maiúsculas de minúsculas - você precisa escrever exatamente como mostrado aqui. Nós recomendamos que copie e cole ambas em vez de digitá-las. Entre em <%= baseUrl %>. Depois de entrar, vá para <%= baseUrl %>/#/options/settings/settings e mude sua senha.", "passwordResetEmailHtml": "Password for <%= username %> has been reset to <%= newPassword %>.

Important! Both username and password are case-sensitive -- you must enter both exactly as shown here. We recommend copying and pasting both instead of typing them.

Log in at <%= baseUrl %>. After you have logged in, head to <%= baseUrl %>/#/options/settings/settings and change your password.", - "invalidLoginCredentialsLong": "Uh-oh - your username or password is incorrect.\n- Make sure your username or email is typed correctly.\n- You may have signed up with Facebook, not email. Double-check by trying Facebook login.\n- If you forgot your password, click \"Forgot Password\".", - "invalidCredentials": "There is no account that uses those credentials.", + "invalidLoginCredentialsLong": "Oh não - seu nome de usuário ou senha está incorreto.\n- Tenha certeza de que seu nome de usuário e senha estão digitados corretamente. \n- Você pode ter se cadastrado com o Facebook, não com o email. Cheque tentando login com o Facebook.\n- Se você esqueceu sua senha, clique \"Esqueci Senha\".", + "invalidCredentials": "Não há conta que use essas credenciais.", "accountSuspended": "Account has been suspended, please contact leslie@habitica.com with your User ID \"<%= userId %>\" for assistance.", - "onlyFbSupported": "Only Facebook is supported currently.", - "cantDetachFb": "Account lacks another authentication method, can't detach Facebook.", - "onlySocialAttachLocal": "Local authentication can be added to only a social account.", - "invalidReqParams": "Invalid request parameters.", + "onlyFbSupported": "Somente Facebook é suportado atualmente.", + "cantDetachFb": "A conta não possui outro método de autenticação, não pode desvincular Facebook.", + "onlySocialAttachLocal": "Autenticação local só pode ser adicionada a uma conta social.", + "invalidReqParams": "Parâmetros requeridos inválidos.", "memberIdRequired": "\"member\" must be a valid UUID.", "heroIdRequired": "\"heroId\" must be a valid UUID." } \ No newline at end of file diff --git a/common/locales/pt/gear.json b/common/locales/pt/gear.json index 70736de764..e194465937 100644 --- a/common/locales/pt/gear.json +++ b/common/locales/pt/gear.json @@ -149,7 +149,7 @@ "weaponSpecialSpring2016WarriorNotes": "Ninguem tem tantos amigos quanto o rato com queijos tenros. Aumenta a força em <%= str %>. Edição Limitada 2016 Equipamento de Primavera.", "weaponSpecialSpring2016MageText": "Cajado de Sinos", "weaponSpecialSpring2016MageNotes": "Abra-Gata-Bra! Tão deslumbrante, você devia se hipnotizar! Ooh. Ele brilha... Aumenta Inteligência em <%= int %>. e percepção em <%= per %>. Edição Limitada 2016 Equipamento de Primavera ", - "weaponSpecialSpring2016HealerText": "Varinha de Flor de Primavera", + "weaponSpecialSpring2016HealerText": "Varinha de Flor da Primavera", "weaponSpecialSpring2016HealerNotes": "Com o aceno e uma piscada, você faz os campos e florestas florescerem! ou acerta ratos problemáticos na cabeça. Aumenta a inteligência em <%=int%> . Equipamento de Edição Limitada da Primavera 2016.", "weaponMystery201411Text": "Forcado de Banquete", "weaponMystery201411Notes": "Apunhale seus inimigos ou cave pelas suas comidas favoritas - esse garfo versátil faz de tudo! Não confere benefícios. Item de Assinante de Novembro 2014.", @@ -189,8 +189,8 @@ "weaponArmoireMiningPickaxNotes": "Extraia a quantidade máxima de ouro de suas tarefas! Aumenta a Percepção em <%= per %>. Armadura Encantada: Conjunto \"Minerador\" (Item 3 de 3).", "weaponArmoireBasicLongbowText": "Arco Longo basico", "weaponArmoireBasicLongbowNotes": "Um arco em segunda mão ainda em condição de uso. Aumenta Força em <%= str %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 1 de 3).", - "weaponArmoireHabiticanDiplomaText": "Habitican Diploma", - "weaponArmoireHabiticanDiplomaNotes": "A certificate of significant achievement -- well done! Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 1 of 3).", + "weaponArmoireHabiticanDiplomaText": "Diploma Habiticano", + "weaponArmoireHabiticanDiplomaNotes": "Um certificado de conquista significativa - parabéns! Aumenta a Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 1 de 3).", "armor": "armadura", "armorBase0Text": "Roupas Modestas", "armorBase0Notes": "Vestimenta ordinária Não concede benefícios.", @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Esse terno foi costurado com milhares de trevos de 4 folhas. Não concede benefícios. Item do Assinante de Março 2016", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Fantasia Steampunk", "armorMystery301404Notes": "Elegante e distinto. Não concede benefícios. Item de Assinante de Fevereiro 3015.", "armorArmoireLunarArmorText": "Armadura Lunar Tranquilizadora", @@ -398,8 +400,8 @@ "armorArmoireMinerOverallsNotes": "Eles podem parecer estragados, mas estão encantados para repelir sujeira. Aumenta Constituição em <%= con %>. Armário Encantado: Set de Minerador (Item 2 de 3).", "armorArmoireBasicArcherArmorText": "Armadura de arqueiro básica", "armorArmoireBasicArcherArmorNotes": "Este colete com camuflagem permite-te passar despercebido em florestas. Aumenta Percepção em <%= per %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 2 de 3).", - "armorArmoireGraduateRobeText": "Graduate Robe", - "armorArmoireGraduateRobeNotes": "Congratulations! This weighty robe hangs heavy with all the knowledge you have accrued. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 2 of 3).", + "armorArmoireGraduateRobeText": "Beca de Graduado", + "armorArmoireGraduateRobeNotes": "Parabéns! Essa beca pesada se pendura solidamente junto com todo o conhecimento que você acumulou. Aumenta Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 2 de 3).", "headgear": "capacete", "headBase0Text": "Sem Elmo", "headBase0Notes": "Sem capacete.", @@ -571,8 +573,10 @@ "headMystery201602Notes": "Proteja sua identidade de todos os seus admiradores. Não confere benefícios. Item de assinante de Fevereiro 2016", "headMystery201603Text": "Chapéu da sorte", "headMystery201603Notes": "Essa cartola é um amuleto da sorte mágico. Não concede benefícios. Item do Assinante de Março 2016 ", - "headMystery201604Text": "Crown o' Flowers", - "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201604Text": "Coroa de Flores", + "headMystery201604Notes": "Essas flores entrelaçadas fazem um capacete surpreendentemente forte! Não confere benefício. Item de Assinante de Abril de 2016.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Cartola Chique", "headMystery301404Notes": "Uma cartola chique para as damas e cavalheiros mais finos! Item de Assinante de Janeiro 3015. Não concede benefícios.", "headMystery301405Text": "Cartola Básica", @@ -621,8 +625,8 @@ "headArmoireMinerHelmetNotes": "Proteja sua cabeça de Tarefas caindo! Aumenta Inteligência em <%= int %>. Armário Encantado. Set de Minerador (Item 1 de 3).", "headArmoireBasicArcherCapText": "Chapéu de arqueiro básico", "headArmoireBasicArcherCapNotes": "Nenhum arqueiro estaria completo sem um chapéu janota! Aumenta Percepção em <%= per %> pontos. Armário Encantado: Conjunto básico de Arqueiro (item 3 de 3).", - "headArmoireGraduateCapText": "Graduate Cap", - "headArmoireGraduateCapNotes": "Congratulations! Your deep thoughts have earned you this thinking cap. Increases Intelligence by <%= int %>. Enchanted Armoire: Graduate Set (Item 3 of 3).", + "headArmoireGraduateCapText": "Chapéu de Graduação", + "headArmoireGraduateCapNotes": "Parabéns! Seus pensamentos profundos te deram esse chapéu da compreensão. Aumenta Inteligência em <%= int %>. Armário Encantado: Conjunto de Graduação (Item 3 de 3).", "offhand": "item da segunda mão", "shieldBase0Text": "Sem Equipamento na Segunda Mão", "shieldBase0Notes": "Sem escudo ou segundo armamento.", @@ -726,8 +730,8 @@ "shieldArmoireDragonTamerShieldNotes": "Distraia os inimigos com esse escudo em forma de dragão; Aumenta sua Percepção em <%= per %>. Armário Encantado: Conjunto do Domador de Dragões (Item 2 de 3).", "shieldArmoireMysticLampText": "Lâmpada Mistíca", "shieldArmoireMysticLampNotes": "Ilumine as cavernas mais sombrias com essa lâmpada mística! Aumenta percepção por <%= per %>. Armário Encantado: Item Independente", - "shieldArmoireFloralBouquetText": "Bouquet o' Flowers", - "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.", + "shieldArmoireFloralBouquetText": "Buquê de Flores", + "shieldArmoireFloralBouquetNotes": "Não ajudam muito em uma batalha, mas não são lindas? Aumenta Constituição em <%= con %>. Armário Encantado: Item Independente.", "back": "Acessório de Costas", "backBase0Text": "Sem Acessório de Fundo", "backBase0Notes": "Sem Acessório de Fundo.", @@ -832,8 +836,8 @@ "eyewear": "Óculos", "eyewearBase0Text": "Sem Acessório Para os Olhos", "eyewearBase0Notes": "Sem Acessório Para os Olhos.", - "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses", - "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.", + "eyewearSpecialBlackTopFrameText": "Óculos Pretos Padrão", + "eyewearSpecialBlackTopFrameNotes": "Óculos com uma armação preta sobre as lentes. Não confere benefício.", "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses", "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.", "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses", diff --git a/common/locales/pt/groups.json b/common/locales/pt/groups.json index ae932b1b31..bb069daf32 100644 --- a/common/locales/pt/groups.json +++ b/common/locales/pt/groups.json @@ -92,7 +92,7 @@ "send": "Enviar", "messageSentAlert": "Mensagem enviada", "pmHeading": "Mensagem privada para <%= name %>", - "pmsMarkedRead": "Your private messages have been marked as read", + "pmsMarkedRead": "Suas mensagens privadas foram marcadas como lidas.", "clearAll": "Deletar Todas as Mensagens", "confirmDeleteAllMessages": "Tem certeza que desja deletar todas as mensagens na sua caixa de entrada? Outros usuários ainda irão ver as mensagens que você enviou para eles.", "optOutPopover": "Não gosta de mensagens privadas? Clique para desativar completamente", @@ -100,15 +100,15 @@ "unblock": "Desbloquear", "pm-reply": "Enviar uma resposta", "inbox": "Caixa de Entrada", - "messageRequired": "A message is required.", - "toUserIDRequired": "A User ID is required", - "gemAmountRequired": "A number of gems is required", - "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.", - "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you", - "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!", - "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!", - "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.", - "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.", + "messageRequired": "Uma mensagem é necessária.", + "toUserIDRequired": "Um ID de usuário é necessário", + "gemAmountRequired": "Um número de gemas é necessário", + "notAuthorizedToSendMessageToThisUser": "Não pode enviar mensagem para esse usuário.", + "privateMessageGiftIntro": "Olá <%= receiverName %>, <%= senderName %> te enviou", + "privateMessageGiftGemsMessage": "<%= gemAmount %> gemas!", + "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> meses de assinatura!", + "cannotSendGemsToYourself": "Não é possível enviar gemas para si mesmo. Tente uma assinatura.", + "badAmountOfGemsToSend": "Quantidade precisa ser entre 1 e seu número atual de gemas.", "abuseFlag": "Reportar violação das Diretrizes de Comunidade", "abuseFlagModalHeading": "Reportar <%= name %> por violação?", "abuseFlagModalBody": "Tem certeza que deseja denunciar essa publicação? Você deve denunciar APENAS uma publicação que viola as <%= firstLinkStart %>Diretrizes de Comunidade<%= linkEnd %> e/ou <%= secondLinkStart %>Termos de Serviço<%= linkEnd %>. Denunciar inapropriadamente uma publicação é uma violação das Diretrizes da Comunidade e pode resultar em uma infração. Razões apropriadas para reportar uma publicação incluem, mas não se limitam a:

  • xingamentos, blasfêmias religiosas
  • intolerância, insultos
  • tópicos adultos
  • violência, inclusive como brincadeira
  • spam, mensagens sem sentido
", @@ -162,28 +162,28 @@ "partyOnName": "Equipe online", "partyUpAchievement": "Entrou em um grupo com outra pessoa! Divirtam-se batalhando contra monstros e ajudando um ao outro.", "partyOnAchievement": "Entrou em um grupo com pelo menos 4 pessoas! Divirta-se aumentando a sua produtividade com seus amigos para derrotar seus inimigos!", - "groupIdRequired": "\"groupId\" must be a valid UUID", - "groupNotFound": "Group not found.", + "groupIdRequired": "\"groupId\" precisa ser um UUID válido.", + "groupNotFound": "Grupo não encontrado.", "groupTypesRequired": "You must supply a valid \"type\" query string.", - "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", - "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", - "onlyLeaderCanRemoveMember": "Only group leader can remove a member!", - "memberCannotRemoveYourself": "You cannot remove yourself!", - "groupMemberNotFound": "User not found among group's members", - "mustBeGroupMember": "Must be member of the group.", - "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", - "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", - "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", - "inviteMissingEmail": "Missing email address in invite.", - "partyMustbePrivate": "Parties must be private", - "userAlreadyInGroup": "User already in that group.", - "userAlreadyInvitedToGroup": "User already invited to that group.", - "userAlreadyPendingInvitation": "User already pending invitation.", - "userAlreadyInAParty": "User already in a party.", - "userWithIDNotFound": "User with id \"<%= userId %>\" not found.", - "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", + "questLeaderCannotLeaveGroup": "Você não pode sair de sua equipe quando você iniciou uma missão. Aborte a missão primeiro.", + "cannotLeaveWhileActiveQuest": "Você não pode sair de sua equipe durante uma missão ativa. Por favor, saia da missão primeiro.", + "onlyLeaderCanRemoveMember": "Somente o líder do grupo pode remover um membro!", + "memberCannotRemoveYourself": "Você não pode se remover!", + "groupMemberNotFound": "Usuário não encontrado entre os membros do grupo", + "mustBeGroupMember": "Deve ser membro do grupo.", + "keepOrRemoveAll": "req.query.keep precisa ser ou \"keep-all\" ou \"remove-all\"", + "keepOrRemove": "req.query.keep precisa ser ou \"keep\" ou \"remove\"", + "canOnlyInviteEmailUuid": "Só pode convidar usando uuids ou emails.", + "inviteMissingEmail": "Faltando o endereço de email no convite.", + "partyMustbePrivate": "Equipes precisam ser privadas.", + "userAlreadyInGroup": "Usuário já nesse grupo.", + "userAlreadyInvitedToGroup": "Usuário já convidado para esse grupo.", + "userAlreadyPendingInvitation": "Usuário ainda com convite pendente.", + "userAlreadyInAParty": "Usuário já está em uma equipe.", + "userWithIDNotFound": "Usuário com id \"<%= userId %>\" não encontrado.", + "userHasNoLocalRegistration": "Usuário não tem um registro local (usuário, email, senha).", "uuidsMustBeAnArray": "User ID invites must be an array.", "emailsMustBeAnArray": "Email address invites must be an array.", - "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", - "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!" + "canOnlyInviteMaxInvites": "Você só pode convidar \"<%= maxInvites %>\" de cada vez", + "onlyCreatorOrAdminCanDeleteChat": "Não autorizado a deletar essa mensagem!" } \ No newline at end of file diff --git a/common/locales/pt/limited.json b/common/locales/pt/limited.json index 6f5d433599..071908cd05 100644 --- a/common/locales/pt/limited.json +++ b/common/locales/pt/limited.json @@ -73,5 +73,5 @@ "sneakySqueakerSet": "Ladrão Sorrateiro (Ladino)", "fallEventAvailability": "Disponível até 31 de Outubro.", "winterEventAvailability": "Disponível até 31 de Dezembro", - "springEventAvailability": "Available until May 31" + "springEventAvailability": "Disponível até 31 de Maio" } \ No newline at end of file diff --git a/common/locales/pt/maintenance.json b/common/locales/pt/maintenance.json index 493454405d..706d4cb5b4 100644 --- a/common/locales/pt/maintenance.json +++ b/common/locales/pt/maintenance.json @@ -1,34 +1,34 @@ { "habiticaBackSoon": "Não se preocupe. Habitica estará de volta em breve!", - "importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.", + "importantMaintenance": "Estamos a realizar manutenção importante que estimamos due até <%= localDate %> na sua zona local.", "maintenance": "Manutenção", - "maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.", + "maintenanceMoreInfo": "Deseja mais informação acerca das operações de manutenção? <%= linkStart %>Veja a nossa página de informação<%= linkEnd %>.", "noDamageKeepStreaks": "NÃO receberá dano ou perderá a sua sequência!", "thanksForPatience": "Obrigado pela sua paciência!", - "twitterMaintenanceUpdates": "For the most recent updates, watch our Twitter, where we will be posting status information.", - "veteranPetAward": "At the end, you will receive a Veteran pet!", + "twitterMaintenanceUpdates": "Para ler acerca das nossas actualizações recentes, siga-nos no Twitter onde colocamos informação acerca do estado corrente.", + "veteranPetAward": "No final, receberá uma mascote veterana!", - "maintenanceInfoTitle": "Information about Upcoming Maintenance to Habitica", - "maintenanceInfoWhat": "What is happening?", - "maintenanceInfoWhatText": "On May 21, Habitica will be down for maintenance for most of the day. You will not take any damage or have your account harmed during that weekend, even if you can’t log in to check off your Dailies in time! We will be working very hard to make the downtime as short as possible, and will be posting regular updates on our Twitter account. At the end of the downtime, to thank everyone for their patience, you will all receive a rare pet!", - "maintenanceInfoWhy": "Why is this happening?", - "maintenanceInfoWhyText": "For the past several months, we have been thoroughly revamping Habitica behind-the-scenes. Specifically, we have rewritten the API. While it may not look much different on the surface, it’s a whole new world underneath. This will allow us WAY more flexibility when we want to build features in the future, and lead to improved performance!", - "maintenanceInfoTechDetails": "Want more details on the technical side of the process? Visit The Forge, our dev blog.", - "maintenanceInfoMore": "More Information", - "maintenanceInfoAccountChanges": "What changes will I see to my account after the rewrite is complete?", - "maintenanceInfoAccountChangesText": "At first, there won’t be any notable changes aside from performance improvements for features such as Challenges. If you notice any changes that shouldn’t be there, email us at admin@habitica.com and we will investigate them for you!", - "maintenanceInfoAddFeatures": "What kind of features will this allow Habitica to add?", - "maintenanceInfoAddFeaturesText": "Completing this rewrite will allow us to start building out improved chat and Guilds, plans for organizations and families, and additional productivity features like Monthlies and the ability to record yesterday’s activity! Those are all involved features on their own, so it will take time to build them, but until we were finished with this rewrite, there was no way we could start them.", - "maintenanceInfoHowLong": "How long will the maintenance take?", - "maintenanceInfoHowLongText": "We have to migrate tasks and data for all 1.3 million Habitica users -- not an easy task! We anticipate that it will take place between approximately 1pm Pacific Time (8pm UTC) and 10pm Pacific Time (5am UTC). Rest assured that we’re doing everything we can to make it go as quickly as possible! You can follow updates on our Twitter.", - "maintenanceInfoStatsAffected": "How will my Dailies, Streaks, Buffs, and Quests be affected?", - "maintenanceInfoStatsAffectedText1": "You will NOT take any damage or lose any streaks that weekend, but otherwise, your day will reset normally! Dailies that you checked will become unchecked, buffs will reset, etc. If you are in a Collection Quest, you will still find items. If you are in a Boss Battle, you will still deal damage to the Boss, but the Boss will not deal damage to you. (Even monsters need a break!)", - "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", - "maintenanceInfoSeeTasks": "What if I need to see my task list?", - "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", - "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", - "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", - "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." + "maintenanceInfoTitle": "Informação acerca de Operações de Manutenção planeadas em Habitica", + "maintenanceInfoWhat": "Que está a acontecer?", + "maintenanceInfoWhatText": "A 21 de Maio, Habitica não estará disponível devido a operações de manutenção durante a maioria do dia. Não receberá dano ou terá a sua conta prejudicada durante o fim-de-semana, mesmo que não consiga aceder à sua lista de tarefas diárias a tempo! Estaremos a trabalhar arduamente para fazer com que o tempo de indisponibilidade seja o mais reduzido possível e colocaremos actualizações acerca das mesmas na nossa conta de Twitter. No final do tempo de indisponibilidade, para agradecer a todos pela vossa paciência, receberá uma mascote rara!", + "maintenanceInfoWhy": "Porque está isto a acontecer?", + "maintenanceInfoWhyText": "Nos passado meses, estivemos ocupados a re-estruturar Habitica por trás da cortina. Especificamente, estivemos a reescrever a API. Embora não pareça muito diferente à superfície, existe um mundo completamente novo por baixo da mesma. Isto permitirá que sejamos muito mais flexíveis quando queremos adicionar novas funcionalidades no futuro e leva a uma performance melhor!", + "maintenanceInfoTechDetails": "Quer mais detalhes acerca do lado técnico do processo? Visite The Forge, o nosso blog de desenvolvimento.", + "maintenanceInfoMore": "Mais informação", + "maintenanceInfoAccountChanges": "Que mudanças verei na minha conta depois de terminar a reescrita?", + "maintenanceInfoAccountChangesText": "Inicialmente não se notarão mudanças por demais aparte de melhorias de performance em funcionalidades como os Desafios. Se notar alguma mudança que não deveria estar presente, envie-nos um email para admin@habitica.com e realizaremos uma investigação!", + "maintenanceInfoAddFeatures": "Que tipo de funcionalidades serão possíveis de adicionar a Habitica com isto?", + "maintenanceInfoAddFeaturesText": "Completar esta re-escrita permitirá que possamos a construir funcionalidades de chat e Guildas melhor, planear para organizações e famílias, bem como criar novas funcionalidades de produtividade como Tarefas Mensais e a possibilidade de gravar a performance do dia anterior! todas estas funcionalidades são complexas por si só, pelo que demorará ainda algum tempo até que as completemos mas, até terminarmos esta reescrita, não nos era sequer possível começar tal trabalho.", + "maintenanceInfoHowLong": "Quanto tempo levaram as operações de manutenção?", + "maintenanceInfoHowLongText": "Teremos de migrar tarefas e dados para todos os 1.3 milhões de utilizadores de Habitica - não é uma tarefa fácil! Antecipamos que este bloco de trabalho ocorrerá aproximadamente entre as 1pm Pacific Time (8pm UTC) e as 10pm Pacific Time (5am UTC). Não se preocupe que faremos tudo para que tudo demore o menos tempo possível. Pode seguir as actualizações de estado na nossa conta de Twitter.", + "maintenanceInfoStatsAffected": "Como serão afectadas as minhas Tarefas Diárias, Combos, Buffs e Missões?", + "maintenanceInfoStatsAffectedText1": "NÃO receberá qualquer dano ou perderá qualquer combo nesse fim-de-semana mas, aparte disso, o seu dia será reposto de forma normal! Tarefas Diárias que complete ficarão por cumprir, buffs serão repostos, etc. Se se encontra numa Missão de Colecta, encontrará items. Se está a participar num Combate de Chefão, continuará a dar dano ao mesmo, mas o Chefão não lhe dará dano a si. (Até monstros precisam de uma pausa!)", + "maintenanceInfoStatsAffectedText2": "Depois de pensar bastante, nossa equipe concluiu que essa não seria a maneira mais justa de lidar com o fato de que muitos usuários não conseguirão marcar suas Tarefas Diárias normalmente durante a manutenção. Nós pedimos desculpas pela inconveniência que isso causa!", + "maintenanceInfoSeeTasks": "E se eu precisar ver minha lista de tarefas?", + "maintenanceInfoSeeTasksText": "Se você souber que precisará ver sua lista de tarefas no sábado para lembrar-se do que precisa fazer, nós recomendamos que, antes do início da manutenção, você dê um \"print screen\" nas suas tarefas para que você possa usar como referência.", + "maintenanceInfoRarePet": "Que tipo de mascote raro eu vou receber?", + "maintenanceInfoRarePetText": "Para te agradecer pela sua paciência durante o período fora do ar, todo mundo ganhará um Mascote Veterano raro. Se você nunca recebeu um Mascote Veterano antes, você receberá um Lobo Veterano. Se você já tem um Lobo Veterano, você receberá um Tigre Veterano. E se você já tem um Lobo Veterano e um Tigre Veterano, você receberá um mascote Veterano nunca antes visto! Depois que a migração estiver completa, pode levar várias horas para o seu mascote aparecer, mas não tema, todos ganharão um.", + "maintenanceInfoWho": "Quem trabalhou nesse enorme projeto?", + "maintenanceInfoWhoText": "Estamos felizes que você perguntou! Foi liderado pelo nosso incrível colaborador paglias, com muita ajuda de Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, e Alys.", + "maintenanceInfoTesting": "A nova versão também foi incansavelmente testada por um grupo de nossos incríveis voluntários de código aberto. Obrigado -- nós não poderíamos ter feito isso sem vocês." } diff --git a/common/locales/pt/npc.json b/common/locales/pt/npc.json index 92657a2d97..db1f3196e5 100644 --- a/common/locales/pt/npc.json +++ b/common/locales/pt/npc.json @@ -21,26 +21,26 @@ "ian": "Ian", "ianText": "Bem-vindo à Loja de Missões! Aqui você pode usar os Pergaminhos de Missões para lutar contra monstros com seus amigos. Não deixe de verificar nossa refinada lista de Pergaminhos de Missões para comprar à direita.", "ianBrokenText": "Bem-vindo à Loja de Missões... Aqui você pode usar Pergaminhos de Missões para enfrentar monstros com seus amigos... Não deixe de dar uma olhada na nossa refinada coleção de Pergaminhos de Missões a venda na direita...", - "missingKeyParam": "\"req.params.key\" is required.", - "itemNotFound": "Item \"<%= key %>\" not found.", - "cannotBuyItem": "You can't buy this item.", - "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.", - "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.", - "invalidPetName": "Invalid pet name supplied.", - "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.", - "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.", + "missingKeyParam": "\"req.params.key\" é necessário.", + "itemNotFound": "Item \"<%= key %>\" não encontrado.", + "cannotBuyItem": "Você não pode comprar esse item.", + "missingTypeKeyEquip": "\"key\" e \"type\" são parâmetros necessários.", + "missingPetFoodFeed": "\"pet\" e \"food\" são parâmetros necessários.", + "invalidPetName": "Nome de mascote fornecido inválido.", + "missingEggHatchingPotionHatch": "\"egg\" e \"hatchingPotion\" são parâmetros necessários.", + "invalidTypeEquip": "\"type\" precisa ser um de 'equipped', 'pet', mount', 'costume'.", "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.", - "typeRequired": "Type is required", - "keyRequired": "Key is required", + "typeRequired": "Tipo é necessário", + "keyRequired": "Chave é necessária", "notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]", - "contentKeyNotFound": "Key not found for Content <%= type %>", - "plusOneGem": "+1 Gem", - "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", + "contentKeyNotFound": "Chave não encontrada para Conteúdo <%= type %>", + "plusOneGem": "+1 Gema", + "typeNotSellable": "Tipo não é vendível. Precisa ser um dos seguintes <%= acceptedTypes %>", "userItemsKeyNotFound": "Key not found for user.items <%= type %>", "pathRequired": "Path string is required", - "unlocked": "Items have been unlocked", - "alreadyUnlocked": "Full set already unlocked.", - "alreadyUnlockedPart": "Full set already partially unlocked.", + "unlocked": "Itens foram destravados", + "alreadyUnlocked": "Conjunto completo já destravado.", + "alreadyUnlockedPart": "Conjunto completo já parcialmente destravado.", "USD": "(US$) Dólar", "newStuff": "Novidades", "cool": "Me diga depois", @@ -84,7 +84,7 @@ "tourPetsPage": "Este é o Estábulo! Depois do nível 4, você pode chocar mascotes usando ovos e poções. Quando você chocar um mascote no Mercado, ele aparecerá aqui. Clique na imagem de um mascote para adicioná-lo ao seu avatar. Alimente-os com comida que você encontrar depois do nível 4 e eles se transformarão em poderosas montarias.", "tourMountsPage": "Depois que você alimentar um mascote o suficiente para transformá-lo em uma montaria, ele aparecerá aqui. (Mascotes, montarias e comida estão disponíveis depois do nível 4.) Clique em uma montaria para subir nela!", "tourEquipmentPage": "É aqui que seu Equipamento fica guardado! Seu Equipamento de Batalha afeta seus atributos. Se você quiser mostrar um Equipamento diferente no seu avatar sem alterar seus atributos, clique em \"Usar Traje\".", - "equipmentAlreadyOwned": "You already own that piece of equipment", + "equipmentAlreadyOwned": "Você já possui esse equipamento", "tourOkay": "Okay!", "tourAwesome": "Incrível!", "tourSplendid": "Esplêndido!", diff --git a/common/locales/pt/pets.json b/common/locales/pt/pets.json index 64df4f0b16..d5c3e814a8 100644 --- a/common/locales/pt/pets.json +++ b/common/locales/pt/pets.json @@ -12,7 +12,7 @@ "etherealLion": "Leão Etéreo", "veteranWolf": "Lobo Veterano", "veteranTiger": "Tigre Veterano", - "veteranLion": "Veteran Lion", + "veteranLion": "Leão Veterano", "cerberusPup": "Filhote de Cérbero", "hydra": "Hidra", "mantisShrimp": "Camarão Mantis", @@ -63,7 +63,7 @@ "hatchedPet": "Você chocou um <%= potion %> <%= egg %>!", "displayNow": "Mostrar Agora", "displayLater": "Mostrar Depois", - "petNotOwned": "You do not own this pet.", + "petNotOwned": "Você não possui esse mascote.", "earnedCompanion": "Com toda sua produtividade, você conseguiu um novo companheiro. Lhe dê comida para fazê-lo crescer!", "feedPet": "Alimentar <%= article %><%= text %> para o seu <%= name %>?", "useSaddle": "Selar <%= pet %>?", @@ -85,8 +85,8 @@ "petKeyBoth": "Solte Ambos", "confirmPetKey": "Tem certeza?", "petKeyNeverMind": "Não ainda", - "petsReleased": "Pets released.", - "mountsAndPetsReleased": "Mounts and pets released", - "mountsReleased": "Mounts released", + "petsReleased": "Mascotes liberados.", + "mountsAndPetsReleased": "Montarias e mascotes liberados.", + "mountsReleased": "Montarias liberadas.", "gemsEach": "gemas cada" } \ No newline at end of file diff --git a/common/locales/pt/quests.json b/common/locales/pt/quests.json index c20ba81f06..9500e9a42b 100644 --- a/common/locales/pt/quests.json +++ b/common/locales/pt/quests.json @@ -79,23 +79,23 @@ "getMoreQuests": "Conseguir mais missões", "unlockedAQuest": "Você desbloqueou uma missão!", "leveledUpReceivedQuest": "Você subiu para o nível <%= level %> e recebeu um pergaminho de missão!", - "questInvitationDoesNotExist": "No quest invitation has been sent out yet.", - "questInviteNotFound": "No quest invitation found.", - "guildQuestsNotSupported": "Guilds cannot be invited on quests.", - "questNotFound": "Quest \"<%= key %>\" not found.", - "questNotOwned": "You don't own that quest scroll.", - "questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.", - "questLevelTooHigh": "You must be level <%= level %> to begin this quest.", - "questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.", - "questAlreadyAccepted": "You already accepted the quest invitation.", - "noActiveQuestToLeave": "No active quest to leave", - "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", - "notPartOfQuest": "You are not part of the quest", - "noActiveQuestToAbort": "There is no active quest to abort.", - "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.", - "questAlreadyRejected": "You already rejected the quest invitation.", - "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.", - "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.", - "questNotPending": "There is no quest to start.", - "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest" + "questInvitationDoesNotExist": "Nenhum convite para missão foi enviado ainda.", + "questInviteNotFound": "Nenhum convite para missão encontrado.", + "guildQuestsNotSupported": "Guildas não podem ser convidadas para missão.", + "questNotFound": "Missão \"<%= key %>\" não encontrada.", + "questNotOwned": "Você não possui esse pergaminho de missão.", + "questNotGoldPurchasable": "Missão \"<%= key %>\" não é comprável com ouro.", + "questLevelTooHigh": "Você precisa ter nível <%= level %> para iniciar essa missão.", + "questAlreadyUnderway": "Sua equipe já está em uma missão. Tente novamente quando a missão atual tiver encerrado.", + "questAlreadyAccepted": "Você já aceitou o convite para a missão.", + "noActiveQuestToLeave": "Não há missão ativa para sair.", + "questLeaderCannotLeaveQuest": "O líder da missão não pode sair da missão.", + "notPartOfQuest": "Você não faz parte da missão.", + "noActiveQuestToAbort": "Não há missão ativa para abortar.", + "onlyLeaderAbortQuest": "Somente o líder do grupo ou da missão podem abortá-la.", + "questAlreadyRejected": "Você já rejeitou o convite para a missão.", + "cantCancelActiveQuest": "Você não pode cancelar uma missão ativa, use a funcionalidade de abortar.", + "onlyLeaderCancelQuest": "Somente o líder do grupo ou da missão podem cancelá-la.", + "questNotPending": "Não há missão para iniciar.", + "questOrGroupLeaderOnlyStartQuest": "Somente o líder da missão ou do grupo podem forçar o início da missão." } \ No newline at end of file diff --git a/common/locales/pt/questscontent.json b/common/locales/pt/questscontent.json index 2563ef9988..98bf81fe66 100644 --- a/common/locales/pt/questscontent.json +++ b/common/locales/pt/questscontent.json @@ -315,10 +315,10 @@ "questFalconBoss": "Aves de Rapinrolação", "questFalconDropFalconEgg": "Falcão (Ovo)", "questFalconUnlockText": "Desbloqueia ovos de Falcão para compra no Mercado", - "questTreelingText": "The Tangle Tree", - "questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time – until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid – you leap forward, ready to do battle.", - "questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe – although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"", - "questTreelingBoss": "Tangle Tree", - "questTreelingDropTreelingEgg": "Treeling (Egg)", - "questTreelingUnlockText": "Unlocks purchasable Treeling eggs in the Market" + "questTreelingText": "A Árvore Emaranhada", + "questTreelingNotes": "É a Competição de Jardinagem anual e todos estão falando sobre o projeto misterioso que @aurakami prometeu revelar. Você se junta à multidão naquele dia do grande anúncio e se admira com a entrada de uma árvore que se mexe. @fuzzytrees explica que a árvore ajudará com a manutenção do jardim, mostrando como ela pode cortar a grama, aparar os arbustos e podar as roseiras tudo ao mesmo tempo - até que de repente a árvore fica louca, virando suas tesouras de poda contra o seu criador! A multidão entra em pânico e todo mundo tenta fugir, mas você não tem medo - você se inclina para a frente, pronto para batalhar.", + "questTreelingCompletion": "Você tira seu pó quando as últimas folhas caem no chão. Em vez de triste, a Competição de Jardinagem agora está segura - apesar de que a árvore que você reduziu a uma pilha de lascas de madeira não ganhará nenhum prêmio! \"Ainda alguns nós para serem trabalhados aqui\", diz @PainterProphet. \"Talvez alguém mais faça um bom trabalho treinando os brotos. Você arrisca uma tentativa?\"", + "questTreelingBoss": "Árvore Emaranhada", + "questTreelingDropTreelingEgg": "Arvorezinha (Ovo)", + "questTreelingUnlockText": "Desbloqueia ovos de Arvorezinhas compráveis no Mercado" } \ No newline at end of file diff --git a/common/locales/pt/rebirth.json b/common/locales/pt/rebirth.json index e25bbab9f4..116a697607 100644 --- a/common/locales/pt/rebirth.json +++ b/common/locales/pt/rebirth.json @@ -5,7 +5,7 @@ "rebirthStartOver": "Renascer reinicia seu personagem no nível 1.", "rebirthAdvList1": "Você recupera a Vida toda.", "rebirthAdvList2": "Você ficará sem Experiência, Ouro ou Equipamentos (com exceção dos itens grátis como os Itens Misteriosos). ", - "rebirthAdvList3": "Your Habits, Dailies, and To-Dos reset to yellow, and streaks reset, except for challenge tasks.", + "rebirthAdvList3": "Seus Hábitos, Tarefas Diárias e Afazeres reiniciaram para amarelo, seus combos reiniciaram, exceto as tarefas de desafios.", "rebirthAdvList4": "Sua classe inicial será Guerreiro até liberar uma nova classe.", "rebirthInherit": "Seu novo personagem herda algumas coisas de seu antecessor:", "rebirthInList1": "Tarefas, histórico, e configurações permanecem.", @@ -25,5 +25,5 @@ "rebirthName": "Orbe do Renascimento", "reborn": "Renascido, nível max <%= reLevel %>", "confirmReborn": "Tem certeza?", - "rebirthComplete": "You have been reborn!" + "rebirthComplete": "Você renasceu!" } \ No newline at end of file diff --git a/common/locales/pt/settings.json b/common/locales/pt/settings.json index 8816d5aa67..b8e4b134b8 100644 --- a/common/locales/pt/settings.json +++ b/common/locales/pt/settings.json @@ -47,7 +47,7 @@ "customDayStart": "Início do Dia Personalizado", "changeCustomDayStart": "Deseja mudar o Início de dia customizado?", "sureChangeCustomDayStart": "Você tem certeza que quer mudar o início customizado de dia?", - "customDayStartHasChanged": "Your custom day start has changed.", + "customDayStartHasChanged": "Seu início de dia personalizado mudou.", "nextCron": "As suas tarefas diárias serão reiniciadas ao utilizar Habitica depois de <%= time %>. Certifique-se de que completou as suas tarefas diárias antes deste horário!", "customDayStartInfo1": "Habitica está predefinido para verificar e reiniciar as suas tarefas diárias à meia noite de seu fuso horária a cada dia. Você pode personalizar esse horário aqui.", "misc": "Variados", @@ -62,7 +62,7 @@ "newUsername": "Novo nome de usuário", "dangerZone": "Zona de Perigo", "resetText1": "ATENÇÃO! Isso redefine várias partes da sua conta. Isso é altamente desencorajado, mas algumas pessoas acham útil no início, após brincarem com o site por um curto período de tempo.", - "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.", + "resetText2": "Você perderá todos os seus níveis, ouro, e pontos de experiência. Todas as suas tarefas (exceto aquelas de desafios) serão deletadas permanentemente e você perderá todo os dados históricos delas. Você perderá todo o seu equipamento, mas poderá comprá-lo de volta, incluindo todo o equipamento de edição limitada ou os itens misteriosos dos assinantes que você já possua (você precisará ter a classe correta para recomprar equipamento classe-específico). Você manterá sua classe atual e seus mascotes e montarias. Você pode preferir usar a Orbe do Renascimento em vez disso, que é uma opção muito mais segura e que preservará suas tarefas.", "deleteText": "Tem certeza? Isso deletará sua conta para sempre, e nunca poderá ser recuperada! Você precisará registrar uma nova conta para usar Habitica de novo. Gemas gastas ou no banco não serão restituídas. Se tiver certeza absoluta, digite <%= deleteWord %> na caixa de texto abaixo.", "API": "API", "APIv3": "API v3", @@ -78,7 +78,7 @@ "otherExtensions": "Outras Extensões", "otherDesc": "Encontre outros aplicativos, extensões e ferramentas na Wiki de Habitica.", "resetDo": "Faça, reinicie minha conta!", - "resetComplete": "Reset complete!", + "resetComplete": "Reset completo!", "fixValues": "Corrigir Valores", "fixValuesText1": "Se encontrou um bug ou cometeu algum erro que alterou seu personagem injustamente (dano que não deveria ter tomado, Ouro que você não mereceu, etc.), você pode manualmente corrigir seus números aqui. Sim, isso cria a possibilidade de trapacear: use essa ferramenta com sabedoria, ou sabotará sua própria construção de hábitos.", "fixValuesText2": "Note que você não pode restaurar Combos de tarefas individuais aqui. Para fazer isso, edite a Tarefa Diária e vá nas Opções Avançadas, onde você encontrará o campo de Restauração de Combos.", @@ -108,7 +108,7 @@ "emailNotifications": "Notificações de E-mail", "wonChallenge": "Você venceu um desafio!", "newPM": "Recebeu Mensagem Privada", - "sentGems": "Sent gems!", + "sentGems": "Gemas enviadas!", "giftedGems": "Gemas Presenteadas", "giftedGemsInfo": "<%= amount %> Gemas - por <%= name %>", "giftedSubscription": "Assinaturas Presenteadas", @@ -148,7 +148,7 @@ "webhooks": "Webhooks", "enabled": "Habilitado", "webhookURL": "URL do Webhook", - "invalidUrl": "invalid url", + "invalidUrl": "url inválida", "invalidEnabled": "the \"enabled\" parameter should be a boolean", "regIdRequired": "RegId is required", "pushDeviceAdded": "Push device added successfully", diff --git a/common/locales/pt/spells.json b/common/locales/pt/spells.json index 1a48277e6b..b18e618b41 100644 --- a/common/locales/pt/spells.json +++ b/common/locales/pt/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "Jogue uma bola de neve em um membro da equipe! O que poderia dar errado? Dura até o novo dia do membro.", "spellSpecialSaltText": "Sal", "spellSpecialSaltNotes": "Alguém jogou uma bola de neve em você. Ha ha, muito engraçado. Agora tire essa neve de mim!", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "Brilhos Assustadores", + "spellSpecialSpookySparklesNotes": "Transforme um amigo em um lençol voador com olhos!", "spellSpecialOpaquePotionText": "Poção Opaca", "spellSpecialOpaquePotionNotes": "Cancela os efeitos de Brilhos Assustadores.", "spellSpecialShinySeedText": "Semente Cintilante", @@ -47,10 +47,10 @@ "spellSpecialSeafoamNotes": "Transforma um amigo em uma criatura marinha!", "spellSpecialSandText": "Areia", "spellSpecialSandNotes": "Cancela os efeitos da espuma do mar.", - "spellNotFound": "Skill \"<%= spellId %>\" not found.", - "partyNotFound": "Party not found", - "targetIdUUID": "\"targetId\" must be a valid User ID.", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", - "spellNotOwned": "You don't own this skill.", - "spellLevelTooHigh": "You must be level <%= level %> to use this skill." + "spellNotFound": "Habilidade \"<%= spellId %>\" não encontrada.", + "partyNotFound": "Equipe não encontrada.", + "targetIdUUID": "\"targetId\" precisa ser um ID de usuário válido.", + "challengeTasksNoCast": "O uso de habilidade em tarefas de desafio não é permitido.", + "spellNotOwned": "Você não possui essa habilidade.", + "spellLevelTooHigh": "Você precisa ter nível <%= level %> para usar essa habilidade." } \ No newline at end of file diff --git a/common/locales/pt/subscriber.json b/common/locales/pt/subscriber.json index 5282895be9..511da225f2 100644 --- a/common/locales/pt/subscriber.json +++ b/common/locales/pt/subscriber.json @@ -4,8 +4,8 @@ "subDescription": "Compre Gemas com ouro, ganhe itens misteriosos mensalmente, mantenha o histórico do progresso, dobre o limite de \"drop\" diário, ajude os desenvolvedores. Clique para mais informações.", "buyGemsGold": "Comprar Gemas com Ouro", "buyGemsGoldText": "Alexander, o Comerciante, venderá a você Gemas por <%= gemCost %> ouro cada gema. Suas entregas mensais são inicialmente limitadas a <%= gemLimit %> Gemas por mês, mas esse limite aumenta em 5 Gemas a cada três meses consecutivos de assinatura , até o máximo de 50 Gemas por mês!", - "mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP", - "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.", + "mustSubscribeToPurchaseGems": "É necessário assinar para comprar gemas com ouro.", + "reachedGoldToGemCap": "Você atingiu o limite de conversão de ouro para gemas <%= convCap %> permitido para esse mês. Nós temos isso para prevenir abuso. O limite irá reiniciar dentro dos três primeiros dias do próximo mês.", "retainHistory": "Guardar Itens adicionais no histórico", "retainHistoryText": "Deixa os Afazeres completos e o histórico de tarefas disponíveis por mais tempo.", "doubleDrops": "Capacidade de drop diário dobrado", @@ -31,7 +31,7 @@ "manageSub": "Clique para gerenciar assinatura", "cancelSub": "Cancelar Assinatura", "canceledSubscription": "Assinatura Cancelada", - "cancelingSubscription": "Canceling the subscription", + "cancelingSubscription": "Cancelando a assinatura.", "adminSub": "Assinaturas Administrativas", "morePlans": "Mais Planos
em Breve", "organizationSub": "Organização Privada", @@ -76,9 +76,9 @@ "timeTravelersPopover": "Notamos que você tem uma Ampulheta Mística, então estamos felizes viajar de volta no tempo por você! Por favor escolha um mascote, montaria ou Conjunto de Itens Misteriosos que deseja. Você pode ver a lista de conjuntos passados aqui! Se esses não te agradarem, talvez você esteja interessado em um de nossos elegantes novos conjuntos futurísticos Steampunk?", "timeTravelersAlreadyOwned": "Parabéns! Você já possui todos os itens que os Viajantes do Tempo oferecem no momento. Obrigado por apoiar o site!", "mysticHourglassPopover": "A Ampulheta Mística permite a compra de certos itens oferecidos por tempo limitado, como os Conjuntos de Itens Misteriosos Mensais e recompensas de Chefões do passado!", - "mysterySetNotFound": "Mystery set not found, or set already owned.", - "mysteryItemIsEmpty": "Mystery items are empty", - "mysteryItemOpened": "Mystery item opened.", + "mysterySetNotFound": "Conjunto misterioso não encontrado, ou conjunto já possuído.", + "mysteryItemIsEmpty": "Itens misteriosos estão vazios.", + "mysteryItemOpened": "Item misterioso aberto.", "mysterySet201402": "Conjunto \"Mensageiro Alado\"", "mysterySet201403": "Conjunto \"Andarilho da Floresta\"", "mysterySet201404": "Conjunto \"Borboleta Crepuscular\"", @@ -105,7 +105,8 @@ "mysterySet201601": "Conjunto \"Campeão de Resolução\"", "mysterySet201602": "Conjunto \"Destruidor de Corações\"", "mysterySet201603": "Conjunto Trevo de 4 Folhas da Sorte", - "mysterySet201604": "Leaf Warrior Set", + "mysterySet201604": "Conjunto Guerreiro de Folha", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Conjunto \"Steampunk Padrão\"", "mysterySet301405": "Conjunto \"Acessórios Steampunk\"", "mysterySetwondercon": "WonderCon", @@ -117,25 +118,25 @@ "hourglassBuyItemConfirm": "Comprar este item por 1 Ampulheta Mística?", "petsAlreadyOwned": "Você já possui este mascote.", "mountsAlreadyOwned": "Você já possui esta montaria.", - "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>", + "typeNotAllowedHourglass": "Tipo de item não permitido para compra com a Ampulheta Mística. Tipos permitidos: <%= allowedTypes %>", "petsNotAllowedHourglass": "Mascore não disponível para compra com Ampulheta Mística.", "mountsNotAllowedHourglass": "Montaria não disponível para compra usando Ampulheta Mística.", "hourglassPurchase": "Você comprou um item usando uma Ampulheta Mística!", "hourglassPurchaseSet": "Você comprou um conjunto de itens usando uma Ampulheta Mística!", - "missingUnsubscriptionCode": "Missing unsubscription code.", - "missingSubscription": "User does not have a plan subscription", + "missingUnsubscriptionCode": "Código de cancelamento de assinatura faltando.", + "missingSubscription": "Usuário não possui um plano de assinatura.", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", "cannotDeleteActiveAccount": "Você possui um cadastro ativo, cancele seu plano antes de deletar sua conta.", - "paymentNotSuccessful": "The payment was not successful", - "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", - "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", - "readCard": "<%= cardType %> has been read", + "paymentNotSuccessful": "O pagamento não foi bem-sucedido.", + "planNotActive": "O plano ainda não foi ativado (devido a um erro com o PayPal). Ele iniciará <%= nextBillingDate %>, depois disso você poderá cancelar para manter seus benefícios completos.", + "notAllowedHourglass": "Mascote/Montaria não disponíveis para compra com a Ampulheta Mística.", + "readCard": "<%= cardType %> foi lida", "cardTypeRequired": "Tipo de cartão requerido.", - "cardTypeNotAllowed": "Unknown card type.", - "invalidCoupon": "Invalid coupon code.", - "couponUsed": "Coupon code already used.", + "cardTypeNotAllowed": "Tipo de carta desconhecido.", + "invalidCoupon": "Código de cupom inválido.", + "couponUsed": "Código de cupom já utilizado.", "noSudoAccess": "Você não possui acesso de administrador.", "couponCodeRequired": "O código do cupom é requerido.", - "eventRequired": "\"req.params.event\" is required.", - "countRequired": "\"req.query.count\" is required." + "eventRequired": "\"req.params.event\" é necessário.", + "countRequired": "\"req.query.count\" é necessário." } \ No newline at end of file diff --git a/common/locales/pt/tasks.json b/common/locales/pt/tasks.json index 28dd55621f..51e877e7e2 100644 --- a/common/locales/pt/tasks.json +++ b/common/locales/pt/tasks.json @@ -73,7 +73,7 @@ "clearTags": "Limpar", "hideTags": "Ocultar", "showTags": "Mostrar", - "toRequired": "You must supply a to value", + "toRequired": "Você precisa fornecer um valor", "startDate": "Data Inicial", "startDateHelpTitle": "Quando esta tarefa deve começar?", "startDateHelp": "Defina a data para a qual esta tarefa entra em vigor. Não será debitada nos dias anteriores.", @@ -89,9 +89,9 @@ "fortifyName": "Poção de Fortificação", "fortifyPop": "Reverte todas tarefas para o valor neutro (cor amarela), e recupera toda Vida perdida.", "fortify": "Fortificar", - "fortifyText": "Fortify will return all your tasks, except challenge tasks, to a neutral (yellow) state, as if you'd just added them, and top your Health off to full. This is great if all your red tasks are making the game too hard, or all your blue tasks are making the game too easy. If starting fresh sounds much more motivating, spend the Gems and catch a reprieve!", + "fortifyText": "A poção fortificante retornará todas as suas tarefas, exceto as tarefas de desafios, para um estado neutro (amarelo), como se você tivesse acabado de adicioná-las, e aumentará sua Saúde para a barra cheia. Isso é ótimo se suas tarefas vermelhas estão deixando o jogo muito difícil, ou se suas tarefas azuis estão deixando o jogo fácil demais. Se começar do zero te parecer mais motivador, gaste suas gemas e ganhe uma moratória!", "confirmFortify": "Tem certeza?", - "fortifyComplete": "Fortify complete!", + "fortifyComplete": "Fortificação completa!", "sureDelete": "Você tem certeza de que deseja deletar <%= taskType %> com o texto \"<%= taskText %>\"?", "streakCoins": "Bônus de Combo!", "pushTaskToTop": "Enviar tarefa para o topo. Segure CTRL ou CMD para enviar para baixo.", @@ -115,17 +115,17 @@ "rewardHelp3": "Equipamentos Especiais irão aparecer durante os Eventos Mundiais.", "rewardHelp4": "Não tenha medo de definir Recompensas Personalizadas! Confira algumas amostras aqui.", "clickForHelp": "Clique para ajuda", - "taskIdRequired": "\"taskId\" must be a valid UUID.", - "taskNotFound": "Task not found.", - "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", - "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", - "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", - "checklistItemNotFound": "No checklist item was found with given id.", - "itemIdRequired": "\"itemId\" must be a valid UUID.", - "tagNotFound": "No tag item was found with given id.", - "tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.", - "positionRequired": "\"position\" is required and must be a number.", - "cantMoveCompletedTodo": "Can't move a completed todo.", + "taskIdRequired": "\"taskId\" precisa ser um UUID válido.", + "taskNotFound": "Tarefa não encontrada.", + "invalidTaskType": "O tipo de tarefa precisa ser um de \"habit\", \"daily\", \"todo\", \"reward\".", + "cantDeleteChallengeTasks": "Uma tarefa que pertença a um desafio não pode ser deletada.", + "checklistOnlyDailyTodo": "Listas só são suportadas em tarefas diárias e afazeres.", + "checklistItemNotFound": "Nenhum item de lista foi encontrado com o id dado.", + "itemIdRequired": "\"itemId\" precisa ser um UUID válido.", + "tagNotFound": "Nenhum item de etiqueta foi encontrado com esse id.", + "tagIdRequired": "\"tagId\" precisa ser um UUID válido correspondente a uma etiqueta pertencente ao usuário.", + "positionRequired": "\"position\" é necessária e precisa ser um número.", + "cantMoveCompletedTodo": "Não é possível mover um afazer concluído.", "directionUpDown": "\"direction\" is required and must be 'up' or 'down'", - "alreadyTagged": "The task is already tagged with given tag." + "alreadyTagged": "A tarefa fá foi etiquetada com essa etiqueta." } \ No newline at end of file diff --git a/common/locales/ro/contrib.json b/common/locales/ro/contrib.json index 0314472091..b7a04f2018 100644 --- a/common/locales/ro/contrib.json +++ b/common/locales/ro/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Vizitează Hala Eroilor (contribuitori și susținători)", "conLearn": "Află mai mult despre răsplățile de contribuitor", "conLearnHow": "Află cum să contribui la Habitica", - "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.", - "surveysMultiple": "Helped Habitica grow by filling out <%= surveys %> surveys. There are no active surveys.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Current Survey", "surveyWhen": "The badge will be awarded to all participants when surveys have been processed, in late March.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/ro/gear.json b/common/locales/ro/gear.json index fb27992b71..7f85401369 100644 --- a/common/locales/ro/gear.json +++ b/common/locales/ro/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/ro/subscriber.json b/common/locales/ro/subscriber.json index 88bf2be5d6..12ecc776df 100644 --- a/common/locales/ro/subscriber.json +++ b/common/locales/ro/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/ru/contrib.json b/common/locales/ru/contrib.json index 6904af79fd..7f5995e042 100644 --- a/common/locales/ru/contrib.json +++ b/common/locales/ru/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Посетите Зал героев (участники и спонсоры)", "conLearn": "Подробнее о наградах для участников", "conLearnHow": "Как внести свой вклад в Habitica", - "surveysSingle": "Помог развитию Habitica, заполнив опрос. Сейчас нет опросов для заполнения.", - "surveysMultiple": "Помог развитию HabitPRG, заполнив опросы: <%= surveys %>. Сейчас нет опросов для заполнения.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Текущий опрос", "surveyWhen": "Участники получат этот значок после обработки опросов, в конце марта.", "blurbInbox": "Здесь хранятся ваши личные сообщения! Вы можете послать кому-нибудь сообщение, нажав на иконку конверта рядом с их именем в Таверне, Команде или в чате Гильдии. Если вы получили неуместное сообщение, вам следует послать скриншот этого сообщения Lemoness (leslie@habitica.com).", diff --git a/common/locales/ru/gear.json b/common/locales/ru/gear.json index c201c03905..b61f9243be 100644 --- a/common/locales/ru/gear.json +++ b/common/locales/ru/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "Этот костюм пошит из тысяч четырехлистных клеверов! Бонусов не дают. Подарок подписчикам марта 2016.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Стимпанковский костюм", "armorMystery301404Notes": "Чудной и лихой! Бонусов не дает. Подарок подписчикам февраля 3015.", "armorArmoireLunarArmorText": "Лунные доспехи успокоения", @@ -573,6 +575,8 @@ "headMystery201603Notes": "Эта шляпа - магический амулет, приносящий удачу. Бонусов не дает. Подарок подписчикам марта 2016.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Модный цилиндр", "headMystery301404Notes": "Модный цилиндр для самых уважаемых господ! Подарок подписчикам января 3015. Бонусов не дает.", "headMystery301405Text": "Обычный цилиндр", diff --git a/common/locales/ru/maintenance.json b/common/locales/ru/maintenance.json index 55dd97dba2..dbae0c703f 100644 --- a/common/locales/ru/maintenance.json +++ b/common/locales/ru/maintenance.json @@ -22,13 +22,13 @@ "maintenanceInfoHowLong": "Как долго займет техническое обслуживание?", "maintenanceInfoHowLongText": "Мы должны перенести задачи и данные всех пользователей Habitica – а их 1,3 миллиона – нелегкая задача! Мы ожидаем, что ее выполнение займет несколько часов: с 13 часов Тихоокеанского времени (20 часов по UTC) до 22 часов Тихоокеанского времени (5 часов утра следующего дня по UTC). Мы делаем все возможное, чтобы перенос завершился максимально быстро! Вы сможете следить за новостями в нашем Twitter. ", "maintenanceInfoStatsAffected": "Как обслуживание повлияет на мои ежедневные задания, серии, баффы и квесты?", - "maintenanceInfoStatsAffectedText1": "Вы НЕ получите урона, а серии ежедневных заданий не будут потеряны в эти выходные, но во всем остальном смена для пройдет как обычно! Отметки о выполнении ежедневных заданий будут сброшены, как и баффы и пр. Если вы участвуете в квесте ваш прогресс будет учтен: вы найдете предметы или нанесете урон боссу, но босс не причинит вреда вам (даже монстрам иногда нужен перерыв!) ", + "maintenanceInfoStatsAffectedText1": "Вы НЕ получите урона, а серии ежедневных заданий не будут потеряны в эти выходные, но во всем остальном смена дня пройдет как обычно! Отметки о выполнении ежедневных заданий будут сброшены, как и баффы и пр. Если вы участвуете в квесте ваш прогресс будет учтен: вы найдете предметы или нанесете урон боссу, но босс не причинит вреда вам (даже монстрам иногда нужен перерыв!) ", "maintenanceInfoStatsAffectedText2": "После долгих раздумий мы пришли к выводу, что это самый честный путь решения проблемы, что многие пользователи во время технического обслуживания не смогут отметить свои ежедневные задания, как обычно. Мы приносим извинения за неудобства!", "maintenanceInfoSeeTasks": "Что, если мне будет нужно посмотреть мой список дел?", "maintenanceInfoSeeTasksText": "Если вы знаете, что список дел вам понадобится в Субботу, мы рекомендуем сделать скриншот до начала технического обслуживания и распечатать его. Так вы сможете иметь под рукой список заданий, если понадобится напомнить себе о том, что надо сделать.", "maintenanceInfoRarePet": "Какого редкого питомца я получу?", "maintenanceInfoRarePetText": "В благодарность за ваше терпение в то время, что Habitica будет недоступна, каждый пользователь получит редкого питомца-ветерана. Если вы никогда прежде не получали питомца-ветерана, у вас появится волк-ветеран. Если он у вас уже есть, тогда к нему добавится тигр-ветеран. Если же и он у вас уже есть, вы получите не-виданного-доселе питомца-ветерана! После завершения переноса данных может пройти несколько часов, прежде чем ваш новый питомец появится в Стойлах, но не вздумайте волноваться – у всех будет по новому питомцу!", "maintenanceInfoWho": "Кто работал над таким масштабным проектом?", - "maintenanceInfoWhoText": "Мы рады, что вы спросили! Ее возглавлял наш великолепный разработчик paglias, а неоценимую помощь ему оказали Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown и Alys.", + "maintenanceInfoWhoText": "Мы рады, что вы спросили! Его возглавлял наш великолепный разработчик paglias, а неоценимую помощь ему оказали Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown и Alys.", "maintenanceInfoTesting": "Новую версию также без устали тестировали активисты из числа наших великолепных участников разработки open-source. Спасибо! Мы бы не справились без вас." } diff --git a/common/locales/ru/subscriber.json b/common/locales/ru/subscriber.json index e380649b97..d961c5e74d 100644 --- a/common/locales/ru/subscriber.json +++ b/common/locales/ru/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Набор Сердцееда", "mysterySet201603": "Набор Счастливого Клевера", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Стандартный Стимпанковый набор", "mysterySet301405": "Набор аксессуаров в стиле Стимпанка", "mysterySetwondercon": "Вандер-Кон", diff --git a/common/locales/sk/contrib.json b/common/locales/sk/contrib.json index ac6393f2e9..1dd10c6382 100644 --- a/common/locales/sk/contrib.json +++ b/common/locales/sk/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Navštív Sieň hrdinov (prispievatelia a podporovatelia)", "conLearn": "Zisti viac o prispievateľských odmenách", "conLearnHow": "Zisti ako prispieť do Habitica", - "surveysSingle": "Pomohol Habitice rásť vyplnením dotazníka. Momentálne nie sú žiadne aktívne dotazníky.", - "surveysMultiple": "Pomohol Habitice rásť vyplnením dotazníka <%= surveys %> krát. Momentálne nie sú žiadne aktívne dotazníky. ", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Aktuálny dotazník", "surveyWhen": "Odznak dostanú všetci účastníci najneskôr v marci po spracovaní dotazníkov.", "blurbInbox": "Tu sú uchované tvoje súkromné správy! Môžeš niekomu poslať správu kliknutím na ikonu obálky pri jeho mene v chate v hostinci, družine alebo cechu. Ak si dostal nevhodnú súkromnú správu, mal by si mailom odoslať screenshot tejto správy hráčovi Lemoness (leslie@habitica.com)", diff --git a/common/locales/sk/gear.json b/common/locales/sk/gear.json index 55cad03973..9a00998023 100644 --- a/common/locales/sk/gear.json +++ b/common/locales/sk/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/sk/generic.json b/common/locales/sk/generic.json index 79eb1424d5..5d423bc44f 100644 --- a/common/locales/sk/generic.json +++ b/common/locales/sk/generic.json @@ -147,9 +147,9 @@ "greetingCardNotes": "Pošli pozdrav členovi družiny.", "greeting0": "Ahoj!", "greeting1": "Len som ťa prišiel pozdraviť :)", - "greeting2": "`waves frantically`", + "greeting2": "´horúčkovito máva´", "greeting3": "Hej ty!", - "greetingCardAchievementTitle": "Čerešňový kamarát", + "greetingCardAchievementTitle": "Veselý kamarát", "greetingCardAchievementText": "Hej! Ahoj! Nazdar! Poslal si alebo získal <%= cards %> pozdravov.", "thankyouCard": "Ďakovná pohľadnica", "thankyouCardExplanation": "Obaja získavate odznak Veľmi vďačný!", diff --git a/common/locales/sk/maintenance.json b/common/locales/sk/maintenance.json index ac1c49b19b..cf554b51b7 100644 --- a/common/locales/sk/maintenance.json +++ b/common/locales/sk/maintenance.json @@ -26,9 +26,9 @@ "maintenanceInfoStatsAffectedText2": "After a lot of thought, our team concluded that this was the most fair way to handle the fact that many users will not be able to check off their Dailies normally during the maintenance. We’re sorry for any inconvenience this causes!", "maintenanceInfoSeeTasks": "What if I need to see my task list?", "maintenanceInfoSeeTasksText": "If you know that you will need to see your task list on Saturday to remind yourself what you have to do, we recommend that before the maintenance begins, you take a screenshot of your tasks so that you can use it as a reference.", - "maintenanceInfoRarePet": "What kind of rare pet will I receive?", + "maintenanceInfoRarePet": "Aký druh vzácneho zvieratka dostanem?", "maintenanceInfoRarePetText": "To thank you for your patience during the downtime, everyone will get a rare Veteran Pet. If you’ve never received a Veteran Pet before, you will receive a Veteran Wolf. If you already have a Veteran Wolf, you will receive a Veteran Tiger. And if you already have a Veteran Wolf and a Veteran Tiger, you will receive a never-before-seen Veteran pet! After the migration is completed, it may take several hours for your pet to show up, but never fear, everyone will get one.", - "maintenanceInfoWho": "Who worked on this massive project?", + "maintenanceInfoWho": "Kto pracuje na tomto obrovskom projekte?", "maintenanceInfoWhoText": "We’re glad you asked! It was spearheaded by our amazing contributor paglias, with lots of help from Blade, TheHollidayInn, SabreCat, Victor Pudeyev, TheUnknown, and Alys.", "maintenanceInfoTesting": "The new version was also tirelessly tested by a bunch of our amazing open-source volunteers. Thank you -- we couldn't have done this without you." } diff --git a/common/locales/sk/messages.json b/common/locales/sk/messages.json index b8b7e9210b..6495f66c47 100644 --- a/common/locales/sk/messages.json +++ b/common/locales/sk/messages.json @@ -29,9 +29,9 @@ "messageFoundQuest": "Našiel si zvitok s popisom výpravy: \"<%= questText %>\"!", "messageAlreadyPurchasedGear": "Tento výstroj si si kúpil v minulosti, ale momentálne ho nevlastníš. Môžeš si ho kúpiť znovu v stĺpci s odmenami na stránke s úlohami.", "messageAlreadyOwnGear": "Tento predmet už vlastníš. Vyzbroj sa ním na stránke s výstrojom. ", - "armoireEquipment": "<%= image %> Našiel si časť vzácneho výstroja v zbrojnici <%= dropText %>! Úžasné!", - "armoireFood": "<%= image %> Prehrabával si sa v zbroji a našiel si <%= dropArticle %><%= dropText %>. Čo to tam robí?", - "armoireExp": "Zápasil si so zbrojou a získal si skúsenosti. Tu máš!", + "armoireEquipment": "<%= image %> Našiel si časť vzácneho výstroja v skrini: <%= dropText %>! Úžasné!", + "armoireFood": "<%= image %> Prehrabával si sa v skrini a našiel si <%= dropArticle %><%= dropText %>. Čo to tam robí?", + "armoireExp": "Zápasil si so skriňou a získal si skúsenosti. Tu máš!", "messageInsufficientGems": "Nemáš dostatok drahokamov!", "messageAuthPasswordMustMatch": ":heslo a :potvrďHeslo sa nezhodujú", "messageAuthCredentialsRequired": ":používateľskéMeno, :email, :heslo, :potvrďHeslo sú požadované", diff --git a/common/locales/sk/subscriber.json b/common/locales/sk/subscriber.json index 2a82b5e58c..6411d937ea 100644 --- a/common/locales/sk/subscriber.json +++ b/common/locales/sk/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/sr/contrib.json b/common/locales/sr/contrib.json index c310d7f9c5..c0b6059364 100644 --- a/common/locales/sr/contrib.json +++ b/common/locales/sr/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Posetiti Dvoranu Heroja (saradnici i sponzori)", "conLearn": "Saznajte više o nagradima za saradnike", "conLearnHow": "Saznajte kako da date svoj doprinos Habitica-u", - "surveysSingle": "Pomogao Habitica-u popunjavanjem ankete. Trenutno nema anketa.", - "surveysMultiple": "Pomogao Habitica-u popunjavanjem <%= surveys %> ankete/a. Trenutno nema anketa.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Nova anketa", "surveyWhen": "Odlikovanje se dodeljuje učesnicima nakon što se ankete obrade, krajem marta.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/sr/gear.json b/common/locales/sr/gear.json index 855b9ad49c..f9a420385f 100644 --- a/common/locales/sr/gear.json +++ b/common/locales/sr/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Stimpank odelo", "armorMystery301404Notes": "Kicoško i zanosno! Ne daje nikakav bonus. Predmet za pretplatnike februar 3015..", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Otmeni cilindar", "headMystery301404Notes": "Otmeni cilindar za pripadnike visokog društva! Predmet za pretplatnike januar 3015. Ne daje nikakav bonus.", "headMystery301405Text": "Jednostavni cilindar", diff --git a/common/locales/sr/subscriber.json b/common/locales/sr/subscriber.json index dc5c6db6a5..c4f8acd3e4 100644 --- a/common/locales/sr/subscriber.json +++ b/common/locales/sr/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/sv/contrib.json b/common/locales/sv/contrib.json index c1117cde8f..f381542488 100644 --- a/common/locales/sv/contrib.json +++ b/common/locales/sv/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Besök Hjältarnas Hall (medarbetare och hjälpare)", "conLearn": "Lär dig mer om belöningar för medarbetare", "conLearnHow": "Lär dig hur du kan bidra till Habitica", - "surveysSingle": "Hjälpte Habitica växa genom att delta i en undersökning. Det finns inga aktiva undersökningar just nu.", - "surveysMultiple": "Hjälpte Habitica växa genom att delta i <%= surveys %> undersökningar. Det finns inga aktiva undersökningar just nu.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Nuvarande undersökning", "surveyWhen": "Detta emblem kommer delas ut till alla deltagare när undersökningarna har bearbetats, sent i Mars.", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/sv/gear.json b/common/locales/sv/gear.json index 35ad747326..4c2a5a8338 100644 --- a/common/locales/sv/gear.json +++ b/common/locales/sv/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk-dräkt", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Stilig cylinderhatt", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Vanlig cylinderhatt", diff --git a/common/locales/sv/subscriber.json b/common/locales/sv/subscriber.json index 5b6ad41dbc..2658b0109b 100644 --- a/common/locales/sv/subscriber.json +++ b/common/locales/sv/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/uk/contrib.json b/common/locales/uk/contrib.json index d6bd2a3f25..369a989a9c 100644 --- a/common/locales/uk/contrib.json +++ b/common/locales/uk/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "Відвідати Залу Героїв (вкладники й меценати)", "conLearn": "Довідатися більше про нагороди вкладникам", "conLearnHow": "Довідатися, як стати співавтором Habitica", - "surveysSingle": "Helped Habitica grow by filling out a survey. There are no active surveys.", - "surveysMultiple": "Допомагає Habitica зростати, заповнивши <%= surveys %> опитувань. Наразі активних опитувань немає.", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "Поточне опитування", "surveyWhen": "Усіх учасників буде нагороджено відзнакою, коли опитування буде оброблено, наприкінці березня", "blurbInbox": "This is where your private messages are stored! You can send someone a message by clicking on the envelope icon next to their name in Tavern, Party, or Guild Chat. If you've received an inappropriate PM, you should email a screenshot of it to Lemoness (leslie@habitica.com)", diff --git a/common/locales/uk/gear.json b/common/locales/uk/gear.json index a41734128b..3c81def29f 100644 --- a/common/locales/uk/gear.json +++ b/common/locales/uk/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "Steampunk Suit", "armorMystery301404Notes": "Dapper and dashing, wot! Confers no benefit. February 3015 Subscriber Item.", "armorArmoireLunarArmorText": "Soothing Lunar Armor", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", diff --git a/common/locales/uk/subscriber.json b/common/locales/uk/subscriber.json index b58a656c5a..da10322f29 100644 --- a/common/locales/uk/subscriber.json +++ b/common/locales/uk/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "Heartbreaker Set", "mysterySet201603": "Lucky Clover Set", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySetwondercon": "Wondercon", diff --git a/common/locales/zh/backgrounds.json b/common/locales/zh/backgrounds.json index 7800ea59cd..f29670cd3a 100644 --- a/common/locales/zh/backgrounds.json +++ b/common/locales/zh/backgrounds.json @@ -164,8 +164,8 @@ "backgrounds052016": "第24组:2016年5月推出", "backgroundBeehiveText": "蜂窝", "backgroundBeehiveNotes": "在蜂窝里嗡嗡嗡地跳舞", - "backgroundGazeboText": "Gazebo", - "backgroundGazeboNotes": "Battle a Gazebo.", - "backgroundTreeRootsText": "Tree Roots", - "backgroundTreeRootsNotes": "Explore the Tree Roots." + "backgroundGazeboText": "亭子", + "backgroundGazeboNotes": "攻击一个亭子", + "backgroundTreeRootsText": "树根", + "backgroundTreeRootsNotes": "探索茂密的树根" } \ No newline at end of file diff --git a/common/locales/zh/character.json b/common/locales/zh/character.json index 668c2404f8..52ef6637dc 100644 --- a/common/locales/zh/character.json +++ b/common/locales/zh/character.json @@ -110,7 +110,7 @@ "mage": "法师", "mystery": "神秘", "changeClass": "更改职业,重新分配属性点", - "lvl10ChangeClass": "To change class you must be at least level 10.", + "lvl10ChangeClass": "您必须达到等级10才可以更换职业。", "levelPopover": "每一级你可以获得一个可自由分配的属性点。你可以手动分配,或者让系统为你自动分配。", "unallocated": "未分配的属性点", "haveUnallocated": "你有 <%= points %> 点未分配的属性点", @@ -167,6 +167,6 @@ "showQuickAllocation": "显示属性分配", "hideQuickAllocation": "隐藏属性分配", "quickAllocationLevelPopover": "每个等级获得一点属性点供你分配。你可以手工分配或者用一种自动分配选项让游戏帮你决定,在 玩家 -> 角色属性及成就 中选择。", - "invalidAttribute": "\"<%= attr %>\" is not a valid attribute.", - "notEnoughAttrPoints": "You don't have enough attribute points." + "invalidAttribute": "\"<%= attr %>\" 不是一个有效的属性点分配数字。", + "notEnoughAttrPoints": "您没有足够的属性点点数。" } \ No newline at end of file diff --git a/common/locales/zh/content.json b/common/locales/zh/content.json index 5ea8474b0a..637e98cd66 100644 --- a/common/locales/zh/content.json +++ b/common/locales/zh/content.json @@ -116,9 +116,9 @@ "questEggFalconText": "猎鹰", "questEggFalconMountText": "猎鹰", "questEggFalconAdjective": "一只迅捷的", - "questEggTreelingText": "Treeling", - "questEggTreelingMountText": "Treeling", - "questEggTreelingAdjective": "a leafy", + "questEggTreelingText": "小树芽", + "questEggTreelingMountText": "小树芽", + "questEggTreelingAdjective": "一只叶子般的", "eggNotes": "将一瓶孵化药水倒在这个宠物蛋上,你就能孵化出一只<%= eggAdjective(locale) %><%= eggText(locale) %>。", "hatchingPotionBase": "普通", "hatchingPotionWhite": "白色", @@ -132,7 +132,7 @@ "hatchingPotionGolden": "金色", "hatchingPotionSpooky": "阴森森的", "hatchingPotionPeppermint": "薄荷", - "hatchingPotionFloral": "Floral", + "hatchingPotionFloral": "草花", "hatchingPotionNotes": "把它倒在宠物蛋上可以孵化出一只<%= potText(locale) %>宠物。", "premiumPotionAddlNotes": "无法在任务奖励宠物蛋上使用", "foodMeat": "肉", diff --git a/common/locales/zh/contrib.json b/common/locales/zh/contrib.json index d690cc6405..ea00e8056a 100644 --- a/common/locales/zh/contrib.json +++ b/common/locales/zh/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "前往英雄殿堂 (贡献者和支持者之殿)", "conLearn": "查看更多关于贡献奖励的信息", "conLearnHow": "如何为Habitica作出贡献", - "surveysSingle": "为了帮助Habitica成长而填写了一份问卷。现在没有任何可填的问卷", - "surveysMultiple": "为了帮助Habitica成长而填写了 <%= surveys %> 份问卷。现在没有任何可填的问卷", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "现在的问卷", "surveyWhen": "那个章将会在三约尾给所有参与过问卷的玩家", "blurbInbox": "这是你的私人邮件箱!你能在酒馆、队伍或者工会聊天中通过点击其他人名字边上的邮件图标给他们发送信息。如果你收到了一封不合适的私人消息,请截图发给柠檬小姐(leslie@habitica.com)。", diff --git a/common/locales/zh/front.json b/common/locales/zh/front.json index 8f5f1794f8..fce8280728 100644 --- a/common/locales/zh/front.json +++ b/common/locales/zh/front.json @@ -228,17 +228,17 @@ "altAttrSlack": "Slack", "missingAuthHeaders": "Missing authentication headers.", "missingAuthParams": "Missing authentication parameters.", - "missingUsernameEmail": "Missing username or email.", - "missingEmail": "Missing email.", - "missingUsername": "Missing username.", - "missingPassword": "Missing password.", - "missingNewPassword": "Missing new password.", - "wrongPassword": "Wrong password.", - "notAnEmail": "Invalid email address.", - "emailTaken": "Email address is already used in an account.", - "newEmailRequired": "Missing new email address.", - "usernameTaken": "Username already taken.", - "passwordConfirmationMatch": "Password confirmation doesn't match password.", + "missingUsernameEmail": "缺少用户名或邮箱。", + "missingEmail": "缺少电子邮件地址。", + "missingUsername": "缺少用户名。", + "missingPassword": "缺少密码。", + "missingNewPassword": "缺少新密码。", + "wrongPassword": "密码错误。", + "notAnEmail": "无效的电子邮件地址。", + "emailTaken": "邮件地址已经在现有账号中存在", + "newEmailRequired": "缺少新的邮件地址", + "usernameTaken": "用户名已被使用", + "passwordConfirmationMatch": "密码不匹配", "invalidLoginCredentials": "Incorrect username and/or email and/or password.", "passwordReset": "If we have your email on file, your password reset link has been sent to your email.", "passwordResetEmailSubject": "Password Reset for Habitica", diff --git a/common/locales/zh/gear.json b/common/locales/zh/gear.json index e3a3a64c0a..d833f4512c 100644 --- a/common/locales/zh/gear.json +++ b/common/locales/zh/gear.json @@ -1,5 +1,5 @@ { - "set": "组", + "set": "套装", "weapon": "武器", "weaponBase0Text": "没有武器", "weaponBase0Notes": "没有武器。", @@ -368,6 +368,8 @@ "armorMystery201603Notes": "这件衣服是成千上万的四叶草缝成的!没有属性加成。2016年3月捐赠者物品。", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "蒸汽朋克套装", "armorMystery301404Notes": "整洁又精神,真聪明!没有属性加成。3015年2月订阅者物品", "armorArmoireLunarArmorText": "静月护甲", @@ -573,6 +575,8 @@ "headMystery201603Notes": "这顶大礼帽有着神奇的好运魔力。没有属性加成。2016年3月捐赠者物品。", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "华丽礼帽", "headMystery301404Notes": "上流社会佼佼者的华丽礼帽!3015年1月捐赠者物品。没有属性加成。", "headMystery301405Text": "基础礼帽", @@ -683,7 +687,7 @@ "shieldSpecialWinter2015HealerText": "抚慰之盾", "shieldSpecialWinter2015HealerNotes": "这块盾牌偏转了刺骨的寒风。增加<%= con %>点体质。2014-2015限定版装备。", "shieldSpecialSpring2015RogueText": "霹雳爆破管", - "shieldSpecialSpring2015RogueNotes": "Don't let the sound fool you - these explosives pack a punch. Increases Strength by <%= str %>. Limited Edition 2015 Spring Gear.", + "shieldSpecialSpring2015RogueNotes": "别让这些声音愚弄你 - 这些炸药威力巨大。 增加力量 <%= str %> 点。2015年限量版弹簧装置套装。", "shieldSpecialSpring2015WarriorText": "菜盘子铁饼", "shieldSpecialSpring2015WarriorNotes": "向你的敌人掷去……或者只是单纯地拿着它,因为到了晚饭时间它就会装满好吃的粗磨粉狗粮啦。提高<%= con %>点体质。2015春季限定版装备。", "shieldSpecialSpring2015HealerText": "印花枕头", @@ -727,7 +731,7 @@ "shieldArmoireMysticLampText": "神秘的灯", "shieldArmoireMysticLampNotes": "用神秘的灯照亮黑暗的洞穴!增加感知<%= per %>点。魔法衣橱:独立装备。", "shieldArmoireFloralBouquetText": "Bouquet o' Flowers", - "shieldArmoireFloralBouquetNotes": "Not much help in battle, but aren't they beautiful? Increases Constitution by <%= con %>. Enchanted Armoire: Independent Item.", + "shieldArmoireFloralBouquetNotes": "在战斗中没有什么用处。但是它们很漂亮,不是吗?提升<%= con %>点体质。魔法衣橱:独立装备。", "back": "背部挂件", "backBase0Text": "没有背部挂件", "backBase0Notes": "没有背部挂件。", @@ -832,20 +836,20 @@ "eyewear": "眼镜", "eyewearBase0Text": "没有眼镜", "eyewearBase0Notes": "没有眼镜。", - "eyewearSpecialBlackTopFrameText": "Black Standard Eyeglasses", - "eyewearSpecialBlackTopFrameNotes": "Glasses with a black frame above the lenses. Confers no benefit.", - "eyewearSpecialBlueTopFrameText": "Blue Standard Eyeglasses", - "eyewearSpecialBlueTopFrameNotes": "Glasses with a blue frame above the lenses. Confers no benefit.", - "eyewearSpecialGreenTopFrameText": "Green Standard Eyeglasses", - "eyewearSpecialGreenTopFrameNotes": "Glasses with a green frame above the lenses. Confers no benefit.", - "eyewearSpecialPinkTopFrameText": "Pink Standard Eyeglasses", - "eyewearSpecialPinkTopFrameNotes": "Glasses with a pink frame above the lenses. Confers no benefit.", - "eyewearSpecialRedTopFrameText": "Red Standard Eyeglasses", - "eyewearSpecialRedTopFrameNotes": "Glasses with a red frame above the lenses. Confers no benefit.", - "eyewearSpecialWhiteTopFrameText": "White Standard Eyeglasses", - "eyewearSpecialWhiteTopFrameNotes": "Glasses with a white frame above the lenses. Confers no benefit.", - "eyewearSpecialYellowTopFrameText": "Yellow Standard Eyeglasses", - "eyewearSpecialYellowTopFrameNotes": "Glasses with a yellow frame above the lenses. Confers no benefit.", + "eyewearSpecialBlackTopFrameText": "黑色标准眼睛", + "eyewearSpecialBlackTopFrameNotes": "有着黑色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialBlueTopFrameText": "蓝色标准眼睛", + "eyewearSpecialBlueTopFrameNotes": "有着蓝色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialGreenTopFrameText": "绿色标准眼睛", + "eyewearSpecialGreenTopFrameNotes": "有着绿色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialPinkTopFrameText": "粉色标准眼睛", + "eyewearSpecialPinkTopFrameNotes": "有着粉色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialRedTopFrameText": "红色标准眼睛", + "eyewearSpecialRedTopFrameNotes": "有着红色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialWhiteTopFrameText": "白色标准眼睛", + "eyewearSpecialWhiteTopFrameNotes": "有着白色眼镜框的眼睛。没有属性加成。", + "eyewearSpecialYellowTopFrameText": "黄色标准眼睛", + "eyewearSpecialYellowTopFrameNotes": "有着黄色眼镜框的眼睛。没有属性加成。", "eyewearSpecialSummerRogueText": "流氓眼罩", "eyewearSpecialSummerRogueNotes": "即使是无赖也能看出这个眼罩有多时髦!没有增益效果。限定版2014夏季装备。", "eyewearSpecialSummerWarriorText": "时髦眼罩", diff --git a/common/locales/zh/groups.json b/common/locales/zh/groups.json index 6e5e39f30c..0f28f8abf2 100644 --- a/common/locales/zh/groups.json +++ b/common/locales/zh/groups.json @@ -100,14 +100,14 @@ "unblock": "解锁", "pm-reply": "发送回复", "inbox": "收件箱", - "messageRequired": "A message is required.", - "toUserIDRequired": "A User ID is required", - "gemAmountRequired": "A number of gems is required", - "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.", - "privateMessageGiftIntro": "Hello <%= receiverName %>, <%= senderName %> has sent you", - "privateMessageGiftGemsMessage": "<%= gemAmount %> gems!", - "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription!", - "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.", + "messageRequired": "请填写信息", + "toUserIDRequired": "需要用户ID", + "gemAmountRequired": "需要指明宝石数量", + "notAuthorizedToSendMessageToThisUser": "无法向该用户发送消息", + "privateMessageGiftIntro": "<%= receiverName %> 您好!<%= senderName %> 给了您", + "privateMessageGiftGemsMessage": "<%= gemAmount %> 宝石!", + "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> 个月的捐助!", + "cannotSendGemsToYourself": "无法向自己发送宝石。使用捐助功能试试看", "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.", "abuseFlag": "举报违反社区准则的用户", "abuseFlagModalHeading": "举报 <%= name %> 违规", @@ -163,15 +163,15 @@ "partyUpAchievement": "和另一个人一起加入一个队伍!愉快的去打怪兽并互相支持,玩的愉快。", "partyOnAchievement": "加入一个队伍,至少4人!享受你肩上扛着的不断增加的对伙伴的承诺,和你的朋友一起打败你的敌人!", "groupIdRequired": "\"groupId\" must be a valid UUID", - "groupNotFound": "Group not found.", + "groupNotFound": "找不到小组", "groupTypesRequired": "You must supply a valid \"type\" query string.", "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", "onlyLeaderCanRemoveMember": "Only group leader can remove a member!", - "memberCannotRemoveYourself": "You cannot remove yourself!", + "memberCannotRemoveYourself": "你不能移除自己!", "groupMemberNotFound": "User not found among group's members", "mustBeGroupMember": "Must be member of the group.", - "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", + "keepOrRemoveAll": "req.query.keep 必须是 \"keep-all\" 或者 \"remove-all\"", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", "inviteMissingEmail": "邀请中缺失电子邮箱地址。", diff --git a/common/locales/zh/limited.json b/common/locales/zh/limited.json index 2dae63db3e..f31d87c1cf 100644 --- a/common/locales/zh/limited.json +++ b/common/locales/zh/limited.json @@ -5,7 +5,7 @@ "annoyingFriends": "吵闹的朋友", "annoyingFriendsText": "被队员的雪球砸中<%= snowballs %>次。", "alarmingFriends": "惊人的朋友", - "alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.", + "alarmingFriendsText": "受到<%= spookySparkles %>次来自队友的惊吓。", "agriculturalFriends": "农民同志", "agriculturalFriendsText": "被队友变成花朵<%= seeds %>次。", "aquaticFriends": "水族朋友", diff --git a/common/locales/zh/npc.json b/common/locales/zh/npc.json index 924df3e038..53a96a1df0 100644 --- a/common/locales/zh/npc.json +++ b/common/locales/zh/npc.json @@ -29,7 +29,7 @@ "invalidPetName": "无效的宠物名提供。", "missingEggHatchingPotionHatch": "\"egg\"和\"hatchingPotion\"是必须的参数。", "invalidTypeEquip": "\"type\"必须是'equipped',,'pet', 'mount', 'costume'中的一个。", - "mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.", + "mustPurchaseToSet": "必须购买 <%= val %>来把它使用在<%= key %>上。", "typeRequired": "需要Type", "keyRequired": "需要Key", "notAccteptedType": "Type必须在 [eggs, hatchingPotions, food, quests, gear] 中", diff --git a/common/locales/zh/pets.json b/common/locales/zh/pets.json index bac6485879..5a72b09dbc 100644 --- a/common/locales/zh/pets.json +++ b/common/locales/zh/pets.json @@ -12,7 +12,7 @@ "etherealLion": "灵狮", "veteranWolf": "退伍军狼", "veteranTiger": "退伍老虎", - "veteranLion": "Veteran Lion", + "veteranLion": "老兵狮子", "cerberusPup": "地狱小狗", "hydra": "三头蛇", "mantisShrimp": "虾蛄", diff --git a/common/locales/zh/quests.json b/common/locales/zh/quests.json index 55c254eb5b..1b61891dea 100644 --- a/common/locales/zh/quests.json +++ b/common/locales/zh/quests.json @@ -89,11 +89,11 @@ "questAlreadyUnderway": "你的队伍已经开始一个探索任务。当前探索任务结束后请再试一次。", "questAlreadyAccepted": "你已经接收探索任务邀请。", "noActiveQuestToLeave": "没有正进行的探索任务来退出", - "questLeaderCannotLeaveQuest": "Quest leader cannot leave quest", - "notPartOfQuest": "You are not part of the quest", + "questLeaderCannotLeaveQuest": "任务业主不能放弃任务", + "notPartOfQuest": "您不是任务的一部分", "noActiveQuestToAbort": "There is no active quest to abort.", "onlyLeaderAbortQuest": "Only the group or quest leader can abort a quest.", - "questAlreadyRejected": "You already rejected the quest invitation.", + "questAlreadyRejected": "您已经拒绝了任务邀请。", "cantCancelActiveQuest": "You can not cancel an active quest, use the abort functionality.", "onlyLeaderCancelQuest": "Only the group or quest leader can cancel the quest.", "questNotPending": "There is no quest to start.", diff --git a/common/locales/zh/settings.json b/common/locales/zh/settings.json index 8919e12068..d8c734165c 100644 --- a/common/locales/zh/settings.json +++ b/common/locales/zh/settings.json @@ -47,7 +47,7 @@ "customDayStart": "自定义起始日期", "changeCustomDayStart": "变更自定义起始日期?", "sureChangeCustomDayStart": "你确定要变更自定义起始日期吗?", - "customDayStartHasChanged": "Your custom day start has changed.", + "customDayStartHasChanged": "你自定义的每日开始时间已经改变。", "nextCron": "从你第一次使用Habitica<%= time %>之后,你的每日任务将会重置。请确保你在这之前已经完成了所有每日任务!", "customDayStartInfo1": "在你所处地区时间每天凌晨12点,Habitica默认核对并重置你的每日任务。你可以在此变更任务重置时间。", "misc": "其他", @@ -62,7 +62,7 @@ "newUsername": "新的登录名", "dangerZone": "危险区域", "resetText1": "警告!这会重置你角色的许多数值。强烈不建议你这样做。不过,在短暂的试玩一段时间后,进行重置或许会有所帮助。", - "resetText2": "You will lose all your levels, gold, and experience points. All your tasks (except those from challenges) will be deleted permanently and you will lose all of their historical data. You will lose all your equipment but you will be able to buy it all back, including all limited edition equipment or subscriber Mystery items that you already own (you will need to be in the correct class to re-buy class-specific gear). You will keep your current class and your pets and mounts. You might prefer to use an Orb of Rebirth instead, which is a much safer option and which will preserve your tasks.", + "resetText2": "你将会失去你所有等级,金币和经验。你的所有任务(除了那些来自挑战的任务)将会被永久删除并且你将失去所有它们的历史数据。你将会丢失所有的装备但你能够再次购买回来,包括所有你已拥有的限定版装备或捐赠者神秘物品(你将需要是恰当的职业才能重新购买职业限定装备)。你将会保留你当前职业和你的宠物和坐骑。你可能更愿意去使用 重生球 来替代,这是一个更安全的选择,它能保存你所有的任务。", "deleteText": "你确定吗?这会永久地删除你的帐号,并且永远也无法恢复!如果希望再次使用Habitica需要注册一个新的帐号。已有的金钱和花掉的宝石无法被退费。如果你非常确定,在下面的文本框中输入<%= deleteWord %>。", "API": "API / 应用程序接口", "APIv3": "API v3", @@ -108,7 +108,7 @@ "emailNotifications": "电子邮件通知", "wonChallenge": "你赢得了一项挑战!", "newPM": "收到悄悄话", - "sentGems": "Sent gems!", + "sentGems": "发送宝石!", "giftedGems": "自然宝石", "giftedGemsInfo": "<%= amount %> 宝石 - 来自 <%= name %>", "giftedSubscription": "捐助有礼", @@ -148,11 +148,11 @@ "webhooks": "Webhooks", "enabled": "已启用", "webhookURL": "Webhook链接", - "invalidUrl": "invalid url", - "invalidEnabled": "the \"enabled\" parameter should be a boolean", - "regIdRequired": "RegId is required", - "pushDeviceAdded": "Push device added successfully", - "pushDeviceAlreadyAdded": "The user already has the push device", + "invalidUrl": "无效的url", + "invalidEnabled": "\"enabled\"参数应该为一个布尔值", + "regIdRequired": "需要RegId", + "pushDeviceAdded": "成功添加推送设备", + "pushDeviceAlreadyAdded": "用户已经有推送设备了", "add": "添加", "buyGemsGoldCap": "上限提升至 <%= amount %>", "mysticHourglass": "<%= amount %> 个神秘沙漏", @@ -167,5 +167,5 @@ "amazonPayments": "亚马逊支付", "timezone": "时区", "timezoneUTC": "Habitica直接引用你电脑上的时区设置:<%= utc %>", - "timezoneInfo": "If that time zone is wrong, first reload this page using your browser's reload or refresh button to ensure that Habitica has the most recent information. If it is still wrong, adjust the time zone on your PC and then reload this page again.

If you use Habitica on other PCs or mobile devices, the time zone must be the same on them all. If your Dailies have been resetting at the wrong time, repeat this check on all other PCs and on a browser on your mobile devices." + "timezoneInfo": "如果那个时区是错误的,首先用你浏览器的重载或刷新按钮重载这个页面以确保Habitica有最新的信息。如果还是不行,调整你电脑上的时区然后再次重载页面。

如果你在其他电脑或移动设备上使用Habitica,它们上的时区必须是一样的才行。如果你的每日任务已经在错误的时间被重置,在你所有其他的电脑和你移动设备的一个浏览器上重复这个检查。" } \ No newline at end of file diff --git a/common/locales/zh/spells.json b/common/locales/zh/spells.json index d838e48d25..cd9382e948 100644 --- a/common/locales/zh/spells.json +++ b/common/locales/zh/spells.json @@ -35,8 +35,8 @@ "spellSpecialSnowballAuraNotes": "向队友丢了一个雪球!会发生什么事情呢?所产生的效果会持续到第二天早上.", "spellSpecialSaltText": "盐", "spellSpecialSaltNotes": "有人向你扔了雪球。哈哈,很好玩。现在把这些雪从我身上弄下来!", - "spellSpecialSpookySparklesText": "Spooky Sparkles", - "spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!", + "spellSpecialSpookySparklesText": "幽灵般的闪光", + "spellSpecialSpookySparklesNotes": "把一个好友变成长眼睛的魔毯!", "spellSpecialOpaquePotionText": "晦暗的药水", "spellSpecialOpaquePotionNotes": "取消鬼火的效果", "spellSpecialShinySeedText": "闪光种子", @@ -50,7 +50,7 @@ "spellNotFound": "找不到技能\"<%= spellId %>\"。", "partyNotFound": "找不到队伍", "targetIdUUID": "\"targetId\" 必须是有效ID。", - "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", + "challengeTasksNoCast": "不允许在挑战任务上使用技能。", "spellNotOwned": "你没有拥有这个技能。", "spellLevelTooHigh": "你必须达到等级<%= level %>来运用这个技能。" } \ No newline at end of file diff --git a/common/locales/zh/subscriber.json b/common/locales/zh/subscriber.json index 593fe5deec..736a16e65e 100644 --- a/common/locales/zh/subscriber.json +++ b/common/locales/zh/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "负心人套装", "mysterySet201603": "幸运草套装", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "蒸汽朋克标准套装", "mysterySet301405": "蒸汽朋克配饰套装", "mysterySetwondercon": "Wondercon漫展", @@ -126,16 +127,16 @@ "missingSubscription": "User does not have a plan subscription", "missingSubscriptionCode": "Missing subscription code. Possible values: basic_earned, basic_3mo, basic_6mo, google_6mo, basic_12mo.", "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", - "paymentNotSuccessful": "The payment was not successful", + "paymentNotSuccessful": "支付失败", "planNotActive": "The plan hasn't activated yet (due to a PayPal bug). It will begin <%= nextBillingDate %>, after which you can cancel to retain your full benefits", "notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.", "readCard": "<%= cardType %> has been read", "cardTypeRequired": "Card type required", "cardTypeNotAllowed": "Unknown card type.", - "invalidCoupon": "Invalid coupon code.", - "couponUsed": "Coupon code already used.", - "noSudoAccess": "You don't have sudo access.", - "couponCodeRequired": "The coupon code is required.", - "eventRequired": "\"req.params.event\" is required.", - "countRequired": "\"req.query.count\" is required." + "invalidCoupon": "无效的优惠劵代码", + "couponUsed": "优惠劵已被使用", + "noSudoAccess": "您没有sudo权限", + "couponCodeRequired": "请填写优惠劵代码", + "eventRequired": "需要 \"req.params.event\"", + "countRequired": "需要 \"req.query.count\"" } \ No newline at end of file diff --git a/common/locales/zh/tasks.json b/common/locales/zh/tasks.json index 76ec6f5a7b..25fda3751f 100644 --- a/common/locales/zh/tasks.json +++ b/common/locales/zh/tasks.json @@ -116,7 +116,7 @@ "rewardHelp4": "别害怕,大胆地为自己设置自定义奖励!来这里看看 一些简单例子。", "clickForHelp": "点击获取帮助", "taskIdRequired": "\"taskId\" must be a valid UUID.", - "taskNotFound": "Task not found.", + "taskNotFound": "找不到任务。", "invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".", "cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.", "checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos", diff --git a/common/locales/zh_TW/contrib.json b/common/locales/zh_TW/contrib.json index 21fd4f6d1e..2037c395a6 100644 --- a/common/locales/zh_TW/contrib.json +++ b/common/locales/zh_TW/contrib.json @@ -56,8 +56,8 @@ "visitHeroes": "前往英雄殿堂(貢獻者和支持者之殿)", "conLearn": "查看更多關於貢獻獎勵的信息", "conLearnHow": "如何為Habitica作出貢獻", - "surveysSingle": "填寫調查表來幫助Habitica成長。沒有進行中的調查表。", - "surveysMultiple": "填寫<%= surveys %>調查表來幫助Habitica成長。沒有進行中的調查表。", + "surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!", + "surveysMultiple": "Helped Habitica grow on <%= surveys %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!", "currentSurvey": "目前的調查表", "surveyWhen": "在三月下旬調查表將處理完畢,徽章將被授予給的所有參與者。", "blurbInbox": "這裡放著你的私人訊息,你可以透過在公會、酒館、隊伍裡發聲的使用者後面的信件圖示來送私人訊息給那個人。如收到不雅文字請截圖寄給Lemoness (leslie@habitica.com)。", diff --git a/common/locales/zh_TW/gear.json b/common/locales/zh_TW/gear.json index f4547e646f..dd5a8756f3 100644 --- a/common/locales/zh_TW/gear.json +++ b/common/locales/zh_TW/gear.json @@ -368,6 +368,8 @@ "armorMystery201603Notes": "This suit is sewn from thousands of four-leafed clovers! Confers no benefit. March 2016 Subscriber Item.", "armorMystery201604Text": "Armor o' Leaves", "armorMystery201604Notes": "You, too, can be a small but fearsome leaf puff. Confers no benefit. April 2016 Subscriber Item.", + "armorMystery201605Text": "Marching Bard Uniform", + "armorMystery201605Notes": "Unlike the traditional bards who join adventuring parties, bards who join Habitican marching bands are known for grand parades, not dungeon raids. Confers no benefit. May 2016 Subscriber Item.", "armorMystery301404Text": "蒸汽龐克套裝", "armorMystery301404Notes": "精巧又瀟灑,哇嗚!沒有屬性加成。3015年1月訂閱者物品。", "armorArmoireLunarArmorText": "潤月護甲", @@ -573,6 +575,8 @@ "headMystery201603Notes": "This top hat is a magical good-luck charm. Confers no benefit. March 2016 Subscriber Item.", "headMystery201604Text": "Crown o' Flowers", "headMystery201604Notes": "These woven flowers make a surprisingly strong helm! Confers no benefit. April 2016 Subscriber Item.", + "headMystery201605Text": "Marching Bard Hat", + "headMystery201605Notes": "Seventy-six dragons led the big parade, with a hundred and ten gryphons close at hand! Confers no benefit. May 2016 Subscriber Item.", "headMystery301404Text": "華麗禮帽", "headMystery301404Notes": "上流社會佼佼者的華麗禮帽!3015年1月訂閱者物品。沒有屬性加成。", "headMystery301405Text": "基礎禮帽", diff --git a/common/locales/zh_TW/subscriber.json b/common/locales/zh_TW/subscriber.json index 903822586b..d7d1564b55 100644 --- a/common/locales/zh_TW/subscriber.json +++ b/common/locales/zh_TW/subscriber.json @@ -106,6 +106,7 @@ "mysterySet201602": "負心者套裝", "mysterySet201603": "幸運手套套裝", "mysterySet201604": "Leaf Warrior Set", + "mysterySet201605": "Marching Bard Set", "mysterySet301404": "蒸氣龐克標準套裝", "mysterySet301405": "蒸氣龐克配件套裝", "mysterySetwondercon": "Wondercon 漫畫展", From 9c7622e5a5fe2a065788cd1e4ffea9c2d3d3cbdf Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 26 May 2016 19:31:01 +0000 Subject: [PATCH 060/152] chore(news): add iOS update --- website/views/shared/new-stuff.jade | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index 1d1c7604cd..6b6f4d34a9 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,13 +1,18 @@ h2 5/25/2016 - ANDROID UPDATE AND MAY SUBSCRIBER ITEMS hr + tr + td + h3 iOS App Update Available + p We've released a new iOS app update that contains tons of bug fixes, including for the pet feeding crash and the annoying fake death popups! Be sure to download it now for a more stable Habitica. + br + p Thank you so much to everyone who reported bugs that cropped up after our massive code overhaul! It was very helpful. And if you have a spare moment, we'd love it if you could review this app. It really helps us out! iOS hides all old reviews, but if you've already written one, it's very easy to repost with a single button click. + p.small.muted by viirus tr td h3 Android Update: Bug Fixes p We've released a new Android update with fixes for lots of bugs and crashes. Download it now for a more stable experience! br p Thank you for your patience as we worked out some of the hiccups that came with our major code overhaul this weekend. If you like the direction that we're taking the app, we'd love it if you could take the time to leave us a review :) It really helps us out! - br - p (Hoping for an iOS update with similar fixes? We've submitted it to Apple, so we're just waiting for them to approve it, which could take several days. Stay tuned!) p.small.muted by viirus tr td From 3c338ddcd74ef3f32d9a08e490d8ce5627dab5ce Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 21:54:26 +0200 Subject: [PATCH 061/152] simplify tests and add console.log for debugging --- common/script/public/config.js | 2 ++ test/api/v3/unit/middlewares/cronMiddleware.js | 4 ++-- website/client/js/services/userServices.js | 2 ++ website/server/controllers/api-v3/members.js | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/script/public/config.js b/common/script/public/config.js index 4456909ef4..49fd576ac3 100644 --- a/common/script/public/config.js +++ b/common/script/public/config.js @@ -20,7 +20,9 @@ angular.module('habitrpg') // Something has changed on the user object that was not tracked here, sync the user if (isNotSync && ($rootScope.User.user._v - oldUserV) > 1) { + console.log('before sync', Date.now()) $rootScope.User.sync(); + console.log('after sync', Date.now()) } } } diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js index cba4cf83c1..37accd9ee6 100644 --- a/test/api/v3/unit/middlewares/cronMiddleware.js +++ b/test/api/v3/unit/middlewares/cronMiddleware.js @@ -47,11 +47,11 @@ describe('cron middleware', () => { it('calls next when user is not attached', (done) => { res.locals.user = null; - cronMiddleware(req, res, (err) => done(err)); + cronMiddleware(req, res, done); }); it('calls next when days have not been missed', (done) => { - cronMiddleware(req, res, (err) => done(err)); + cronMiddleware(req, res, done); }); it('should clear todos older than 30 days for free users', async (done) => { diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 255ac85d14..811b8d81b2 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -82,7 +82,9 @@ angular.module('habitrpg') .then(function (response) { if (response.data.message) Notification.text(response.data.message); + console.log('before extend', Date.now()) _.extend(user, response.data.data); + console.log('after extend', Date.now()) $rootScope.$emit('userUpdated', user); diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 22a40afed2..7eca05160b 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -355,7 +355,7 @@ api.transferGems = { receiverName: receiver.profile.name, senderName: sender.profile.name, }); - message += res.t('privateMessageGiftGemsMessage', {gemAmount}); + message += ` ${res.t('privateMessageGiftGemsMessage', {gemAmount})}`; if (req.body.message) { message += req.body.message; From 457264faa1d9b76d1ef6c0fb58e327c9198fc07a Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 22:11:48 +0200 Subject: [PATCH 062/152] remove console.log used for debugging --- common/script/public/config.js | 2 -- website/client/js/services/userServices.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/common/script/public/config.js b/common/script/public/config.js index 49fd576ac3..4456909ef4 100644 --- a/common/script/public/config.js +++ b/common/script/public/config.js @@ -20,9 +20,7 @@ angular.module('habitrpg') // Something has changed on the user object that was not tracked here, sync the user if (isNotSync && ($rootScope.User.user._v - oldUserV) > 1) { - console.log('before sync', Date.now()) $rootScope.User.sync(); - console.log('after sync', Date.now()) } } } diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 811b8d81b2..255ac85d14 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -82,9 +82,7 @@ angular.module('habitrpg') .then(function (response) { if (response.data.message) Notification.text(response.data.message); - console.log('before extend', Date.now()) _.extend(user, response.data.data); - console.log('after extend', Date.now()) $rootScope.$emit('userUpdated', user); From b03985c6b240933807593c587f1a32055f1652e1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 22:17:15 +0200 Subject: [PATCH 063/152] fix(cron): only process checklists if they exist --- website/server/libs/api-v3/cron.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js index 9f74df610b..12a3728fe3 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/api-v3/cron.js @@ -72,7 +72,9 @@ function performSleepTasks (user, tasksByType, now) { if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) { // TODO also untick checklists if the Daily was due on previous missed days, if two or more days were missed at once -- https://github.com/HabitRPG/habitrpg/pull/7218#issuecomment-219256016 - daily.checklist.forEach(box => box.completed = false); + if (daily.checklist) { + daily.checklist.forEach(box => box.completed = false); + } } daily.completed = false; @@ -196,7 +198,9 @@ export function cron (options = {}) { task.completed = false; if (completed || scheduleMisses > 0) { - task.checklist.forEach(i => i.completed = false); + if (task.checklist) { + task.checklist.forEach(i => i.completed = false); + } } }); From f95e19c14e7bc23e12290b41a87d35ab688d1031 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 26 May 2016 20:38:53 +0000 Subject: [PATCH 064/152] chore: Bump version to 3.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55c837b0d6..4f75ac9f62 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.3.1", + "version": "3.3.2", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 0ca44fd5c2a4465c15a15f5245f15f7f3e9fb133 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 22:45:12 +0200 Subject: [PATCH 065/152] fix tests when transferring gems --- website/server/controllers/api-v3/members.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 7eca05160b..22a40afed2 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -355,7 +355,7 @@ api.transferGems = { receiverName: receiver.profile.name, senderName: sender.profile.name, }); - message += ` ${res.t('privateMessageGiftGemsMessage', {gemAmount})}`; + message += res.t('privateMessageGiftGemsMessage', {gemAmount}); if (req.body.message) { message += req.body.message; From 792aafd737608a5cb4a1f76c866576ac6d2cd19c Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 26 May 2016 20:51:55 +0000 Subject: [PATCH 066/152] fix(test): delay for task creation --- ...ks_challenges_challengeId_tasks_id_score_direction.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js index a264826884..d9f55d97d0 100644 --- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js @@ -26,6 +26,7 @@ describe('POST /tasks/:id/score/:direction', () => { text: 'test habit', type: 'habit', }); + await Bluebird.delay(1000); let updatedUser = await user.get('/user'); usersChallengeTaskId = updatedUser.tasksOrder.habits[0]; }); @@ -63,6 +64,7 @@ describe('POST /tasks/:id/score/:direction', () => { text: 'test daily', type: 'daily', }); + await Bluebird.delay(1000); let updatedUser = await user.get('/user'); usersChallengeTaskId = updatedUser.tasksOrder.dailys[0]; }); @@ -99,6 +101,7 @@ describe('POST /tasks/:id/score/:direction', () => { text: 'test todo', type: 'todo', }); + await Bluebird.delay(1000); let updatedUser = await user.get('/user'); usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; }); @@ -123,6 +126,7 @@ describe('POST /tasks/:id/score/:direction', () => { text: 'test reward', type: 'reward', }); + await Bluebird.delay(1000); let updatedUser = await user.get('/user'); usersChallengeTaskId = updatedUser.tasksOrder.todos[0]; }); From c6192dd24b3a50c80baf9837c6e4205f41f9e36c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 23:10:38 +0200 Subject: [PATCH 067/152] feat(logging): improve logging removing not useful data and distinguishing between user and server errors --- website/server/libs/api-v3/logger.js | 26 +++++++++++++++++-- .../server/middlewares/api-v3/errorHandler.js | 14 +++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index a5a46fec87..4260e72a48 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -2,6 +2,9 @@ import winston from 'winston'; import nconf from 'nconf'; import _ from 'lodash'; +import { + CustomError, +} from './errors'; const IS_PROD = nconf.get('IS_PROD'); const IS_TEST = nconf.get('IS_TEST'); @@ -42,8 +45,27 @@ let loggerInterface = { // pass the error stack as the first parameter to logger.error let stack = err.stack || err.message || err; - if (_.isPlainObject(errorData) && !errorData.fullError) errorData.fullError = err; - logger.error(stack, errorData, ...otherArgs); + if (_.isPlainObject(errorData) && !errorData.fullError) { + // If the error object has interesting data (not only httpCode, message and name from the CustomError class) + // add it to the logs + if (err instanceof CustomError) { + let errWithoutCommonProps = _.omit(err, ['name', 'httpCode', 'message']); + + if (Object.keys(errWithoutCommonProps).length > 0) { + errorData.fullError = errWithoutCommonProps; + } + } else { + errorData.fullError = err; + } + } + + let loggerArgs = [stack, errorData, ...otherArgs]; + // Treat 4xx errors as warnings, 5xx and uncaught errors as serious problems + if (!errorData || !errorData.statusCode || errorData.statusCode > 499) { + logger.error(...loggerArgs); + } else { + logger.warn(stack, errorData, ...otherArgs); + } } else { logger.error(...args); } diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js index 11d75042e3..891f0b36af 100644 --- a/website/server/middlewares/api-v3/errorHandler.js +++ b/website/server/middlewares/api-v3/errorHandler.js @@ -12,12 +12,6 @@ import { } from 'lodash'; module.exports = function errorHandler (err, req, res, next) { // eslint-disable-line no-unused-vars - logger.error(err, { - originalUrl: req.originalUrl, - headers: omit(req.headers, ['x-api-key']), - body: req.body, - }); - // In case of a CustomError class, use it's data // Otherwise try to identify the type of error (mongoose validation, mongodb unique, ...) // If we can't identify it, respond with a generic 500 error @@ -70,6 +64,14 @@ module.exports = function errorHandler (err, req, res, next) { // eslint-disable responseErr = new InternalServerError(); } + // log the error + logger.error(err, { + originalUrl: req.originalUrl, + headers: omit(req.headers, ['x-api-key', 'cookie']), // don't send sensitive information that only adds noise + body: req.body, + statusCode: responseErr.httpCode, + }); + let jsonRes = { success: false, error: responseErr.name, From 10cf22cd4e797d482ae07a0bfd7e5afc9ed2a7a9 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 23:19:29 +0200 Subject: [PATCH 068/152] better distinguish between handled and not handler errors --- website/server/libs/api-v3/logger.js | 7 ++++--- website/server/middlewares/api-v3/errorHandler.js | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index 4260e72a48..dd0ca8396e 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -60,11 +60,12 @@ let loggerInterface = { } let loggerArgs = [stack, errorData, ...otherArgs]; - // Treat 4xx errors as warnings, 5xx and uncaught errors as serious problems - if (!errorData || !errorData.statusCode || errorData.statusCode > 499) { + + // Treat 4xx errors that are handled as warnings, 5xx and uncaught errors as serious problems + if (!errorData || !errorData.isHandledError || errorData.httpCode >= 500) { logger.error(...loggerArgs); } else { - logger.warn(stack, errorData, ...otherArgs); + logger.warn(...loggerArgs); } } else { logger.error(...args); diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js index 891f0b36af..cd76277c78 100644 --- a/website/server/middlewares/api-v3/errorHandler.js +++ b/website/server/middlewares/api-v3/errorHandler.js @@ -69,7 +69,8 @@ module.exports = function errorHandler (err, req, res, next) { // eslint-disable originalUrl: req.originalUrl, headers: omit(req.headers, ['x-api-key', 'cookie']), // don't send sensitive information that only adds noise body: req.body, - statusCode: responseErr.httpCode, + httpCode: responseErr.httpCode, + isHandledError: responseErr.httpCode < 500, }); let jsonRes = { From cb4676980d188f6946a9fab4f36ac4918a70d43d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Thu, 26 May 2016 23:35:57 +0200 Subject: [PATCH 069/152] fix tests for error handler middleware --- test/api/v3/unit/middlewares/errorHandler.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js index 72cad12a32..0cb1e1bb97 100644 --- a/test/api/v3/unit/middlewares/errorHandler.test.js +++ b/test/api/v3/unit/middlewares/errorHandler.test.js @@ -168,6 +168,8 @@ describe('errorHandler', () => { originalUrl: req.originalUrl, headers: req.headers, body: req.body, + httpCode: 400, + isHandledError: true, }); }); }); From 31d2c5d6043477abfa5021548e558af308dd1bbe Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Fri, 27 May 2016 00:58:16 +0000 Subject: [PATCH 070/152] fix(test): import Bluebird --- ...asks_challenges_challengeId_tasks_id_score_direction.test.js | 1 + website/views/shared/new-stuff.jade | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js index d9f55d97d0..0fa8bafd30 100644 --- a/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/challenges/POST-tasks_challenges_challengeId_tasks_id_score_direction.test.js @@ -3,6 +3,7 @@ import { generateGroup, generateChallenge, } from '../../../../../helpers/api-integration/v3'; +import Bluebird from 'bluebird'; import { find } from 'lodash'; describe('POST /tasks/:id/score/:direction', () => { diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index 6b6f4d34a9..81f060df57 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,4 +1,4 @@ -h2 5/25/2016 - ANDROID UPDATE AND MAY SUBSCRIBER ITEMS +h2 5/25/2016 - iOS UPDATE, ANDROID UPDATE, AND MAY SUBSCRIBER ITEMS hr tr td From 9b7458c022813665f3e9315f39bfd434a642a354 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Fri, 27 May 2016 01:20:00 +0000 Subject: [PATCH 071/152] chore(patch): bump version to 3.3.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4f75ac9f62..695af4727b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.3.2", + "version": "3.3.3", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 91aba965b0fbb74e13336f91cbc5639b316d4aea Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 26 May 2016 22:08:31 -0500 Subject: [PATCH 072/152] tests: Add tests for recoverCron --- test/api/v3/unit/libs/cron.test.js | 68 ++++++++++++++++++- .../api/v3/unit/middlewares/cronMiddleware.js | 30 ++++++++ website/server/libs/api-v3/cron.js | 25 +++++++ website/server/middlewares/api-v3/cron.js | 26 +------ 4 files changed, 124 insertions(+), 25 deletions(-) diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index 6f16f9973f..ef09868c27 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -1,6 +1,7 @@ /* eslint-disable global-require */ import moment from 'moment'; -import { cron } from '../../../../../website/server/libs/api-v3/cron'; +import Bluebird from 'bluebird'; +import { recoverCron, cron } from '../../../../../website/server/libs/api-v3/cron'; import { model as User } from '../../../../../website/server/models/user'; import * as Tasks from '../../../../../website/server/models/task'; import { clone } from 'lodash'; @@ -562,3 +563,68 @@ describe('cron', () => { }); }); }); + +describe('recoverCron', () => { + let locals, status, execStub; + + beforeEach(() => { + execStub = sandbox.stub(); + sandbox.stub(User, 'findOne').returns({ exec: execStub }); + + status = { times: 0 }; + locals = { + user: new User({ + auth: { + local: { + username: 'username', + lowerCaseUsername: 'username', + email: 'email@email.email', + salt: 'salt', + hashed_password: 'hashed_password', // eslint-disable-line camelcase + }, + }, + }), + }; + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('throws an error if user cannot be found', async (done) => { + execStub.returns(Bluebird.resolve(null)); + + try { + await recoverCron(status, locals); + } catch (err) { + expect(err.message).to.eql(`User ${locals.user._id} not found while recovering.`); + + done(); + } + }); + + it('increases status.times count and reruns up to 3 times', async (done) => { + execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'})); + execStub.onCall(3).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'})); + + await recoverCron(status, locals); + + expect(status.times).to.eql(3); + expect(locals.user).to.eql({_cronSignature: 'NOT_RUNNING'}); + + done(); + }); + + it('throws an error if recoverCron runs 4 times', async (done) => { + execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'})); + + try { + await recoverCron(status, locals); + } catch (err) { + expect(status.times).to.eql(4); + expect(err.message).to.eql(`Impossible to recover from cron for user ${locals.user._id}.`); + + done(); + } + }); +}); diff --git a/test/api/v3/unit/middlewares/cronMiddleware.js b/test/api/v3/unit/middlewares/cronMiddleware.js index cba4cf83c1..2e4269e9fc 100644 --- a/test/api/v3/unit/middlewares/cronMiddleware.js +++ b/test/api/v3/unit/middlewares/cronMiddleware.js @@ -4,12 +4,14 @@ import { generateTodo, generateDaily, } from '../../../../helpers/api-unit.helper'; +import { cloneDeep } from 'lodash'; import cronMiddleware from '../../../../../website/server/middlewares/api-v3/cron'; import moment from 'moment'; import { model as User } from '../../../../../website/server/models/user'; import { model as Group } from '../../../../../website/server/models/group'; import * as Tasks from '../../../../../website/server/models/task'; import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; +import * as cronLib from '../../../../../website/server/libs/api-v3/cron'; import { v4 as generateUUID } from 'uuid'; describe('cron middleware', () => { @@ -45,6 +47,10 @@ describe('cron middleware', () => { .catch(done); }); + afterEach(() => { + sandbox.restore(); + }); + it('calls next when user is not attached', (done) => { res.locals.user = null; cronMiddleware(req, res, (err) => done(err)); @@ -191,4 +197,28 @@ describe('cron middleware', () => { done(); }); }); + + it('recovers from failed cron and does not error when user is already cronning', async (done) => { + user.lastCron = moment(new Date()).subtract({days: 2}); + await user.save(); + + let updatedUser = cloneDeep(user); + updatedUser.nMatched = 0; + + sandbox.spy(cronLib, 'recoverCron'); + + sandbox.stub(User, 'update') + .withArgs({ _id: user._id, _cronSignature: 'NOT_RUNNING' }) + .returns({ + exec () { + return Promise.resolve(updatedUser); + }, + }); + + cronMiddleware(req, res, () => { + expect(cronLib.recoverCron).to.be.calledOnce; + + done(); + }); + }); }); diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js index c59403d545..92ae202c12 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/api-v3/cron.js @@ -1,4 +1,6 @@ import moment from 'moment'; +import Bluebird from 'bluebird'; +import { model as User } from '../../models/user'; import common from '../../../../common/'; import { preenUserHistory } from '../../libs/api-v3/preening'; import _ from 'lodash'; @@ -79,6 +81,29 @@ function performSleepTasks (user, tasksByType, now) { }); } +export async function recoverCron (status, locals) { + let {user} = locals; + + await Bluebird.delay(300); + + let reloadedUser = await User.findOne({_id: user._id}).exec(); + + if (!reloadedUser) { + throw new Error(`User ${user._id} not found while recovering.`); + } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { + status.times++; + + if (status.times < 4) { + await recoverCron(status, locals); + } else { + throw new Error(`Impossible to recover from cron for user ${user._id}.`); + } + } else { + locals.user = reloadedUser; + return null; + } +} + // Perform various beginning-of-day reset actions. export function cron (options = {}) { let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index 75589e5af3..b6555cb682 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -5,33 +5,11 @@ import * as Tasks from '../../models/task'; import Bluebird from 'bluebird'; import { model as Group } from '../../models/group'; import { model as User } from '../../models/user'; -import { cron } from '../../libs/api-v3/cron'; +import { recoverCron, cron } from '../../libs/api-v3/cron'; import { v4 as uuid } from 'uuid'; const daysSince = common.daysSince; -async function recoverCron (status, req, res) { - let user = res.locals.user; - - await Bluebird.delay(300); - let reloadedUser = await User.findOne({_id: user._id}).exec(); - - if (!reloadedUser) { - throw new Error(`User ${user._id} not found while recovering.`); - } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { - status.times++; - - if (status.times < 4) { - await recoverCron(status, req, res); - } else { - throw new Error(`Impossible to recover from cron for user ${user._id}.`); - } - } else { - res.locals.user = reloadedUser; - return null; - } -} - async function cronAsync (req, res) { let user = res.locals.user; if (!user) return null; // User might not be available when authentication is not mandatory @@ -210,7 +188,7 @@ async function cronAsync (req, res) { times: 0, }; - recoverCron(recoveryStatus, req, res); + recoverCron(recoveryStatus, res.locals); } else { // For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running // at the next request From a9d4bbd87d8e575a6015692b5e99faea908cbb0d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 10:53:20 +0200 Subject: [PATCH 073/152] dataexport: correctly handle 403 errors --- website/server/controllers/top-level/dataexport.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/server/controllers/top-level/dataexport.js b/website/server/controllers/top-level/dataexport.js index 08bc4a1f8e..9042547a7a 100644 --- a/website/server/controllers/top-level/dataexport.js +++ b/website/server/controllers/top-level/dataexport.js @@ -203,7 +203,8 @@ api.exportUserAvatarPng = { try { response = await got.head(s3url); } catch (gotError) { - if (gotError.code !== 'ENOTFOUND' && gotError.statusCode !== 404) { + // If the file does not exist AWS S3 can return a 403 error + if (gotError.code !== 'ENOTFOUND' && gotError.statusCode !== 404 && gotError.statusCode !== 403) { throw gotError; } } From 39a9753fd67f18cdaaadb489b7dbad633184f732 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 11:05:12 +0200 Subject: [PATCH 074/152] chore: Bump version to 3.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 695af4727b..c0082c0b0a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.3.3", + "version": "3.4.0", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 4deb5ada2f805f157f37c9ccd8ab909e75340384 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 11:29:46 +0200 Subject: [PATCH 075/152] add timestamps to logs --- website/server/libs/api-v3/logger.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/server/libs/api-v3/logger.js b/website/server/libs/api-v3/logger.js index dd0ca8396e..21622d8634 100644 --- a/website/server/libs/api-v3/logger.js +++ b/website/server/libs/api-v3/logger.js @@ -16,6 +16,7 @@ const logger = new winston.Logger(); if (IS_PROD) { if (ENABLE_LOGS_IN_PROD) { logger.add(winston.transports.Console, { + timestamp: true, colorize: false, prettyPrint: false, }); @@ -23,6 +24,7 @@ if (IS_PROD) { } else if (!IS_TEST || IS_TEST && ENABLE_LOGS_IN_TEST) { // Do not log anything when testing unless specified logger .add(winston.transports.Console, { + timestamp: true, colorize: true, prettyPrint: true, }); From 3ce80bae2abec7edd6c9b903894968a8a0e00fb6 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 13:17:05 +0200 Subject: [PATCH 076/152] make checklist.id required --- website/server/models/task.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/models/task.js b/website/server/models/task.js index 3af644eee1..2882e0530d 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -181,7 +181,7 @@ let dailyTodoSchema = () => { completed: {type: Boolean, default: false}, text: {type: String, required: false, default: ''}, // required:false because it can be empty on creation _id: false, - id: {type: String, default: shared.uuid, validate: [validator.isUUID, 'Invalid uuid.']}, + id: {type: String, default: shared.uuid, required: true, validate: [validator.isUUID, 'Invalid uuid.']}, }], }; }; From 20ce39499950c093ad2126d69bd967532f397b87 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 13:24:17 +0200 Subject: [PATCH 077/152] chore: Bump version to 3.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0082c0b0a..769b82a0eb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.4.0", + "version": "3.4.1", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 01a7880bc94ccb6eebb321f98061abfad90d9c5d Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 15:14:27 +0200 Subject: [PATCH 078/152] add migration to fix empty checklist.id --- migrations/20160527_fix_empty_checklist_id.js | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 migrations/20160527_fix_empty_checklist_id.js diff --git a/migrations/20160527_fix_empty_checklist_id.js b/migrations/20160527_fix_empty_checklist_id.js new file mode 100644 index 0000000000..0aaa36d1ac --- /dev/null +++ b/migrations/20160527_fix_empty_checklist_id.js @@ -0,0 +1,78 @@ +var uuid = require('uuid').v4; +var mongo = require('mongodb').MongoClient; +var _ = require('lodash'); + +var taskIds = require('checklists-no-id.json').map(function (obj) { + return obj._id; +}); + +// Fix empty task.checklistt.id + +var progressCount = 100; +var count = 0; + +function displayData() { + console.warn('\n' + count + ' tasks 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); } + } +} + +mongo.connect('db url') +.then(function (db) { + var dbTasks = db.collection('tasks'); + + // specify a query to limit the affected tasks (empty for all tasks): + var query = { + '_id':{ $in: taskIds }, + }; + + // specify fields we are interested in to limit retrieved data (empty if we're not reading data): + var fields = { + 'checklist': 1, + }; + + console.warn('Updating tasks...'); + + dbTasks.find(query, fields, {batchSize: 250}).toArray(function(err, tasks) { + if (err) { return exiting(1, 'ERROR! ' + err); } + + tasks.forEach(function (task) { + var checklist = task.checklist || []; + checklist.forEach(function (item) { + if (!item.id || item.id === "") { + item.id = uuid(); + } + }); + + // specify user data to change: + var set = { + checklist: checklist, + }; + //console.log(set); + + dbTasks.update({_id: task._id}, {$set: set}, function (err, res) { + if (err) console.error('Error while updating', err); + }); + + count++; + if (count % progressCount == 0) console.warn(count + ' ' + task._id); + }); + + if (count === tasks.length) { + console.warn('All appropriate tasks found and modified.'); + return displayData(); + } + }); +}) +.catch(function (err) { + throw err; +}); \ No newline at end of file From 46be1553f0d12ab295b97ac8fe0be8204936e4d5 Mon Sep 17 00:00:00 2001 From: "Lucas Torroba (@zesme)" Date: Thu, 26 May 2016 18:40:32 -0300 Subject: [PATCH 079/152] Close #7493 --- website/client/js/controllers/tasksCtrl.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index d09ebe1f24..d3eafa4eb2 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -193,10 +193,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N // Don't allow creation of an empty checklist item // TODO Provide UI feedback that this item is still blank } else if ($index == task.checklist.length - 1) { - Tasks.addChecklistItem(task._id, task.checklist[$index]) - .then(function (response) { - task.checklist[$index] = response.data.data.checklist[$index]; - }); + $scope.saveTask(task, true); task.checklist.push({completed:false, text:''}); focusChecklist(task, task.checklist.length - 1); } else { From b20f5545b9acd429025f53bb0b6bb47b9e0770e4 Mon Sep 17 00:00:00 2001 From: "Lucas Torroba (@zesme)" Date: Thu, 26 May 2016 18:51:43 -0300 Subject: [PATCH 080/152] Cleanup add checklist item function Cleaned up `addChecklistItem` function, shortening it --- website/client/js/controllers/tasksCtrl.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index d3eafa4eb2..a4c9db9971 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -189,16 +189,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N } $scope.addChecklistItem = function(task, $event, $index) { - if (!task.checklist[$index].text) { - // Don't allow creation of an empty checklist item - // TODO Provide UI feedback that this item is still blank - } else if ($index == task.checklist.length - 1) { - $scope.saveTask(task, true); - task.checklist.push({completed:false, text:''}); - focusChecklist(task, task.checklist.length - 1); - } else { + if (task.checklist[$index].text) { $scope.saveTask(task, true); + if ($index == task.checklist.length - 1) + task.checklist.push({ completed: false, text: '' }); focusChecklist(task, $index + 1); + } else { + // TODO Provide UI feedback that this item is still blank } } From 2d81b31c3a455bd523d166a39ccf13fe08cc302b Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 16:38:55 +0200 Subject: [PATCH 081/152] remove legacy loggly code --- package.json | 1 - website/server/libs/api-v2/logging.js | 26 ++----------------- .../server/middlewares/api-v2/errorHandler.js | 7 ----- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 769b82a0eb..b36648d917 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,6 @@ "js2xmlparser": "~1.0.0", "lodash": "^3.10.1", "lodash.setwith": "^4.2.0", - "loggly": "~1.0.8", "markdown-it": "^6.0.1", "merge-stream": "^1.0.0", "method-override": "^2.3.5", diff --git a/website/server/libs/api-v2/logging.js b/website/server/libs/api-v2/logging.js index 9737159f43..454b5a5aa8 100644 --- a/website/server/libs/api-v2/logging.js +++ b/website/server/libs/api-v2/logging.js @@ -1,24 +1,7 @@ var nconf = require('nconf'); var winston = require('winston'); -var logger, loggly; - -// Currently disabled -if (nconf.get('LOGGLY:enabled')){ - loggly = require('loggly').createClient({ - token: nconf.get('LOGGLY:token'), - subdomain: nconf.get('LOGGLY:subdomain'), - auth: { - username: nconf.get('LOGGLY:username'), - password: nconf.get('LOGGLY:password') - }, - // - // Optional: Tag to send with EVERY log message - // - tags: [('heroku-'+nconf.get('BASE_URL'))], - json: true - }); -} +var logger; if (!logger) { logger = new (winston.Logger)({}); @@ -49,9 +32,4 @@ module.exports.warn = function(/* variable args */) { module.exports.error = function(/* variable args */) { if (logger) logger.error.apply(logger, arguments); -}; - -module.exports.loggly = function(/* variable args */){ - if (loggly) - loggly.log.apply(loggly, arguments); -}; +}; \ No newline at end of file diff --git a/website/server/middlewares/api-v2/errorHandler.js b/website/server/middlewares/api-v2/errorHandler.js index e62753f5f9..fcea9d175c 100644 --- a/website/server/middlewares/api-v2/errorHandler.js +++ b/website/server/middlewares/api-v2/errorHandler.js @@ -11,13 +11,6 @@ module.exports = function(err, req, res, next) { "\n\nbody: " + JSON.stringify(req.body) + (res.locals.ops ? "\n\ncompleted ops: " + JSON.stringify(res.locals.ops) : ""); logging.error(stack); - /*logging.loggly({ - error: "Uncaught error", - stack: (err.stack || err.message || err), - body: req.body, headers: req.header, - auth: req.headers['x-api-user'], - originalUrl: req.originalUrl - });*/ var message = err.message ? err.message : err; message = (message.length < 200) ? message : message.substring(0,100) + message.substring(message.length-100,message.length); res.status(500).json({err:message}); //res.end(err.message); From 7090ed5f4bf0544ba1316259e53d2e42352fc21f Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 17:01:25 +0200 Subject: [PATCH 082/152] update elastic beanstalk config to install loggly --- .ebextensions/deploy.config | 4 ++++ config.json.example | 9 ++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.ebextensions/deploy.config b/.ebextensions/deploy.config index b53a92dacb..51fae238d8 100644 --- a/.ebextensions/deploy.config +++ b/.ebextensions/deploy.config @@ -1,6 +1,10 @@ +sources: + /tmp: https://s3.amazonaws.com/habitica-assets/scripts/loggly-setup-script-2016-05-27.zip commands: 01_sym: command: "ln -sf $(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/node /bin/node" + 02_loggly_config: + command: "su --session-command="python /tmp/configure-syslog.py setup --auth $LOGGLY_TOKEN --account $LOGGLY_ACCOUNT --yes" container_commands: 01_makeBabel: command: "touch /tmp/.babel.json" diff --git a/config.json.example b/config.json.example index 01c549c803..c7f801326c 100644 --- a/config.json.example +++ b/config.json.example @@ -61,13 +61,8 @@ "client_secret":"client_secret" }, "IAP_GOOGLE_KEYDIR": "/path/to/google/public/key/dir/", - "LOGGLY": { - "enabled": false, - "subdomain": "subdomain", - "token": "token", - "username": "username", - "password": "password" - }, + "LOGGLY_TOKEN": "token", + "LOGGLY_ACCOUNT": "account", "PUSH_CONFIGS": { "GCM_SERVER_API_KEY": "", "APN_PEM_FILES": { From 36042d42f9e8d0651b63164facf663136b21cd61 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Fri, 27 May 2016 17:17:00 +0200 Subject: [PATCH 083/152] disable loggly config while testing --- .ebextensions/deploy.config | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.ebextensions/deploy.config b/.ebextensions/deploy.config index 51fae238d8..b53a92dacb 100644 --- a/.ebextensions/deploy.config +++ b/.ebextensions/deploy.config @@ -1,10 +1,6 @@ -sources: - /tmp: https://s3.amazonaws.com/habitica-assets/scripts/loggly-setup-script-2016-05-27.zip commands: 01_sym: command: "ln -sf $(ls -td /opt/elasticbeanstalk/node-install/node-* | head -1)/bin/node /bin/node" - 02_loggly_config: - command: "su --session-command="python /tmp/configure-syslog.py setup --auth $LOGGLY_TOKEN --account $LOGGLY_ACCOUNT --yes" container_commands: 01_makeBabel: command: "touch /tmp/.babel.json" From 4ad81c4c282008528211ac32003901df40bf244e Mon Sep 17 00:00:00 2001 From: velezsarain Date: Sat, 28 May 2016 03:53:39 +0200 Subject: [PATCH 084/152] Change install_node.sh to install npm 3 instead. Default RAM for vagrant box is now 4 GB. (#7478) --- Vagrantfile.example | 2 +- vagrant_scripts/install_node.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Vagrantfile.example b/Vagrantfile.example index 73fcbf38c4..061c20f277 100644 --- a/Vagrantfile.example +++ b/Vagrantfile.example @@ -6,7 +6,7 @@ VAGRANTFILE_API_VERSION = "2" Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.provider "virtualbox" do |v| - v.memory = 768 + v.memory = 4096 v.cpus = 1 v.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/vagrant", "1"] end diff --git a/vagrant_scripts/install_node.sh b/vagrant_scripts/install_node.sh index 8de976aa62..a420e7bdf1 100755 --- a/vagrant_scripts/install_node.sh +++ b/vagrant_scripts/install_node.sh @@ -16,7 +16,7 @@ nvm use nvm alias default current echo Update npm... -npm install -g npm@2 +npm install -g npm@3 echo Installing global modules... npm install -g gulp bower grunt-cli mocha From 00aebed8c092ed7cf8da9d380b101817c6a2f122 Mon Sep 17 00:00:00 2001 From: hairlessbear Date: Sat, 28 May 2016 19:08:14 -0400 Subject: [PATCH 085/152] Use npm 3 in Dockerfile (#7529) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6f153c6078..771adc462d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ RUN apt-get clean RUN rm -rf /var/lib/apt/lists/* # Install global packages -RUN npm install -g gulp grunt-cli bower +RUN npm install -g gulp grunt-cli bower npm@3 # Clone Habitica repo and install dependencies WORKDIR /habitrpg From db3fd62af8f5d31f577269c757f28ca5453c0604 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 29 May 2016 07:34:59 -0500 Subject: [PATCH 086/152] fix: correct link in issue template --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a87be64767..4322c53807 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issue If you find your issue there, read at least the first post to see if there is a workaround for you) +[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issues If you find your issue there, read at least the first post to see if there is a workaround for you) [//]: # (Github is primarily used for reporting bugs. If you have a feature request, use "Help > Request a Feature" so that the feature request can be vetted by the larger Habitica community) From d23f79d00112eeaac6271e0de7f4ad24d60662f8 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 29 May 2016 09:24:19 -0500 Subject: [PATCH 087/152] fix: Correct typo in tag documentation Per https://twitter.com/Rio_Alx/status/736109816964227073 --- website/server/controllers/api-v3/tags.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/api-v3/tags.js b/website/server/controllers/api-v3/tags.js index 250c343537..62e740a225 100644 --- a/website/server/controllers/api-v3/tags.js +++ b/website/server/controllers/api-v3/tags.js @@ -34,7 +34,7 @@ api.createTag = { }; /** - * @api {get} /api/v3/tag Get a user's tags + * @api {get} /api/v3/tags Get a user's tags * @apiVersion 3.0.0 * @apiName GetTags * @apiGroup Tag From bbafe3d52dacac361030d2ee48fe71175a99239d Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Sun, 29 May 2016 10:09:02 -0500 Subject: [PATCH 088/152] fix: Prevent user from inivting self to group fixes #7491 --- common/locales/en/groups.json | 1 + .../v3/integration/groups/POST-groups_invite.test.js | 11 +++++++++++ website/server/controllers/api-v3/groups.js | 2 ++ 3 files changed, 14 insertions(+) diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json index 2e0f245232..00257c8670 100644 --- a/common/locales/en/groups.json +++ b/common/locales/en/groups.json @@ -177,6 +177,7 @@ "inviteMissingEmail": "Missing email address in invite.", "partyMustbePrivate": "Parties must be private", "userAlreadyInGroup": "User already in that group.", + "cannotInviteSelfToGroup": "You cannot invite yourself to a group.", "userAlreadyInvitedToGroup": "User already invited to that group.", "userAlreadyPendingInvitation": "User already pending invitation.", "userAlreadyInAParty": "User already in a party.", diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js index 92328463b2..3e6ed0be30 100644 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -33,6 +33,17 @@ describe('Post /groups/:groupId/invite', () => { }); }); + it('returns an error when inviting yourself to a group', async () => { + await expect(inviter.post(`/groups/${group._id}/invite`, { + uuids: [inviter._id], + })) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('cannotInviteSelfToGroup'), + }); + }); + it('returns an error when uuids is not an array', async () => { let fakeID = generateUUID(); diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 07e9b5be54..de59bd7e17 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -487,6 +487,8 @@ async function _inviteByUUID (uuid, group, inviter, req, res) { if (!userToInvite) { throw new NotFound(res.t('userWithIDNotFound', {userId: uuid})); + } else if (inviter._id === userToInvite._id) { + throw new BadRequest(res.t('cannotInviteSelfToGroup')); } if (group.type === 'guild') { From 8520987afbf1931aefa6c47dd860ae2f747e5d19 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 30 May 2016 17:42:59 +0000 Subject: [PATCH 089/152] chore(news): May Last Chance Bailey --- website/views/shared/new-stuff.jade | 51 ++++++++++++++++++----------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index 81f060df57..f884040296 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,33 +1,46 @@ -h2 5/25/2016 - iOS UPDATE, ANDROID UPDATE, AND MAY SUBSCRIBER ITEMS +h2 5/30/2016 - LAST CHANCE FOR MARCHING BARD ITEM SET AND FLORAL POTIONS hr - tr - td - h3 iOS App Update Available - p We've released a new iOS app update that contains tons of bug fixes, including for the pet feeding crash and the annoying fake death popups! Be sure to download it now for a more stable Habitica. - br - p Thank you so much to everyone who reported bugs that cropped up after our massive code overhaul! It was very helpful. And if you have a spare moment, we'd love it if you could review this app. It really helps us out! iOS hides all old reviews, but if you've already written one, it's very easy to repost with a single button click. - p.small.muted by viirus - tr - td - h3 Android Update: Bug Fixes - p We've released a new Android update with fixes for lots of bugs and crashes. Download it now for a more stable experience! - br - p Thank you for your patience as we worked out some of the hiccups that came with our major code overhaul this weekend. If you like the direction that we're taking the app, we'd love it if you could take the time to leave us a review :) It really helps us out! - p.small.muted by viirus tr td .promo_mystery_201605.pull-right - h3 May Subscriber Items - p The May Subscriber Items have been revealed: the Marching Bard Item Set! You still have six days to subscribe and receive the item set. - br - p Subscribers also receive the ability to buy Gems for Gold -- the longer you subscribe, the more Gems you can buy per month! There are other perks as well, such as longer access to uncompressed data. Best of all, your support directly keeps Habitica running. Thank you very much -- it means a lot to us! + .promo_floral_potions.pull-left.slight-right-margin + h3 Last Chance for Marching Bard Item Set + p Reminder: this is the final day to subscribe and receive the Marching Bard Item Set! If you want the Marching Bard Hat or the Marching Bard Uniform, now's the time. Thanks so much for your support -- we hope you enjoy your Gems. p.small.muted by Lemoness + tr + td + h3 Last Chance for Floral Hatching Potions + p Reminder: this is the final day to buy Floral Hatching Potions! If they come back, it won't be until next year at the earliest, so don't delay! + p.small.muted by Mako413 if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 5/25/2016 - iOS UPDATE, ANDROID UPDATE, AND MAY SUBSCRIBER ITEMS + tr + td + h3 iOS App Update Available + p We've released a new iOS app update that contains tons of bug fixes, including for the pet feeding crash and the annoying fake death popups! Be sure to download it now for a more stable Habitica. + br + p Thank you so much to everyone who reported bugs that cropped up after our massive code overhaul! It was very helpful. And if you have a spare moment, we'd love it if you could review this app. It really helps us out! iOS hides all old reviews, but if you've already written one, it's very easy to repost with a single button click. + p.small.muted by viirus + tr + td + h3 Android Update: Bug Fixes + p We've released a new Android update with fixes for lots of bugs and crashes. Download it now for a more stable experience! + br + p Thank you for your patience as we worked out some of the hiccups that came with our major code overhaul this weekend. If you like the direction that we're taking the app, we'd love it if you could take the time to leave us a review :) It really helps us out! + p.small.muted by viirus + tr + td + .promo_mystery_201605.pull-right + h3 May Subscriber Items + p The May Subscriber Items have been revealed: the Marching Bard Item Set! You still have six days to subscribe and receive the item set. + br + p Subscribers also receive the ability to buy Gems for Gold -- the longer you subscribe, the more Gems you can buy per month! There are other perks as well, such as longer access to uncompressed data. Best of all, your support directly keeps Habitica running. Thank you very much -- it means a lot to us! + p.small.muted by Lemoness h2 5/21/2016 - WELCOME BACK, HABITICA! tr td From d89b1e3b08ea0467f35952fc2a1a89214cecdffd Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 30 May 2016 18:07:32 +0000 Subject: [PATCH 090/152] chore(patch): Bump version to 3.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b36648d917..0f619109bc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.4.1", + "version": "3.4.2", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 038dd64b12f11f6af3bcd719d69c99828986b9ba Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Wed, 25 May 2016 22:08:22 -0500 Subject: [PATCH 091/152] chore: Create broken challenges migration --- migrations/20160529_fix_challenges.js | 276 ++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 277 insertions(+) create mode 100644 migrations/20160529_fix_challenges.js diff --git a/migrations/20160529_fix_challenges.js b/migrations/20160529_fix_challenges.js new file mode 100644 index 0000000000..979a314e25 --- /dev/null +++ b/migrations/20160529_fix_challenges.js @@ -0,0 +1,276 @@ +'use strict'; + +/**************************************** + * Reason: After the api v3 maintenance migration, some challenge tasks + * became unlinked from their challenges. We're still not sure why, + * but this re-links them + * + * Note: We ran this on a local backup of the DB, and from that, grabbed + * the ids of the tasks that could be fixed and the updates that would + * be applied to them. We only ran the `updateTasks` promise task. + ***************************************/ + +const authorName = 'Blade'; +const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b'; + +global.Promise = require('bluebird'); +const MongoClient = require('mongodb').MongoClient; +const chalk = require('chalk'); +const TaskQueue = require('cwait').TaskQueue; + +const logger = { + info: _logger('info', 'cyan'), + success: _logger('info', 'green'), + error: _logger('error', 'red'), + log: _logger('log', 'white'), + warn: _logger('warn', 'yellow'), +} + +function _logger (type, color) { + return function () { + let args = Array.from(arguments).map(arg => chalk[color](arg)); + console[type].apply(null, args); + } +} + +// PROD: Enable prod db +// const NODE_DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX'; +const NODE_DB_URI = 'mongodb://localhost/new-prod-copy'; + +// Cached ids from running the findBrokenChallengeTasks query on a local copy of the db +// These are all the ids that _are_ fixable +const TASK_IDS = require('../fixable_task_ids.json'); +const TASK_UPDATE_DATA = require('../challenge_fixes.json'); + +let db; +let count = 0; + +var timer = setInterval(function(){ + count++; + if (count % 30 === 0) { + logger.warn('Process has been running for', count / 60, 'minutes'); + } +}, 1000); + +connectToDb() + // .then(findBrokenChallengeTasks) + // .then(getDataFromTasks) + // .then(getUserChallenges) + // .then(getChallengeTasks) + // .then(correctUserTasks) + .then(updateTasks) + .then(closeDb) + .catch(reportError) + +function connectToDb () { + return new Promise((resolve, reject) => { + MongoClient.connect(NODE_DB_URI, (err, database) => { + if (err) { + logger.error('Uh oh... Problem connecting to the new database'); + return reject(err); + } + + logger.success('Connected to the database'); + + db = database; + + resolve(db); + }); + }); +} + +function reportError (err) { + logger.error('Uh oh, an error occurred'); + closeDb(); + throw err; +} + +function unique (array) { + return Array.from(new Set(array)); +} + +function findBrokenChallengeTasks () { + logger.info('Looking for broken tasks...'); + + // return db.collection('tasks').find({'challenge.broken': 'CHALLENGE_TASK_NOT_FOUND'}).toArray() + return db.collection('tasks').find({'_id': { '$in': TASK_IDS }}).toArray() + .then((tasks) => { + logger.success('Found', tasks.length, 'broken tasks.'); + return Promise.resolve(tasks); + }); +} + +function getDataFromTasks (tasks) { + logger.info('Collecting data about the tasks...'); + + let userTasks = {}; + + tasks.forEach((task) => { + let userId = task.userId; + + if (!userTasks[userId]) { + userTasks[userId] = []; + } + userTasks[userId].push(task); + }); + + let users = unique(tasks.map(task => task.userId)); + + return Promise.resolve({ + users, + userTasks, + tasks, + }); +} + +function getUserChallenges (data) { + logger.info('Collecting user challenges...'); + + return db.collection('users').find({_id: { '$in': data.users }}, {challenges: 1}).toArray().then((docs) => { + logger.success('Found', docs.length, 'users from broken challenge tasks.'); + + let challenges = []; + docs.forEach((user) => { + challenges.push.apply(challenges, user.challenges); + }); + + challenges = unique(challenges); + + let userChallenges = {}; + + docs.forEach((user) => { + let userId = user._id; + if (!userChallenges[userId]) { + userChallenges[userId] = []; + } + userChallenges[userId].push.apply(userChallenges[userId], user.challenges); + }); + + data.userChallenges = userChallenges; + data.challenges = challenges; + + logger.success('Found', challenges.length, 'unique challenges.'); + + return Promise.resolve(data); + }); +} + +function getChallengeTasks (data) { + logger.info('Looking up original challenge tasks...'); + + return db.collection('tasks').find({'userId': null, 'challenge.id': { '$in': data.challenges }}, [ 'text', 'type', 'challenge', '_legacyId' ]).toArray().then((docs) => { + logger.success('Found', docs.length, 'challenge tasks.'); + + let challengeTasks = {}; + + docs.forEach((task) => { + let chalId = task.challenge.id; + if (!challengeTasks[chalId]) { + challengeTasks[chalId] = []; + } + challengeTasks[chalId].push(task); + }); + data.challengeTasks = challengeTasks; + + return Promise.resolve(data); + }); +} + +function correctUserTasks (data) { + logger.info('Correcting user tasks...'); + + let tasksToUpdate = {}; + let duplicateTasks = {}; + + for (let user in data.userChallenges) { + if (user === authorUuid) { + logger.success('Processing data for', authorName); + } + if (data.userChallenges.hasOwnProperty(user)) { + let challenges = data.userChallenges[user]; + + challenges.forEach((chal) => { + let challengeTasks = data.challengeTasks[chal]; + let userTasks = data.userTasks[user]; + + if (challengeTasks) { + challengeTasks.forEach((challengeTask) => { + let text = challengeTask.text; + let type = challengeTask.type; + let taskId = challengeTask._id; + let legacyId = challengeTask._legacyId; + + let foundTask = userTasks.find((task) => { + return TASK_IDS.indexOf(task._id) > -1 && task._legacyId === legacyId && task.type === type && task.text === text; + }) + + if (foundTask && !tasksToUpdate[foundTask._id]) { + tasksToUpdate[foundTask._id] = { + id: chal, + broken: null, + taskId, + } + } else if (foundTask && taskId !== tasksToUpdate[foundTask._id].taskId) { + logger.error('Duplicate task found, id:', foundTask._id); + duplicateTasks[foundTask._id] = duplicateTasks[foundTask._id] || [tasksToUpdate[foundTask._id].taskId]; + duplicateTasks[foundTask._id].push(taskId); + } + }); + } + }); + } + } + + let numberOfDuplicateTasksFound = Object.keys(duplicateTasks).length; + + if (numberOfDuplicateTasksFound > 0) { + logger.error('Found', numberOfDuplicateTasksFound, 'duplicate taks'); + } + + + data.tasksToUpdate = tasksToUpdate; + + return Promise.resolve(data); +} + +function updateTasks (data) { + let tasksToUpdate = TASK_UPDATE_DATA; + let taskIdsToUpdate = Object.keys(tasksToUpdate); + let queue = new TaskQueue(Promise, 300); + let promiseCount = 0; + + logger.info('About to update', taskIdsToUpdate.length, 'user tasks'); + + function updateTaskById (taskId) { + promiseCount++; + + if (promiseCount % 500 === 0) { + logger.info(promiseCount, 'updates started'); + } + + return db.collection('tasks').findOneAndUpdate({_id: taskId, 'challenge.broken': 'CHALLENGE_TASK_NOT_FOUND'}, {$set: {challenge: tasksToUpdate[taskId]}}, {returnOriginal: false}) + } + + return Promise.map(taskIdsToUpdate, queue.wrap(updateTaskById)).then((result) => { + let updates = result.filter(res => res.lastErrorObject.updatedExisting) + let failures = result.filter(res => !res.lastErrorObject.updatedExisting); + + logger.success(updates.length, 'tasks have been fixed'); + + if (failures.length > 0) { + logger.error(failures.length, 'tasks could not be updated'); + logger.error('Manually check these results'); + logger.error(failures); + } + + return Promise.resolve(data); + }); +} + +function closeDb (data) { + logger.success('The process took ' + count + ' seconds'); + + clearInterval(timer) + + db.close(); +} diff --git a/package.json b/package.json index 0f619109bc..53b6c5c2c5 100644 --- a/package.json +++ b/package.json @@ -126,6 +126,7 @@ "babel-eslint": "^6.0.0", "chai": "^3.4.0", "chai-as-promised": "^5.1.0", + "chalk": "^1.1.3", "coveralls": "^2.11.2", "csv": "~0.3.6", "deep-diff": "~0.1.4", From 6e78a14f8776b24e5fc095ecff2fca614e58e57c Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 30 May 2016 14:20:49 -0500 Subject: [PATCH 092/152] refactor: Move logger to separte file --- migrations/20160529_fix_challenges.js | 17 +---------------- migrations/utils/logger.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 16 deletions(-) create mode 100644 migrations/utils/logger.js diff --git a/migrations/20160529_fix_challenges.js b/migrations/20160529_fix_challenges.js index 979a314e25..f5f6041ba3 100644 --- a/migrations/20160529_fix_challenges.js +++ b/migrations/20160529_fix_challenges.js @@ -15,23 +15,8 @@ const authorUuid = '75f270e8-c5db-4722-a5e6-a83f1b23f76b'; global.Promise = require('bluebird'); const MongoClient = require('mongodb').MongoClient; -const chalk = require('chalk'); const TaskQueue = require('cwait').TaskQueue; - -const logger = { - info: _logger('info', 'cyan'), - success: _logger('info', 'green'), - error: _logger('error', 'red'), - log: _logger('log', 'white'), - warn: _logger('warn', 'yellow'), -} - -function _logger (type, color) { - return function () { - let args = Array.from(arguments).map(arg => chalk[color](arg)); - console[type].apply(null, args); - } -} +const logger = require('./utils/logger'); // PROD: Enable prod db // const NODE_DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX'; diff --git a/migrations/utils/logger.js b/migrations/utils/logger.js new file mode 100644 index 0000000000..023049a9c2 --- /dev/null +++ b/migrations/utils/logger.js @@ -0,0 +1,18 @@ +const chalk = require('chalk'); + +const logger = { + info: loggerGenerator('info', 'cyan'), + success: loggerGenerator('info', 'green'), + error: loggerGenerator('error', 'red'), + log: loggerGenerator('log', 'white'), + warn: loggerGenerator('warn', 'yellow'), +}; + +function loggerGenerator (type, color) { + return function () { + let args = Array.from(arguments).map(arg => chalk[color](arg)); + console[type].apply(null, args); + } +} + +module.exports = logger; From 7a9e50dbbac55847c980ec6e38378e279dd6e4d6 Mon Sep 17 00:00:00 2001 From: MathWhiz Date: Mon, 30 May 2016 13:33:54 -0500 Subject: [PATCH 093/152] Fix #7388 - Invert tag collapse when adding tasks --- common/script/ops/addTask.js | 14 +++----------- test/common/ops/addTask.js | 34 ++++++++++++++++++---------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/common/script/ops/addTask.js b/common/script/ops/addTask.js index 592f877248..2c4ea159e2 100644 --- a/common/script/ops/addTask.js +++ b/common/script/ops/addTask.js @@ -7,17 +7,9 @@ module.exports = function addTask (user, req = {body: {}}) { user.tasksOrder[`${task.type}s`].unshift(task._id); user[`${task.type}s`].unshift(task); - if (user.preferences.newTaskEdit) { - task._editing = true; - } - - if (user.preferences.tagsCollapsed) { - task._tags = true; - } - - if (!user.preferences.advancedCollapsed) { - task._advanced = true; - } + task._editing = user.preferences.newTaskEdit; + task._tags = !user.preferences.tagsCollapsed; + task._advanced = !user.preferences.advancedCollapsed; return task; }; diff --git a/test/common/ops/addTask.js b/test/common/ops/addTask.js index cb207036db..7137880c61 100644 --- a/test/common/ops/addTask.js +++ b/test/common/ops/addTask.js @@ -113,27 +113,29 @@ describe('shared.ops.addTask', () => { expect(reward.up).to.not.exists; }); - context('respects preferences', () => { - it('true', () => { + context('user preferences', () => { + it('respects newTaskEdit preference', () => { user.preferences.newTaskEdit = true; - user.preferences.tagsCollapsed = true; - user.preferences.advancedCollapsed = false; - let task = addTask(user); + expect(addTask(user)._editing).to.be.ok; - expect(task._editing).to.be.true; - expect(task._tags).to.be.true; - expect(task._advanced).to.be.true; + user.preferences.newTaskEdit = false; + expect(addTask(user)._editing).not.be.ok }); - it('false', () => { - user.preferences.newTaskEdit = false; - user.preferences.tagsCollapsed = false; - user.preferences.advancedCollapsed = true; - let task = addTask(user); + it('respects tagsCollapsed preference', () => { + user.preferences.tagsCollapsed = true; + expect(addTask(user)._tags).to.not.be.ok; - expect(task._editing).to.not.exists; - expect(task._tags).to.not.exists; - expect(task._advanced).to.not.exists; + user.preferences.tagsCollapsed = false; + expect(addTask(user)._tags).to.be.ok; + }); + + it('respects advancedCollapsed preference', () => { + user.preferences.advancedCollapsed = true; + expect(addTask(user)._advanced).not.be.ok + + user.preferences.advancedCollapsed = false; + expect(addTask(user)._advanced).to.be.ok; }); }); }); From 212395cef48967ec1cb073d6eacce3b6798cf12a Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 30 May 2016 15:16:46 -0500 Subject: [PATCH 094/152] lint: Add missing semicolons --- test/common/ops/addTask.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/common/ops/addTask.js b/test/common/ops/addTask.js index 7137880c61..9d29b5d129 100644 --- a/test/common/ops/addTask.js +++ b/test/common/ops/addTask.js @@ -119,7 +119,7 @@ describe('shared.ops.addTask', () => { expect(addTask(user)._editing).to.be.ok; user.preferences.newTaskEdit = false; - expect(addTask(user)._editing).not.be.ok + expect(addTask(user)._editing).not.be.ok; }); it('respects tagsCollapsed preference', () => { @@ -132,7 +132,7 @@ describe('shared.ops.addTask', () => { it('respects advancedCollapsed preference', () => { user.preferences.advancedCollapsed = true; - expect(addTask(user)._advanced).not.be.ok + expect(addTask(user)._advanced).not.be.ok; user.preferences.advancedCollapsed = false; expect(addTask(user)._advanced).to.be.ok; From b1a2632319f6173c14f62a0b4218d2a038df3005 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Mon, 30 May 2016 17:22:53 -0500 Subject: [PATCH 095/152] fix: Use === in comparison --- website/client/js/controllers/tasksCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index a4c9db9971..fb73fa5042 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -191,7 +191,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N $scope.addChecklistItem = function(task, $event, $index) { if (task.checklist[$index].text) { $scope.saveTask(task, true); - if ($index == task.checklist.length - 1) + if ($index === task.checklist.length - 1) task.checklist.push({ completed: false, text: '' }); focusChecklist(task, $index + 1); } else { From 6b214e4e3fcfadcf0361e1fbe8fffafb6eaaf8d1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 11:09:32 +0200 Subject: [PATCH 096/152] sanitize task when updating in v2 --- website/server/controllers/api-v2/user.js | 15 ++++++++++++++- website/server/models/task.js | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js index 656a48c908..20c147aab9 100644 --- a/website/server/controllers/api-v2/user.js +++ b/website/server/controllers/api-v2/user.js @@ -867,7 +867,20 @@ api.updateTask = function(req, res, next) { if(!task) return res.status(404).json({err: 'Task not found.'}) try { - _.assign(task, shared.ops.updateTask(task.toObject(), req)[0]); + // we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances? + let [updatedTaskObj] = shared.ops.updateTask(task.toObject(), req); + + // Sanitize differently user tasks linked to a challenge + let sanitizedObj; + + if (task.userId && task.challenge && task.challenge.id) { + sanitizedObj = Tasks.Task.sanitizeUserChallengeTask(updatedTaskObj); + } else { + sanitizedObj = Tasks.Task.sanitize(updatedTaskObj); + } + + _.assign(task, sanitizedObj); + task.save(function(err, task){ if(err) return next(err); diff --git a/website/server/models/task.js b/website/server/models/task.js index 2882e0530d..0664fab855 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -12,7 +12,7 @@ let discriminatorOptions = { }; let subDiscriminatorOptions = _.defaults(_.cloneDeep(discriminatorOptions), { _id: false, - minimize: false, + minimize: false, // So empty objects are returned }); export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; From 87066ef6c751eb52ec1269d436fb80343267c687 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 11:13:28 +0200 Subject: [PATCH 097/152] do not allow casting on challenge tasks in v2 --- website/server/controllers/api-v2/user.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js index 20c147aab9..b9439de09a 100644 --- a/website/server/controllers/api-v2/user.js +++ b/website/server/controllers/api-v2/user.js @@ -657,6 +657,8 @@ api.cast = async function(req, res, next) { if (!task) { return res.status(404).json({err: 'Task "' + targetId + '" not found.'}); } + if (task.challenge.id) return res.status(40).json({err: 'Cannot cast spell on challenge task.'}); + spell.cast(user, task, req); await task.save(); From d9de6282a4f8efc326e848cc77f925bbf0e93d9c Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 11:27:21 +0200 Subject: [PATCH 098/152] increase number of concurrent users that are synced when editing a challenge --- website/server/models/challenge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/models/challenge.js b/website/server/models/challenge.js index 4fddabac2c..511c969429 100644 --- a/website/server/models/challenge.js +++ b/website/server/models/challenge.js @@ -191,7 +191,7 @@ schema.methods.addTasks = async function challengeAddTasks (tasks) { let challenge = this; let membersIds = await _fetchMembersIds(challenge._id); - let queue = new cwait.TaskQueue(Bluebird, 5); // process only 5 users concurrently + let queue = new cwait.TaskQueue(Bluebird, 25); // process only 5 users concurrently await Bluebird.map(membersIds, queue.wrap((memberId) => { return _addTaskFn(challenge, tasks, memberId); From bcbf00d42c598ffd06051cce68fbfe1dd1b4e7c7 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 11:32:33 +0200 Subject: [PATCH 099/152] fix api v2 tests, do not allow task.value to be updated --- test/api/v2/user/tasks/PUT-tasks_id.test.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/api/v2/user/tasks/PUT-tasks_id.test.js b/test/api/v2/user/tasks/PUT-tasks_id.test.js index 3bb9d8e9c6..ee41a4fe68 100644 --- a/test/api/v2/user/tasks/PUT-tasks_id.test.js +++ b/test/api/v2/user/tasks/PUT-tasks_id.test.js @@ -28,17 +28,15 @@ describe('PUT /user/tasks/:id', () => { }); }); - it('updates text, attribute, priority, value and notes', async () => { + it('updates text, attribute, priority and notes', async () => { return user.put(`/user/tasks/${task.id}`, { text: 'new text', notes: 'new notes', - value: 10000, priority: 0.1, attribute: 'str', }).then((updatedTask) => { expect(updatedTask.text).to.eql('new text'); expect(updatedTask.notes).to.eql('new notes'); - expect(updatedTask.value).to.eql(10000); expect(updatedTask.priority).to.eql(0.1); expect(updatedTask.attribute).to.eql('str'); }); From ee836c76a4207a3350755b6019f7ecb63ea356d1 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 16:37:34 +0200 Subject: [PATCH 100/152] log req.method --- test/api/v3/unit/middlewares/errorHandler.test.js | 1 + website/server/middlewares/api-v3/errorHandler.js | 1 + 2 files changed, 2 insertions(+) diff --git a/test/api/v3/unit/middlewares/errorHandler.test.js b/test/api/v3/unit/middlewares/errorHandler.test.js index 0cb1e1bb97..da7d54e667 100644 --- a/test/api/v3/unit/middlewares/errorHandler.test.js +++ b/test/api/v3/unit/middlewares/errorHandler.test.js @@ -165,6 +165,7 @@ describe('errorHandler', () => { expect(logger.error).to.be.calledOnce; expect(logger.error).to.be.calledWithExactly(error, { + method: req.method, originalUrl: req.originalUrl, headers: req.headers, body: req.body, diff --git a/website/server/middlewares/api-v3/errorHandler.js b/website/server/middlewares/api-v3/errorHandler.js index cd76277c78..b71fb9666e 100644 --- a/website/server/middlewares/api-v3/errorHandler.js +++ b/website/server/middlewares/api-v3/errorHandler.js @@ -66,6 +66,7 @@ module.exports = function errorHandler (err, req, res, next) { // eslint-disable // log the error logger.error(err, { + method: req.method, originalUrl: req.originalUrl, headers: omit(req.headers, ['x-api-key', 'cookie']), // don't send sensitive information that only adds noise body: req.body, From b33505141b806518d3cd07a04220c0d05decb360 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 31 May 2016 13:03:45 -0500 Subject: [PATCH 101/152] docs: Correct url for member routes --- website/server/controllers/api-v3/members.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index 22a40afed2..f59bf25bbf 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -259,7 +259,7 @@ api.getChallengeMemberProgress = { }; /** - * @api {posts} /members/send-private-message Send a private message to a member + * @api {posts} /api/v3/members/send-private-message Send a private message to a member * @apiVersion 3.0.0 * @apiName SendPrivateMessage * @apiGroup Members @@ -308,7 +308,7 @@ api.sendPrivateMessage = { }; /** - * @api {posts} /members/transfer-gems Send a gem gift to a member + * @api {posts} /api/v3/members/transfer-gems Send a gem gift to a member * @apiVersion 3.0.0 * @apiName TransferGems * @apiGroup Members From 0b122f3b43ce2903b78af357b6a6944e61837cd3 Mon Sep 17 00:00:00 2001 From: Lucas F T Hennigen Date: Tue, 31 May 2016 16:55:30 -0300 Subject: [PATCH 102/152] fix(tags): Fix tag removal functionality (#7553) Tag removal functionality was broken. Turns out the issue was simply that we weren't removing the tag from the local `task` object, so the tag was removed, but readded upon sync/pressing save. Closes #7412 --- website/client/js/controllers/tasksCtrl.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/client/js/controllers/tasksCtrl.js b/website/client/js/controllers/tasksCtrl.js index fb73fa5042..39619a12c2 100644 --- a/website/client/js/controllers/tasksCtrl.js +++ b/website/client/js/controllers/tasksCtrl.js @@ -305,7 +305,7 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N task.tags.push(tagId); } else { Tasks.removeTagFromTask(task._id, tagId); - task.tags.splice(tagIndex, 0); + task.tags.splice(tagIndex, 1); } } }]); From 9b56c6f2ba4ce82151fccb1643958b0d9716278f Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 31 May 2016 14:55:59 -0500 Subject: [PATCH 103/152] Don't persist properties of tag filters (#7564) * fix(filters): don't persist tag props Partial fix for #7412 * fix(test): correct expectation --- test/spec/controllers/filtersCtrlSpec.js | 2 +- website/client/js/controllers/filtersCtrl.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/spec/controllers/filtersCtrlSpec.js b/test/spec/controllers/filtersCtrlSpec.js index b2adac581d..54126ca60b 100644 --- a/test/spec/controllers/filtersCtrlSpec.js +++ b/test/spec/controllers/filtersCtrlSpec.js @@ -27,7 +27,7 @@ describe('Filters Controller', function() { scope.toggleFilter(tag); expect(userService.user.filters[tag.id]).to.eql(true); scope.toggleFilter(tag); - expect(userService.user.filters[tag.id]).to.eql(false); + expect(userService.user.filters[tag.id]).to.not.eql(true); })); }); diff --git a/website/client/js/controllers/filtersCtrl.js b/website/client/js/controllers/filtersCtrl.js index 4a3ed6e59c..0020a17eee 100644 --- a/website/client/js/controllers/filtersCtrl.js +++ b/website/client/js/controllers/filtersCtrl.js @@ -28,7 +28,7 @@ habitrpg.controller("FiltersCtrl", ['$scope', '$rootScope', 'User', 'Shared', if (!user.filters[tag.id]) { user.filters[tag.id] = true; } else { - user.filters[tag.id] = !user.filters[tag.id]; + delete user.filters[tag.id]; } // no longer persisting this, it was causing a lot of confusion - users thought they'd permanently lost tasks From fcc14520c5d94ed0e8e546c70f5f7be5488d6545 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 31 May 2016 14:56:33 -0500 Subject: [PATCH 104/152] Don't $watch drops (#7547) * WIP(drops): get drops out of _tmp * fix(notifications): no watch for drops/mystery * fix(ops): only two response values * fix(test): update op expectation Also update API docs for modified route. * fix(lint): remove trailing space * fix(test): update integration check --- common/script/ops/openMysteryItem.js | 8 +-- .../user/POST-user_open_mystery_item.test.js | 7 ++- test/common/ops/openMysteryItem.js | 3 +- .../client/js/controllers/notificationCtrl.js | 49 ----------------- .../js/services/notificationServices.js | 9 +++- website/client/js/services/userServices.js | 53 +++++++++++++++++-- website/server/controllers/api-v3/user.js | 2 +- 7 files changed, 68 insertions(+), 63 deletions(-) diff --git a/common/script/ops/openMysteryItem.js b/common/script/ops/openMysteryItem.js index 743104c48b..9728bc9eb9 100644 --- a/common/script/ops/openMysteryItem.js +++ b/common/script/ops/openMysteryItem.js @@ -13,7 +13,6 @@ module.exports = function openMysteryItem (user, req = {}, analytics) { } item = _.cloneDeep(content.gear.flat[item]); - item.notificationType = 'Mystery'; user.items.gear.owned[item.key] = true; user.markModified('purchased.plan.mysteryItems'); @@ -28,16 +27,11 @@ module.exports = function openMysteryItem (user, req = {}, analytics) { }); } - if (typeof window !== 'undefined') { - if (!user._tmp) user._tmp = {}; - user._tmp.drop = item; - } - if (req.v2 === true) { return user.items.gear.owned; } else { return [ - user.items.gear.owned, + item, i18n.t('mysteryItemOpened', req.language), ]; } diff --git a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js index d9e9fe7326..6cfc0fe254 100644 --- a/test/api/v3/integration/user/POST-user_open_mystery_item.test.js +++ b/test/api/v3/integration/user/POST-user_open_mystery_item.test.js @@ -2,10 +2,13 @@ import { generateUser, translate as t, } from '../../../../helpers/api-integration/v3'; +import content from '../../../../../common/script/content/index'; describe('POST /user/open-mystery-item', () => { let user; let mysteryItemKey = 'eyewear_special_summerRogue'; + let mysteryItemIndex = content.gear.flat[mysteryItemKey].index; + let mysteryItemType = content.gear.flat[mysteryItemKey].type; beforeEach(async () => { user = await generateUser({ @@ -21,6 +24,8 @@ describe('POST /user/open-mystery-item', () => { expect(user.items.gear.owned[mysteryItemKey]).to.be.true; expect(response.message).to.equal(t('mysteryItemOpened')); - expect(response.data).to.deep.equal(user.items.gear.owned); + expect(response.data.key).to.eql(mysteryItemKey); + expect(response.data.index).to.eql(mysteryItemIndex); + expect(response.data.type).to.eql(mysteryItemType); }); }); diff --git a/test/common/ops/openMysteryItem.js b/test/common/ops/openMysteryItem.js index 712c032254..b9c896cbdf 100644 --- a/test/common/ops/openMysteryItem.js +++ b/test/common/ops/openMysteryItem.js @@ -5,6 +5,7 @@ import { import { BadRequest, } from '../../../common/script/libs/errors'; +import content from '../../../common/script/content/index'; import i18n from '../../../common/script/i18n'; describe('shared.ops.openMysteryItem', () => { @@ -33,6 +34,6 @@ describe('shared.ops.openMysteryItem', () => { expect(user.items.gear.owned[mysteryItemKey]).to.be.true; expect(message).to.equal(i18n.t('mysteryItemOpened')); - expect(data).to.equal(user.items.gear.owned); + expect(data).to.eql(content.gear.flat[mysteryItemKey]); }); }); diff --git a/website/client/js/controllers/notificationCtrl.js b/website/client/js/controllers/notificationCtrl.js index 39dc1e4790..57f180515e 100644 --- a/website/client/js/controllers/notificationCtrl.js +++ b/website/client/js/controllers/notificationCtrl.js @@ -82,55 +82,6 @@ habitrpg.controller('NotificationCtrl', } }); - $rootScope.$watch('user._tmp.drop', function(after, before){ - // won't work when getting the same item twice? - if (_.isEqual(after, before) || !after) return; - var text, notes, type; - $rootScope.playSound('Item_Drop'); - - // Note: For Mystery Item gear, after.type will be 'head', 'armor', etc - // so we use after.notificationType below. - - if (after.type !== 'gear' && after.type !== 'Quest' && after.notificationType !== 'Mystery') { - if (after.type === 'Food') { - type = 'food'; - } else if (after.type === 'HatchingPotion') { - type = 'hatchingPotions'; - } else { - type = after.type.toLowerCase() + 's'; - } - if(!User.user.items[type][after.key]){ - User.user.items[type][after.key] = 0; - } - User.user.items[type][after.key]++; - } - - if (after.type === 'HatchingPotion'){ - text = Content.hatchingPotions[after.key].text(); - notes = Content.hatchingPotions[after.key].notes(); - Notification.drop(env.t('messageDropPotion', {dropText: text, dropNotes: notes}), after); - } else if (after.type === 'Egg'){ - text = Content.eggs[after.key].text(); - notes = Content.eggs[after.key].notes(); - Notification.drop(env.t('messageDropEgg', {dropText: text, dropNotes: notes}), after); - } else if (after.type === 'Food'){ - text = Content.food[after.key].text(); - notes = Content.food[after.key].notes(); - Notification.drop(env.t('messageDropFood', {dropArticle: after.article, dropText: text, dropNotes: notes}), after); - } else if (after.type === 'Quest') { - $rootScope.selectedQuest = Content.quests[after.key]; - $rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'}); - } else if (after.notificationType === 'Mystery') { - text = Content.gear.flat[after.key].text(); - Notification.drop(env.t('messageDropMysteryItem', {dropText: text}), after); - } else { - // Keep support for another type of drops that might be added - Notification.drop(User.user._tmp.drop.dialog); - } - - Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'}); - }); - $rootScope.$watch('user.achievements.streak', function(after, before){ if(before == undefined || after <= before) return; Notification.streak(User.user.achievements.streak); diff --git a/website/client/js/services/notificationServices.js b/website/client/js/services/notificationServices.js index 096c1643af..b6b854cde1 100644 --- a/website/client/js/services/notificationServices.js +++ b/website/client/js/services/notificationServices.js @@ -39,7 +39,14 @@ angular.module("habitrpg").factory("Notification", case "Food": dropClass = 'Pet_Food_' + item.key; break; - case "Mystery": + case "armor": + case "back": + case "body": + case "eyewear": + case "head": + case "headAccessory": + case "shield": + case "weapon": dropClass = 'shop_' + item.key; break; default: diff --git a/website/client/js/services/userServices.js b/website/client/js/services/userServices.js index 8b99c2c3b3..62edbc9ac2 100644 --- a/website/client/js/services/userServices.js +++ b/website/client/js/services/userServices.js @@ -14,8 +14,8 @@ angular.module('habitrpg') /** * Services that persists and retrieves user from localStorage. */ - .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Notification', 'ApiUrl', 'Tasks', 'Tags', - function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Notification, ApiUrl, Tasks, Tags) { + .factory('User', ['$rootScope', '$http', '$location', '$window', 'STORAGE_USER_ID', 'STORAGE_SETTINGS_ID', 'Notification', 'ApiUrl', 'Tasks', 'Tags', 'Content', + function($rootScope, $http, $location, $window, STORAGE_USER_ID, STORAGE_SETTINGS_ID, Notification, ApiUrl, Tasks, Tags, Content) { var authenticated = false; var defaultSettings = { auth: { apiId: '', apiToken: ''}, @@ -162,6 +162,11 @@ angular.module('habitrpg') if (response.data.message && response.data.message !== clientMessage) { Notification.text(response.data.message); } + if (opName === 'openMysteryItem') { + var openedItem = clientResponse[0]; + var text = Content.gear.flat[openedItem.key].text(); + Notification.drop(env.t('messageDropMysteryItem', {dropText: text}), openedItem); + } save(); }) @@ -231,7 +236,49 @@ angular.module('habitrpg') var critBonus = crit * 100 - 100; Notification.crit(critBonus); } - if (drop) user._tmp.drop = drop; + if (drop) { + var text, notes, type; + $rootScope.playSound('Item_Drop'); + + // Note: For Mystery Item gear, drop.type will be 'head', 'armor', etc + // so we use drop.notificationType below. + + if (drop.type !== 'gear' && drop.type !== 'Quest' && drop.notificationType !== 'Mystery') { + if (drop.type === 'Food') { + type = 'food'; + } else if (drop.type === 'HatchingPotion') { + type = 'hatchingPotions'; + } else { + type = drop.type.toLowerCase() + 's'; + } + if(!user.items[type][drop.key]){ + user.items[type][drop.key] = 0; + } + user.items[type][drop.key]++; + } + + if (drop.type === 'HatchingPotion'){ + text = Content.hatchingPotions[drop.key].text(); + notes = Content.hatchingPotions[drop.key].notes(); + Notification.drop(env.t('messageDropPotion', {dropText: text, dropNotes: notes}), drop); + } else if (drop.type === 'Egg'){ + text = Content.eggs[drop.key].text(); + notes = Content.eggs[drop.key].notes(); + Notification.drop(env.t('messageDropEgg', {dropText: text, dropNotes: notes}), drop); + } else if (drop.type === 'Food'){ + text = Content.food[drop.key].text(); + notes = Content.food[drop.key].notes(); + Notification.drop(env.t('messageDropFood', {dropArticle: drop.article, dropText: text, dropNotes: notes}), drop); + } else if (drop.type === 'Quest') { + $rootScope.selectedQuest = Content.quests[drop.key]; + $rootScope.openModal('questDrop', {controller:'PartyCtrl', size:'sm'}); + } else { + // Keep support for another type of drops that might be added + Notification.drop(drop.dialog); + } + + // Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'acquire item','itemName':after.key,'acquireMethod':'Drop'}); + } }); }, diff --git a/website/server/controllers/api-v3/user.js b/website/server/controllers/api-v3/user.js index c3b03f620d..64287811c7 100644 --- a/website/server/controllers/api-v3/user.js +++ b/website/server/controllers/api-v3/user.js @@ -888,7 +888,7 @@ api.readCard = { * @apiName UserOpenMysteryItem * @apiGroup User * -* @apiSuccess {Object} data user.items.gear.owned +* @apiSuccess {Object} data The item obtained * @apiSuccess {string} message Success message */ api.userOpenMysteryItem = { From c746f643fdb9150f4d14b7edfe619d50cef71f30 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 31 May 2016 20:14:20 +0000 Subject: [PATCH 105/152] chore(patch): Bump version to 3.4.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 53b6c5c2c5..2abc77b302 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.4.2", + "version": "3.4.3", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From a11a9998396392afcc527fbbe2de4a0f7d5703c8 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 31 May 2016 18:04:53 -0500 Subject: [PATCH 106/152] chore: Add migration file for correcting users tasks Fix for aftermath of https://github.com/HabitRPG/habitrpg/issues/7546 and https://github.com/HabitRPG/habitrpg/issues/7551 --- migrations/20160529_fix_challenges.js | 5 +- ...ks_from_null_value_in_challenges_broken.js | 229 ++++++++++++++++++ migrations/utils/logger.js | 2 + 3 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js diff --git a/migrations/20160529_fix_challenges.js b/migrations/20160529_fix_challenges.js index f5f6041ba3..51c31ee0c9 100644 --- a/migrations/20160529_fix_challenges.js +++ b/migrations/20160529_fix_challenges.js @@ -8,6 +8,9 @@ * Note: We ran this on a local backup of the DB, and from that, grabbed * the ids of the tasks that could be fixed and the updates that would * be applied to them. We only ran the `updateTasks` promise task. + * + * IMPORTANT - Setting challenge.broken to null caused issues + * see https://github.com/HabitRPG/habitrpg/issues/7546 ***************************************/ const authorName = 'Blade'; @@ -192,7 +195,7 @@ function correctUserTasks (data) { if (foundTask && !tasksToUpdate[foundTask._id]) { tasksToUpdate[foundTask._id] = { id: chal, - broken: null, + broken: null, // NOTE: this caused a lot of problems taskId, } } else if (foundTask && taskId !== tasksToUpdate[foundTask._id].taskId) { diff --git a/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js b/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js new file mode 100644 index 0000000000..f5ae6c9925 --- /dev/null +++ b/migrations/20160530_fix_tasks_from_null_value_in_challenges_broken.js @@ -0,0 +1,229 @@ +'use strict'; + +/**************************************** + * Reason: After running the 20160529_fix_challenges.js migration + * challenge.broken was set to null, which is not a valid value + * which caused cron to fail and run many times, messing up daily values, + * history and streaks + * + * Note: Part of this code does calculation to look up users that + * were affected. After the user ids were found, @crookedneighbor + * pm'ed each user asking if they would like their tasks reset to the previous day + ***************************************/ + +global.Promise = require('bluebird'); +const MongoClient = require('mongodb').MongoClient; +const TaskQueue = require('cwait').TaskQueue; +const logger = require('./utils/logger'); + +const TASK_IDS = require('../task_ids.json'); +// cached call to getAffectedUsers minus the users I've helped manually +const POSSIBLY_AFFECTED_USERS = require('../users.json'); +const AFFECTED_USERS = require('../users_with_bad_history.json'); + +// PROD: Enable prod db +const OLD_DB_URI = 'mongodb://localhost/new-prod-copy'; +const NEW_DB_URI = 'mongodb://username:password@dsXXXXXX-a0.mlab.com:XXXXX,dsXXXXXX-a1.mlab.com:XXXXX/habitica?replicaSet=rs-dsXXXXXX'; + +let oldDb, newDb, OldTasks, NewTasks, OldUsers, NewUsers; +let count = 0; + +var timer = setInterval(function(){ + count++; + if (count % 30 === 0) { + logger.warn('Process has been running for', count / 60, 'minutes'); + } +}, 1000); + +Promise.all([ + connectToDb(OLD_DB_URI, 'backup'), + connectToDb(NEW_DB_URI, 'prod'), +]) + .then(assignDbVariables) + // .then(getAffectedUsersEmail) + // .then(findAffectedUsers) + // .then(determineIfTasksNeedAdjusting) + .then(getValuesOfOldTasksFromBackup) + .then(updateNewTasks) + .then(closeDb) + .catch(reportError) + +function connectToDb (dbUri, type) { + return new Promise((resolve, reject) => { + MongoClient.connect(dbUri, (err, database) => { + if (err) { + logger.error(`Uh oh... Problem connecting to the ${type} database`); + return reject(err); + } + + logger.success(`Connected to the ${type} database`); + + resolve(database); + }); + }); +} + +function reportError (err) { + logger.error('Uh oh, an error occurred'); + // closeDb(); + throw err; +} + +function assignDbVariables (results) { + oldDb = results[0]; + OldTasks = oldDb.collection('tasks_backup'); + OldUsers = oldDb.collection('users'); + + newDb = results[1]; + NewTasks = newDb.collection('tasks'); + NewUsers = newDb.collection('users'); + + return Promise.resolve(); +} + +function getAffectedUsersEmail () { + logger.info('Looking up emails and pm-ability for affected users'); + + let pmsWithEmail = []; + let emails = []; + let missing = []; + + return NewUsers.find({_id: {$in: AFFECTED_USERS}}, ['preferences', 'inbox', 'auth.facebook.email', 'auth.local.email']).toArray().then((users) => { + users.forEach((user) => { + if (user.preferences.emailNotifications.newPM && user.inbox.optOut !== true) { + pmsWithEmail.push(user._id); + } else { + if (user.auth && user.auth.local && user.auth.local.email) { + emails.push(user.auth.local.email); + } else if (user.auth && user.auth.facebook && user.auth.facebook.email) { + emails.push(user.auth.facebook.email); + } else { + missing.push(user._id); + } + } + }); + + logger.log('PMable users with email notification'); + logger.log(pmsWithEmail); + + logger.log('Emailable users'); + logger.log(emails); + + logger.log('Unreachable users'); + logger.log(missing); + + return Promise.resolve(); + }); +} + +function findAffectedUsers () { + logger.info('finding affected users'); + return NewTasks.find({_id: {$in: TASK_IDS}}).toArray().then((tasks) => { + let users = unique(tasks.map(task => task.userId)); + + logger.success('Found', users.length, 'users that may have been affected'); + + return Promise.resolve(users); + }); +} + +function determineIfTasksNeedAdjusting (tasks) { + return Promise.all([ + OldTasks.find({userId: {$in: POSSIBLY_AFFECTED_USERS}, type: 'daily'}, ['value', 'history', 'userId']).toArray(), + NewTasks.find({userId: {$in: POSSIBLY_AFFECTED_USERS}, type: 'daily'}, ['value', 'history', 'userId']).toArray(), + ]).then((results) => { + let backupTasks = results[0]; + let prodTasks = results[1]; + + logger.success(backupTasks.length, 'dailys found in backup db'); + logger.success(prodTasks.length, 'dailys found in prod db'); + + let backupTasksAsObject = backupTasks.reduce((object, task) => { + object[task._id] = task; + return object; + }, {}); + let prodTasksGroupedByUser = prodTasks.reduce((object, task) => { + if (!object[task.userId]) object[task.userId] = []; + object[task.userId].push(task); + return object; + }, {}); + + let usersWithBadHistory = POSSIBLY_AFFECTED_USERS.filter((user) => { + let tasks = prodTasksGroupedByUser[user]; + if (!tasks) return false; + + for (let i = 0; i < tasks.length; i++) { + let prodTask = tasks[i]; + let backupTask = backupTasksAsObject[prodTask._id]; + + if (!backupTask) { continue; } + + let historyDifference = prodTask.history.length - backupTask.history.length; + + if (historyDifference > 4) { + return true; + } + } + + return false; + }); + + logger.info('Total possibly affected users:', POSSIBLY_AFFECTED_USERS.length); + logger.info('Users with wonky task data:', usersWithBadHistory.length); + + return Promise.resolve(usersWithBadHistory); + }); +} + +let usersToRun = [ + // Since this is happening a little at a time as users get back to us, just fill this in with user ids +]; + +function getValuesOfOldTasksFromBackup () { + logger.info('looking for tasks'); + + return OldTasks.find({userId: {$in: usersToRun}}, ['value', 'streak', 'history']).toArray(); +} + +function updateNewTasks (oldTasks) { + let queue = new TaskQueue(Promise, 300); + let promiseCount = 0; + + logger.info('About to update', oldTasks.length, 'user tasks'); + + function updateTaskById (task) { + promiseCount++; + + if (promiseCount % 100=== 0) { + logger.info(promiseCount, 'updates started'); + } + + return NewTasks.findOneAndUpdate({_id: task._id}, {$set: {value: task.value, streak: task.streak, history: task.history}}, {returnOriginal: false}) + } + + return Promise.map(oldTasks, queue.wrap(updateTaskById)).then((result) => { + let updates = result.filter(res => res.lastErrorObject && res.lastErrorObject.updatedExisting) + let failures = result.filter(res => !(res.lastErrorObject && res.lastErrorObject.updatedExisting)); + + logger.success(updates.length, 'tasks have been fixed'); + + if (failures.length > 0) { + logger.error(failures.length, 'tasks could not be found'); + } + + return Promise.resolve(); + }); +} + +function unique (array) { + return Array.from(new Set(array)); +} + +function closeDb () { + logger.success('The process took ' + count + ' seconds'); + + clearInterval(timer) + + oldDb.close(); + newDb.close(); +} diff --git a/migrations/utils/logger.js b/migrations/utils/logger.js index 023049a9c2..a2ddc96fe2 100644 --- a/migrations/utils/logger.js +++ b/migrations/utils/logger.js @@ -1,3 +1,5 @@ +'use strict'; + const chalk = require('chalk'); const logger = { From de21487038cc720f21150b7c662536a8a37d965d Mon Sep 17 00:00:00 2001 From: Shervin Sarain Date: Mon, 30 May 2016 19:04:13 +0200 Subject: [PATCH 107/152] Fix challenge sorting Closes #7543 Closes #7507 --- .../GET-challenges_group_groupid.test.js | 66 +++++ .../challenges/GET-challenges_user.test.js | 280 +++++++++++------- .../server/controllers/api-v3/challenges.js | 8 +- 3 files changed, 244 insertions(+), 110 deletions(-) diff --git a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js index 275fe798f5..c507d0a4b9 100644 --- a/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_group_groupid.test.js @@ -64,6 +64,20 @@ describe('GET challenges/group/:groupId', () => { profile: {name: user.profile.name}, }); }); + + it('should return newest challenges first', async () => { + let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); + expect(foundChallengeIndex).to.eql(0); + + let newChallenge = await generateChallenge(user, publicGuild); + + challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); + expect(foundChallengeIndex).to.eql(0); + }); }); context('Private Guild', () => { @@ -115,4 +129,56 @@ describe('GET challenges/group/:groupId', () => { }); }); }); + + context('official challenge is present', () => { + let publicGuild, user, officialChallenge, challenge, challenge2; + + before(async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestGuild', + type: 'guild', + privacy: 'public', + }, + }); + + user = groupLeader; + publicGuild = group; + + await user.update({ + 'contributor.admin': true, + }); + + officialChallenge = await generateChallenge(user, group, { + official: true, + }); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); + }); + + it('should return official challenges first', async () => { + let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + let foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id }); + expect(foundChallengeIndex).to.eql(0); + }); + + it('should return newest challenges first, after official ones', async () => { + let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id }); + expect(foundChallengeIndex).to.eql(2); + + foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); + expect(foundChallengeIndex).to.eql(1); + + let newChallenge = await generateChallenge(user, publicGuild); + + challenges = await user.get(`/challenges/groups/${publicGuild._id}`); + + foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); + expect(foundChallengeIndex).to.eql(1); + }); + }); }); diff --git a/test/api/v3/integration/challenges/GET-challenges_user.test.js b/test/api/v3/integration/challenges/GET-challenges_user.test.js index ecb9f55383..b46367f46f 100644 --- a/test/api/v3/integration/challenges/GET-challenges_user.test.js +++ b/test/api/v3/integration/challenges/GET-challenges_user.test.js @@ -5,128 +5,196 @@ import { } from '../../../../helpers/api-v3-integration.helper'; describe('GET challenges/user', () => { - let user, member, nonMember, challenge, challenge2, publicGuild; + context('no official challenges', () => { + let user, member, nonMember, challenge, challenge2, publicGuild; - before(async () => { - let { group, groupLeader, members } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestGuild', - type: 'guild', - privacy: 'public', - }, - members: 1, + before(async () => { + let { group, groupLeader, members } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestGuild', + type: 'guild', + privacy: 'public', + }, + members: 1, + }); + + user = groupLeader; + publicGuild = group; + member = members[0]; + nonMember = await generateUser(); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); }); - user = groupLeader; - publicGuild = group; - member = members[0]; - nonMember = await generateUser(); + it('should return challenges user has joined', async () => { + await nonMember.post(`/challenges/${challenge._id}/join`); - challenge = await generateChallenge(user, group); - challenge2 = await generateChallenge(user, group); - }); + let challenges = await nonMember.get('/challenges/user'); - it('should return challenges user has joined', async () => { - await nonMember.post(`/challenges/${challenge._id}/join`); - - let challenges = await nonMember.get('/challenges/user'); - - let foundChallenge = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge).to.exist; - expect(foundChallenge.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, + let foundChallenge = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge).to.exist; + expect(foundChallenge.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); }); - expect(foundChallenge.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, + + it('should return challenges user has created', async () => { + let challenges = await user.get('/challenges/user'); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge1.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge2.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + }); + + it('should return challenges in user\'s group', async () => { + let challenges = await member.get('/challenges/user'); + + let foundChallenge1 = _.find(challenges, { _id: challenge._id }); + expect(foundChallenge1).to.exist; + expect(foundChallenge1.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge1.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); + expect(foundChallenge2).to.exist; + expect(foundChallenge2.leader).to.eql({ + _id: publicGuild.leader._id, + id: publicGuild.leader._id, + profile: {name: user.profile.name}, + }); + expect(foundChallenge2.group).to.eql({ + _id: publicGuild._id, + id: publicGuild._id, + type: publicGuild.type, + privacy: publicGuild.privacy, + name: publicGuild.name, + }); + }); + + it('should return newest challenges first', async () => { + let challenges = await user.get('/challenges/user'); + + let foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); + expect(foundChallengeIndex).to.eql(0); + + let newChallenge = await generateChallenge(user, publicGuild); + + challenges = await user.get('/challenges/user'); + + foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); + expect(foundChallengeIndex).to.eql(0); + }); + + it('should not return challenges user doesn\'t have access to', async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestPrivateGuild', + type: 'guild', + privacy: 'private', + }, + }); + + let privateChallenge = await generateChallenge(groupLeader, group); + + let challenges = await nonMember.get('/challenges/user'); + + let foundChallenge = _.find(challenges, { _id: privateChallenge._id }); + expect(foundChallenge).to.not.exist; }); }); - it('should return challenges user has created', async () => { - let challenges = await user.get('/challenges/user'); + context('official challenge is present', () => { + let user, officialChallenge, challenge, challenge2, publicGuild; - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge1.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge2.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - }); + before(async () => { + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { + name: 'TestGuild', + type: 'guild', + privacy: 'public', + }, + }); - it('should return challenges in user\'s group', async () => { - let challenges = await member.get('/challenges/user'); + user = groupLeader; + publicGuild = group; - let foundChallenge1 = _.find(challenges, { _id: challenge._id }); - expect(foundChallenge1).to.exist; - expect(foundChallenge1.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge1.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); - expect(foundChallenge2).to.exist; - expect(foundChallenge2.leader).to.eql({ - _id: publicGuild.leader._id, - id: publicGuild.leader._id, - profile: {name: user.profile.name}, - }); - expect(foundChallenge2.group).to.eql({ - _id: publicGuild._id, - id: publicGuild._id, - type: publicGuild.type, - privacy: publicGuild.privacy, - name: publicGuild.name, - }); - }); + await user.update({ + 'contributor.admin': true, + }); - it('should not return challenges user doesn\'t have access to', async () => { - let { group, groupLeader } = await createAndPopulateGroup({ - groupDetails: { - name: 'TestPrivateGuild', - type: 'guild', - privacy: 'private', - }, + officialChallenge = await generateChallenge(user, group, { + official: true, + }); + + challenge = await generateChallenge(user, group); + challenge2 = await generateChallenge(user, group); }); - let privateChallenge = await generateChallenge(groupLeader, group); + it('should return official challenges first', async () => { + let challenges = await user.get('/challenges/user'); - let challenges = await nonMember.get('/challenges/user'); + let foundChallengeIndex = _.findIndex(challenges, { _id: officialChallenge._id }); + expect(foundChallengeIndex).to.eql(0); + }); - let foundChallenge = _.find(challenges, { _id: privateChallenge._id }); - expect(foundChallenge).to.not.exist; + it('should return newest challenges first, after official ones', async () => { + let challenges = await user.get('/challenges/user'); + + let foundChallengeIndex = _.findIndex(challenges, { _id: challenge._id }); + expect(foundChallengeIndex).to.eql(2); + + foundChallengeIndex = _.findIndex(challenges, { _id: challenge2._id }); + expect(foundChallengeIndex).to.eql(1); + + let newChallenge = await generateChallenge(user, publicGuild); + + challenges = await user.get('/challenges/user'); + + foundChallengeIndex = _.findIndex(challenges, { _id: newChallenge._id }); + expect(foundChallengeIndex).to.eql(1); + }); }); }); diff --git a/website/server/controllers/api-v3/challenges.js b/website/server/controllers/api-v3/challenges.js index 6d7bc56667..652fcc76d8 100644 --- a/website/server/controllers/api-v3/challenges.js +++ b/website/server/controllers/api-v3/challenges.js @@ -201,7 +201,7 @@ api.leaveChallenge = { * @apiName GetUserChallenges * @apiGroup Challenge * - * @apiSuccess {Array} data An array of challenges + * @apiSuccess {Array} data An array of challenges sorted with official challenges first, followed by the challenges in order from newest to oldest */ api.getUserChallenges = { method: 'GET', @@ -218,7 +218,7 @@ api.getUserChallenges = { ], _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit }) - .sort('-official -timestamp') + .sort('-official -createdAt') // see below why we're not using populate // .populate('group', basicGroupFields) // .populate('leader', nameFields) @@ -249,7 +249,7 @@ api.getUserChallenges = { * * @apiParam {groupId} groupId The group _id * - * @apiSuccess {Array} data An array of challenges + * @apiSuccess {Array} data An array of challenges sorted with official challenges first, followed by the challenges in order from newest to oldest */ api.getGroupChallenges = { method: 'GET', @@ -268,7 +268,7 @@ api.getGroupChallenges = { if (!group) throw new NotFound(res.t('groupNotFound')); let challenges = await Challenge.find({group: groupId}) - .sort('-official -timestamp') + .sort('-official -createdAt') // .populate('leader', nameFields) // Only populate the leader as the group is implicit .exec(); From c0d9c54c442ec18073b1629e186c539cc3f3595a Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 31 May 2016 20:58:04 -0500 Subject: [PATCH 108/152] test: Pend erronously failing test --- test/api/v3/unit/libs/analyticsService.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/api/v3/unit/libs/analyticsService.test.js b/test/api/v3/unit/libs/analyticsService.test.js index 771678cc3d..553dfb676c 100644 --- a/test/api/v3/unit/libs/analyticsService.test.js +++ b/test/api/v3/unit/libs/analyticsService.test.js @@ -1,3 +1,6 @@ +// TODO These tests are pretty brittle +// rewrite them to not depend on nock +// Trust that the amplitude module works as intended and sends the requests import analyticsService from '../../../../../website/server/libs/api-v3/analyticsService'; import nock from 'nock'; @@ -115,7 +118,7 @@ describe('analyticsService', () => { }); }); - it('sends english item name for quest if itemKey is provided', () => { + xit('sends english item name for quest if itemKey is provided', () => { data.itemKey = 'atom1'; amplitudeNock From 52f0057c3d322e127f7cc1fd9e274cca145c96a2 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Tue, 31 May 2016 21:10:58 -0500 Subject: [PATCH 109/152] fix: Remove unnecessary flags.contributor property from user Closes #7550 --- test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js | 2 -- website/client/js/controllers/notificationCtrl.js | 4 ++-- website/server/models/user.js | 1 - website/views/shared/modals/achievements.jade | 3 +-- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js index 9948cbe3c7..bb724a51b4 100644 --- a/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js +++ b/test/api/v3/integration/hall/PUT-hall_heores_heroId.test.js @@ -66,7 +66,6 @@ describe('PUT /heroes/:heroId', () => { await hero.sync(); expect(hero.balance).to.equal(3 + 0.75); // 3+0.75 for first contrib level expect(hero.contributor.level).to.equal(1); - expect(hero.flags.contributor).to.equal(true); expect(hero.purchased.ads).to.equal(true); expect(hero.auth.blocked).to.equal(true); }); @@ -95,7 +94,6 @@ describe('PUT /heroes/:heroId', () => { await hero.sync(); expect(hero.balance).to.equal(1); // 0+1 for sixth contrib level expect(hero.contributor.level).to.equal(6); - expect(hero.flags.contributor).to.equal(true); expect(hero.items.pets['Dragon-Hydra']).to.equal(5); }); diff --git a/website/client/js/controllers/notificationCtrl.js b/website/client/js/controllers/notificationCtrl.js index 57f180515e..738af5bd8a 100644 --- a/website/client/js/controllers/notificationCtrl.js +++ b/website/client/js/controllers/notificationCtrl.js @@ -106,8 +106,8 @@ habitrpg.controller('NotificationCtrl', $rootScope.openModal('achievements/rebirth', {controller:'UserCtrl', size: 'sm'}); }); - $rootScope.$watch('user.flags.contributor', function(after, before){ - if (after === before || after !== true) return; + $rootScope.$watch('user.contributor.level', function(after, before){ + if (after === before || after < before || after == null) return; $rootScope.openModal('achievements/contributor',{controller:'UserCtrl'}); }); diff --git a/website/server/models/user.js b/website/server/models/user.js index ac68ac2846..c3783274b8 100644 --- a/website/server/models/user.js +++ b/website/server/models/user.js @@ -201,7 +201,6 @@ export let schema = new Schema({ itemsEnabled: {type: Boolean, default: false}, newStuff: {type: Boolean, default: false}, rewrite: {type: Boolean, default: true}, - contributor: Boolean, classSelected: {type: Boolean, default: false}, mathUpdates: Boolean, rebirthEnabled: {type: Boolean, default: false}, diff --git a/website/views/shared/modals/achievements.jade b/website/views/shared/modals/achievements.jade index 6f03746a07..19eb529c4e 100644 --- a/website/views/shared/modals/achievements.jade +++ b/website/views/shared/modals/achievements.jade @@ -106,7 +106,6 @@ script(id='modals/achievements/triadBingo.html', type='text/ng-template') +achievementFooter // Contributor -// activated by user.flags.contributor script(id='modals/achievements/contributor.html', type='text/ng-template') .modal-content(style='min-width:28em') .modal-body.text-center @@ -115,7 +114,7 @@ script(id='modals/achievements/contributor.html', type='text/ng-template') !=env.t('contribModal', {name: "{{user.profile.name}}", level: "{{user.contributor.level}}"}) + ' ' a(href='http://habitica.wikia.com/wiki/Contributor_Rewards' target='_blank')=env.t('contribLink') br - button.btn.btn-primary(style='margin-top:1em' ng-click='set({"flags.contributor":false}); $close()')=env.t('huzzah') + button.btn.btn-primary(style='margin-top:1em' ng-click='$close()')=env.t('huzzah') +achievementFooter //Rebirth From e07ade5a4a45b3a26d663254f783e9888cf991c7 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 17:10:31 +0200 Subject: [PATCH 110/152] cron: set lastCron at the beginning and misc fixes --- test/api/v3/unit/libs/cron.test.js | 10 ++--- website/server/libs/api-v3/cron.js | 46 +++++++++++------------ website/server/middlewares/api-v3/cron.js | 30 ++++++--------- website/server/models/group.js | 12 +++--- 4 files changed, 47 insertions(+), 51 deletions(-) diff --git a/test/api/v3/unit/libs/cron.test.js b/test/api/v3/unit/libs/cron.test.js index ef09868c27..dc76bb3666 100644 --- a/test/api/v3/unit/libs/cron.test.js +++ b/test/api/v3/unit/libs/cron.test.js @@ -603,25 +603,25 @@ describe('recoverCron', () => { } }); - it('increases status.times count and reruns up to 3 times', async (done) => { + it('increases status.times count and reruns up to 4 times', async (done) => { execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'})); - execStub.onCall(3).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'})); + execStub.onCall(4).returns(Bluebird.resolve({_cronSignature: 'NOT_RUNNING'})); await recoverCron(status, locals); - expect(status.times).to.eql(3); + expect(status.times).to.eql(4); expect(locals.user).to.eql({_cronSignature: 'NOT_RUNNING'}); done(); }); - it('throws an error if recoverCron runs 4 times', async (done) => { + it('throws an error if recoverCron runs 5 times', async (done) => { execStub.returns(Bluebird.resolve({_cronSignature: 'RUNNING_CRON'})); try { await recoverCron(status, locals); } catch (err) { - expect(status.times).to.eql(4); + expect(status.times).to.eql(5); expect(err.message).to.eql(`Impossible to recover from cron for user ${locals.user._id}.`); done(); diff --git a/website/server/libs/api-v3/cron.js b/website/server/libs/api-v3/cron.js index 3f5c416ed9..6b46a7a435 100644 --- a/website/server/libs/api-v3/cron.js +++ b/website/server/libs/api-v3/cron.js @@ -12,6 +12,29 @@ const shouldDo = common.shouldDo; const scoreTask = common.ops.scoreTask; // const maxPMs = 200; +export async function recoverCron (status, locals) { + let {user} = locals; + + await Bluebird.delay(300); + + let reloadedUser = await User.findOne({_id: user._id}).exec(); + + if (!reloadedUser) { + throw new Error(`User ${user._id} not found while recovering.`); + } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { + status.times++; + + if (status.times < 5) { + await recoverCron(status, locals); + } else { + throw new Error(`Impossible to recover from cron for user ${user._id}.`); + } + } else { + locals.user = reloadedUser; + return null; + } +} + let CLEAR_BUFFS = { str: 0, int: 0, @@ -83,29 +106,6 @@ function performSleepTasks (user, tasksByType, now) { }); } -export async function recoverCron (status, locals) { - let {user} = locals; - - await Bluebird.delay(300); - - let reloadedUser = await User.findOne({_id: user._id}).exec(); - - if (!reloadedUser) { - throw new Error(`User ${user._id} not found while recovering.`); - } else if (reloadedUser._cronSignature !== 'NOT_RUNNING') { - status.times++; - - if (status.times < 4) { - await recoverCron(status, locals); - } else { - throw new Error(`Impossible to recover from cron for user ${user._id}.`); - } - } else { - locals.user = reloadedUser; - return null; - } -} - // Perform various beginning-of-day reset actions. export function cron (options = {}) { let {user, tasksByType, analytics, now = new Date(), daysMissed, timezoneOffsetFromUserPrefs} = options; diff --git a/website/server/middlewares/api-v3/cron.js b/website/server/middlewares/api-v3/cron.js index b6555cb682..18d9907d0d 100644 --- a/website/server/middlewares/api-v3/cron.js +++ b/website/server/middlewares/api-v3/cron.js @@ -103,13 +103,15 @@ async function cronAsync (req, res) { let _cronSignature = uuid(); - // To avoid double cron we first set _cronSignature to now and then check that it's not changed while processing + // To avoid double cron we first set _cronSignature and then check that it's not changed while processing let userUpdateResult = await User.update({ _id: user._id, _cronSignature: 'NOT_RUNNING', // Check that in the meantime another cron has not started }, { $set: { _cronSignature, + lastCron: now, // setting lastCron now so we don't risk re-running parts of cron if it fails + 'auth.timestamps.loggedin': now, }, }).exec(); @@ -170,8 +172,6 @@ async function cronAsync (req, res) { }, { $set: { _cronSignature: 'NOT_RUNNING', - lastCron: now, - 'auth.timestamps.loggedin': now, }, }).exec(); @@ -182,27 +182,23 @@ async function cronAsync (req, res) { // If cron was aborted for a race condition try to recover from it if (err.message === 'CRON_ALREADY_RUNNING') { // Recovering after abort, wait 300ms and reload user - // do it for max 4 times then reset _cronSignature so that it doesn't prevent cron from running + // do it for max 5 times then reset _cronSignature so that it doesn't prevent cron from running // at the next request let recoveryStatus = { times: 0, }; - recoverCron(recoveryStatus, res.locals); + await recoverCron(recoveryStatus, res.locals); } else { // For any other error make sure to reset _cronSignature so that it doesn't prevent cron from running // at the next request - try { - await User.update({ - _id: user._id, - }, { - _cronSignature: 'NOT_RUNNING', - }).exec(); + await User.update({ + _id: user._id, + }, { + _cronSignature: 'NOT_RUNNING', + }).exec(); - throw err; // re-throw original error - } catch (secondErr) { - throw secondErr; - } + throw err; // re-throw the original error } } } @@ -212,7 +208,5 @@ module.exports = function cronMiddleware (req, res, next) { .then(() => { next(); }) - .catch(err => { - next(err); - }); + .catch(next); }; diff --git a/website/server/models/group.js b/website/server/models/group.js index 46a5a4f449..cff731c034 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -279,6 +279,7 @@ export function chatDefaults (msg, user) { } const NO_CHAT_NOTIFICATIONS = [TAVERN_ID]; + schema.methods.sendChat = function sendChat (message, user) { this.chat.unshift(chatDefaults(message, user)); this.chat.splice(200); @@ -425,9 +426,9 @@ schema.statics.cleanGroupQuest = function cleanGroupQuest () { }; }; -// Participants: Grant rewards & achievements, finish quest -// Returns the promise from update().exec() -schema.methods.finishQuest = function finishQuest (quest) { +// Participants: Grant rewards & achievements, finish quest. +// Changes the group object update members +schema.methods.finishQuest = async function finishQuest (quest) { let questK = quest.key; let updates = {$inc: {}, $set: {}}; @@ -471,14 +472,14 @@ schema.methods.finishQuest = function finishQuest (quest) { let q = this._id === TAVERN_ID ? {} : {_id: {$in: _.keys(this.quest.members)}}; this.quest = {}; this.markModified('quest'); - return User.update(q, updates, {multi: true}).exec(); + + return await User.update(q, updates, {multi: true}).exec(); }; function _isOnQuest (user, progress, group) { return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true; } -// Returns a promise schema.statics.collectQuest = async function collectQuest (user, progress) { let group = await this.getGroup({user, groupId: 'party'}); if (!_isOnQuest(user, progress, group)) return; @@ -504,6 +505,7 @@ schema.statics.collectQuest = async function collectQuest (user, progress) { await group.finishQuest(quest); group.sendChat('`All items found! Party has received their rewards.`'); + return await group.save(); }; From d7cd2ce329caa075556643675a5a1bc34d9963a4 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Tue, 31 May 2016 18:46:17 +0200 Subject: [PATCH 111/152] do not run cron when getting a group --- website/server/controllers/api-v3/groups.js | 1 + 1 file changed, 1 insertion(+) diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index de59bd7e17..84bb256ddb 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -117,6 +117,7 @@ api.getGroups = { api.getGroup = { method: 'GET', url: '/groups/:groupId', + runCron: false, // Do not run cron to avoid double cronning because it's called in parallel to GET /user when the site loads middlewares: [authWithHeaders()], async handler (req, res) { let user = res.locals.user; From c33b7273fa1ef3777ccd7c48418432d9d018b6e9 Mon Sep 17 00:00:00 2001 From: Matteo Pagliazzi Date: Wed, 1 Jun 2016 08:59:22 +0200 Subject: [PATCH 112/152] 3.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2abc77b302..f4eb892a74 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.4.3", + "version": "3.4.4", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 271f4b760debd087bee0e786cd5ea631ade67aae Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 1 Jun 2016 15:08:35 +0000 Subject: [PATCH 113/152] chore(event): end Floral Potion availability --- common/script/content/index.js | 2 +- website/views/options/inventory/drops.jade | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/script/content/index.js b/common/script/content/index.js index 8164d4e0e6..9cdc643023 100644 --- a/common/script/content/index.js +++ b/common/script/content/index.js @@ -520,7 +520,7 @@ api.premiumHatchingPotions = { text: t('hatchingPotionFloral'), limited: true, canBuy: (function() { - return true; + return false; }), }, }; diff --git a/website/views/options/inventory/drops.jade b/website/views/options/inventory/drops.jade index 322a5083f5..67c2b80d4e 100644 --- a/website/views/options/inventory/drops.jade +++ b/website/views/options/inventory/drops.jade @@ -136,7 +136,7 @@ | {{::pot.value}}  span.Pet_Currency_Gem1x.inline-gems - li.customize-menu + // li.customize-menu menu.pets-menu!=env.t('magicHatchingPotions') + " - " + env.t('springEventAvailability') p.muted=env.t('premiumPotionNoDropExplanation') div(ng-repeat='pot in Content.hatchingPotions', ng-if='pot.premium && pot.canBuy(user)') From afea91740914fc1103338d77a2151a52888f4de3 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 1 Jun 2016 16:25:43 +0000 Subject: [PATCH 114/152] chore(minor): Bump version to 3.5.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4eb892a74..1ebffa1b3d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.4.4", + "version": "3.5.0", "main": "./website/server/index.js", "dependencies": { "accepts": "^1.3.2", From 6084ea2017bcaac3a1f5f4577da05a88af6eb268 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 1 Jun 2016 15:34:57 -0500 Subject: [PATCH 115/152] Fix mystery set award to new subscribers (#7586) * fix(mystery): flip da sign * test(subscription): check mystery box --- test/api/v3/unit/libs/payments.test.js | 15 +++++++++++++++ website/server/libs/api-v3/payments.js | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js index 30fe78b643..c5553c3e77 100644 --- a/test/api/v3/unit/libs/payments.test.js +++ b/test/api/v3/unit/libs/payments.test.js @@ -9,8 +9,17 @@ describe('payments/index', () => { let user; describe('#createSubscription', () => { + const MYSTERY_AWARD_COUNT = 2; + const MYSTERY_AWARD_UNIX_TIME = 1464725113000; + let fakeClock; + beforeEach(async () => { user = new User(); + fakeClock = sinon.useFakeTimers(MYSTERY_AWARD_UNIX_TIME); + }); + + afterEach(() => { + fakeClock.restore(); }); it('succeeds', async () => { @@ -19,6 +28,12 @@ describe('payments/index', () => { await api.createSubscription(data); expect(user.purchased.plan.planId).to.exist; }); + + it('awards mystery items', async () => { + data = { user, sub: { key: 'basic_3mo' } }; + await api.createSubscription(data); + expect(user.purchased.plan.mysteryItems.length).to.eql(MYSTERY_AWARD_COUNT); + }); }); describe('#cancelSubscription', () => { diff --git a/website/server/libs/api-v3/payments.js b/website/server/libs/api-v3/payments.js index 144b4fe568..ae825abff3 100644 --- a/website/server/libs/api-v3/payments.js +++ b/website/server/libs/api-v3/payments.js @@ -20,7 +20,7 @@ function revealMysteryItems (user) { moment().isAfter(shared.content.mystery[item.mystery].start) && moment().isBefore(shared.content.mystery[item.mystery].end) && !user.items.gear.owned[item.key] && - user.purchased.plan.mysteryItems.indexOf(item.key) !== -1 + user.purchased.plan.mysteryItems.indexOf(item.key) === -1 ) { user.purchased.plan.mysteryItems.push(item.key); } From 4ea52728ee21540e42b282de5e7e058b884d037e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 1 Jun 2016 22:12:02 +0000 Subject: [PATCH 116/152] fix(links): Report a Bug Guild --- common/locales/en/groups.json | 4 ++-- website/views/options/social/tavern.jade | 4 ++-- website/views/shared/footer.jade | 2 +- website/views/shared/header/menu.jade | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/locales/en/groups.json b/common/locales/en/groups.json index 00257c8670..cc9bcf9cd7 100644 --- a/common/locales/en/groups.json +++ b/common/locales/en/groups.json @@ -15,8 +15,8 @@ "resources": "Resources", "askQuestionNewbiesGuild": "Ask a Question (Newbies Guild)", "tavernTalk": "Tavern Talk", - "tavernAlert1": "Note: if you're reporting a bug, the developers won't see it here. Please", - "tavernAlert2": "use GitHub instead", + "tavernAlert1": "To report a bug, visit", + "tavernAlert2": "the Report a Bug Guild", "moderatorIntro1": "Tavern and guild moderators are: ", "communityGuidelines": "Community Guidelines", "communityGuidelinesRead1": "Please read our", diff --git a/website/views/options/social/tavern.jade b/website/views/options/social/tavern.jade index d0f16c187d..c03503256a 100644 --- a/website/views/options/social/tavern.jade +++ b/website/views/options/social/tavern.jade @@ -51,7 +51,7 @@ a(target='_blank', href='https://oldgods.net/habitrpg/habitrpg_user_data_display.html')=env.t('dataTool') tr td - a(target='_blank', href='https://github.com/HabitRPG/habitrpg/issues/2760')=env.t('reportAP') + a(target='_blank', href='https://habitica.com/#/options/groups/guilds/a29da26b-37de-4a71-b0c6-48e72a900dac')=env.t('reportAP') tr td a(target='_blank', href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')= ' ' + env.t('requestAF') @@ -148,7 +148,7 @@ h3=env.t('tavernTalk') include ./chat-box .alert.alert-info.alert-sm - != ' ' + env.t('tavernAlert1') + ' ' + env.t('tavernAlert2') + '.
' + env.t('moderatorIntro1') + != ' ' + env.t('tavernAlert1') + ' ' + env.t('tavernAlert2') + '!
' + env.t('moderatorIntro1') //- FIXME this could be rendered server-side? span(ng-repeat='mod in env.mods') |    diff --git a/website/views/shared/footer.jade b/website/views/shared/footer.jade index 26e017696c..aaa14f8b42 100644 --- a/website/views/shared/footer.jade +++ b/website/views/shared/footer.jade @@ -44,7 +44,7 @@ footer.footer(ng-controller='FooterCtrl') li a(target='_blank', href='/static/community-guidelines')=env.t('communityGuidelines') li - a(target='_blank', href='/static/clear-browser-data')=env.t('communityBug') + a(target='_blank', href='https://habitica.com/#/options/groups/guilds/a29da26b-37de-4a71-b0c6-48e72a900dac')=env.t('communityBug') li a(target='_blank', href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents')=env.t('communityFeature') li diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade index d009ebdd4d..93841f80d8 100644 --- a/website/views/shared/header/menu.jade +++ b/website/views/shared/header/menu.jade @@ -60,7 +60,7 @@ nav.toolbar(ng-controller='MenuCtrl') li a(target='_blank' href='https://habitica.com/static/overview')=env.t('overview') li - a(target='_blank' href='https://github.com/HabitRPG/habitrpg/issues/2760')=env.t('reportBug') + a(target='_blank' href='https://habitica.com/#/options/groups/guilds/a29da26b-37de-4a71-b0c6-48e72a900dac')=env.t('reportBug') li a(target='_blank' href='https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a')=env.t('askQuestion') li @@ -161,7 +161,7 @@ nav.toolbar(ng-controller='MenuCtrl') li a(target='_blank' href='https://habitica.com/static/overview')=env.t('overview') li - a(target='_blank' href='https://github.com/HabitRPG/habitrpg/issues/2760')=env.t('reportBug') + a(target='_blank' href='https://habitica.com/#/options/groups/guilds/a29da26b-37de-4a71-b0c6-48e72a900dac')=env.t('reportBug') li a(target='_blank' href='https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a')=env.t('askQuestion') li From c9d8c3ee404eaa2adc369f51b36c2ca6120dd205 Mon Sep 17 00:00:00 2001 From: Blade Barringer Date: Thu, 2 Jun 2016 12:57:59 -0500 Subject: [PATCH 117/152] tests: Use dependency injection to re-enable randomdrop tests --- common/script/fns/randomDrop.js | 7 ++++--- test/common/fns/randomDrop.test.js | 33 +++++++++++++----------------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/common/script/fns/randomDrop.js b/common/script/fns/randomDrop.js index 92064e62f1..ec6f350d4c 100644 --- a/common/script/fns/randomDrop.js +++ b/common/script/fns/randomDrop.js @@ -3,7 +3,7 @@ import content from '../content/index'; import i18n from '../i18n'; import { daysSince } from '../cron'; import { diminishingReturns } from '../statHelpers'; -import predictableRandom from './predictableRandom'; +import _predictableRandom from './predictableRandom'; import randomVal from './randomVal'; // Clone a drop object maintaining its functions so that we can change it without affecting the original item @@ -13,7 +13,7 @@ function cloneDropItem (drop) { }); } -module.exports = function randomDrop (user, modifiers, req = {}) { +module.exports = function randomDrop (user, options, req = {}) { let acceptableDrops; let chance; let drop; @@ -23,7 +23,8 @@ module.exports = function randomDrop (user, modifiers, req = {}) { let rarity; let task; - task = modifiers.task; + let predictableRandom = options.predictableRandom || _predictableRandom; + task = options.task; chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + 0.02; chance *= task.priority * // Task priority: +50% for Medium, +100% for Hard diff --git a/test/common/fns/randomDrop.test.js b/test/common/fns/randomDrop.test.js index 9569067ffc..39920e02bf 100644 --- a/test/common/fns/randomDrop.test.js +++ b/test/common/fns/randomDrop.test.js @@ -1,7 +1,3 @@ -// TODO disable until we can find a way to stub predictableRandom - -/* eslint-disable */ - import randomDrop from '../../../common/script/fns/randomDrop'; import { generateUser, @@ -10,10 +6,9 @@ import { generateDaily, generateReward, } from '../../helpers/common.helper'; -// import predictableRandom from '../../../common/script/fns/predictableRandom'; // eslint-disable import content from '../../../common/script/content/index'; -xdescribe('common.fns.randomDrop', () => { +describe('common.fns.randomDrop', () => { let user; let task; let predictableRandom; @@ -40,9 +35,9 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.0001; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user.party.quest.progress.collect[collectWhat]).to.eql(1); - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user.party.quest.progress.collect[collectWhat]).to.eql(2); }); @@ -54,7 +49,7 @@ xdescribe('common.fns.randomDrop', () => { it('does nothing if user.items.lastDrop.count is exceeded', () => { user.items.lastDrop.count = 100; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp).to.eql({}); }); @@ -64,7 +59,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.1; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp).to.not.eql({}); }); @@ -75,7 +70,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.1; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp).to.not.eql({}); }); @@ -86,7 +81,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.1; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp).to.not.eql({}); }); @@ -97,7 +92,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.1; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp).to.not.eql({}); }); @@ -105,7 +100,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.65; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('Food'); }); @@ -113,7 +108,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.35; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('Egg'); }); @@ -122,7 +117,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.01; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('HatchingPotion'); expect(user._tmp.drop.value).to.eql(5); expect(user._tmp.drop.key).to.eql('Golden'); @@ -132,7 +127,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.08; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('HatchingPotion'); expect(user._tmp.drop.value).to.eql(4); let acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']; @@ -143,7 +138,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.17; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('HatchingPotion'); expect(user._tmp.drop.value).to.eql(3); let acceptableDrops = ['Red', 'Shade', 'Skeleton']; @@ -154,7 +149,7 @@ xdescribe('common.fns.randomDrop', () => { predictableRandom = () => { return 0.20; }; - randomDrop(user, { task }); + randomDrop(user, { task, predictableRandom }); expect(user._tmp.drop.type).to.eql('HatchingPotion'); expect(user._tmp.drop.value).to.eql(2); let acceptableDrops = ['Base', 'White', 'Desert']; From 21f3814368352ce5631f16d53223ac169e35a892 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 2 Jun 2016 16:51:05 -0500 Subject: [PATCH 118/152] feat(content): June Armoire and BGs (#7592) --- .../sprites/spritesmith-largeSprites-0.css | 164 +- .../sprites/spritesmith-largeSprites-0.png | Bin 296773 -> 346911 bytes common/dist/sprites/spritesmith-main-0.css | 872 +-- common/dist/sprites/spritesmith-main-0.png | Bin 319474 -> 339544 bytes common/dist/sprites/spritesmith-main-1.css | 1470 ++-- common/dist/sprites/spritesmith-main-1.png | Bin 69545 -> 67993 bytes common/dist/sprites/spritesmith-main-10.css | 806 +- common/dist/sprites/spritesmith-main-10.png | Bin 150236 -> 146365 bytes common/dist/sprites/spritesmith-main-11.css | 894 +-- common/dist/sprites/spritesmith-main-11.png | Bin 154718 -> 154949 bytes common/dist/sprites/spritesmith-main-12.css | 2208 +++--- common/dist/sprites/spritesmith-main-12.png | Bin 133905 -> 136840 bytes common/dist/sprites/spritesmith-main-2.css | 6494 ++++++++--------- common/dist/sprites/spritesmith-main-2.png | Bin 33317 -> 32763 bytes common/dist/sprites/spritesmith-main-3.css | 6470 ++++++++-------- common/dist/sprites/spritesmith-main-3.png | Bin 131785 -> 135239 bytes common/dist/sprites/spritesmith-main-4.css | 1178 ++- common/dist/sprites/spritesmith-main-4.png | Bin 139405 -> 138868 bytes common/dist/sprites/spritesmith-main-5.css | 1060 +-- common/dist/sprites/spritesmith-main-5.png | Bin 261763 -> 194294 bytes common/dist/sprites/spritesmith-main-6.css | 558 +- common/dist/sprites/spritesmith-main-6.png | Bin 301758 -> 371757 bytes common/dist/sprites/spritesmith-main-7.css | 622 +- common/dist/sprites/spritesmith-main-7.png | Bin 151921 -> 153827 bytes common/dist/sprites/spritesmith-main-8.css | 490 +- common/dist/sprites/spritesmith-main-8.png | Bin 149583 -> 148415 bytes common/dist/sprites/spritesmith-main-9.css | 738 +- common/dist/sprites/spritesmith-main-9.png | Bin 145649 -> 141296 bytes .../background_lighthouse_shore.png | Bin 0 -> 82764 bytes .../backgrounds/background_lilypad.png | Bin 0 -> 3462 bytes .../backgrounds/background_waterfall_rock.png | Bin 0 -> 9706 bytes .../broad_armor_armoire_stripedSwimsuit.png | Bin 0 -> 35342 bytes .../armoire/head_armoire_greenFloppyHat.png | Bin 0 -> 3241 bytes .../armoire/shield_armoire_sandyBucket.png | Bin 0 -> 35342 bytes .../shop_armor_armoire_stripedSwimsuit.png | Bin 0 -> 3127 bytes .../shop/shop_head_armoire_greenFloppyHat.png | Bin 0 -> 3082 bytes .../shop/shop_shield_armoire_sandyBucket.png | Bin 0 -> 2981 bytes .../shop/shop_weapon_armoire_sandySpade.png | Bin 0 -> 2967 bytes .../slim_armor_armoire_stripedSwimsuit.png | Bin 0 -> 35342 bytes .../armoire/weapon_armoire_sandySpade.png | Bin 0 -> 35342 bytes .../promo_backgrounds_armoire_201606.png | Bin 0 -> 46990 bytes common/locales/en/backgrounds.json | 10 +- common/locales/en/gear.json | 8 + .../script/content/appearance/backgrounds.js | 14 + common/script/content/gear/sets/armoire.js | 33 + website/views/shared/new-stuff.jade | 43 +- 46 files changed, 12149 insertions(+), 11983 deletions(-) create mode 100644 common/img/sprites/spritesmith/backgrounds/background_lighthouse_shore.png create mode 100644 common/img/sprites/spritesmith/backgrounds/background_lilypad.png create mode 100644 common/img/sprites/spritesmith/backgrounds/background_waterfall_rock.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/broad_armor_armoire_stripedSwimsuit.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/head_armoire_greenFloppyHat.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/shield_armoire_sandyBucket.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/shop/shop_armor_armoire_stripedSwimsuit.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/shop/shop_head_armoire_greenFloppyHat.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/shop/shop_shield_armoire_sandyBucket.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/shop/shop_weapon_armoire_sandySpade.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/slim_armor_armoire_stripedSwimsuit.png create mode 100644 common/img/sprites/spritesmith/gear/armoire/weapon_armoire_sandySpade.png create mode 100644 common/img/sprites/spritesmith_large/promo/promo_backgrounds_armoire_201606.png diff --git a/common/dist/sprites/spritesmith-largeSprites-0.css b/common/dist/sprites/spritesmith-largeSprites-0.css index a45d6a8a3f..30b0d02ef5 100644 --- a/common/dist/sprites/spritesmith-largeSprites-0.css +++ b/common/dist/sprites/spritesmith-largeSprites-0.css @@ -1,144 +1,150 @@ .2014_Fall_HealerPROMO2 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -808px -706px; + background-position: -1497px -1252px; width: 90px; height: 90px; } .2014_Fall_Mage_PROMO9 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -734px -317px; + background-position: -1456px -735px; width: 120px; height: 90px; } .2014_Fall_RoguePROMO3 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -843px -408px; + background-position: -199px -1252px; width: 105px; height: 90px; } .2014_Fall_Warrior_PROMO { background-image: url(spritesmith-largeSprites-0.png); - background-position: -942px -1157px; + background-position: -951px -1252px; width: 90px; height: 90px; } .promo_android { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -508px; + background-position: 0px -1076px; width: 175px; height: 175px; } .promo_backgrounds_armoire_201602 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -954px 0px; + background-position: -1017px -103px; width: 141px; height: 294px; } .promo_backgrounds_armoire_201603 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -306px -220px; + background-position: -875px -103px; width: 141px; height: 294px; } .promo_backgrounds_armoire_201604 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -452px 0px; + background-position: -593px 0px; width: 140px; height: 441px; } .promo_backgrounds_armoire_201605 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -593px 0px; + background-position: -734px 0px; width: 140px; height: 441px; } +.promo_backgrounds_armoire_201606 { + background-image: url(spritesmith-largeSprites-0.png); + background-position: -452px 0px; + width: 140px; + height: 447px; +} .promo_backtoschool { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -402px; + background-position: -1305px -735px; width: 150px; height: 150px; } .promo_burnout { background-image: url(spritesmith-largeSprites-0.png); - background-position: -734px 0px; + background-position: 0px -525px; width: 219px; height: 240px; } .promo_chairs_glasses { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -1248px; + background-position: -306px -311px; width: 51px; height: 210px; } .promo_classes_fall_2014 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -326px -957px; + background-position: -363px -862px; width: 321px; height: 100px; } .promo_classes_fall_2015 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -430px -706px; + background-position: -875px -712px; width: 377px; height: 99px; } .promo_dilatoryDistress { background-image: url(spritesmith-largeSprites-0.png); - background-position: -770px -525px; + background-position: -678px -1252px; width: 90px; height: 90px; } .promo_egg_mounts { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -809px; + background-position: -875px -398px; width: 280px; height: 147px; } .promo_enchanted_armoire { background-image: url(spritesmith-largeSprites-0.png); - background-position: -271px -621px; + background-position: -685px -862px; width: 374px; height: 76px; } .promo_enchanted_armoire_201507 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -1000px; + background-position: -1305px -1034px; width: 217px; height: 90px; } .promo_enchanted_armoire_201508 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -776px; + background-position: -1122px -546px; width: 180px; height: 90px; } .promo_enchanted_armoire_201509 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -861px -525px; + background-position: -860px -1252px; width: 90px; height: 90px; } .promo_enchanted_armoire_201511 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -954px -569px; + background-position: -1456px -584px; width: 122px; height: 90px; } .promo_enchanted_armoire_201601 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -578px -1157px; + background-position: -1224px -1252px; width: 90px; height: 90px; } .promo_floral_potions { background-image: url(spritesmith-largeSprites-0.png); - background-position: -954px -295px; + background-position: -1159px -103px; width: 105px; height: 273px; } .promo_habitica { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -332px; + background-position: -176px -1076px; width: 175px; height: 175px; } @@ -150,301 +156,301 @@ } .promo_haunted_hair { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1484px -704px; + background-position: -1462px -285px; width: 100px; height: 137px; } .promo_item_notif { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px 0px; + background-position: -1305px -182px; width: 249px; height: 102px; } .promo_mystery_201405 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1033px -1157px; + background-position: -182px -1344px; width: 90px; height: 90px; } .promo_mystery_201406 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1494px -251px; + background-position: -358px -417px; width: 90px; height: 96px; } .promo_mystery_201407 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -881px -241px; + background-position: -1253px -712px; width: 42px; height: 62px; } .promo_mystery_201408 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1272px -414px; + background-position: -1523px -1125px; width: 60px; height: 71px; } .promo_mystery_201409 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1005px -809px; + background-position: -1042px -1252px; width: 90px; height: 90px; } .promo_mystery_201410 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1249px -867px; + background-position: -1156px -474px; width: 72px; height: 63px; } .promo_mystery_201411 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -760px -1157px; + background-position: -1406px -1252px; width: 90px; height: 90px; } .promo_mystery_201412 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1277px -776px; + background-position: -1226px -637px; width: 42px; height: 66px; } .promo_mystery_201501 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1272px -578px; + background-position: -1229px -474px; width: 48px; height: 63px; } .promo_mystery_201502 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -52px -1248px; + background-position: -364px -1344px; width: 90px; height: 90px; } .promo_mystery_201503 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -234px -1248px; + background-position: -587px -1252px; width: 90px; height: 90px; } .promo_mystery_201504 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1272px -508px; + background-position: -1122px -637px; width: 60px; height: 69px; } .promo_mystery_201505 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -325px -1248px; + background-position: -769px -1252px; width: 90px; height: 90px; } .promo_mystery_201506 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1295px -684px; + background-position: -1183px -637px; width: 42px; height: 69px; } .promo_mystery_201507 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1500px -103px; + background-position: -358px -311px; width: 90px; height: 105px; } .promo_mystery_201508 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -855px -317px; + background-position: -399px -1252px; width: 93px; height: 90px; } .promo_mystery_201509 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -669px -1157px; + background-position: -1133px -1252px; width: 90px; height: 90px; } .promo_mystery_201510 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1494px -402px; + background-position: -493px -1252px; width: 93px; height: 90px; } .promo_mystery_201511 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -851px -1157px; + background-position: -1315px -1252px; width: 90px; height: 90px; } .promo_mystery_201512 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1272px -332px; + background-position: -1523px -1034px; width: 60px; height: 81px; } .promo_mystery_201601 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -954px -660px; + background-position: -306px -220px; width: 120px; height: 90px; } .promo_mystery_201602 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1124px -1157px; + background-position: 0px -1344px; width: 90px; height: 90px; } .promo_mystery_201603 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1215px -1157px; + background-position: -91px -1344px; width: 90px; height: 90px; } .promo_mystery_201604 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1494px -553px; + background-position: -305px -1252px; width: 93px; height: 90px; } .promo_mystery_201605 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -143px -1248px; + background-position: -273px -1344px; width: 90px; height: 90px; } .promo_mystery_3014 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -1091px; + background-position: -1305px -1125px; width: 217px; height: 90px; } .promo_orca { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1484px -852px; + background-position: -1456px -433px; width: 105px; height: 105px; } .promo_partyhats { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -1182px; + background-position: -1456px -675px; width: 115px; height: 47px; } .promo_pastel_skin { background-image: url(spritesmith-largeSprites-0.png); - background-position: -979px -957px; + background-position: -657px -965px; width: 330px; height: 83px; } .customize-option.promo_pastel_skin { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1004px -972px; + background-position: -682px -980px; width: 60px; height: 60px; } .promo_peppermint_flame { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -852px; + background-position: -1305px -886px; width: 140px; height: 147px; } .promo_pet_skins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -704px; + background-position: -1446px -886px; width: 140px; height: 147px; } .customize-option.promo_pet_skins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1368px -719px; + background-position: -1471px -901px; width: 60px; height: 60px; } .promo_shimmer_hair { background-image: url(spritesmith-largeSprites-0.png); - background-position: -648px -957px; + background-position: -326px -965px; width: 330px; height: 83px; } .promo_splashyskins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -684px; + background-position: 0px -1252px; width: 198px; height: 91px; } .customize-option.promo_splashyskins { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1121px -699px; + background-position: -25px -1267px; width: 60px; height: 60px; } .promo_spring_classes_2016 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -281px -809px; + background-position: 0px -862px; width: 362px; height: 102px; } .promo_springclasses2014 { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -1157px; + background-position: -1305px -91px; width: 288px; height: 90px; } .promo_springclasses2015 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -289px -1157px; + background-position: -1305px 0px; width: 288px; height: 90px; } .promo_summer_classes_2014 { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -706px; + background-position: -875px 0px; width: 429px; height: 102px; } .promo_summer_classes_2015 { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -1068px; + background-position: -352px -1076px; width: 300px; height: 88px; } .promo_updos { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -103px; + background-position: -1305px -285px; width: 156px; height: 147px; } .promo_veteran_pets { background-image: url(spritesmith-largeSprites-0.png); - background-position: -734px -241px; + background-position: -1156px -398px; width: 146px; height: 75px; } .promo_winter_classes_2016 { background-image: url(spritesmith-largeSprites-0.png); - background-position: -644px -809px; + background-position: -499px -766px; width: 360px; height: 90px; } .promo_winterclasses2015 { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -957px; + background-position: 0px -965px; width: 325px; height: 110px; } .promo_winteryhair { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -867px; + background-position: -1060px -862px; width: 152px; height: 75px; } .avatar_variety { background-image: url(spritesmith-largeSprites-0.png); - background-position: -271px -525px; + background-position: 0px -766px; width: 498px; height: 95px; } .npc_viirus { background-image: url(spritesmith-largeSprites-0.png); - background-position: -734px -408px; + background-position: -738px -525px; width: 108px; height: 90px; } @@ -456,31 +462,31 @@ } .scene_coding { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -251px; + background-position: -1305px -433px; width: 150px; height: 150px; } .scene_phone_peek { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1343px -553px; + background-position: -1305px -584px; width: 150px; height: 150px; } .welcome_basic_avatars { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px -166px; + background-position: -875px -546px; width: 246px; height: 165px; } .welcome_promo_party { background-image: url(spritesmith-largeSprites-0.png); - background-position: 0px -525px; + background-position: -220px -525px; width: 270px; height: 180px; } .welcome_sample_tasks { background-image: url(spritesmith-largeSprites-0.png); - background-position: -1096px 0px; + background-position: -491px -525px; width: 246px; height: 165px; } diff --git a/common/dist/sprites/spritesmith-largeSprites-0.png b/common/dist/sprites/spritesmith-largeSprites-0.png index 7790f21bc2c69c21534e0686d58a64d5a3717b4a..33098e2f971efc1e77b5ca02dcd6157805b1ba58 100644 GIT binary patch literal 346911 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�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

u$&0MUV3Ff2` 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

HFDEhKla9U=S0 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 z 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 literal 295886 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 diff --git a/common/dist/sprites/spritesmith-main-10.css b/common/dist/sprites/spritesmith-main-10.css index c473e2d7c0..d14ebdd787 100644 --- a/common/dist/sprites/spritesmith-main-10.css +++ b/common/dist/sprites/spritesmith-main-10.css @@ -1,1806 +1,1788 @@ -.Mount_Head_TRex-CottonCandyBlue { - background-image: url(spritesmith-main-10.png); - background-position: 0px 0px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-CottonCandyPink { - background-image: url(spritesmith-main-10.png); - background-position: -136px 0px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Desert { - background-image: url(spritesmith-main-10.png); - background-position: -136px -544px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Golden { - background-image: url(spritesmith-main-10.png); - background-position: 0px -136px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Red { - background-image: url(spritesmith-main-10.png); - background-position: -136px -136px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Shade { - background-image: url(spritesmith-main-10.png); - background-position: -272px 0px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Skeleton { - background-image: url(spritesmith-main-10.png); - background-position: -272px -136px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-White { - background-image: url(spritesmith-main-10.png); - background-position: 0px -272px; - width: 135px; - height: 135px; -} -.Mount_Head_TRex-Zombie { - background-image: url(spritesmith-main-10.png); - background-position: -136px -272px; - width: 135px; - height: 135px; -} -.Mount_Head_Turkey-Base { - background-image: url(spritesmith-main-10.png); - background-position: -786px -318px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Base { - background-image: url(spritesmith-main-10.png); - background-position: -786px -212px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-CottonCandyBlue { - background-image: url(spritesmith-main-10.png); - background-position: -786px -106px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-CottonCandyPink { - background-image: url(spritesmith-main-10.png); - background-position: -786px 0px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Desert { - background-image: url(spritesmith-main-10.png); - background-position: -636px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Golden { - background-image: url(spritesmith-main-10.png); - background-position: -530px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Red { - background-image: url(spritesmith-main-10.png); - background-position: -424px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Shade { - background-image: url(spritesmith-main-10.png); - background-position: -318px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Skeleton { - background-image: url(spritesmith-main-10.png); - background-position: -212px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-White { - background-image: url(spritesmith-main-10.png); - background-position: -272px -544px; - width: 105px; - height: 105px; -} -.Mount_Head_Unicorn-Zombie { - background-image: url(spritesmith-main-10.png); - background-position: 0px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Base { - background-image: url(spritesmith-main-10.png); - background-position: -680px -530px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-CottonCandyBlue { - background-image: url(spritesmith-main-10.png); - background-position: -680px -424px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-CottonCandyPink { - background-image: url(spritesmith-main-10.png); - background-position: -786px -424px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Desert { - background-image: url(spritesmith-main-10.png); - background-position: -378px -544px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Golden { - background-image: url(spritesmith-main-10.png); - background-position: -106px -680px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Red { - background-image: url(spritesmith-main-10.png); - background-position: -484px -544px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Shade { - background-image: url(spritesmith-main-10.png); - background-position: -680px 0px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Skeleton { - background-image: url(spritesmith-main-10.png); - background-position: -680px -106px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-White { - background-image: url(spritesmith-main-10.png); - background-position: -680px -212px; - width: 105px; - height: 105px; -} -.Mount_Head_Whale-Zombie { - background-image: url(spritesmith-main-10.png); - background-position: -680px -318px; - width: 105px; - height: 105px; -} -.Mount_Head_Wolf-Base { - background-image: url(spritesmith-main-10.png); - background-position: 0px -544px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-CottonCandyBlue { - background-image: url(spritesmith-main-10.png); - background-position: -544px -408px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-CottonCandyPink { - background-image: url(spritesmith-main-10.png); - background-position: -544px -272px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Desert { - background-image: url(spritesmith-main-10.png); - background-position: -544px -136px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Floral { - background-image: url(spritesmith-main-10.png); - background-position: -544px 0px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Golden { - background-image: url(spritesmith-main-10.png); - background-position: -408px -408px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Peppermint { - background-image: url(spritesmith-main-10.png); - background-position: -272px -408px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Red { - background-image: url(spritesmith-main-10.png); - background-position: -136px -408px; - width: 135px; - height: 135px; -} -.Mount_Head_Wolf-Shade { +.Mount_Head_TRex-Base { background-image: url(spritesmith-main-10.png); background-position: 0px -408px; width: 135px; height: 135px; } -.Mount_Head_Wolf-Skeleton { +.Mount_Head_TRex-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -408px -272px; + background-position: 0px -136px; width: 135px; height: 135px; } -.Mount_Head_Wolf-Spooky { +.Mount_Head_TRex-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -408px -136px; + background-position: -136px -136px; width: 135px; height: 135px; } -.Mount_Head_Wolf-White { +.Mount_Head_TRex-Desert { background-image: url(spritesmith-main-10.png); - background-position: -408px 0px; + background-position: -272px 0px; width: 135px; height: 135px; } -.Mount_Head_Wolf-Zombie { +.Mount_Head_TRex-Golden { + background-image: url(spritesmith-main-10.png); + background-position: -272px -136px; + width: 135px; + height: 135px; +} +.Mount_Head_TRex-Red { + background-image: url(spritesmith-main-10.png); + background-position: 0px -272px; + width: 135px; + height: 135px; +} +.Mount_Head_TRex-Shade { + background-image: url(spritesmith-main-10.png); + background-position: -136px -272px; + width: 135px; + height: 135px; +} +.Mount_Head_TRex-Skeleton { background-image: url(spritesmith-main-10.png); background-position: -272px -272px; width: 135px; height: 135px; } -.Mount_Icon_BearCub-Base { +.Mount_Head_TRex-White { + background-image: url(spritesmith-main-10.png); + background-position: -408px 0px; + width: 135px; + height: 135px; +} +.Mount_Head_TRex-Zombie { + background-image: url(spritesmith-main-10.png); + background-position: -408px -136px; + width: 135px; + height: 135px; +} +.Mount_Head_Treeling-Skeleton { + background-image: url(spritesmith-main-10.png); + background-position: -212px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Treeling-White { + background-image: url(spritesmith-main-10.png); + background-position: -106px -786px; + width: 105px; + height: 105px; +} +.Mount_Head_Treeling-Zombie { + background-image: url(spritesmith-main-10.png); + background-position: 0px -786px; + width: 105px; + height: 105px; +} +.Mount_Head_Turkey-Base { + background-image: url(spritesmith-main-10.png); + background-position: -318px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Base { + background-image: url(spritesmith-main-10.png); + background-position: -424px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-CottonCandyBlue { + background-image: url(spritesmith-main-10.png); + background-position: -530px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-CottonCandyPink { + background-image: url(spritesmith-main-10.png); + background-position: -636px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Desert { + background-image: url(spritesmith-main-10.png); + background-position: -786px 0px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Golden { + background-image: url(spritesmith-main-10.png); + background-position: -786px -106px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Red { + background-image: url(spritesmith-main-10.png); + background-position: -786px -212px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Shade { + background-image: url(spritesmith-main-10.png); + background-position: -786px -318px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Skeleton { + background-image: url(spritesmith-main-10.png); + background-position: -786px -424px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-White { background-image: url(spritesmith-main-10.png); background-position: -786px -530px; + width: 105px; + height: 105px; +} +.Mount_Head_Unicorn-Zombie { + background-image: url(spritesmith-main-10.png); + background-position: -786px -636px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Base { + background-image: url(spritesmith-main-10.png); + background-position: -408px -544px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-CottonCandyBlue { + background-image: url(spritesmith-main-10.png); + background-position: -514px -544px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-CottonCandyPink { + background-image: url(spritesmith-main-10.png); + background-position: -680px 0px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Desert { + background-image: url(spritesmith-main-10.png); + background-position: -680px -106px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Golden { + background-image: url(spritesmith-main-10.png); + background-position: -680px -212px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Red { + background-image: url(spritesmith-main-10.png); + background-position: -680px -318px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Shade { + background-image: url(spritesmith-main-10.png); + background-position: -680px -424px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Skeleton { + background-image: url(spritesmith-main-10.png); + background-position: -680px -530px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-White { + background-image: url(spritesmith-main-10.png); + background-position: 0px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Whale-Zombie { + background-image: url(spritesmith-main-10.png); + background-position: -106px -680px; + width: 105px; + height: 105px; +} +.Mount_Head_Wolf-Base { + background-image: url(spritesmith-main-10.png); + background-position: -408px -272px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-CottonCandyBlue { + background-image: url(spritesmith-main-10.png); + background-position: 0px 0px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-CottonCandyPink { + background-image: url(spritesmith-main-10.png); + background-position: -136px -408px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Desert { + background-image: url(spritesmith-main-10.png); + background-position: -272px -408px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Floral { + background-image: url(spritesmith-main-10.png); + background-position: -408px -408px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Golden { + background-image: url(spritesmith-main-10.png); + background-position: -544px 0px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Peppermint { + background-image: url(spritesmith-main-10.png); + background-position: -544px -136px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Red { + background-image: url(spritesmith-main-10.png); + background-position: -544px -272px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Shade { + background-image: url(spritesmith-main-10.png); + background-position: -544px -408px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Skeleton { + background-image: url(spritesmith-main-10.png); + background-position: 0px -544px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Spooky { + background-image: url(spritesmith-main-10.png); + background-position: -136px -544px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-White { + background-image: url(spritesmith-main-10.png); + background-position: -272px -544px; + width: 135px; + height: 135px; +} +.Mount_Head_Wolf-Zombie { + background-image: url(spritesmith-main-10.png); + background-position: -136px 0px; + width: 135px; + height: 135px; +} +.Mount_Icon_BearCub-Base { + background-image: url(spritesmith-main-10.png); + background-position: -212px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -786px -630px; + background-position: -294px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -590px -544px; + background-position: -376px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Desert { background-image: url(spritesmith-main-10.png); - background-position: 0px -786px; + background-position: -458px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Floral { background-image: url(spritesmith-main-10.png); - background-position: -82px -786px; + background-position: -540px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Golden { background-image: url(spritesmith-main-10.png); - background-position: -164px -786px; + background-position: -622px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -246px -786px; + background-position: -704px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Polar { background-image: url(spritesmith-main-10.png); - background-position: -328px -786px; + background-position: -786px -786px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Red { background-image: url(spritesmith-main-10.png); - background-position: -410px -786px; + background-position: -892px 0px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Shade { background-image: url(spritesmith-main-10.png); - background-position: -492px -786px; + background-position: -892px -100px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -574px -786px; + background-position: -892px -200px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -656px -786px; + background-position: -892px -300px; width: 81px; height: 99px; } .Mount_Icon_BearCub-White { background-image: url(spritesmith-main-10.png); - background-position: -738px -786px; + background-position: -892px -400px; width: 81px; height: 99px; } .Mount_Icon_BearCub-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -892px 0px; + background-position: -892px -500px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Base { background-image: url(spritesmith-main-10.png); - background-position: -892px -100px; + background-position: -892px -600px; width: 81px; height: 99px; } .Mount_Icon_Bunny-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -892px -200px; + background-position: -892px -700px; width: 81px; height: 99px; } .Mount_Icon_Bunny-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -892px -300px; + background-position: -974px 0px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Desert { background-image: url(spritesmith-main-10.png); - background-position: -892px -400px; + background-position: -974px -100px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Golden { background-image: url(spritesmith-main-10.png); - background-position: -892px -500px; + background-position: -974px -200px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Red { background-image: url(spritesmith-main-10.png); - background-position: -892px -600px; + background-position: -974px -300px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Shade { background-image: url(spritesmith-main-10.png); - background-position: -892px -700px; + background-position: -974px -400px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -974px 0px; + background-position: -974px -500px; width: 81px; height: 99px; } .Mount_Icon_Bunny-White { background-image: url(spritesmith-main-10.png); - background-position: -974px -100px; + background-position: -974px -600px; width: 81px; height: 99px; } .Mount_Icon_Bunny-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -974px -200px; + background-position: -974px -700px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Base { background-image: url(spritesmith-main-10.png); - background-position: -974px -300px; + background-position: 0px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -974px -400px; + background-position: -82px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -974px -500px; + background-position: -164px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Desert { background-image: url(spritesmith-main-10.png); - background-position: -974px -600px; + background-position: -246px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Floral { background-image: url(spritesmith-main-10.png); - background-position: -974px -700px; + background-position: -328px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Golden { background-image: url(spritesmith-main-10.png); - background-position: 0px -886px; + background-position: -410px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -82px -886px; + background-position: -492px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Red { background-image: url(spritesmith-main-10.png); - background-position: -164px -886px; + background-position: -574px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Shade { background-image: url(spritesmith-main-10.png); - background-position: -246px -886px; + background-position: -656px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -328px -886px; + background-position: -738px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -410px -886px; + background-position: -820px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-White { background-image: url(spritesmith-main-10.png); - background-position: -492px -886px; + background-position: -902px -892px; width: 81px; height: 99px; } .Mount_Icon_Cactus-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -574px -886px; + background-position: -1056px 0px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Base { background-image: url(spritesmith-main-10.png); - background-position: -656px -886px; + background-position: -1056px -100px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -738px -886px; + background-position: -1056px -200px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -820px -886px; + background-position: -1056px -300px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Desert { background-image: url(spritesmith-main-10.png); - background-position: -902px -886px; + background-position: -1056px -400px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1056px 0px; + background-position: -1056px -500px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Red { background-image: url(spritesmith-main-10.png); - background-position: -1056px -100px; + background-position: -1056px -600px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1056px -200px; + background-position: -1056px -700px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1056px -300px; + background-position: -1056px -800px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-White { background-image: url(spritesmith-main-10.png); - background-position: -1056px -400px; + background-position: 0px -992px; width: 81px; height: 99px; } .Mount_Icon_Cheetah-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1056px -500px; + background-position: -82px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Base { background-image: url(spritesmith-main-10.png); - background-position: -1056px -600px; + background-position: -164px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1056px -700px; + background-position: -246px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1056px -800px; + background-position: -328px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Desert { background-image: url(spritesmith-main-10.png); - background-position: 0px -986px; + background-position: -410px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Golden { background-image: url(spritesmith-main-10.png); - background-position: -82px -986px; + background-position: -492px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Red { background-image: url(spritesmith-main-10.png); - background-position: -164px -986px; + background-position: -574px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Shade { background-image: url(spritesmith-main-10.png); - background-position: -246px -986px; + background-position: -656px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -328px -986px; + background-position: -738px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-White { background-image: url(spritesmith-main-10.png); - background-position: -410px -986px; + background-position: -820px -992px; width: 81px; height: 99px; } .Mount_Icon_Cuttlefish-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -492px -986px; + background-position: -902px -992px; width: 81px; height: 99px; } .Mount_Icon_Deer-Base { background-image: url(spritesmith-main-10.png); - background-position: -574px -986px; + background-position: -984px -992px; width: 81px; height: 99px; } .Mount_Icon_Deer-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -656px -986px; + background-position: -1138px 0px; width: 81px; height: 99px; } .Mount_Icon_Deer-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -738px -986px; + background-position: -1138px -100px; width: 81px; height: 99px; } .Mount_Icon_Deer-Desert { background-image: url(spritesmith-main-10.png); - background-position: -820px -986px; + background-position: -1138px -200px; width: 81px; height: 99px; } .Mount_Icon_Deer-Golden { background-image: url(spritesmith-main-10.png); - background-position: -902px -986px; + background-position: -1138px -300px; width: 81px; height: 99px; } .Mount_Icon_Deer-Red { background-image: url(spritesmith-main-10.png); - background-position: -984px -986px; + background-position: -1138px -400px; width: 81px; height: 99px; } .Mount_Icon_Deer-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1138px 0px; + background-position: -1138px -500px; width: 81px; height: 99px; } .Mount_Icon_Deer-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1138px -100px; + background-position: -1138px -600px; width: 81px; height: 99px; } .Mount_Icon_Deer-White { background-image: url(spritesmith-main-10.png); - background-position: -1138px -200px; + background-position: -1138px -700px; width: 81px; height: 99px; } .Mount_Icon_Deer-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1138px -300px; + background-position: -1138px -800px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Base { background-image: url(spritesmith-main-10.png); - background-position: -1138px -400px; + background-position: -1138px -900px; width: 81px; height: 99px; } .Mount_Icon_Dragon-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1138px -500px; + background-position: 0px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1138px -600px; + background-position: -82px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1138px -700px; + background-position: -164px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Floral { background-image: url(spritesmith-main-10.png); - background-position: -1138px -800px; + background-position: -246px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1138px -900px; + background-position: -328px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: 0px -1086px; + background-position: -410px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Red { background-image: url(spritesmith-main-10.png); - background-position: -82px -1086px; + background-position: -492px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Shade { background-image: url(spritesmith-main-10.png); - background-position: -164px -1086px; + background-position: -574px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -246px -1086px; + background-position: -656px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -328px -1086px; + background-position: -738px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-White { background-image: url(spritesmith-main-10.png); - background-position: -410px -1086px; + background-position: -820px -1092px; width: 81px; height: 99px; } .Mount_Icon_Dragon-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -492px -1086px; + background-position: -902px -1092px; width: 81px; height: 99px; } .Mount_Icon_Egg-Base { background-image: url(spritesmith-main-10.png); - background-position: -574px -1086px; + background-position: -984px -1092px; width: 81px; height: 99px; } .Mount_Icon_Egg-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -656px -1086px; + background-position: -1066px -1092px; width: 81px; height: 99px; } .Mount_Icon_Egg-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -738px -1086px; + background-position: -1220px 0px; width: 81px; height: 99px; } .Mount_Icon_Egg-Desert { background-image: url(spritesmith-main-10.png); - background-position: -820px -1086px; + background-position: -1220px -100px; width: 81px; height: 99px; } .Mount_Icon_Egg-Golden { background-image: url(spritesmith-main-10.png); - background-position: -902px -1086px; + background-position: -1220px -200px; width: 81px; height: 99px; } .Mount_Icon_Egg-Red { background-image: url(spritesmith-main-10.png); - background-position: -984px -1086px; + background-position: -1220px -300px; width: 81px; height: 99px; } .Mount_Icon_Egg-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1086px; + background-position: -1220px -400px; width: 81px; height: 99px; } .Mount_Icon_Egg-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1220px 0px; + background-position: -1220px -500px; width: 81px; height: 99px; } .Mount_Icon_Egg-White { background-image: url(spritesmith-main-10.png); - background-position: -1220px -100px; + background-position: -1220px -600px; width: 81px; height: 99px; } .Mount_Icon_Egg-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1220px -200px; + background-position: -1220px -700px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Base { background-image: url(spritesmith-main-10.png); - background-position: -1220px -300px; + background-position: -1220px -800px; width: 81px; height: 99px; } .Mount_Icon_Falcon-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1220px -400px; + background-position: -1220px -900px; width: 81px; height: 99px; } .Mount_Icon_Falcon-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1220px -500px; + background-position: -1220px -1000px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1220px -600px; + background-position: 0px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1220px -700px; + background-position: -82px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Red { background-image: url(spritesmith-main-10.png); - background-position: -1220px -800px; + background-position: -164px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1220px -900px; + background-position: -246px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1220px -1000px; + background-position: -328px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-White { background-image: url(spritesmith-main-10.png); - background-position: 0px -1186px; + background-position: -410px -1192px; width: 81px; height: 99px; } .Mount_Icon_Falcon-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -82px -1186px; + background-position: -492px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Base { background-image: url(spritesmith-main-10.png); - background-position: -164px -1186px; + background-position: -574px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -246px -1186px; + background-position: -656px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -328px -1186px; + background-position: -1712px -200px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Desert { background-image: url(spritesmith-main-10.png); - background-position: -410px -1186px; + background-position: -820px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Floral { background-image: url(spritesmith-main-10.png); - background-position: -492px -1186px; + background-position: -902px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Golden { background-image: url(spritesmith-main-10.png); - background-position: -574px -1186px; + background-position: -984px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -656px -1186px; + background-position: -1066px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Red { background-image: url(spritesmith-main-10.png); - background-position: -1712px -400px; + background-position: -1148px -1192px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Shade { background-image: url(spritesmith-main-10.png); - background-position: -820px -1186px; + background-position: -1302px 0px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -902px -1186px; + background-position: -1302px -100px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -984px -1186px; + background-position: -1302px -200px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-White { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1186px; + background-position: -1302px -300px; width: 81px; height: 99px; } .Mount_Icon_FlyingPig-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1148px -1186px; + background-position: -1302px -400px; width: 81px; height: 99px; } .Mount_Icon_Fox-Base { background-image: url(spritesmith-main-10.png); - background-position: -1302px 0px; + background-position: -1302px -500px; width: 81px; height: 99px; } .Mount_Icon_Fox-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1302px -100px; + background-position: -1302px -600px; width: 81px; height: 99px; } .Mount_Icon_Fox-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1302px -200px; + background-position: -1302px -700px; width: 81px; height: 99px; } .Mount_Icon_Fox-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1302px -300px; + background-position: -1302px -800px; width: 81px; height: 99px; } .Mount_Icon_Fox-Floral { background-image: url(spritesmith-main-10.png); - background-position: -1302px -400px; + background-position: -1302px -900px; width: 81px; height: 99px; } .Mount_Icon_Fox-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1302px -500px; + background-position: -1302px -1000px; width: 81px; height: 99px; } .Mount_Icon_Fox-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -1302px -600px; + background-position: -1302px -1100px; width: 81px; height: 99px; } .Mount_Icon_Fox-Red { background-image: url(spritesmith-main-10.png); - background-position: -1302px -700px; + background-position: -1384px 0px; width: 81px; height: 99px; } .Mount_Icon_Fox-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1302px -800px; + background-position: -1384px -100px; width: 81px; height: 99px; } .Mount_Icon_Fox-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1302px -900px; + background-position: -1384px -200px; width: 81px; height: 99px; } .Mount_Icon_Fox-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -1302px -1000px; + background-position: -1384px -300px; width: 81px; height: 99px; } .Mount_Icon_Fox-White { background-image: url(spritesmith-main-10.png); - background-position: -1302px -1100px; + background-position: -1384px -400px; width: 81px; height: 99px; } .Mount_Icon_Fox-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1384px 0px; + background-position: -1384px -500px; width: 81px; height: 99px; } .Mount_Icon_Frog-Base { background-image: url(spritesmith-main-10.png); - background-position: -1384px -100px; + background-position: -1384px -600px; width: 81px; height: 99px; } .Mount_Icon_Frog-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1384px -200px; + background-position: -1384px -700px; width: 81px; height: 99px; } .Mount_Icon_Frog-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1384px -300px; + background-position: -1384px -800px; width: 81px; height: 99px; } .Mount_Icon_Frog-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1384px -400px; + background-position: -1384px -900px; width: 81px; height: 99px; } .Mount_Icon_Frog-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1384px -500px; + background-position: -1384px -1000px; width: 81px; height: 99px; } .Mount_Icon_Frog-Red { background-image: url(spritesmith-main-10.png); - background-position: -1384px -600px; + background-position: -1384px -1100px; width: 81px; height: 99px; } .Mount_Icon_Frog-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1384px -700px; + background-position: 0px -1292px; width: 81px; height: 99px; } .Mount_Icon_Frog-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1384px -800px; + background-position: -82px -1292px; width: 81px; height: 99px; } .Mount_Icon_Frog-White { background-image: url(spritesmith-main-10.png); - background-position: -1384px -900px; + background-position: -164px -1292px; width: 81px; height: 99px; } .Mount_Icon_Frog-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1384px -1000px; + background-position: -246px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Base { background-image: url(spritesmith-main-10.png); - background-position: -1384px -1100px; + background-position: -328px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: 0px -1286px; + background-position: -410px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -82px -1286px; + background-position: -492px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Desert { background-image: url(spritesmith-main-10.png); - background-position: -164px -1286px; + background-position: -574px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Golden { background-image: url(spritesmith-main-10.png); - background-position: -246px -1286px; + background-position: -656px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Red { background-image: url(spritesmith-main-10.png); - background-position: -328px -1286px; + background-position: -738px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-RoyalPurple { background-image: url(spritesmith-main-10.png); - background-position: -410px -1286px; + background-position: -820px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Shade { background-image: url(spritesmith-main-10.png); - background-position: -492px -1286px; + background-position: -902px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -574px -1286px; + background-position: -984px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-White { background-image: url(spritesmith-main-10.png); - background-position: -656px -1286px; + background-position: -1066px -1292px; width: 81px; height: 99px; } .Mount_Icon_Gryphon-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -738px -1286px; + background-position: -1148px -1292px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Base { background-image: url(spritesmith-main-10.png); - background-position: -820px -1286px; + background-position: -1230px -1292px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -902px -1286px; + background-position: -1312px -1292px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -984px -1286px; + background-position: -1466px 0px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1286px; + background-position: -1466px -100px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1148px -1286px; + background-position: -1466px -200px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Red { background-image: url(spritesmith-main-10.png); - background-position: -1230px -1286px; + background-position: -1466px -300px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1312px -1286px; + background-position: -1466px -400px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1466px 0px; + background-position: -1466px -500px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-White { background-image: url(spritesmith-main-10.png); - background-position: -1466px -100px; + background-position: -1466px -600px; width: 81px; height: 99px; } .Mount_Icon_Hedgehog-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1466px -200px; + background-position: -1466px -700px; width: 81px; height: 99px; } .Mount_Icon_Horse-Base { background-image: url(spritesmith-main-10.png); - background-position: -1466px -300px; + background-position: -1466px -800px; width: 81px; height: 99px; } .Mount_Icon_Horse-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1466px -400px; + background-position: -1466px -900px; width: 81px; height: 99px; } .Mount_Icon_Horse-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1466px -500px; + background-position: -1466px -1000px; width: 81px; height: 99px; } .Mount_Icon_Horse-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1466px -600px; + background-position: -1466px -1100px; width: 81px; height: 99px; } .Mount_Icon_Horse-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1466px -700px; + background-position: -1466px -1200px; width: 81px; height: 99px; } .Mount_Icon_Horse-Red { background-image: url(spritesmith-main-10.png); - background-position: -1466px -800px; + background-position: 0px -1392px; width: 81px; height: 99px; } .Mount_Icon_Horse-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1466px -900px; + background-position: -82px -1392px; width: 81px; height: 99px; } .Mount_Icon_Horse-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1466px -1000px; + background-position: -164px -1392px; width: 81px; height: 99px; } .Mount_Icon_Horse-White { background-image: url(spritesmith-main-10.png); - background-position: -1466px -1100px; + background-position: -246px -1392px; width: 81px; height: 99px; } .Mount_Icon_Horse-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1466px -1200px; + background-position: -328px -1392px; width: 81px; height: 99px; } .Mount_Icon_JackOLantern-Base { background-image: url(spritesmith-main-10.png); - background-position: 0px -1386px; + background-position: -410px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Base { background-image: url(spritesmith-main-10.png); - background-position: -82px -1386px; + background-position: -492px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -164px -1386px; + background-position: -574px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -246px -1386px; + background-position: -656px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Desert { background-image: url(spritesmith-main-10.png); - background-position: -328px -1386px; + background-position: -738px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Ethereal { background-image: url(spritesmith-main-10.png); - background-position: -410px -1386px; + background-position: -820px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Floral { background-image: url(spritesmith-main-10.png); - background-position: -492px -1386px; + background-position: -902px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Golden { background-image: url(spritesmith-main-10.png); - background-position: -574px -1386px; + background-position: -984px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -656px -1386px; + background-position: -1066px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Red { background-image: url(spritesmith-main-10.png); - background-position: -738px -1386px; + background-position: -1148px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Shade { background-image: url(spritesmith-main-10.png); - background-position: -820px -1386px; + background-position: -1230px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -902px -1386px; + background-position: -1312px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -984px -1386px; + background-position: -1394px -1392px; width: 81px; height: 99px; } .Mount_Icon_LionCub-White { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1386px; + background-position: -1548px 0px; width: 81px; height: 99px; } .Mount_Icon_LionCub-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1148px -1386px; + background-position: -1548px -100px; width: 81px; height: 99px; } .Mount_Icon_MagicalBee-Base { background-image: url(spritesmith-main-10.png); - background-position: -1230px -1386px; + background-position: -1548px -200px; width: 81px; height: 99px; } .Mount_Icon_Mammoth-Base { background-image: url(spritesmith-main-10.png); - background-position: -1312px -1386px; + background-position: -1548px -300px; width: 81px; height: 99px; } .Mount_Icon_MantisShrimp-Base { background-image: url(spritesmith-main-10.png); - background-position: -1394px -1386px; + background-position: -1548px -400px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Base { background-image: url(spritesmith-main-10.png); - background-position: -1548px 0px; + background-position: -1548px -500px; width: 81px; height: 99px; } .Mount_Icon_Monkey-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1548px -100px; + background-position: -1548px -600px; width: 81px; height: 99px; } .Mount_Icon_Monkey-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1548px -200px; + background-position: -1548px -700px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1548px -300px; + background-position: -1548px -800px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1548px -400px; + background-position: -1548px -900px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Red { background-image: url(spritesmith-main-10.png); - background-position: -1548px -500px; + background-position: -1548px -1000px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1548px -600px; + background-position: -1548px -1100px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1548px -700px; + background-position: -1548px -1200px; width: 81px; height: 99px; } .Mount_Icon_Monkey-White { background-image: url(spritesmith-main-10.png); - background-position: -1548px -800px; + background-position: -1548px -1300px; width: 81px; height: 99px; } .Mount_Icon_Monkey-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1548px -900px; + background-position: 0px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Base { background-image: url(spritesmith-main-10.png); - background-position: -1548px -1000px; + background-position: -82px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1548px -1100px; + background-position: -164px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1548px -1200px; + background-position: -246px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1548px -1300px; + background-position: -328px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Golden { background-image: url(spritesmith-main-10.png); - background-position: 0px -1486px; + background-position: -410px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Red { background-image: url(spritesmith-main-10.png); - background-position: -82px -1486px; + background-position: -492px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Shade { background-image: url(spritesmith-main-10.png); - background-position: -164px -1486px; + background-position: -574px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -246px -1486px; + background-position: -656px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-White { background-image: url(spritesmith-main-10.png); - background-position: -328px -1486px; + background-position: -738px -1492px; width: 81px; height: 99px; } .Mount_Icon_Octopus-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -410px -1486px; + background-position: -820px -1492px; width: 81px; height: 99px; } .Mount_Icon_Orca-Base { background-image: url(spritesmith-main-10.png); - background-position: -492px -1486px; + background-position: -902px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Base { background-image: url(spritesmith-main-10.png); - background-position: -574px -1486px; + background-position: -984px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -656px -1486px; + background-position: -1066px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -738px -1486px; + background-position: -1148px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Desert { background-image: url(spritesmith-main-10.png); - background-position: -820px -1486px; + background-position: -1230px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Golden { background-image: url(spritesmith-main-10.png); - background-position: -902px -1486px; + background-position: -1312px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Red { background-image: url(spritesmith-main-10.png); - background-position: -984px -1486px; + background-position: -1394px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1486px; + background-position: -1476px -1492px; width: 81px; height: 99px; } .Mount_Icon_Owl-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1148px -1486px; + background-position: -1630px 0px; width: 81px; height: 99px; } .Mount_Icon_Owl-White { background-image: url(spritesmith-main-10.png); - background-position: -1230px -1486px; + background-position: -1630px -100px; width: 81px; height: 99px; } .Mount_Icon_Owl-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1312px -1486px; + background-position: -1630px -200px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Base { background-image: url(spritesmith-main-10.png); - background-position: -1394px -1486px; + background-position: -1630px -300px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1476px -1486px; + background-position: -1630px -400px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1630px 0px; + background-position: -1630px -500px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1630px -100px; + background-position: -1630px -600px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Floral { background-image: url(spritesmith-main-10.png); - background-position: -1630px -200px; + background-position: -1630px -700px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Golden { background-image: url(spritesmith-main-10.png); - background-position: -1630px -300px; + background-position: -1630px -800px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Peppermint { background-image: url(spritesmith-main-10.png); - background-position: -1630px -400px; + background-position: -1630px -900px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Red { background-image: url(spritesmith-main-10.png); - background-position: -1630px -500px; + background-position: -1630px -1000px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Shade { background-image: url(spritesmith-main-10.png); - background-position: -1630px -600px; + background-position: -1630px -1100px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1630px -700px; + background-position: -1630px -1200px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Spooky { background-image: url(spritesmith-main-10.png); - background-position: -1630px -800px; + background-position: -1630px -1300px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-White { background-image: url(spritesmith-main-10.png); - background-position: -1630px -900px; + background-position: -1630px -1400px; width: 81px; height: 99px; } .Mount_Icon_PandaCub-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1630px -1000px; + background-position: 0px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Base { background-image: url(spritesmith-main-10.png); - background-position: -1630px -1100px; + background-position: -82px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -1630px -1200px; + background-position: -164px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -1630px -1300px; + background-position: -246px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Desert { background-image: url(spritesmith-main-10.png); - background-position: -1630px -1400px; + background-position: -328px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Golden { background-image: url(spritesmith-main-10.png); - background-position: 0px -1586px; + background-position: -410px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Red { background-image: url(spritesmith-main-10.png); - background-position: -82px -1586px; + background-position: -492px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Shade { background-image: url(spritesmith-main-10.png); - background-position: -164px -1586px; + background-position: -574px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -246px -1586px; + background-position: -656px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-White { background-image: url(spritesmith-main-10.png); - background-position: -328px -1586px; + background-position: -738px -1592px; width: 81px; height: 99px; } .Mount_Icon_Parrot-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -410px -1586px; + background-position: -820px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Base { background-image: url(spritesmith-main-10.png); - background-position: -492px -1586px; + background-position: -902px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-CottonCandyBlue { background-image: url(spritesmith-main-10.png); - background-position: -574px -1586px; + background-position: -984px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-CottonCandyPink { background-image: url(spritesmith-main-10.png); - background-position: -656px -1586px; + background-position: -1066px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Desert { background-image: url(spritesmith-main-10.png); - background-position: -738px -1586px; + background-position: -1148px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Golden { background-image: url(spritesmith-main-10.png); - background-position: -820px -1586px; + background-position: -1230px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Red { background-image: url(spritesmith-main-10.png); - background-position: -902px -1586px; + background-position: -1312px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Shade { background-image: url(spritesmith-main-10.png); - background-position: -984px -1586px; + background-position: -1394px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Skeleton { background-image: url(spritesmith-main-10.png); - background-position: -1066px -1586px; + background-position: -1476px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-White { background-image: url(spritesmith-main-10.png); - background-position: -1148px -1586px; + background-position: -1558px -1592px; width: 81px; height: 99px; } .Mount_Icon_Penguin-Zombie { background-image: url(spritesmith-main-10.png); - background-position: -1230px -1586px; + background-position: -1712px 0px; width: 81px; height: 99px; } .Mount_Icon_Phoenix-Base { background-image: url(spritesmith-main-10.png); - background-position: -1312px -1586px; + background-position: -1712px -100px; width: 81px; height: 99px; } .Mount_Icon_Rat-Base { background-image: url(spritesmith-main-10.png); - background-position: -1394px -1586px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-CottonCandyBlue { - background-image: url(spritesmith-main-10.png); - background-position: -1476px -1586px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-CottonCandyPink { - background-image: url(spritesmith-main-10.png); - background-position: -1558px -1586px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-Desert { - background-image: url(spritesmith-main-10.png); - background-position: -1712px 0px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-Golden { - background-image: url(spritesmith-main-10.png); - background-position: -1712px -100px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-Red { - background-image: url(spritesmith-main-10.png); - background-position: -1712px -200px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-Shade { - background-image: url(spritesmith-main-10.png); - background-position: -1712px -300px; - width: 81px; - height: 99px; -} -.Mount_Icon_Rat-Skeleton { - background-image: url(spritesmith-main-10.png); - background-position: -738px -1186px; + background-position: -738px -1192px; width: 81px; height: 99px; } diff --git a/common/dist/sprites/spritesmith-main-10.png b/common/dist/sprites/spritesmith-main-10.png index 2ed15607c317afce59d99ddd2157b217764c875e..a1b6ed7434af4baf3cd2070002e627023eadb6a2 100644 GIT binary patch literal 150236 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�QaT zi!^GYZ= zlj;M@8on;}&FmuPyIn~0CgdEZT89OR32vu;+D%=m7#IlLip6s64mz&z9jrL1syxW} zj3PFD=hV?sF&H|YzJ?wB4K470}WuyU|H?Ef~J8M@vtiABl1xMq4rFIh7_zIUJHopGZ zDg&c$qXz-Ev3=yCe9rF?G*;V(=+Ap(mvfv{cO2+C^d0k1Vg|M=8+hOCD^q{p!Z$nd zE@#~>)|<~4V#I@Aq5yNKa3!%Y2!zD|()QAtJqEN|S*@7fT;l*xKIH+>7g0VPjSSA# zJQ|7`(4o6|#gA*T;Kvckp_;KxRS-OC6Z<=1io2Omo*bit^><9W-kiCt*vIO|-bv-` zeyn=w66@*M!m`GessKRw#{fPM0kZ=rL;qrZ(D zDI_y$z%z>1)uthOkoK0W^`iPdk?Lh42CgJDo z0BwaitQHZFMJ~=Fh*m1xvYPaZOJrq9L)WWUoU)0k)OG2uPEW=qSJd;07QH@=02Ke zVG99-GxR{}e%Wvv>&3Hny)l}jkL_2OelNA^_CJh`7Bd;UWBnu_$Q)5BpZS6^eX8~Y z>b@2Fxvg^TWSwwl=B_{B1Td5SYUebL5r7ppCXSoQ zA+5NZh35(HA1${DEQSUeT%|Z{s@0sCo9mgm z-DF{Ley|{Hy*gB&z#{&f?z$4MbM{0&^N}*-DFVkjJdMK=^5(Bm1YG<*y+CBI%YD?_c03nSiH}(0IP@xE}i9Q|k^8)rf1JU>DRj z`*x`JigJsNU?R(xgxp)Bn(|S%F_2svPxC!kTf%;JPs_8$i?|seK=eH-3(Pf~7f)J+ zQ*CnUM4Y?eJBZi6%{~x zkf%6@5kEwn+zdIB+mTlw37b8@vSWs0{2v_mbVAC;qJ^HKKeMwo$FfYzv+FM1ZZ2Nn*g#UJW)Mq@4bdoSXYA{njS8=q``@?r8DdzXqRk6TD|~jj}v2SxlFqrDFx`LzDL%;R{^*4 zb`zc96*)^vKa{s;mvs3n>1HnxWjwkKVkjD-CiYJKMbgK39Zdo{uBRLBKA0t-S@q~r z%mlurZ2oEs3Zn~~nsX1>gIq0nRD}H67z)GVJE>C&Ngq&#kG^{z9fTIKTS-zhKUaPH zJO(35YdagB09}@$gYWZgbqoXlj!1P7COK_;CByF7QZUx;;CsZA4BQ99(O%nNDNg>U zocD`Xp}kK~#`ATSs1Y1C?fP~z#2m^=79 zTfznUuFN@(b#LK*J=d21piUsQn0toao6r!`36v@*sjZf?OIBK1_nm>9uN?izXf0wQTIDgel1Vd z_Sqr}tBsJ5=p^A7s&pOMA%jtCkYsvb+^>3`|&3MQ@D72#(kBdfp39wQ^^T|k7v@&OlV&7Pr zfNVoq-F76A)zoogei(3SBi9R+w8KGzV1>udurhPM?y;jZO`i}E%A2?|;(0Gc`Kxw( z#Yp56P%F;ADWr6(tHL&VV{NzF<$*<)nSTN|VMCcJ=Yg7vg3i0Be$Qx|XkVo3Kw9M- zr8ISFny-0VclmbrRVYhi@e(L}>$QR*50Yy3lm=z<{_*~FBHP_?%b#WrH#TdFRJBN9vWY8YjW=q0sMY4OLp`)giDRd*fpek`%o$ni!Vi}7syodVJ+-bY zm#eY|sNa{KfKh=V%?BUL)0fMUQl1=i7}3a7UbwOmN>}z(uNz4YunAPHMR9XPYR!Dumfw-DM!>~h1+@cRJWDed4 zms+klpI}v7KbIC@1C1?qip($2#GM4%31e_a?y7E7{b>`PS(oOqo zW&1;;ipx*dHf%Mg<=J;WOFYR5*VGzPWO?kJX5Tp%UL;J_1#|}J)Iy8n9l#jp_e^K; zK@KLWsYnPaNbK3N8Tg z7wnA;M?v>zjm#O>EvFq;kZ%fM|1+$We3NP0z^HjOQWC-1MwQ)76`xwoBmC}+BnWQ4 zlkC>@q0NiI&)YV~`cY7-X@DQh+{-w_>5hb-quVcbmD+cBA|Ghh@)i52-m<`ATU%j1 z?kB%|`D=#W1O#J;cTlG~rxbTLIa8MNc%L}2cZR3ZR1Mq3;tq;Du+8?@tbRMYT&Ruh zewG%qj7|EeJ#6_#L=$KmQ(KN_nW7@~npv||$~j&^4DgYVbP9OUKq%Daj^6Z8K_;?qJ2m^)kaXFZ+zr@YSlORk`oyAixyqsSvsK0lYfV;E zDv&0k6CA+gAt`|rE8@m(`6|l^I=UG!AOG^G} zp&YFUr62Go_-FknY7M5?uMl`hZTNM@uCt~pNAOwGfuG?8MHmoGlUuBqUxAw%et0AMBl8|k#l9w;)wBQ~Nc)37o;VlmIfKShC)3aW3L1%FU&eFo}Fp_E+!D$TnA zDyq~nDG;?|_Gkq&FY7+2GFFAjf8Oet;Jx<4CQd*Fl9X%+8^r~q`o)AxfH-;IeMn4~ zvNG;XK!qbJK+RDo`P#N^K|fw6png0#{@+GbZJb!DzY_`~KsBUNj?5XN7wEVa>*7Bb z!L^Vl8j%dX*yDoky7)D>U_uUI6D{${ZueQ!o_VOEg%(_YvKGDn)entZ^)0ZX4=gFZ zW#zOAIQ6!>uN~PKkqRgZAe;d(JJ~jF5xMWE#VsE&y*hFnR@51RqF%y?Xtk3hWArpZr6Dj?-eK zy2CURSX%%P>u3>~r`WBXm%AU>Uj%sk-6q*KN!bqvwiY`r#z8ssE#ZCQGMp^{oUqjf zgISmw?veZHNh>q7R%r-BG0YecPC>mieSc)U8Vq{V%N>su_Z(6Z;&uwcq>Q5HTcRE# zqd8Q%LJRxjV1aQ^P)VaI5t3)NQ`+ncp}=A03J|@^yhjTaGb;o~s5Fb1A|m#!9>s5R z(40A51LpY3QaR{Ed9M3hSG?##gHgxNtbG~f8UbXbhUOd$Xg~(z!#?P1D9i}I5dM&Z zlOy0To9J~O5(gKY@huvg|C%&|^4k&29XhwK9dJ~_CyC5XT-$sxI5|V4khg2g>~Mz1 z5QT8TNu}-gw}<3`UnMk{yN2)-05zB{!D+na^U4tr-LABQYT%}S;@QcX?A9*N=cPYB zdBG?H$b#4a;7`FQf9sV9mEg{gDQGuvt_t6}23eJw=BHcGR)jM;%_CanvykFxSQ~-U zcba0NO)xZ{l)L+dpku;HmRvO7>)!@g^(*l5?a^aDKA>of)thl*Gu`wbr=V4<8KR6+ zO+B)EJBU3}*;lzxy3c5lMID@4P%2`mZCsq^qDOSY{o1X<=@WE!gj|n^oBZN8 zh`x`zyiGaP<85A?2bpo#f#cwQTGXLqI=Pkxy%NdkpR;yy2EX8+d(do^AuLgmQzHmb zT?#mR`BvcyM!4^P>fpF-ZBHAS&f1gy{Vrm(mkS2 zwTKse&Z=TbNxYc{xxz*P@a?qMz%w6}`#Wg*?~j6;q?^<$&~?$}Xe5 zyWZF_yIS_G^IH)@u`97umZP&Z)TeN)GSDF#y)_t((=IuBPlS_?6&W zy%I!3czCRyFdxR8?#{jI*vzM9r8?}j_EO=sX(h0liD;v_QyuA+@|TWb4iGjO*W@pH z>9KN+)>490&sbmYAr%5eTDfu0nc%2y7l*lJ37`ob*!6y zW7SOLRy;Uq23+@i<{Y$NB6QRi*_$D+cG_J@dlg=%?c1|x_qDuF^Dd_yPD(c zf36-#QS6yUXDW)Y_5g*_^Zi{%P$T2_#XEJd- zoo|o+_k@19#+h~$io&ss&f14gp%M$LbI6*YY5cpz`KH;;@*%-H98R4)A3S0NVq#z! z$jd{7cRu)*<&E1bFYV#1)co;NerB!Rj7QU>d%9xYRB~; zhvdU6N}`#yd336G_+DgWj6Lrh=0raXMy%J*kRerUa=)HTfqmilGx(>5b09$8 zQVfSk1-_-+M|PB>?kIF=2Y2*yQdW+vKY`=ztiD2}=Ej~Dem9aI8h;ffXq}+Nd79E6 zUDN%k)@NSt67lN$a4*&B%-7*g*@0ji!>ogU>J}mUrGsSVAKQkGrLTfXfej2R`9;6V zIGa}MwPxhHXr=3Hy!G#XJ+kp}O&rY5*@c_Yx0F_TX8rEDMM*DQ;v{h4Q^}I>8hZ*Z zzo9NO4vFjKo0Ht`$sh4^EXSh!~OewFIpn7uNAwRwlMP1e$A zNcA;;Fj%AP3i1U&op=p2HX=q^{Ie1&+H_$8yHusR{nIAHTo?*sJ*m8FXLOK7#BM^a zh~q_Ae82s@d0bm}s#4&x*pZV>2NN$BtsqQ|HpcDwE4Y@#+0WQ9f8BF&g$JtLR?q%z zxN5Lg!Nvtigrl!EXSTZn^|X;-$G7~rFQmq1I*?L?ShI5;vH6<3SqD43eI}DZkXnVh%~lDKgJ0nq8W4mDk`Wy7KVrLvm(67&-y} zO&eCJl!xFuxvm1zDQ~@pe)s!0%y+eFh*&&;>osi9XPyW7pbnuiYRpvUJ&ST59YdYw z9mJ{R{{z?~&2W{nVJ5OfY-LJiI1WT}1UUvlLMee(&yI^@nT%`C4nDaoYBE+H;FeUQ zH<^#P{TtW4sUFn&P$}D}=WkFutSVu4z*BE@7Qi~WkNcj+tQEPuWe*7M4z8E*IV`AT zy7TcOvAu78B_j|c0Ct*pQFr}PY4#K;-7hR9=196@veqDVtJTu2Kf+N6IA);iX`s6X zb*uh(i<#Ro`X_LuGoC6)|4Tzji30c|<^&0m<$*`fdwmp~%|91q6cTw`nNx zoQm1C=S|}6!@~lNB81P=x90XezvpfU&7rnh+ucv$Egb4A&$y!KzH9z61u;q<#ix9{ zQJ(^uE@3`G0NL_*LIIH2<8#WbTeCtVmX7K{^b2?PTIo!ELvwNV5M}I3jM$!}hJjMY zSjyWn@_3PKVY$A2phaW+UY-tyMSvr)4{D&fcdl5g&+t1ES648XFzPWe?;uOJH|X}3 z)x;*vrZKOT_q*sHIrEn6ySKKG=w}3eM4vrl5;vRcFt{uXAL-g5D@B*Zm>AU_~a#KJ;rg??qU#73JyBCR5lmQhMlLJo|ypEGKeg8 zK9r{nWW*3$#FmpjY}Mblf&qN-wD!tf-=pa^;i>0@-38!c))G!EW$__12%NcQWdln>-R1_>Don^D)`Y z&(9DNP>tb%3?C98C+3I;e=wQ<3Z0k{#j1_|72CF{mngUW1XVhf`K+P|SP{nkw{tQl zRDcBj7reyYx4A?(1H-+tmrRv2J4$43wbz(vLAP=*mI<{MU%MZw#+5aLrSYVezhwba zramIV%vk_Yz{atJ4mzqey6(;y=uJF!Nv<6k_UqSs00_^ADTtK|3@GvTUl39)lpfnK z0VMZ*__`ET^$4bO5OlpR?E8^@_^evR*{7LC?9sz>Su&wdr~Eqf?oMz3gAX1RGRGT9 z4z)#;vquprzhC_pk5mlDW=Z!Cz#2;09lR}KA4dKaJnHk{{G+=knRS;3*he?vXKx4u z7T;dcml!jostqdHoABj;aW~$$Uh4+>Ne&x>X=5ixl0Hw$bY5>w?9RMMG9xm@W4|H3 zBB6Be?65d~0so7Aa?Kg2JvP%{{$~zuHP-l`n8q@49UtUI+qwJ%oBg^~&jn z4?nPl+||;_7hlowIP4GjF!!PSDmmAiX@PQucTagNhCv^rOgqvO3RlkYEmX*a-PT#Imb`3frkrc9WZ8 zR$I^Bi81YVV!7&+*gefW8Xjzu0Rox!_2*Gi6+2#6?xy~7ZfSV%(phUB?+UVg#E8aD zX@Y45Md4f0R`enF?HhrZu`Gc+)v+I|={f5UWI^xs5kZ{3S7(u|kX6r25N(EaLtB^; z>mtk$W)Q*Oi-_O~nY|h$kTg1x-F5i5_oGHr*g3wqzAYYUGm zw9A3l&^Of~5=xH}d>I-v(-p^upDra*j`xrpi=&y*7;;g8V`tqbq`GYuqGRN*)EYGyau;8Wa#&fWZOGf~h5l^!?u*fevShOS; zSLv(AImi(rS1m#SqR|JHHK)`wO6=ECm$8i+rX!-So_dCM;&{*0xkW*vG9=Wv0rQ<@ zKvlFHc_NE3skG0P+|#=w14!+?#dE&PTPJ~uMu>hQ#l1;c)f&$%@^<*N1MA}UK!5m?7vp!;NdJETHCP^sriiq(w-uIWNv_x?-sjJi%3%Qg&F-y`DugdrP&>Ds0dz zJ83$n{zJuWC*l2c;AJI|%Hh=k4WNp`E?{LX3Nco70Khwd2evIJ8b~;S8j!4@`=j-z zdp}O`rLyv4K+psS$~TT_N+~8fCY#CL6KT%`R-25;D?$ot=i`KXLv8yHaGFvgBKn0J z-{x*1;-h>;a0(%vtUvY2xlK93(N24*)&3Pb6SVu241DBViQuU!5?LpiO;8sqTlPATzA76WU&fZ6tUfB7mm1%eNGs*Vubb=~UF~pf%948)`U4eiSWq?; zD!qOQ-$pGZS;vhF=$ESsO{I8K=wIvX47y5WTvLY%`vT*Z;1u=UFBo>su^I6vZ5Z8A&=4b<4Pv`&Q8}u~I@4_DGZqG*DCnsmK1>$u+Zx{GlS3+m+tJT*U zNjB`!!(L|o&G>dx8<_bk@lJsDgaS`#_(BqR{@j>}o;w#T>ok%hJcVitlhMJg^kgZU zy|W6OXJ}gNiVAo-yx(wE2T1Da3eSAk^*PYJs^CC&H0$wM_rBU|MtX$R76gKalA zFZc>Sd?58N6ZuwFl7{zPFVr{#J=cT`jUO)DlHeOj_q6a)t%7*w1AzMV7ykpQZ^ndVtK5sK!1erh==6*#LoSdUMFN|}mJ z28Q<5Zj|_!fEmlyk7S?W!=CPs2XqOrwO|)#S0SMJ)=&fH|Hk47sINvB*+W3maB0)C zICbIA$?eebQ?I9*7WH~<9UtDD{F2+;RGKPEY3nA4zK!r`b=%4h2w+uGMZcJ$LWJh$ zN6KaW#?HB7Z|rQaW|pXa5If!-{hg0Vp;_B^=aa;&7naq z)WiU+5D(`KB;}A-=wFII6dLs!*uDlZwy#y-Kg9<#TkXX@D9*!?N(n1Rb5`Q|`N@Sz z!WY56=Kx(9si&WKbkVtS-b#QE@P_ni>m33O^mlVpD_8dk?pemXE<3yV7amtah|gMI z37h@QtHGDtaf?Ob8Zj*0`g5HPI~5jfVh)j&mce#^oFBmzNfiXsL%#nNfo+w<<~ zeH&=;zI#_?%3K{NY(&n{n+}?6j_8er<0}-jG{3Ip;&kAZEtG+$F3g}ipnDPV=vZP) zd9e&hqGVn&t>iy;1K(KB`X!Lu3jK1-1Yhr#`R1y<-2hBq_wilUyGae}z$2VSDQ^oM z*A5b`0=i^2J^w6ZA0A9$wPhMP4o92NrW#@({44KcU#O{XmXB($np*4-9-Qqo&5AcC z7s$8@>$_Q+E4j{HU0V}Zk6uy}|)d6>DkZ*X%9nvaVH;w&4vl;|P7f zxFuy%{M@>JOXh{(AmDI^@^+25INsq>aQGo=*|M>MleFX`uu|S3-RJ0Mfrt*v4-g#J zmwymi4YCdw{9*3^cv(!dI0b{73-b6sLft6~kvlfbUj;2feOBLH$u`B~H1L)3sh+!# zU>igi$_sl6;3E=nCfb?(#@!@0x5g{nUOn+p&Zc8QXTwSorr4wN$+9Vx!OUr$b~%X> zUfeqIugf_<_Nn;vNw(F^0w|b$dfC0Gvx?NBs5!VuzqsXslh}ye`y=Ci#d~#JbyA5s z%rawAUnOWP4+u#7b81CnEC}E728vC-3ruk-q}LS!5$lL@wBMeH|K;q9b+q?xP+>S^ z&HJ+XrE#xjMg#cEHQ@y_XS`YtnuCn@*FSpq5inKz&wN=*c^?qwFLndq&Lbtu#l8P% z-*hgwNTnO|4KHpT<|Uh%ehQK`JztD9ZiZ4I3`nS;!ePcNUv66@2+qH+3B;IOdH{noN|`A!Lw~#BJh8dKj9x^fn;@+N&0MI=ymv z7SufGAda^PjS+O;eQEG^?4RN$E6DT^Qhw zfzlM;VF(>+E)hg2H>z`6=N9Sbl*MNYB;2&>&@+ZJUn|%xXu#aK=TY`KN=*1{tgVrb zN!Tvx`^$>uBcp)SdG`$vqSY*7$bWFsy+Ohs6eCl3c;-C>cXKpRu&A925S-sF=-Nvt z-CjVOQ0`B*E(mB$i7Bo3Np=5Y0Yl}R=a@$?Zg&M{N_Vx9nP7FssCsVTVZ+JkbCjF6 zS1MpndcNPW7%|5{_k@(#d~?Ls`tkw z--B8LVz1xL3+Kg;O&sTgX z3w@|}@Dty2>V(fEH8E6V<(6xTj4}KG#~Z1gI*&Q!iQr(d$?7%la{#cusny3x&#k&r zs;&8;^%ey)XfHuJ2|o4m)`trk{6kcqRZMjb-5VU<11+Z|jZ4HD*TTatr61A{ay(y{ z;2EuXOXiRESBO|=RL8bw5-j*D@vwF8>;!v5!?MA!dk(3|St3rFyLJJKS`#K7KBr%BaQQKCK##|kDAr5eXzt=9EGE@<+i#UvHKd7DDQ-kt?zFyan63? zcX;3t?1K#QA@ffp08F8B3e1eU3MU=+gX2Aa1l54KGsUT^DJl}W&JN2aV=5(e2xRx> zF*stq;q~o(SH@pou@4I_nR{{17#i%S6iR$6_K3INX>iP9~dyDRmXDw-Dk} z^Vp$rH~|&m{)K86{OR|&Fi_u5S{G|k?Hg+8dtvWMrJX`(lX*vgnIqh0gM-z7(cqum z8wSlC z%~~Y-R%F)B%u`S6;a0T*Q@G}J^s9|9CK>~8swT2doU?K2(Ytq!f+B{7sl`k&wJ!R{ zXNA87GZ|1_s#0g>1aywda=c={vWH9Mg$N-We4Y@Wvi_N#HJ}C5g_t0vEmHpZYs2Ra zoGtsqs9@*5(fDz0Z0*Tos8Z!_WITMZwO|d5<1KLtvM*ugm-EFJf;@)Gp-%Vcq+0c{ z^MyN~RHWSN^5cLFUdSM^w;6*wWI#FQpljZI;PAtQAr%#|zMlXGreqO(%gJXh&=sZ5 zgv(mH!@%wGFf}p?diR5F%O>b0iV+`-thw~MsXr9yn9NqYNKe|Gwgi?m{T>h7S;uFS z`K>eX8TuVXjIJ zba|A;>|+qA93vLPd90iFz8tGUH-_`Ut0m__a(PU~Wx+VRL-}#wy3#LBFnfINZGSSQ z;i_+kghvHNmrXSE0)-cBwj`uf{eM3v-;zAmRwLNejnJnTf}E&1GUh}jk^}>RRr#d( zr&g`RJ<|#j8iZ+DOet-=$a+=*$ixe0;RXnC_7jTpT%KFL)q`~pciqOCmS8%BOQ!!? z|0GWaD997}_iO6Zrt zW&(b4fFB1wJ?*O)kb9DY7V{KKfiw{4vBsMI6Y15hxB32~x6qJ-!SLXbRi)Ed<^)ei# zakJM0@nJEM@)S_%@*r?yGpj>lh@^aby8lR*$Gg==lRCZCvMK8!xPV*0eNcI3KMeeT zdC@k}9a?^({nRYB!vin!v-w-*{yB%}N*j>SOX6rz5&0I`7LMZi}f9TY* ze=g%{f0TE&|9u*%er2y=Pvm4`qsFXjRR!uac(;?9OYU1LdzhfPSnk0m)Km_NDkY6P zxFdY%QVQy|dd9`X#*6T)_$PyKE(88&4>#C6b;i`f06dvP5nu}^qF;FK#sUG@(vZ%z z;ltcmks&nmRGN0b9P3=4v=7RaAlLHpfs{>_*EV-G!^zJ5V~TNAy}PG?p2C^G^5|$s zfz!1i+U;AlW&n^gx}Nkd$I{@n{>DbJp<~;(@nc#y|MF?oAG6Sc*DX2Sl9SHfzX5%8 zA|!Y)+w0whW z76?%XsMBAX12wc$O$W7)H`>YW_*M@{G^ggVf%DL7lvP7^NqU6){DV> z71k>JJoY~>slZ!U(iYA-JrY~|^Nk+8B4{;{y;z!^SByb=`e^ z5f5~&#C+%!OvqTZ6bC5_pn(C}7v=q%K|8?)4hwarA1i=VdYf0+4!UfiYjhbsjQaob zZsG_{IFGlqC{o8kJ-|6N8z?nOB4Dn7Mu}#3;5Bzcg`*8M!0{d(QQV~D_do(fEHIN* z=}LI*21;PXG3u$&>&==eCS~5>4U9ZQhT7_X7jLCimLc3eSY@8v4%=JDEbv)|#U!lj z6_jP5zc2Qt@_tS=EUnhe1AOQI$J%#CHMuqYk`N_Ws4Aj#1w^D+Xd=ye1Q7+5-V{Ye zx{CAw9uYxMy7Z$UdZ>!@lAx4B5wH=YB?5v<2}KAHNbWodSkC)?-(Bmj`v=Q&AbFm> zXV0EJd*(O4L13|cd%uasyp=Bm-3DI>d}D9`5XbUwNW5Wy6_{okNZtjxvley#eGSaZ zG*s;n`W%aUEFvxQ)_gUC8lwyUTwIT_k^%k#p<<>31!&WjvT)gb;hoRU)!&HBQNDF?u1kfs*}`X{ zHu+^_7j;14_-^YCOnlX|&u1lO*Z3>&JOt!QS&--1igklI2`r35L@-JI!o&rZC!o8xp&$ z@60uxj>vsvI9U6_;q!%c%K*!zz%jD#OV0K`xoypKimnvyJh;%Mg06l|U)crvrT9$Z z?P4b5d#Y{~eL64B!guN_S}hV9M+IwFs!XX_QKn~UhFh4ZgblaRlvMF zfIqy&MwED;{nMb^>;&tk^;yTo!9h}uf9Fne{_2>rJ1MRESmo2+V_;wzo*%~kMzeP+xS0V zpP8>m^C|}LU+hyl9#ATMjx;2U9+7)6Xfo#U3uP9Iz^QR-=zm}e$EfRvRa!c{yKhAP z6;Vh{e^dtmQCQYbF+h3?g>q~!8&#=Vj45u93-7Dieb#;hOoLFf=mR_6s25A{L|umM zE+j&7#alT%^Pg^-6;>5eys3=$29?=%9|5yq4t1ZVhj_xUHzpgTSwm}@CH(cQiv7kbM$gWG%1(_8O(l^lDP?mw6JwgWHc+-7D*b&apE zDD5V{-UUzL58rG_w3Nz6GNCtvcutfnH%Lfbiecf$qv`DO4TciruOtR85}G~wB|qTH z(Uq9#L6C(t?n{9RM0t@T8ZpR#Kl%ZQUkujP#v^^>ckJalZ7CaUsw9W`VTTb<+l;)ah)h zKA%>^A0BVG7qlCwz9eeQ`}bMai{B#S@C%rTS>L%_5|Ko4Hn36oVYGHAbqWK7e{yU*?Vb^=_VWT3YjP)GBH}_+oSSJ3q z%&0iK44!ddUU7q{_Ws^IAxs*y%+pep8&feg zPmFu7ZDk!&zgXPrWzLCU>R1a7r2x7NR2;y_2zOKORuZgbvEY`&Evco?%QHtTel&C1 z$Ew+M79ra{G>4EPFj%}t4+uW!SNC`=~-H5Ci6MMbsf$mQ?l5Z z!XkvU@W4yb1=^k49=z{UzJJe*+c$06I-h4*I@O|HZyB;tAV5KHU+}dV)9}P(^L>nx z)#C*nn_T|@^f}batU!H=%wC7)TKhY6Vs+q({;ltz9F_$>(3OdD>BA2o zSn7~G{0INr^j8RGi`=e5i0_dWQA!!$`G@wdNpEbS%m3u$-8`XYaR)S=q#)D4<~mfg zrAnjWVZ>_Ism@f%l4xv)XGAj}{rMUV5o6s4H~FL9>N3QoCZ>z$xlPIR{=n7O??#20 zJya(8i9std@>-Sd6K{bp917OWR|C$%p|*Nt%CTPP+bUE|`hy;^xKtWdtHf@peu zs;8HIp9huEH3WdHs8Q(ygeqotqKsoNLylg8suS$CdGkdMFj3&)*OSWNS~cCL45v*Z z^UPhUF7O0?(+P<5wBwnOOw}E3=LyC}=A-ykaB2KqXOn#%}M?)-3!bep9>K*f8?}gtsG=4hi75 zH{TPjT+ahXT6m(&%;PQ;+b30b>^`b58oOcTLvX)M3CmUjYi@q(Kv3E%yu8g!{|W=k@ol0$7I=1wayjF{e+Bwf+kb}Fpk4=WJI{Cgn6511alC<@ z2O4Q%iW!mRrBy9_R*0*hR#N1C`V)wT2`r4NUURnZQ+{TKS2z2Mu^ll+Q{q4D4-n=- z#DlffM}$!@AP+|E2g&m5qu-i)QoR8&<@6h^mN*$|SUu&;muYua`j;F2RBl+^;2nx+ zr%jmMgxQ~zZ6$wTJ3eYYZ^StZk96e=!zl=iYbR#!Y5-=jbvLjAj6IA{xy&P{S5vNZ zkI3ooz>_belB)+{nn9mFI)l#580U~Sy;nH$oWX0*0JPP5e*F*o^;qyHcM;#AeYyP# zjIrIiB8aPJ#ydWF(qr(NFuqdAXP+& z{RNo;vq{I?W(c`FO!Yjg5Q0^eh3xDvo7;loRR_R!0Q31H(n|Z+te0h&jj8q?qqkLe zPk#z3rPbXF>T2fyyW1RWH8NQ&aEjL z`ar1r+i`HngkSLtyuID=41#$3*VhAdj2(I_lEgzN)#XwI!!@f|xY^cY(q}o4uBC8Cm3|%@5^uc1d5Hrvaph&PAiI8+4w^o*=IB-}xwj(SStsnI;}l z2|^XX-dc-0H*WZCGr9g@HQAfjyQjfDmn6D7;h=dk!!C(*)ZbxYDe1z~#+b#zSAIOp zrOms%fCNbk-4Tbf*uewQyEvt-G-Kd4qN9zEqPNe2PT(uxvZBh%7M1(5mbf&Y?(#IGf_!WAb+I(~=es`(EcSaZs9xhQEG7FmZ{fYT?W=xu;s3ePbodH|9<4!FEksHwsgar0G7?CmCj9h-ojC8mC}LB+`PiLDOz zaaku$?B=UT;7tNIT}@d88=DCa~)en`1o6e`5a%(a0Nk6!GZ0P(uN;+PN-i931U;G4^?QpQ$ zAk@u^-cBh53A{B8Id1Y*i_8aQW5|TX$nVDmh)7$bL+sV!^PH?uL_hk2zo?Mxe)D5B zB_)2OHtNBmGd2es{z^I8SqK?7sM({M^^0U&J@b$sTm!%?vxbjo2ZZGyF`|g97r7NITm(J%}@-UG-7TlY{X@DEQeNWO(o|04GOFNYt7* zRU`%cvk;CL)!#Pxx=)mfpXzvXUxlT{b$U zFDMzBOuPdA(xH|UU-sx>D*Eoo>Y6$1S(wN5SDj-9`L@-q2%WCtdq&BlUuKlZx+Yh7 zF6vJRwI_AalvhFU55kFofY319D~n)4;YD9Oc70jm5N_3jOc~-IvlcR=S=xc9>*$Kv z!7teA+Z!Xfo2|MxBJ15KoIPWZAXH$ z7HRXI)(vTnykzL)il2;8uaC+OieG_2qCAiRfvc}<1`yEgwPmX0cXh$|h<`0&(Zh_Jzb*K#`GlK2l_KQ6u+waq4kZ_LA)Rz*0|PW5zR2i#t5g zwkOwa6Q0GZKUzx-!ud%DS+PvV_lkAIHzjPa$KD`P-j$kdDGTxTG~mbi#?nf`?_AIy z`Sw+t@|J_-rlN+TR+s#htAgvQ{;J;zq;hLoc7=i2IkPWUt@x65{`$50+mQg>-PP|$ zfSyb?sAS)W4;Bu?-1{CW;($xWtYp=y?=jbhi5&I*WD zy}!f+0sK0ECCgE_`Ilr*q1tS@FC#}%X?-zAAY`okXX;6&tP&GzH`-}Z;n$kQn#KBl zCT7~e(!&w(3lY8}=Ok%V3gy_aG7@LuadG8P7u=9@R9gO0R)P1&|4@{%Q8L% zF$7Czfer;YSXX(PuB|*n-YeE|)pys(<}zoIRVW9-Fq0w|tEvF(L(rnHl@-A*(Pm}x zmeM{y(SzXd=_r6e5tT%&ObLN@hjx^eKtNu7*=u(5!^={-;p&%&H~6&bNA_j`x1-ZO zn3H>p=hcQ_2HjRtp>m;Da$*XYKt38E5KInXKa-H--q(}I0T#a}Wh+#o#yjNsUOT!L zAz>bsmO8V~pptNQ?e*s&ZMXFv)hQh+IA=NkQbGRIg`FXcB7^Qq+req|4@u?@v0pvj zE?tliBY5)It$$@CSvz3aeJ_bztbdYujg2U19D9(N{>KP?9%id^A71F%GG2I=6mTwX z2M?4csoTwU%I59}NiEK0?+}2$SUaQwlakbWeF)?CzKU)z9qoiPa?E5B0^?YU_WyF` zLz6a0&0O5o{4Z&_j}yygtR2oftT;UD-LpAQcH&(#2g+dY*K{a-73}<_`az7m0IB8v zvrusXu+Z~B5j>>T(IBG@Fk0WOF4MHSFV6gi=xVt{A%~ zagsEr*F!m~CC9*TU|tYmP0}ASaa%2yA$?X7p0Yb-_t=qtXK}?a-5BhJd&UK+?bq1M zX)z`dYLtb@E0uT?y?iN~AzCfM#|POAmCCRBKqpMTc-&#;!J6GqzW{9pGCwoui-*kb zaQ9LV>7Qa)GW89LOzcC<@4~ncfrC1(c(Ae;M)3*fatbQH`SamV0SQ_Eu6(50-?wgR z4(~VHoBZZdYc79yTdcU{TA;&&K$Jtf?E5X>N&d&!{U?4Qa(k(CdmTS9IZ+AWY5Vzy zrg`RHIr}@IpFj|!a9A#B0{z1!oXl2|7bL2x`@-2SEEYQ|jhlG7P=2ev5! z;$teq7tt65iNTZ#YtBonwi@lvT>mxDHEx{Xq5FKaz%llcKH(bK`2mx9`VyPN4P?~`{b$e_z{z)k5NB)s zdgvq6B8VXEe}N;RZ^$F=%L{?ksNg4OFcqI;&2_(XEgtK$XJYw)duh0NkC-aZsp*@% zPcRiDKRGK*IP;IXZCAuH#_#<6^5n6|fDxn{;6sQfza#jNauyHYuVW?zkQJn$K`qDb zS%@0uUGOEW*C9NnhRf!bJha<{uz!lzweYlo+XEinFOOP=nk?JipJG6Khst0X1hFx^ z4MA)?vdsFp#pYl!)_tdIqDc<0NjSZp$N5zOq?CRfIvY1KWS&HBnpMYyu+JRAgpJ1? z%2eNhb|O_(reF3YcvSkn0vh}4-3W)o^Di><&|J731q#=;dJaM+C~DCxU|bk7A$Og1ObBA@Nv1Eic$NI&cy7$NMSmo>jj z)|O@D9deg~^+S@iUo?)fj>9F4#uC11c_mz2X%Q_GgFH{6Mev?5hZR6!c?_uu*^A>{ zWei=bJN>8j6U00BFcYdj%r z-b0w|n-%&&HWgLx2;X?3FidS&l9(}L?~GEcnY8byyZq>BKrHo({O19|6u_OMPQ*8M z@oriy&b!ZQKr%RgBR`BXd*bNYw;3lk&bVV|PqA6A#m+ZCH3QZK>6ida>FubB#WG5# z;@+NR+%&VsCV%nBO5o;J#c*+9!u3L+K&?=M%%iGW^P|Rb>4ntw%wrayDRm`BbhMMf zJDp&O2r)0K_t0+-lX6T~=TWCo1=k^(`_LBQtbC&`aw@X=eD&AA2#{|2&C8RGQt z3Y37YFu-G^N-d92Y#Rr%p=u$k-XK*g-Woq?Gd7(WA%96&ua;8h%Y_O=_AAWsKW!w_ zU?QF~qIPoM2d?N_PA~ZH?cJ8+PwB1n=1xq{8&0zX5BkYEVBh=ruy7@@OnL|3`zFnZ z2XNM{5nQ~yxP`oj*{1;D;B}Ow=eAeNUR{3)&8`#H(E6SMl2BHoHXl(LD1b}plGs89 z2WX1X)SFEBX!wEpki@ZGdLK^bAn&~(F^<}_+na9vbN>$^f-2_G{U;a0ralbc9h9Qh z-mc*=sO0k8RFifx^{|hi(79tiHeq+rbpvw`Z;Pq9tf=-pdxko!IW+Qt;WYEjDl6-9 z1c@;;MHB1lEcD1TJ6(9$_p6mm)NP!;cD`NeM7jPh^z~~=VM}MJ{im4g>mo#0r^lE1 zC*Ag8051I3UU3h_x!$cPic~kyZJ!Rk7k7Zr?RWN&$qv&4iX3o;W_LdSOgOV^6dP}z z=eo{e#irtyvZG(rjxo;?yjP2+j2gyb1%`MNU&6qKI|if;KXOamId<~5BkRZifM0wg zLwJa;HZl>>`Bv1fw~j|ii(2xX_6=e>G3Dtu_B_lW%P3*cbVil42(4Kh;KL1Mr=085cQF4m$Z8 zX5G~KT>T&H;)E`5GI6dFHgec@w6(l~?=;a!3WYl2yDY3IWwpdCY2m_BtsRBLfb|s1 zsgC2aR-eBSix)2Yn_TYNSw+RMbN%t4@cMV5rP3ydzCp^<^xvndYCpE)kXvfva9*hr z%6r8yyE3XevNd@ZW`uh(>YdfS=UmXq8{@#M=#+YZU{SaN>8$>9w}i^T@Ux$Dk=t7T z#5KC-#3L~w%Ln1g${*iIJ2ms*wzcSu`lm8e%C~N7(agj;YU4*VIfP%l6O*-d z>hO~osAL=v-IQQ5W#WkW3Z7uUGn|@( znoTD+bE5}OC~*9_Gf~-5H>86q%9zTJ7Fzh6ui)nt7CLqV=_FWFUZU?*iI0~WUaY;7 zy18HX(Q&DsPP%#3dvTJK*S@J{2RmtG>Bm=h-m@spqtb`4Ji5m7ddFjilD- z3$D#(=s-zYLjYo7KpcCKX1Q1Lj_;eOp^NDkokTiJ{(E77j8?aS+Wg7Jf?rXh=l&7l zxDeUO*_`5cY~bp?f|kE6f<) zw#8+%Q07}zY&VMM z8*zV=vI7&gURq96XPWwaKX>y3J^wQv|pEF2<-4zpQ7n?V_% z%a}D)A^EsB;{8V*WF`vwJZ_WZ#_YR?rY{#>diPkTFH*6~O7=!;+lx6l>l{rmx5D?yrhJslQ2ODTepaxlk-oRG-gn(GY7O4 z{eowE9%E4Nl<0D;CEGHJ>zu2y`BB7aW}ccb_KdPw>x=RENEfKS=0*jsz;z1_i%lPLb>w!x7zs_4hP>dUoU7|jOu|=J+az?IRDb}6kgZvp{9+6FJ$TWuKKa0YQc5q&A|C6uZ=9UKTNZ&sQ^}$lk$3ThZ4=%O-OUx*qz&s<#bdiPHk3w@}Dh6mw6R|`XLEG2|<&5m@9!Hm#<=H zGn)M4cggeY_(=b3pQ66!5vJNzg1i-H(=fyB^^e}I3(;a1+p;W%{#7JtH82USpBoX8nq~+>66v?=t=ScN{ZW@drG9+ zM~eLCJ45vAjVgZBVnbyLe&_jjz2)hwq$fF^$7S2Xw1TQ)#_Czxh^co*7$1ubsrWEq zSaZB6n(rS6*$H=~-jrqRzRWWdVtc-vt_xWLIh@-hFwowVwW7M)?=s%tb%JkJwe_f9 zXH~?Z6SO(G;_L@Pb%$nUtIMq|+z;tGC3aGPsQX5feyU#S#6yD&V0n0P$>?<7Phocj z;3Ki5tDN*(*;*Gc5D?od^Kjvx6#TWBVzKUy~iPW@&FhRKM7;9 z4-kZYqj}SqrF}V~6$_mH7iv|vo`xg7*P4s&HoUTKaj5FR7>y~q|8WJ@phaB2q%7*p z;D?e8i|D;ZM!7y?&YAlraFvF!uSiCQv5p1fbe$s2AsHE7`pidn8J1u>Q0Stn8EYbx zK|Ev{G8bP5bT}U>hZEr3K+OSrlC=n%zHcXbvV0-}r#BbKy{LUdwq;YE-mHV0Xjg^j z)UDd=g9OpVFJ($bfv%;FEwq;chPp)xbDHmN;V6p>N;0CQvx9$y5DTRsbi-WOTv572 zuEsz}o<5+j0(}LnJkOohbzxehWwG<-H|zz|sSHYPZ4S0);laQrE%cz&z63F`*~IXM zPq%SEpeA|N!fCNesP}UbMM6@m$(ugc*qeV$0u2sr+-=v=X`0)OxUQ5< z&YeP|x&UVfEhVo1cF2Cl-gu=|p(Pm{#5?Ks1blKna+5EU9S(%>TQm%OP^J}5@&uzn z@!L!b8px+;`BR=A;D4r&yLqE^1g+bnwTp4~-cf(9AlztAJy;zf74-G{t~yI{9u5fS zqq{XstN(JMrwucmu(mW~9q74?ED>Sn-PLYi&IAUP5>>{EXl>CI#TZ&A4lfTdNs5n= z?#&{K&uouODbYPX-rKpm%8oHHJ*$zq7Ntj!5B61?l?^zAjFkL^M=>SKHPH+cM!8D^ zOT6fzCWdVP!j-*+g=vZKLGGp45g(3Z1zc={H z7t8zR$J7qo@56b>=+#^8dO7+YRDt;@Blf7q24-$`M0zeA4ED2pE-Oj}}jSDnJ%XhUQS z^lFPk->v6jqHhQB(f|llDGL>TFbkOCkw0;kY!>|6m?r!BUXKE&S=u{>o1v~i*AM{5 z5O4tN!9Q&Yq6f^Ah<~5_wt$zj^ToOIt$)dQI(HFNbTMHLGqvx5NR33JbNgNccFYSd z^2iow@nGS0!x-&f=&(9rAYlh)$5Wx+YXKoh)z~i0KwMVDiY>jCjGQT>1f(WFCe*`R6b~otnHY3F(~c+5^epw?bMG8G$NNUN z1%KOXr>j<}BlL-+Y4V5Rz9uz^TRJ7icW?499qre2Khg1dkprC@j$6&xj6TkZj>Xw< zq6a7Y6Xy>YP4)_*i9^FTwm@CBuF6XeXfM!28%O|Sd1)T%5h1V`O>(Y^6qs;91QE~4TP z*ebjcXU?HmtUo$aNU}!*)0&q1M&fjnqfsbMUo&fzhO9{eVhO|8s4WHysu>$F4xbeK z&Ma^_G9eg$vumGv7l3m-Z5|KwNmCNt8>F^_z<#r6%sUq}wp4pxP7Yjcc$u^s*&EP0 zh2`0%F>$ag*||V}KEHPwhE=q7EnT&70(y zOZZUV1HL!p4ftm_SGVT=pyGsKTsEu^;iczl40c4%dWoaM&-X1eE0S|~6{U7Rc zT1?+69Yb-$-RQYS%zzwqKu?6~kGSxIj;aloB;LR<2 z(RG&ra9z1mAF3i#L;*w9Xa`_8L|=5Y`};z)p`21pXh#5kwioL^6^L`)KVL@!7XTdy z-NfKmzwEjZgtzU(g)>Zu;#4bqKLCep-(@dFbr(;?=f|x(KG(I%!@qy-aNKeH^S_f zM7{?a8D!CSOB%Z-y1s&=(4_JfIV!b7bZbf;X>PpSNMb=_{(gjBNM8MDzh56cb5!#k z#?}pPJGd+Y>7Sw3u+NjE;oeH@5B)>HsGQ5jNy{>K5!f8T$U=e_Ud$@T{bjm0aqsTp z4n*j1O>54-iO7hq5ZRrDpi|T4>@yGyAWMX-b5KYS8(ZVP5iznZR=oK+g=prudlo+q z2mv8+Iiegf&xR z%nnp_Nv`$LI_AEFa2C`svNWFX;1I#tchpjbch+hm+FH-^?a4%=zF{??=4#TD6!W?O zq=DD~-JoirHkz*GvqQ2f$esJDWuq%%XdTsEW*ypn>1-K6+h;1a&LdT7tpRsXDt~Ld4hi9R| z!0&l9oBzZ#kUc}GrR}mhl$TemnVcwSHQB8(?vVuw-?mU-XmsNlA9pIU0}?2%XU8fv zn4U%sl1mnP?VCO7$Fynt9y8_zC@F6-81E*A&pu4jqd;8G!pDJ=R?@GvWR+e;srP*{ zLV)&lA;X6rnPEoOlWh=b!oxIz@1{%4=x<-h8KDf1#yxWz7icY&w~-%{4zyX|Ds6Qf zu7HpQb4f106<-5OeAxZvonR=KGSq!dRed&4y$;vW=-gd2|im6`=tWhI44zV zezcV&HWt@Y(!Od$NBg{0&-6)|&u1m&Uoz$4gb9m3z3XITmP3S^^7&qE=DI`t_AYn^ ziOMn#r$8@wzFP{V|gcJI#|e#Cl)+cQ1^TSS2Rvdp7%{Yd1T}T=XfFWwEQmgIDu53LJbsf+~(MiwJ(j< zHYc8*^mSv!I-@k%uJUT?PL}zA$~e~CC0O#ita`i| zxqXl8s^1yPcV&9dNl*&wj)ZS2!~e=3aCg^=`)@;bEwr58PNqC>^3V@~;5p6tBfa&8 zqpq`>)J)y|qRw{%J1X9Wr@VSUKX&fb{CWPXn*|mMr+bo!Joy~UQbcSa>u`)JcnFy$ zeVXs`(t~+B@l-kYXmuk-Roc3d`X%h_3CZxN{;lCrs>j2lZj`{C z0nmSik7$3+DK1H$76#prwyiwfr>b@w`9VIuG}braV*-P(l8C#$T?cd7no?-2>r2%SWiIr)>Kwpo`lH z87k!4l25Mr`^Q^d`Is^;3%_&yDqc}ks>NXxax2lauqiY9bwMbWC=qdD&@)g>VuJQB zy~f_J8Ry$Bn5W;MV}7u~qR+l|JF42uBrG+NU1tb5#^FgP5CL?)fz3-594dUvT-Bv{ zF1_&%^K<|CM%N_dZOrk#S_>Qxz*%=OzPI#|&jo*Mz{teO|oa#2z2 ze_m&YBct2Kc8~3zf7G(g=`3rZL;43)LxK?|QOhHT)XS{nx%r0*ufGo;+ui7U*#Es? zt$y>b1IrS#i7I5cHH=C>G|x68%bJ$wZ362c5Fo;Cq*in6UEwK%*9?|g$i>id!8lif9l44KLc|F}T! zxNX?M$%NfjiQuiNg@vxy8Sgw7t}lbCG8>Vd6EvRt=p9tkSn1?*IVlhJG1T(^GPF~B zA0@`au0aYr#(wl@5l`DVWoQK8J+ zXC&*NV(@)_tg9l-OZ_tRCX}*ICO#Cl6pf*U!TAO5U_Gm23Uhg|s=?Tev}PpP^VUuu zvOM~bkMad&4^5GznO+Qyj_g0Xyed%zzi#E5dMnw(qjVDcmk60zj{_`&sk(CKrdzkQD z#=|}0q`dV{;%7FSCz6zkxom36JH{kC;=1VFM}%VC0KKevzBkWXlE3j!vH0y()OX$^ zJNJ}^T%u+cLWCYmZ>lBfhfsXKX5lsWNUCB%R5tuP74&oFMt$D+nT4rak1-sqxdO)L zIukCGOzb>qfh_(MaK3=~MHKRv2^{A|vY5r!t>n;j#CNC|Oo}pe8z(yC_gXvA%y=r? zh9zYr9XINh-|i@N}&a?Q%&2=Pcpw|x>3xtad^k?{O&fGzs5rTnFqa_?%?Fi$@71|58hrG7x7qYjL7OikUE8`0=n2UWjm)wq8v zsXZI2D-h4evqOhqHe$Zg-S#g2&sM#V81`z!4U{aht$|=NJj-kB6rxkVND%u)^*=L6 zfZqWv05I8JFult=+Ew2|*EC@Nflp610H)mmzNlZFfOC!fio~Z4SNk~X*b1a7QLwXZ z!J2zAFevw~1BB~5<9E16QzH)$to$nRO4Y9xFI~mP>vj9t;$?VT_Q8MN#Q(YYn04I( zu)ru24u`z8L~}b;5b-4bbBSBj&~;`jCT~gD9Luv2CRm*DDL%zJ;)X;zX1x}&ExZw| zXO^&r$96<+t@w=A+N|^(-|){qr$K^wwfz(%+?jb#oj(8Qx42+7N?#`x4K6 zH@ue%z4v&3>9%W?vDTxSqEj9GhL?F=VkpYjRElz|8~{OeU2;55ue)`p_gHLSnX$^o zZC{C!`S%Zmcb5fBDULhvgC`l^gtu7eiM|a%(x#lvSXgv8j|)5Eg7EO6rFD-qUtafC zJWt#7NyO>ehP`;=Gi$XpFw2XU#E)UAH2(g4g8Ok@86Cn)%Iz3&N8H1_EHTrxsY2p8SE-WMu%AI3K32*kaTOxshY z6Vty(7+VsqL~9qiS9@)xw|fN_`H85Zo7gNpuGi^x4a5t)+>6G#;d1117q3)YIx=Z# zBxXZHSfDPf7{~#>r3J6Xty^r2xnEf#Va)jT_ll*^e&0(BGLG#+dm&C|+f+PqU`yGH zr$PTsq&;YwD+HzVSYl*G%30x8!YyokPzACZGBA^FG}BpzVV^R@n*;JTpw3keh7lhR zbgqJ;rO_{?NJo<|bH(dW{~O$;4g4!+_{9sm9ECyr2NvGNZ`a5rX{}1Fe=1(Pepnc> z?1`hex9O;_<+CO+^A*uANdjJaYU9ysWz=|tM*Kh9cTbJ?-qK+nbj&)J-9>2qEW2&; zU)vsSKBaa%**e78v)a z6BqsC=^?^Vb{(d#8!O!U+I!@UXly6Fu&>hLxY$eCei!Hpj9ex`ADKA_ ze^B)bl2ulLz7Y0x%;EmR^65c|hw^tj^^{$E{XW-z1HY?GvA(<{DMwklYNWIMU2TX} zS)Qs)o88(nsrs)a3`a)qp7gB#Q>$Gh(~7vl^w#4c4VLY;&pqm>QKfQ^s`E=;niV~| zE3<}Z?ZN0V?1NxyzBy*!=V&9t%MKqs{pWIJCZ-RFderxdaeH^X`rFW3SiWJ8xXr=J zOsYt9WDwpJkhZn1jeVrm!I?7XJvxNjUK6UH$<7wT?8oQlkm7w{uELbl*OPST{;5^T zjE0D=U)_Z0$M~8ztb7L3NDV)wbq~ug>Lgrw1bQFwQxSi$534a@Hbe|6aYQ;82MAZG z@i3kUXN?hq+^$wU9cDU-Psgfh<3a8Bu^} zE*hnGnHr=#!|`CSVSAKq{}FqUkk`n%XMMwxU(w2k@8ZDtdTZoLZ-nSxCw%>I@(ID*m(6kV>OR<4U)yA`{;A3@O;o! z4dtKPi#eL5Wdm;ty4{40Q(r};+(V->IH5iH$Mjd$o3+jvQ$O1~ew*g-oIMu@rE+D% zBB^}V5gh`=RTWL_ac*zbXLQv@Jd5?zK1FyOM_}-s{r7s6f{rzMR+>3>zPNPH@1G*# zs@~mu9R1p#>|BrDPw>osh^Q?bc9PJTPr0H1d$0 zJ}62xlHx_V*DC~jmF^gk&6t@!*=Lh`mIl0HtTUF#(VR1E95^o76`gdTKSeRVwjsEq zikB{d^_vouPKUVC>parAix)UK>O*j9s!g^Qqw_fZ6VK&%vJbsFdW6tPg^a96(q-}g z;WvoWZNCB(pZY-pcABoXixsp6CLv|~AUDF%{REtQlT9uUR~B?ePlZU*W|TWgox?|g z{_Y4^0g6XskCAR0@=@0<8<7I2YGy^q^W;+)(eQ<}OQ28=-9R2MD!m*j$t_K3zT3o?AHQto;NX<;U6tnIiysDU%XT zm#3aw>tipBI;VcPqA7(uFt!kD1@|^0C2rTHyeAuaws09s!dq*^Tpv>TNqO09%@%g<&5lp!tID4654|E z{*Qz5x1DUW6FtZ`ktK(@vRbhG4<{DBi9GpirYcc6BYI!xs9Sv(*K0?o0&&SIkIZD{ z?;J97I}m@)hKe;=g%iXkUB@KtWpM}OQdLlKY)-A#JsqT}_|XvjBDM1!C1El(PqP?5 z{bFjK;n`WbNY$MCPWLw0Tgj;U)8C0UiWkPCW?#s8XD;-xb6fp0=*XxbQ7T=$6q3crBs5?GnEle%)xD@d!2A{VSBX6xxp%%O!@z)p? zDlVl^x}RzNlS?gsozMVWEksoJ=QgWGdcFMTo4cvU=H9j+Nw=7)Jo0$q6KU)|pBA-5 zg%Z0f2YX}sgK_4;bn`!)@3F84VX$vuC?gwYb@M6Gl-aANFT3XJZ5x?XLGh!Vf(Yg5 zN^B9N09@W`tm;3?7@x`On0>OLxJ0#|uKtUedZI1HQeBxHC>q{fL`{}d~^0N_{- z|Neu7NNeIKnx2r(gCS-M&A>sv2CI72C6>LadsxyJpMJSj8rz|{@EM{-k~G9UJFr_$ ztYbY~{D?A{Ns)ZKE*3Y4E5<*ZwKjG@x1%k%=x6Kx^st2N0rbydOTGG!w_(|SfEz*v zYPWpFFnad#v(|(qRKZUl_xL_^u3CMea29Yq`4zyG@4bP^^GYeAW>P2o%mnkkxcS`k8w_~j{rVeW2 z|LY2FFm`%fZqYX*Nh;VNn0Y>%`7w)doJYmGWbmycJewaGiCmoxG8CC5zcLb;>Znie z_5+a6b<$|d5(r53@et{+aq5H-KJM*`h061P-CZdt$?CjPY~Pj2=tjAGEnYaT?onUn z-&HV6VP8;ZEKJc-Y28Cbi@V#&zM+C?q_@rtxq(vW5r3*Vb{FU;H^3*V9oBtH(mVzRrw%Clx>R>bbl+t2k)V|xaMHZS0tw_Q>-p2p=EWFw7L zHQ{i<;ne+_oYmPh)y2td-!`CJ`usdso~PKzefw}>9k*T+?i1p1&(v-Yz>AVf&R+zO$E#^f)`>i2 z{;Ma=rf!MQDDP6|vZJM-tjFkiks#&@2Z}VGNCj|iHQ%f{O@<@qfe=@V82U`Id3lPj zCq(M63loZPc^hb}M6vC6`7Oo4voc2IMO$6as6L6dmYY#hJHA@W-1(jfFCEktii9TL z^lQevZOWEb)g@=uR=K7afCXsTg&rK|LjMxIaoNwKSopO;9Gc4Y&G4eO^wv((-Nxh0 zRTRgxsKRa}4s;7JwwQ-H)4Y5*X|an^jq zqv3;+v9rD)HC_ZA&L}O|fax+b&%Z8*m;aO}gBGEcY`MN^D>yA`iLa{pj&=J?CIk2_ zB6YJ40N1)((}A69$ST2Y>WI8ZLP@N3lw%+32nN()gx08$3@`akK1CF76o(cHBZ>PJ z7wO~|XmONn2_lV$VoTy3Z9LxQ1UQeI2ZVZBYgeK~&Z)&82Efb0fsf*P46EbO7^fqI6H$#BjxMSnx$({#`GdGxtL8reEhyTkIO_VCrZGd zU(qLcHZkPK*|qdcrjluNGwG6ThDv)J;hZ(SHEt5<!K!w_t*O*Z zsJXIgXsmpVeuKAOe%{(y2k2*;c>5ynSKXG6@j_CAGp3;trM}1c5ilJDu*nJnh`WG5 z_=+!B%b{bl)d1E(qqu1|f?i3?NMbDhxLM(7Qp`TmyV;NCJl^G(r`{h|wD__0$k;!M z7#~tp>&81ca|3m5bPGHBI?CNLn^RQ*yOD^oIPY7zJzloDst7nlbqKQsLss~i_;V2c z5r&SYGX+NQK@m*$it5GdUo(=tx_b_8JkDGdSncCm(OjdUt~ms(yTOg#zhqi()ZUj- z+ZM(Iytt{xuf`-8*($U0P9J7$f; zc0((HE(%;xu!`W%873S*40>fzN#{m)z#=VoN|%yZ+1bGzt* z#FZna{6Ed%@4ACU5$Ou$ThQ%+HjdL5$gO|xC(JhnSaMO^PQ-gx+9h_=^{Tqk#ch0O zwhG;nl2ug;`z*aQ#)fSDD|Sj!d%K4QXGHO>=i{agFQ8C)s-rKXXXmMSw?#mYGD|p0 z=yzPsR7%<4Dx^hR-5}a9*sH!9yYYncMs$ileeM9g`;n3{hf{wMtKlGAUI<}kVcPzrm-7{gPV=pYs#_yg<`7u*p2t) ziFt{qMghYMkr^;3*uaTtDTftR#V(ip~b=Y|J zjCiXHzLi4Qne9Znx#ZV^4KwQd1R+lQ(HJs)aBo(*jLFtGUP?X8Gjck2F|KJxl7X&1 zY?!e^Ynqi-Mh<6n$!|+d*cqN_WIjNq05!tq;p1GG)r?2=&jkJtbMGD(LYDQz4XcDoUl0!a+sSy_rPmCX`Co(deejbe)-+ zn!f9KW}@Sq^Z9*Wzu!N<*UJktv!DI!z4lsb@4YVXg_CG=#wxV8N$8fk zgp%}{*1a~5+!pZqsE}X9Rs{pp>~gDmb76O?C!Ktnsl`^bVs+KL>MOY6J7#S4ZcfLN z%6`ggR%=lrb``~HXW^O|Us=?LYqXEm00I^DC+jFK$={K7EKM5(dTPgt1Ky#ztOl0OYQjbrRUT*Vt z*+Fwb8`1kFxi_szpa|XdTGlvnv>mc?0&gz zc_>}WcHX38DWyY5Z7C-FOZK@{6bx436p0iPKr$UZa76jBc_7J>Y9q>y_r&#K`82Sl z8@PsZ*Yobnh0>$VrP6U@1|O7GuffHKsbCVpb_8aH&=s)knZ0vwJr!5G9DMgeqA{~= zY`9e#%+byUb?f4yz*sg|KM&G`aAtjOhDa`=f^Pb^-9{CjydpMOyYD#Urm&rkmcMLOM8}d5PO2un(Lb1b`L!HGOKPK=n~SgHV7c!Yaom{k zY+B zQd$G1@{rOCFRZlwtjMyt`%fwfQMXY@GyP_Q6zmS#tiOl zRO5iok1lFhkZxZ&{Ak?2Fa(}^w8Uf9VKK~B=e8)FiKMQFedKA5-pI9RopoR(h^MJ@ z_bH2hu~(b>90G$JVU2*HT*ih@;Id6yN2#2xZP{861?$E`$Q_=QOD5my%v(9_iG}O2 z-q&`{e74no0nVPmjM+QrjKVks3`seToKBEFGY@!86~TN+HqCN{S^KEs^RTT<&w&Oh zh_!9^g~R`a35C3;(GkT(UWKJo-$=^Qtf_UU4Jr5P-^4sgEBq}Pjkk5^`WH(K_6-!cPS*$! zh0@t=I0@;Nt!-Xj3}ZrqB$XaI*c2$g@m2Wr^!Ag@Lrs_G&auc5Kp+3Pcl@Flglz}o zoHZ3YE+;B<oI(1%>xmqm=s(0HwNZBpMx+!ddpDzr^IeezwSha#eBa- z99q(;%dwNa01Aga?fG30jw32 zc@&!rnJ(m?P$0BEqbe4@>eJF=+!S)*!|kNV`>k)zz2IP=Pf^;YNT$ok3$uOr>oA+V zod?*pC)!&1bhacvd2i(_+`T$Yrq>-N$^(A{fe?LvuVP5Ka zNnOeUY*8a{H!km$@_SnCSkAI~P7n$5=Y)q#0FRYQ|7xjlJn6*0h?Wv<7BZF&*_W4 zs$yu)xYWs!yF*h!1mN<0^?Wb@-Z>Qu%K8 znU-#?9+CYcB$8~TaHcdXU&L2lR&B-B1tLAIRep*pE&Fg?#6PWAibF!ua^+x;`~*>d=j zdch`7TZ1yj$o5m0NSkGBCf)9a5V7tBc(0xjr?GXr`H1J20(t*&hq%kzC*uEdWo^4o z<{m2s9oh@fc9s?T$X^u+P4nJnNuoqO=q;QiX3 zIilDw1t_Z~b-LZxfw|SN^l`{Y%5u6i6>tgyEidMqtft=D%$G9ReJe4!_O&%Qoc_qu zrm7)zBGDh$rp<-zunO=~7X-Q%knu#2+Ri$$Srh<T8`&vadzHTe#c-a~>JL2*R1Y2%=dZ2@1`St2fY766< zvM>LZ#zw~iCkL=(Qr8CAED)$$Z6hKfF&0bOg1EY)_JW#}2+cum+9xa_zBfuvt>JSl zX?~iJ%Tj#9m@hh^Dj_8~CpfV<2CTvLfvr!$<-lWe@S*8o>P{}n%;_kcX&v}#vc=uyUZQP7;H^ItmhQfgnfZD@P85GP}^oczfZ_4q!%1V?v( zxKMD!oZhS!wh}{gz4pf+g~)Q*SC{^7<%rb z2Y&W|W;vUce3dIcWDj|f&WXjhmRRfbUCMjl&QFw;jtL^Oa%aW7#IF|$?Pd(KC0`7T zmU9$Fa<{$@PQF5-VT-Q#ARE>N`&;3WF@AH1k!TzMev@oa?$MJMNf(g%PG=mZ;g;iZ zXNk)>pZHu|$`RMNR}AYsZJYWbm!$pLD|D_wXcSV17nbr-RBsXzV}qP9ua%*7au6X$ zG@kOf%_KzNP9e3CyDm7u#Rd`Eyvwg6s;j0fAbw4)c?PEA?@Jyj2m$x8i%su9DroNY zMVu1XG#4ubt|!Vh+qD`zbfDO;u82Nntr+Z?)1UWyS#^X%@LumD6DR;iMB?VZ=i$Eg zC^$ZJz`;dpAb&bJt5@*gB5y;8t`qDNJ-PlBL|aHG@1$h@sSv$)Pf2P`rz_Zn+-{#d z8IO}VRQ@e!D9K|fZ`nW`4wZaG1H%!%=LIexS{MMaq4F&fFNQ9dIbvdv>s_GD6qW+R zd0|ZR#ATraV!=?;4dso0eX`^l3@=ucO~Y3+VJROrBM zKOveQ1UFWW33H)R4k4uHRf**H*W%^Fqw5BZmHd}N;6@Xw(RC)4}*^VJyZHI?N8w30F^p2zG8DVsGdPho)pruz1??3(Ga5Pc}@G*&uZ%4av(K)R&;x<*abP& zEw1rE(WU4Zg7F>dSnM_E)xE_<>epNVq*r95u$(_e3fUn)M>;Ed{XR0I+OYeu>|S9) zQzl5V3=)|h?75|<>*vy3R7hAes-JA3`sRWjahvvMc%a3;*&e>ynhU}74^Sriu`VX^ z_5Ahx$vP_&VX=7044=eW-f)=*HqpjECVrXkoQUe)3B0TX?>SpidY^Uz*Nptv{$V71-&CKO{I688sSO;*!_t18^PPMD3#6Uzo zcj3Cj%P$mr)6sb&5{IOP+}8$UM!lMX60fZtPZhbGLC6{=pKm{M=t;gRM4n?~W!md0A+{EQ-LE&;< zuf3N!>q{O&7rE8zA(6y&kMLmic5V5NG*92U5ALo?(@l4>z;S~KbdY0e?SuFM*4xDC zve?JF3mlwTv`=<_EQCKFFbCL1m`)JEY1uU4i})s)N2j6F0b~w_`!q zZ!)h3zVrzHYkU*R=N=MW#Yf7jw_i_Gx#pp1dz=^)HF?gJE%$ipsl4x%n1+!?y=TEk zllNcFaaunmoV(vs!-(48deZj56q<}eoI(3tgz^XX+yO_d+6{Kfl^67SxOlIwD2M#P z4{#M(XV(?1W^DG`Q@*fvAA}o0GNe5~G#qfASNVr*)^sCa2pqY~l`C#%4TJd(9loJ7 z3&w#b`!8RGX>qIDBk4CErO@-CkkTD%XYcO{dLw_?`pP@w1!Ncd;p6rdRj!p5oFKC=${{y*&CW_ zaK?)G9>(WZYs+_O4PS|p(>dlx&w8IL-9fkZJ6!ImO*m8j@)a$-aOlCk@e*(Slx>(I zxkQzDg2Fv{Rzvh@?QL1jer^WGn7!H6F|3?OB{3+z%A*SLqM7n`oW;r}KR0>eP+)TN z-SwL;0=iP@YsLsRZl(Mv4=J1;h!K)LHcGpb55mt*%>RA{YFL|j#8 zzoxB)4EVItD9!BZmDo<3)CG*sa(sc8gm|g-Kf0t2Leax69piC^aoWDeZ}XZNu8Bh# zK5Hhok_~Pz76B0##-7!O^97=?b`jAd*84434pVR7#(A1fLgb5u*i;SVnRd^Zy1fHB zgr;QqtmeV-D2Ymyz0LNMPg$!{GFOqkO1ciF=6?U{?_aku)}*YlEZN~3%*tE2Z%yI1-KT1GA_}YNIDuox-R))kyUuQ7gb*TAlO=V z>sA(6<)xHvT4VFo$M26cBI9+)!Pvg7!9nV9G5kH5q)InrH7|_+*#MC1M2ZHGys)xoy@=^9ry4=#;J*1T zh!w;r%no}-2GRXrfXxTxir-wYwH(W>FG$sScU1Zjgy#YYA=xckANXW5$wq*B;_O$& z6vjQj<#26x%*h--`{zK0s8y}_EeM~>lK;Xxvj4Oq=n&WsO(AV!E~KMCP9W3#$KZ4k zzb|ZDKo#CHSz3L&9B5OI&n}M*y3_Ju&*!kDU6eoSB;qGL0O>9DMJKg3i?$w8AyoJt zrmS+Kh5IHzxbLw+vj~NNyA@nb_f0q50ay7QRo!Ldw4>HhQBr+}ja)&%6tOrvw~Jm#`c*CI#vUW|p6fLGQTpu zkV z>1$<4tS^fc4ax#+o-R9(-RZjqt1#(nP&SjWGD8{{ZLolQI5$j?4;tK6auR8aYm!7k zJ*Bx-5^dtDpJdW1T&wI+oPZg6{*AahbpL<#3Aq7oK(Qpa2WU-~9Vpgg(RPY=Ujnpexf&=w#wA9+#M9(;ENq0OJ z7rIyX)-h8>tc8Vk3Zk%VQp=8c=iu3ZG)94?z^>eL zz1R$m<#r_2p;&b}+-SC$dCpS4ci|8tO^vC;g^oi#)VNPL%(|}yj*}KbE{wPx zl!b(7p>_Gvv<2Ylbp!aY)S76pYV}n(iiDdwv6uDE<)xD$EWq6ij=U(bjyxj5h0dbl z!#{o~yfB?7*<^4ykB+ioshe@mxJFm%?d+*D#!c%`8&Ii)cDmX6VyQ_8$SDWQ&wUl-8AD=TY2^Mwj2)~A(vd?!4jgTy}T%418j$D26B9V!Jxb1 z8ce@X9|gF7)7s3de??Jk1~gvs2SSF`r(KY;_yQlO-Us&lS65HRRE)GuUIf^bE$T-s zo|jnvwXDr8MaX5|E&h2$ct`d1mCxqJ?A!Z!!2T_BW(8){gA##WZQP>)}svxuh` z&h@Ow3?uY%fiG>cn9)o6@`m@(e=p;rBYqfJ!d=M2Y0UcFr^GP&x6SuZOitYrc8R)e zhwJ+dpmUNcH?5(rN0Rvuj$9+D_vH;;?mg&~+jBYUm{XyAN0$!U+@QI$L_F2F>?@CK z5Fqg^ZK{6kH&W(lbab1*!g*m%lfi(--$KS_@%9J3XCSaH`|GbC6*}j@Ul_sc94^1) zvFZUo#8B5s2zx#Rjd~-wCq`p zsEi`h2Ui%e3t~+a%_5fdE-)IKAZ-*@if+#J6{em~i+*`#?`O$}EW&x6pFoJ{$m2Hx zhE}0)Pa|X>P!dr_nBfh|xGn`mqsZDx2NeZ$M~q*ElbE31m|Mgw97{i}EWi@i*XYz8 zyd|#PW>A75(oUC3;H@rv{^Qw6i9yvckIDdQhbYHzqZr@h_3y`@;&9fC_q3yX%VL9S zYQyX|^*P^I0_Z~})magsdD){S>kQ7b=_?V{aR|{U(bn~fpR4^n_8Fwv zXz@T!CHQ9J(lKwEytY=z=`ybVXZjwx`J%9FVLiEj>SZmN70O2IU8nf6Lpq19ejQ*v zvH>15z;XfOCdFC-nKR|13^iJNYA8xys$n0TC6F`!QCu@IdM*M%2EPAF*;H`=lFk8l zm2^yTGON|1Lm>Bm0#Y0Lh;2buEaJgM?`G^yTsrKyO;D)6N5$BOJKwK!8OA{NX0C6c7h>gAaBCW-Mkm%SHWa|DS^w5(XTdt%H+qU2fbzFIOl znK!*zHmfSySZiCdhfYP;hr*9&#@j2Re^-QFL4mRVK7$3=Jx3&XG3PR<*tlpOQF8zE zylUPt7AR6M2_)3QyQVdfphjLtH$Tt7Pl{XY!X`$4cN_#h+Am7 z7Zs2Y0NAxYzqW=*h?EtW;Dmba$IA8G5~C?%m})=v7r?Wbe5>gH=MvZ_5TA9d&`q3T|eg(3ba`daZ`PMS&;JJ|uDijlCM!>I77d zmIn3&S1y1O+>*9YXv-PyoehX*v>7|#!JJZE4Ews9^a?cSVUdHxm99D%QLjEl0*S6= zwn7RzWNe^1YSut)`qd98i*4OI*u|3+1pcvL)Nncq(j!obZGBYg>X16b94uxUmpN`d zg9en%bmLNa8Qjwa9&dAwY7Av45Gk>3M+TM5V!KKDO>e+tV;(VE@s49{L4pl{=@7 z7-^<%v`ROc5NpF0V!_n4gYs-Dku$0{L_6x*?LsJBNqkrJtv4UqD z0DguZ&(7KZKK9#k-V6kV^&8ST2bG zhH$^VNGf?~C%s~*a4qdJLWXsc7&rBkBSns~0WGinpFmc%9At>pJzLhW)^%Fw0BRrw zEAkrmEw>dXlFlP*bpZSUh^hq($%(dYfL{GmE&XkV`xAiEtG-=GQvKQs5em#LG*f)` z@FaPBjp)P54cr8!Ct+nDhW0JMFF(;1i%2NC^grI3RAw4EeqU!pJwIMe4+7rVA!*U) zlE5e6F)vW|c8LeP*u`pKD{|!a7r%Xzy%=}sdao?m^-XLK7H&0DH=hD9gg$J6V}Rqz zf04wql7q{uHWr~AiUNFhNFHX8K_b=o(0>*o;BhUoRtVRYnet5KC^GBza(dhJ1vJ*M z208w?;hw%OdcB_wz?jGc5SSC5OQFovZuk(CHM~%KA&FmT(^W8UZvg(t1t1FKD*Z~> z#Zbs%9{Xqj8JI8P+TFu(c(;u=oB$Xp-T5t}Aae=1m^@W#cDaOFPc{4D&4{V2N4**N zM?eeUh?#%Tx6da_uI4S9#( zBf{v3b{lIRG6c>eEa?P08Fv{10ee$K9f|U_2_sXF+#;<+dzF?Cm8F-7b;PyW46k z2Z^%ov8x-8{psBQo7q0dT2L5W$l9=Wnb$uY6Q886Bf59#48j!xnFfx&A07<1i>P0O z>%o8}0COeRqqO6u8hDDho*nQgeUd`u8=1hzFxy>}=2!q`-BeVR%0sQ={BY*k=>Ef* z2R@9S1mijo0Q?I9c>@xmgB}&Shd2n)hTY*O{1yU*W>BhD96}sxJl+AX?34+YbM%Ny z`_;Ze0)3ESoyg=Ao2&qUbSKXDii1#rL&4;?q#_bB2i~e{f@2T$cDb}1m`Fmodm@rM zTY!Sw@QYV*=gd256GLYa5J1_uExF@H{Et6cE~V8hI8(YrNR$0%;_m=?cPMfU=hhI- z7LdLBScmJm;bG*VZexTs5RWjA4-L5LgXaTC-n#P8)sP6(1$ofmHgT}|vCg&e4fqv{ zKu8cDlL?=SS0ata10uAqUu9jD0cA!mK( z6CgLwtvoZheGO%)G_f$2OzWnTL*TJAA^%*MD3M; z%?WV{$Ss370d}Z|JHefBXabOyB9Bib>59UyT=!}BabvV&VE``M_V{!&IHg|#Fi~&P zCn-a4&eTZV3xPelTRRDHWD5GqxkkaLK~y$GUMtapYU|*|&(;1O4o38oF&w<*E|3|n zLrV)CumJ+G3eHZNBvY*ak%g2cNK)kS`*A(tA9nxrxCU!Qsv+Vw4>lje4cx|n1ZaUa zmMe~ej)AZ|F}6P>=L(+?e;vRP4S)|Hu>OE%J>k5?5cDly8-|P$Kydf&M&6tZ;9LOD z#d#E;mcX3wA2EREN8`*4X-m>gG<_PET0cZeHw~tM=5)4yPCA>rpM@ke=G{%J)v{14a5GN^C9k(X_DZ{o=tCD zE_z5rXv3YdX8JG5C8w13viCk284(K3L>pxhKj4^opDICv|x_@ z-_Qx&fd7k5d~Nc{-!34_l+1lYwQnN^Hr5&XBa904zscp}c@XesDbul&XQmxg}M&Gw?;$NFWlf2a*SDp=?m&K_GaB2+)ua4WGN&{-lw zgHq3?h7Z)2kN>4fN6jzbI(QTnODUoic(mU5^tN|I8Q26isQ5du%LGD9<4GUTcAU4C1-nmYROxr9x>$4kU zFPxb-NN3AUzMax{fiOe#jl=2o%KOoi7wnEupSBrMKfw8j{sE4c)aUvtAo>mBRv8Zg&cS`eGmY> z0+g$b7nGcyJWh;@@clin#Z}{(Mow@+bC*%?+V{Sa%wYK(56#JX>sJHc8n-ZWz@?kW z&R{?M);N^iB$4~$OEb;K(Gd=%d~}cC3+w8`z{`0l_%)uWlh2?+`raME26frjZTk|u z65-Pq`_|1|m;6&1s)L~SVS`j1$%1LEx;qxcO2WWPqYbzA8%Z@bdAHn-Dx;@{f0mbG zohx6T77o4Eu`A8_TGq(chdCyHbCaR^t+6Q^d-AMucx=d7(%s8oAYYrqqSfDN_P)cm zz(1;_fhqw|hyTi~AV|7tSWYbEg+=sN#hP*ZAI{W2S0)k5ME{QsJ4oJ#D ztF3~!Zh23epO-;dud9x1qK0AQak#JdlM5kvMpA|z-p2VHB}nW~iHfMF`5S0^#x`!j zHXG{vriK0-zmBNZ(HU+Le{k5R*|@CB$W&twiT!2TOBZ@KIz(pIcBS}@KGpeM-dk(n z;WxL3J@;JR{zd@6@YRzAeRoQeC&|aA^`Y%+Yd@_4oq-V<;%I7A;~3w%d6+enJH$^K z5VI#dTIbCa(; z{^lBOB6)u>sbkCfruMV4jjeX;#T1&ZK0cn=vO4ymY{lG^2h!JW9l$3XcfjX&$HsLG zI4^Ls-?Y(35UthOjNRR7UyDcq1O!|q zL#h%rjs8zITeFLzme%^>>-~r=LDHMhyVpY8q4FwT1QKY*ADs4hB4$UEoDH*O+A89B zn|PbM(cWu8Up2+>brfrH3G@QGp;v8&g`F@-t2D1B2##tNA=_ox$o#mA8dJv~gNj z3B867@A0J+@2JDcGmD<=Hmq)pnd4^oJgsxgi^IwpVvk}1En7ABu-Kov1m&;#PVY_+ zX<$<8`^`Rej^T+zZh~s#va+_|opYQ%Ust-XkX6o%-WB6;ji8cNI#LP_r-y<05|vVr*#&Qp7fkT1{oi>aDh!Y+k%N4_ow#P|2WHP@R9rJssD!46td^KIJ>;h$Y%f z=50Y+;gD7e&XQ{e?~IgK8mz|eSwA@h#0+1xMFbaOPoeJFk?x?mFF)jBsNM;5-eZMc(DbQ8M5{hp3e(_wvP9SBRBvP&f zi80>R+rA}Co%kHTuAREwx9$1KP^c#!7)Q0y|JlzbLzA?sA(b}!P^Oj`zQp|!NTYq* z@!Jes=!fMquir8N)_8V-_MX!rJqy`qt*Q%)mHQX0xVJfFW8V{REw%sycc{)4Wqy&J zl;j}9%qC-#1%XWUb}!zz)k#>7@V3^!5ohMZfyctDsmbE42O=;Q`C&Rk`+{$sq+d@f zrO~{V_lF`*j=?6WDTBSca#z+q*-`BEp2ZDJ@efFw&u)+1Zy4K7jnI{W+AlwahP$$q zv1^dJm;8HHeW9!&lk?;^^CvH*$YLjWHi;R;y!#IXS$REET{jmm;QCe)_fU?g+^GNK1ot5N zAEL8|lAT)=OAEdT^&l+pE2b;dttTdBWX15U^VtTL0HP@SrImpNzn@*a^Sr;X?z#6D!$BpAfR+hrRdpXQ#ScL`HQ;Yb;a(Aa! zL>#w{8+k*|h@iVF=R%4AEd2o(Wy4#nk5ohbQmzh|DsGcYXHI59(R1smnkre8RaCxD zhwi#eGPF+HQIL|xWF`LDogXfS-A*W&KNee9xSvo7m~Fi(kLpLq?7rLnXsB32l$6t> zuvBaF6Yg16dAI8w@T2t5+7|XXXr6+)v#Fh9i^pOU*7+wEme@)yY7)clNe@x=85&!O zs|>4t4@egc%>44yaIuol`Bp(bhSt_8-YN4j|||W2xA$(vqzAt zeX6@|bS&kO*~mmgmjkc-jf-1%?dMh^fj+lkDL<;k#_$d9Qk2va#y?LJzsiMf@+~~Z zL02g#e|wtX0-}(MHxrl7=rg|A%pTXMH1vC4z=`9FdSl;uq**Xiy#1@eO!z{u!i+oRSU;w@5NsA_l8J^}DkEloewX3>kJs zom5*pI-W_4FZ0-spg-2iMrlDP$cBE%oaghONA>f~wf0iq7EC9@mi-AN%Lh zeU6reXb)eBNb#NK?$e$NeX)gogs$lP;Y9M`PwsT`U-iXsYB4%)Oo<~8zlu2KG_h-M zO~EvBjtfuJxTJ+i%b^CznvsVqXZ2#v#DxmVdFmWzN3cGa1Q86ak=EiHu^s2GGI05v z*UX!K(`3s-$P2i~BIE^6oK{6Xxq7pIsNUU};^p_0m1EiJaa*zfLh{qfm$(rRlb?J%>eKmD?~8C?IyJUZf>TRN{mo2uwbkR3PP%^Y&> z(xY_jq%7_`abh=1qw9KJ)SIrMST}Sjg7u)(RH?*^gP)dSGML+UQ`pUR^d*RH|EKu7 ze9%ta)+_>vaHgR4{3Az*OmcMXCNAtWIMz*fQl_#BM$V~-eJbd>uXqzQPoE|#)kA<7 zxe8hPN4J(>92hvwu72b7J##S`Bc=43&w+Z@A;(pGv3p{AxnE^~R5Ch4l~BxR^jC=%%CP{_m+^v%_ z(`iKGGO0$F(nn4Oq)hGbdLH;q$Fi?a`LTuHzi32l+fC_1%6jK||FXL9odQ4h)J++x z+#}H&3QaU%pFT~o!whaxsog3*Y#2X~7Za*BWGMY9Z0-9H9iG4r$1Y4Xw~G|*uFQvc46TzyfCC8oLh=eZY)8>~)! zI~Jv%kV)A4s;*`!r`@}KoVH~Ms@di&_JzI0iPRCQ#aLfvqp;J8|GQnOJ#|H59{VB^ z(EKovq#}JJh-BVAqQAe&cPma}%OrxlFe3$wU=p41^$WVAK=Wv*Lcf85d-{d#+*QD^ z+^XU`ujJ`yx**CAsO(mRkyHjfUne^6=jZwa+4_`o`FmI)kC>G=+0jr?nYU@3Juc`A zykAFcF{1lc#-0fxZ4?38&!=0B_NhL*y&a#e9H5197#uJgxG?)?BQ=(`Lkd4OHPp`- z{Qh(+(+44f(hn1|jraN$$YbG6>}7?_OZL&|TCL327}?8C{n6b4I!B}BLeqAkr7_6OiW(Yy7k*&#d74AIO^< zMUX8RGzGO*cyK~T``H8M_2=dWs<07^A3UaMjm>2r!|)j#>3`EG2AerYE@aj&hB+=e}ii8EyZ|0b0-R>C-kL4^}-G)dx4FclQPSjWOJT&qeyz(rQZ?u+ifk?4-bHH=hbQL z!aWRMB2yvWd=Z%{=u&@ZMyWN6GM~_vMAsK1|T3-&|-T8d0ki-%U`!e~SsOxG9 z|5Xy9xa|WWbdKs;EQ6n@$K0ZzBnV$Bo)a88u;MFy!dddb5^LvNGaH7_QES2DcIVreeKfM#0(h)6REB2q(`lQFXCYwLe-pN3E+ zgFloKT~N558|r`eI66_x%_7t|TJC8S?x0i14 zOz~^mI@C+ES#~^?zCR~iDBV}6Ps6lO8@5>uQ^{~-anTJXb(4z}C50QEBO!^iWi-20 ztC+e;4t90&Dz^PfD~O*roP)8I9QkwZzD8jA>PR5%xp0#0>nAe0N*vMiFI<;}n{NXR z3WV;3Z|ZNKeIWqs!n=aJe5ds)_;r$%$3Xd5nM-pMgwX?PpMrPVnS3OKdjmcu;))o_p;1tu5LmaL~_2ZRCd#_8J$>q|lK_d?vuN8N>`nn|Om zA5bhDVdQFG&~;7`z0<6h5!s7&F?xPcVvUgJK4c=dXdh9C@dG@lOx16c{g*rD5T zyqZT?JZAHT4G3ir-)m}kNz$Z$X&zKc2bCT?OP#$h!`rG^^FepVmYiLBU!fY7>t}KZ3jQ<)cT<%ouy1 zmPwBG%J6dfGA!avs@X&NLbfznB0TuaKK@|Dlw@hTv(07Fknf)EZIXy5vN&P41o)5< zAL!9ThX#Brw<2Cc3#se+9n2Z$5K@y9;f#;}TuHBOk~iBB9~u$`cCASa+HF9_6pr`< zj~;Swv(@~b1kM~J9o}TTz^7HDTluvXY*tT;iVE+gce-VeE4)B<&-6a|?x;efKuLTk%dfU0&eT7h~>5J)gDx3tjOGRQ^LK8>j!@ zl>NgS2n*9)X$*qc(fJYKx};%n=xyGROE;O#7%GCkR%lby+MVgeLw6THIYJCsVeCQD{Tn5hO!bck|nTG zGScA=QNq$69saMSwuJuv2g`Al2SPBICJ>evAb@1IKYqLXbr9*pZmHeKfc`40N{Tr- zDXzz2hDZ36*P>P*2+cb= zpU~Qm=(i0}dS~w*(6Wg1q3<$4hQ_NreN<2co&a_aB*}>dkPy+}_VqpK6S69Gaxam6 z=zokJ9zeIKh1jElHToZC`a(w4lZ1?$`v)EU0d7bP8u5lmIZ7;aIAcg?%A=>jSI3lK zJkxS_m8(~T&rkY~ftUNlj42^pqtGd~MUsC{WGMKI5M{vY3tqfa*Fj{tZ`7C)cj%Ut zTTf`N3dhZ?{O~ib3thgomPrHIf}vlm@k((U}2E?nxpjZK9R5` zAyhx;wBcb|`%pm2uvTVUUPf&cIwS132S;_=mTewwfED zySW&)H^q4=N*nItbN{ZPL(ScvR4fPWI*W?4TOxasomQn!jP~~+?VX|{q3E*A#~$@d zN0iJA|04hS4@y#6my0R*)r`rClIbZbTfg0ZqdYc5S8O{leEUg#nD+Z=w(8_mhpZ}b zGFBxid}Nw~Y;NDS(%DJek8#dkf0S(&S2((LLT#E(=9C$4${9#jG)F%Yn`y@Dz2Cbf zWSkmx<+Ks2RO+KDIbG8hTPLzTq{!1P^i(lI{4|r!j(=ug#R@KccKaUt5l|0VNnqW3 zAX9q!tss4XuC}i5eZpf)Z8WsVSB=strvg;u}{RT}D4QI-{S@ORZtp z4KkDVkyTX= z^LpF7?QM}fbIa;KOaWFpsP}oeFzbb+3i931=#9l3D1=@7<#sIK^p50dtJLUKDQ zeK99ztigYq-gbzQwsxMPnMFO#$_!>@RnOfKqJ=UOs-bdY?B9TJw2b~&zSaTx-s5$} z(l)Z6s#05HRCSzfopQ$SM;N@@SPD@5ZVXId2?PhDg6Li|7o(3Ms^DYpWIVZa<@xAy z{>jf^XL|p5?El68oLLp`bF3zg%tbqNJ4?uCd=*r@N_4{&L7cddQt=xFHnN&1b0AY! z;0lJS)@)dv9}7MBFwC5xa=FhrfGbMK(d5VEL5u(7g;|X8h^vy2v++>AmVq*^c`+W) z)`upACGxO4Xb^yHAF6Msl-mOkhZ#E&pWZ7d|BoqLg&@J8ke$|9}07 zc4N?N#N4#kcD}bGHLY{9qOmkYu`qir=MG{=e;PpEWElV?m^C%7GRy4|{y~A6t1;5< zv+k??gIUSMq_>h>eSz={PwfJ~BTZ&RL+0lPE;)qK!7@orJF`7QklI{*i3Iv@-!Pw}yz3qBe=oF8 zC2H8=&dnu$2|B>Yft$kiK@bc{P1P@5Mq-#IigjIYpWDMwk|UL#X`i4DU+<)x8jQ9> z`+4zPGgG^8;DY^4$oO+n3bf&$d`p;btBfER8~|FOJ1Mk5B=VE)Kjb{J~-k5T3x@a-L z(SuZ68OnYWG*rm$-*&9(D^u&~H)_GFjaN&h+!OQOA9kCb>uI@ls6dcNvC8=TraHHJ zz;lDSqN!-Xg2Wll1lixz;4X-~b^e}8yncum5=h*jis_!Z8|R-+AW;Pn682VM>U z>22XKvJ|hTdCm*eV-6Ka73Qv0(5&bE*6&sQFzQV`xUKK8y*^9%<(?GT^tgd9U4HUy z^>0q4KYnA?Z8eg=D!5LCJ!ktRGRypnXZJ!}@BnSIwOaDp*A`XUB*U+Y{&gX*)Kk_L z!qs1(rPOok(_Y!KQ$<-vr*e$!kvnd zoni15`H0aSwgS*O|H7lFVz*caApqu5c(u#SQ^bm+U0DrD=t^BA0fRL)o9Uuk<9|Jk zFpqplhR^{iFh$0mtUd*WlEA~b^*Can?0%8Zb-T~AWob>`WKG?KKnXxyuga|!kPW7j z-OBWJCO$?m3z-G7Lp$hmrC7RqHLGQOovoGaaDL5q1md4{KG0~%TD!VM@y_^&tf23h zk*wfUP`_$t=qiKlMMXDsd>Q-p4Ww5bmNnf0UhT8G>HL$1{FtqwPcTK>2US&Yl}f4e zaZQxATEIWRzX~8fma)0)sv-?Cm2#N`E;N#jI4f=?_S6b9u;WnaTAn%jWlO>9PHXI& zf*q+A^k()-ArLoLO(P73W~;j^*S>jz{7jB>N0{A6X;UqHegVNYFerNH`ZU-vmC@*s z_PKJW|M1bbzMUx*)!)xbptg#U-zMxC2`Wfao-JvXp5Se+i(m@x+x@)_cPMEX*U&TS z5(t@t^`n}o_3;QMAdX@oD*^k{X0)JE9L3rcXR&l8Dq{b=A0b-IPHQn%-~~nec?JtsjTO4guNThYmJ=8B}6NJvUJA`di z|7tF{QGM)=;`;F_&-kOmF{N5BzL^t!&X?^?^EtcsBKh$eoBbE_uegd$+97K0)-sH3 z`_2n*vKMod5fxVfuqe+(g=xyH3Ru3$%^R_&1a)SAEO`^0!TNBk$eLkO&wcy^d3A~1 zV6+S@>oGZ8C`;I6?M|z|btty_GsXI{k)e*m?ZFM=;lji1ZQw?^3<}fv_QZ-U3UdLv zP;s)DW}uuN@(6kb& z_ot`e7hMWM4W6+`J<8EJ`=wpJ2)p!ft)>l7P4Qgz_>_ZOLPjSm7B-yTr5Ia*eC8`K zCKH>V%MKkMLyWmOeJ?5u3dgIQZ~3*0B(A@TU*S5~n%DC9q&a7VFEeM#=vdX^ktam2 zon>pRvj}Br`D1S)MB_aT$`-Ya=fnS)y|UpxEef*L{sS}+!I8tVa~ZDLy9Y?FhR0Lo z9N)LO7ajjIL`|3$``W^=F`kGJH&wp!^Mk7K=*Qvz&tJzR&+m+Fw`?2CdZ%47#X8IA zbco&FWu2nEC)8n~o5~qC4tUkyWTz;ZMQ-nMZbGjxV@mi2Ev%EWS)rfEqhscVrnpf0 z^{(bhoo{G{Vc!ZCLd=$dW}wvUAT zw+{6#z-$GV$6ZhNh{as%dPs4h;#5g+-F{6@@HF!#H#mTK>3?*Mn(6DMuq4taYA!-ol zBW0eV?Qw}fIC*=bVHsoWPl!6XFOo2FHlG=@sWxkXl~` z%C#ZvIVC4gvy7)ljb%Oai|b@zH(XaZJS1!E%NUXs9+&zQ=W$fr>qNOJxW=RZp}{p~N-z^f`i4`aWuEClUoCd7F6g?VZdp3==tN!J zEn8>%uaP95&DF;KbYj14_6k$Ya?8?#O}+16XD+9zxNqAD8A zQ&UPnsj+e$z$aO2<&Ozn)a#K4KHvLP)wzGpmyy!#tU(Gk-SImt9JNgbg2-$C^Bek? zfV%=Nm(D`~yZg=_paI{@91-=CZFzgodCnfOXwBDwwcJbdMBZ;aP`4rWDM5D7-JoUm z9IyJ;w^(FW122t47GAiPoC!d%PHnjO7$y<};izjegcDc7T7wf;bm9gc2O#^Ae?Pyp z1iht~#i|IpY=0NBFV<_Jr6&7K3-^6TP;SD!UJncxqjEe;+mKH(5dybm{#QhnuUQBP zKtl!}{PGv$_>zRpipZxdTj92Y)4&H?1kj@^9L1h!_bH#RqF=%*E*K~9ScDi>M_~O( zL>RljB%m5oGNhuy?$6^@(eWFrF(h_?Wpv=6qKcatIZzVpRp^{kKkW5KU>E9n`f2B` zqKJhiz?w8))c>itD=0x8W8!rk;)p*K)I4Q0J1pJam3|b0Ru5yB{j|(u0C&SKGadI! z;(cfT5Ke~KsE(lHYwE}~{u7_E{49#C+#g3~NH4#LR{{4<&Q*sR2HVrNSYfghr90QY*q{ zfjAA|*H4x%vW=qa9<>lUS6u}V2SXjdH>=*JH5P-~41?Ba~{sPdi zRWUnVIM-upwAjb0MNB4=V$7aVf&O9k(bm$lfJ=+Rg(vupK4ICurHCGNDH(mQGWGrM zhw3)Sv{L51d5jZccZdMsPp`-E5Jq=b&8Xs+U<{dV_~wq^bzJR6&G-6Q3~)6tcKOwV zwuGJ@qmoO<1Zqo7rz@bIhSdO8rtUkYW7+D$QNg2cmpkoQN2jp>f;anUZ)kuq+K|8e zv<3r~ZPFAfQ=EF9jk+cg4IX<+cIdtnP#og6HZJm-?fEmUOj&~}#y;B!QapG2@1~cD2MBg@x3)0=Gu8LM0)n2p~KOj@u>{Em72R^PX26(zIDbc3Yk5r^2}`4^9G%$m)=39esq zu$|3tks=U?rW^H*&!6j*45wrD-OiG-6#)a9NA-0Z$m-)nWe~1P=fP$yGH8{;9a+Wr%u8xM`b{w1DchMUbod*$cZSB^uB3DY$?RB%t(vU-T zXn5u{TZba5jqf9cuUK-&rmR(hT7ul=_Y%6!*kwfc@|flLRn>!9>scli9C^Cjsg~ww4UNQs86P5!Xd0i4hI7@=VF9ti2cz~ zh}L@cG=vd*nR?6P`cJofy5FVB+q>e0#US~qUIT5^U6bA8+O>!G|3YdkD+*}TLU-BEeq5Ge$G;Q{Km^=sd zj6*u3&e5ODSG1MmvqLNe||WdRu(kzGQlr3Tb>r2(@?i@4ZCfg zy4RE!dA2~XOQ+L9VJ`_ET|Z_gO;m6*dE`P^c2{VKWbeU*0in3N4;o5%uxP-5n0dp% zcgbl1W@7E_r(&sT7hr^W1dp2)!)3!RgldUFJdWVN8%st--D1M2Y>t)u|1AEyimtZ7 zv(V!*o86%5xlJagCn-2HK4B&tro~Ru z$G)lj9hYjC`C32ex0nvkj}(nlh|?+G8I}uST4|`>G+>7Br4{)55B(?@OEp$pL zbv|ZHqA-C|ua&+n8&hNCy9WXSA{)t)%U$PNZx`bqTd#6W;)C14?Pf{$I#q}4D%*y) zF+^Jyqah9M`nj%d_VfDYAdv_|-Xb`8^1oj-oUP7E-$bT!_I!4;9kc~N;P``=luW?v z0XA=Ee?a@}=|>DMefdklP=jW)oybPrs#LM@N=g zVVeC$^b~aIeMcePr{&a%%Iq)|eXxF4K1c|bq9)%%TX4tboYQm7dsu?Ft=2U}tVIH21?7*)cjWcfV+kno>B2u?|G58toVtx7>(#Ydzd;d>;45A`XGyjndAIiqHT zBj=QtX7La`I{)gB=2RGYD)_#6BkdJKZaU(8gF6vdch;>%e~l?QZP3#QiRv;R&fBVQ z3~n&ZORLZ@Hb1g!i3uN=jufNeEj8LJi0y3$5%IwvNZF|{`eJ@NdXc~v8o)^@Ll$d3 zN@ofYfd=LQZGeX5TZ|Ws&cCRBnpfbB#=VfJ;r1r5P6YphR*HtVQGQM&QvMo6BYkTu z(nWrl9Bl)j8$c8MP(BQ8H8ch;7j-a(k7|%XkraGH0)m9QHL@mKA-7|qWswLmaI#4S z_e(MpiOy7|to=tLpz$*s!LOPhUf1kW`z8XUwV)KxMg@e+H*eGtPeim~W4#vD#%g{+ z{=bOzJXTPh$X~$DZyYKOMI)YQXg>q~z&D9${K1USzQoIKF=RgQ1MSc>Sm0a#K?jQv zOaX+bbWkY6betX)0tPW?n4auFq5z?I0ayXKsM#r4K8Au{1cf~yh!l`h-+}uO+;_Uk zJcJ@Beu5I2gIas}u+1_+O`2NZ?=j+Q1f;2{0aR0~kE{UEuQOwwclls^PX#91NVgW3jlV!hn@y)<%9O9PfudJC7bWZSK5U@H-x znOBT=xO~LVd9$#8GAhob78BZh(CEzJQ4sKpE+OG3&ySbxn?|XHHV>3*c3m;kO?5rX zFA9Lhm-OH5F`8J>8ASo>$S=Bo-5$#_!J+Ut6Hew;bMd+tra9083DLvX--jGevOyts zp93DXg*#L{J)Cy)+$`vP_}8NK3dZY#Tfne?qBU$GAveP7mG!DwbHNzI5vUp>S#`9m z=1Fx_!zkSf^(osY8!h^&(4D&m&vJ_7>leG4uix?g@ly~;M8A0v>e6-4go9|&r37dz zYB$RfQu1v4GCABHm9EGNntT$Bp*vdEfuFvvq&#IqdCIt>&qVAe)}w;#=XyKW{NY z!kaCc59`1jX;A=Ae76Q9o)_6RJvMMfrxJx+Z>q7`K!20g?$}38A$yo*fi`M9y zmvps5GNeaKvn}S}mCVoQt(YZp>=NcAZc7Pil5`i`O z?L*AyKoI4TCGs-t4k3XPuyW-usG`Mr!L4a|a;TX-cty$oGh*G^4np{-6~f`*}5< zx8aI|ptfY(B6aVSq>3xRGWPgbqn`c{KBmY^zFviA?ccUkddp zKbsR#gM^RPH#klv{_8@k)rv&T2KIRy2u2_N190&Xr5e3fU%8Gu!J^e z^26p}fym0+Ee;a&z^LoCD)l15&XH?l>y3JY-iQ%H8 zffWIcE!+-j=+i^=xg9j;Ym+-J2YJ-618GjB+aHE^Q6Tc9CbBlh<>c%6LqmwAv>pgwmTTaoc?L3wA29+4(E=$h zyQH$U-(#|RJu6X*hE?z&H!KY1p{yR5htPd2vb9BgLQcrXQ&b@&IVt@tz=lOx$>Uu? z!8Z?y=F3VrF64HND;WT1;FN?nZ3NT(=SFbY2)4bhVkn+-N-lCyLRgm8mjgtGh#4Xj z`Wyj~FSKW4Un42J4j#+7+JaF~2zUqp<=4sofJDC zk7mzrfqXffP*)3)-~`HaBlQd$>1ql`JsqkyY}hwsYmO~G#WLEE#)$#=1ZMYRhM2bY zx1#72hX@EA5u%^}E;CesNuKCd8Yvs*hdARBJapN=)K5AB_JxyK2@vc-beHru-`Gm6|I}%g7%d{QleWCqsgQ2ujA_BIvmrxT^k6_px4p}u8s-dJ4 z_+fDB{vt!z@Ws97wj-}j2)Go`9IUo(-h+akC`P}K@MI<>@A=&i?Ut2zXXW4%w_lj` zeJwDhYqm6;qi-$t`c(lA@#~fmfRLcY7NgVw2iK4jx6^R+;1gw9?3US#$73Ex{OYkA zvxt3e6-8;y6jq1Eb|L|(LElb~g!Mn~*4lF>Nmg6b|FW$wfkX-#6|ySjWQO15upr2| z1#&O1>}je^a~&H}G2srRScVRl)2xs?u_Z|FTO`-qe}6udVXT4=7{3&=U1Bh{L1V+0 z#SE?LPVLPdk_|XF3nRQWX8{xLB~En}0P}r0OYjO4+4cPde>io$qhm84~CO@bq(dH^XVPJch2Q=}g}l%8a#oJXY0A zV=|3eP2EBmYmL{r6n>bmqK7N~B93*6DbX)gAP{?zzl<;)Y@M8ee}KcLo~c@;(i%+b zW3n;`t-+LtFy@JTBsaR^3Xwum>^7yC+*3a1BwUuivQs72ako?hFE_8`k@7yHhP8^F z^r{@oqoK_%yq1P35(BT$W@ZTtmSm&8;+VQ$9PMhUyZEmr^i;3WDGi91E})q@L!gbp z<0y*URWfq zZ=t%Os-Y*sW^!9hKzov&tCA>?A2SJwnNtrrVRFW_UXL&f`!dz5qc7umi)|fk_zh_w z5{9Tn*3FdWWc*2sI$T3L;e8yvggFuz(nP_HwG)9kV#!d13-(xULi<>&Zyje)gmQC> za0q=CLv36;nfo**;?zzeb<)Em0Lhp`7%g(~@wu1L@H zf`+pMJKctmAz+t*Ohm&k`n>zfE1b5J1SZi(5*Q0fBxDaJAbe+mpa+j^cQ?iITI#jzopEG749v1&lvYxXygedL*t!`^ewNq*%HSn0> z$db!Bzf*HVzb552L~_O?qIwuY+=_0ij&Gn6s{ROw(sBE|F~jDB9eyvfhNG zjPfd6A}RLt%^O+qu`-+l9=K?X6!%h+CxpANjpb94cTGJAuB!}zvU%(TFwJY=w1RZo z644;>pwF*h^U)-4vW_!92v@rc2CrVTiuv3MJV z4I=_{NUZxJv5>$IccQnKENDON?Ho|8YqhqssP_`B+1e=dy6BQkngi5WmGb;aLg*%o zJ=V3+M<#F@$WVs_J;?o3aodUp?w7+PF}E`aHTIN*7cb_qpn~tIiNK~lYmp~^zg=7n*9-Zx=I0-+QHb8q_Ts# zcE#364x#MFr$M-FtfdF97tI#deeN|h@p3Tl;rOr5909@V3>uQ&(6A-0W~$zo5Z1~f z$icTkxTrO;o17Dy!!MD{oG?nY?o;i!4lFk=5Q>JpX=TpS5^^@n)_(AP<;W9aT&BNx> zfv4dw$=b?ykRWPRIB_qqo}Pr>McraN_}0~h({QKxk5vms#SoOu4nYB$&~KwYi+K$& z1Rgi%BwkEl7!8~Hy~d6T;9L9l47g!H*rj1u?{R14>VPYFQ^j@8=paGMot&70}?Zy4`o_NWHF_<= zaVKi;!RQaUCQhDLIR9JSSnrpj`8XVh>oQ=|h&)3Ze-MI0>zn=^+l%EmhqoWkP4Fxo zWyK)5bS9tAzQ46am_ySTSn6~ycncr0_quC+_6lfr@(M*oPB3#(b9Rho1+ zXBF0?2M~uK*8C9BIAiTQBZ(^?#Rqa8bg zuwjWygO87>@Ue-aX!P5fwH1uQx@V(f+|l$ODHDo1+dYixJ*e@Cw=VHoFH={t>mfGWz5j;h8$2yR-O9hiiyFf^vt7@4XClL>V#K#x zJU2dZ9v5x(`T~YU>CizU!Hdu;>Stegj-;XIcAVazJJqY%XOddG=(%G6>M~-~k=E!V zfK%cXjnZ|CgdH)WE#p6sy*QwJ6o>zd>jfF6_jcc1-uX#{N>s*% zgH7kyjk8Tzp(af0SEVJyY zEL|lE;2hv&O1w@hs(z5M#GE zMlK1ciL6&EKw#ZZ&XNL?0=a?K94s?98n=_Ijq5j6k!^9WhIu`xYs_ADJSi+T)c<`< zgqI{OX|Q7D?TWtrutA_uhAM_AZe3%SseVz-l)lWOOyzwQ`LB1Hlqho*0Q8SE%t&<~ z2(lPk%H`a!>cVZU$PYIjbyi^y6uDTaOU(N&CA)!gCBahGUDDpK4)~EjVq8qv7h+_J zKgn1novBi?S|M0EepRHdE*8ss850qfT=My>-$Fuz&){c~;|^=3g`)yvQ)km2yS} zae6BdXDkGM8T!eXUN@tEmPD5A9Fc-~jH3vXMMYd`Cy_D!>t=^hfPw_ba^AWgw8jkT zR~Rk)UGs<65{Xz313*^a-Se38!5o19&KU_bgfS5inx-xor6=y}VfR4W9N^ASts0(% z$4!0rw$o`gUN3G5tU>dAfq(FJ|7)2OWov=#LR$d?S@ZiagUz3CtN}LNz@d)j%p%87 zj2I7N-0+{cDomErK+*QOzY`eT8?iHZExx$*z)S-q30ALx0j2&m zP+*xZcU`Z*8__kt`HlV}dfN4JU4 zYj^`NWjt8ZU_?J4{=Jx%7Y3+A7LU_WA4Mb-*&wZRAE0$5&`$);nw$F4bU`NYcBoIL{J)QcS?z zB7F5(bA~;#dAs@Mj!DfC5{?*SwxY9aWock;0#Kg(!hC33Tm*R&N#VwSDjSw|b5A!q#&p^Mu$4L-J_^05&a zm;e#X$-@?xd_@#a?W%q-X8c%LsFZaEa2hMw8UKD`(6qI{a28M;cpX~?_&iAc@~>wt zn*i!I1B}3lyCl4nX#P6Ud{ux?buLUAya22To_9|-wNfBK;%r=>*zedp7*&el{hvB59}6bb^4$BXu2`57$ndh_+u3O z+DzfN*G(mbgj7?D`y*mZCW#`qk)>UQ5K?tOWo7B`=NQp*;q3vjF^arQK=*!}JaIRe zr%Pbx?1Y0`-ut96&C4iRVb?fBBT;e*@_~(Oo&ma<%{bK@Gmo3Vhv)sz&9);&ly#dF zXm3H?)iPVyYlKX;ynAhlQhDTbVi#Nr(-9AC4^`2F4Y>kB;a1lXpb;-8SjDF-GmEB}8Lxv_HqfzP zI8_ENB##IEQ%@`O#G{05*iTpwqJaJaEY!$J z9Ot zqwAHUk>L8uQbm#p2<6*i^)I6XjyQk}S}#~u3iP(<9H-9{{{^gr9+w7ith^enqS`au z_y44}o4HT3+`d?ZSp4-xOeQex^fYXh-0?EasA!qjOEpX##^BqrPXN=(m(rFy7 znAH^thorZ~0|6yW24fC4%^y?RAT*%ku>AsQW%4LFrn$tQ2sZloO;{&>e3*EPUfK|? z0{*J84TF*hdC`@CSU^NSAuL|L1n6+5whMi81t_zBLN*uK_IH7UN`R;Ke3{GZl3BX^ zyn)ehAk=D6ZRO_}DE^CP&|Pagm7=^naJ+0RG0~Fc@CMx?_X!H*_{v|`QWYxIXbqBP z8is>Zn~Vjk3V~1!>{5n+F$Iu?-rxhmkXQM3{hM!KcNMw?=NMQzgyF$rT9QmMh14aO z-$FI?josKz&1C8le6!m_#$>;(XZoM8$15KHwJE?7Il`StYPHkv2!sp>K-x72V`xHE zC>itiN}6J4)jM$dd+?v=qjCNMsO8%wjNqf4C;n*Ay{mDOL&y2YKkBxTAr(T_6f%+c zk74QFD#uGrXP+Ykacxv~o+;0Pqljl-48*kx>27;ieRI0If)dWT#^U&thz9d!V5feN zZZ}F5GbKL%5KIyu2?F!Hv@rcB5k1+v_VAK3#Lnpi4;1ty{JCN0cJ2R=H_#9slGIOk zHz%(*{_SYV*-pgcbV#MO`GhVd>I1S}AOn8e>zr#_Gf_1Xy-i8cbvLz$9z?VCgW)hnF(p5o<$3E(6eZ9|xXljP zs<$3JIuV#it7}e=1YGIf-GIL;4)SorP4>BHBef|AUSckm<|*B)0yiuo`oiIy_x1bs zoaW{CGBXK0`(~#ACyVVE8hlvVH`0^rh*ZH@B;iveqOcaRc9D|4Jj+q+NRQPD&S^29 z^u-OlYds1MKnasAQ=IZ~fFkeIC{6U3JCVnm$lt2@g*u0?fEH~*EjDmC%^dOn_&`yLj zchNv$Uq+-xIRIn|lkN4z>uQ_}@*_^jqgxi=cDVC!~BS%yWjeL@06!D&t!s4N2#G8;syjECnt3l0LWsV-K ziO3EENz;%aAb39eCdkJ^24c9!Cw@3(FF!Jg2G*uRli(Reasm=RYT}uX5jvtpuVK}3 z>=A8w`!xGTpR({U5=TdIH*gOL5*G$|3q<$y_|r2QnS@xgEd;}HtmlmKN+f*z3)F*#UHS3jhkQuIbgY?= z1CbX*fCo+Df*mJ^%v2-=sG)7XD(cl(7d{AzWvp~60fOw`-}o3HADR$jzXg#}l&S^+ z1c<7o$$pRW{w-WR4YO!8ANzpz0_mR)9>TBRASb{&0f-1B6#pG_f)+h`Rx}QPxo2JH zBY-Hq+)ENK;WbmL`w`qRh1gKMX9xBHoo>d@X!<{3CeK&;`?iy|O6m{e6|-J$yFqVokRjf9+#T2qbmgS~SV(vVsa z5}X-D=K@#aljrjV>U8Hot#SlQq}F4`wT%6Iz8tjZP!!9_;SMB%!bEf_4B=Qosmc6u z0-@IK08M~T9*%hhaH<#5yL^I5z@Np-A?g04@4A3hU zRYtUcM<^LwWZN9U1kZbseJN`dZuNLH)E$J^zeD+d0J?cN2AH@=UXeM7?ZftLSFP^L zN`yKJ5%psOFW-G2BSqjd;x`d!lF%m3;&=S!h)CaosuYA&r_vsQl2rqKj`6Nb11#~y z3VlF~s>zt43|Q`yWoP1;YDian_W|$J_O-}svkhPwySEk?)$>&0e%mFaqw>~5B4e0Yr zqZC9ufYf`;bv3RHTm)49p5PPX##qYnqgumt_vhR?rZkn z0o>Xmz78>(mtYY)eBEKw#-Vwqd~th$v#tpPz)R*~Kq(#s5dlQ@7HIojR|d>|FF}@z z(1I|jLcxsiRVb3xn0^^VWU)0KUakpdF$<=B(~Uz>=d@53ycZMaN7m465L4^Q0JBH{ zS#|{+_eD}s+^;VMvseW0o~}}sfN)Gndyae?NI>}jY#`l_6A!(#;$5Sb9Xf$vqywL^-qstVuveDc@c`g;t?b`ND+D`0>K*k~1MsC-tXDVg0w)u1tzyNU=GS!tnTSqdITS77ehe`hh;v7oJd zEx@YASHnrAff+VX31d#P{!7Z|6m)cI0;vkJIS0Osbq)W^THH4wcNHX-4MAGMd@G?Vv$n3GO1@ea-G6IA) z?hZe2|0SFnSymMh_MVQTv=*;3E>(5Hi44^mbjM@Ac;-6C*I7Z5P~+PGADld8a9b+j7>a{Zw*>25e3%_AX};= zlp=9@-25OjrO;-p0)yb1R+-pCbJLh{X@73$StK#|l~HR6DS4E2+v_&KdmZb{7~?rH z$3AROc)d=q=Z>IY(&%kBtLYLC!ou{agMY;w+Yy020iUzi#ubgmH@xdQgk0}q58LzD z(m#}oZdbY`q;=ncyk!351*ouD^Q+RH@^S-`PG2|Ue6D&!X%F>Jx0kVJ;2e7gObDq2 zt2RouV;Y ziKTX516&G&>fneA;fEfOZ+?P_!6z{lnLEJ~32>547^-NvD=63jy#6ohHQsAZ^ zoc=pHPT|82Dag5dCs*9J8mIky!C5#00Z+ee?^5pa&acZ|3p<_81~{cMF7_)*tPv_h zi)9M-2)=^RiZl% z_~rtU`kZqM?LQ94vlGsBzM05>Xx*9iU?JU0>>Lo{)Mq2bJ_Ou7;Ie&%C>(t~DJY}F z-_D{_XQ-cs^IcCG8;xuVP?wbwp!F=!=>a6m^gu?gIg4HDwDt}A6(RJEn%lFoSzO+p zs`^_lMR02GVs20V1e>NGoM?`=qAcx(i5j>xVOA4;Ql`PgUD#;6(naeCa5mS$r5+p* zW9HN6lb2p6!95H_azY8OYaH0D&`T9&JjAIlf}q{>LqN+QP7NB1sSz)Au~oqSp0JVBu72T^EvJpBz>ZQy_I}S^MGWZ3#vSssd0DgLYO%A>n4@sVQ>#iP2lSs z9+%y)&o7abD)|v^*|;2L3cD)u=`ip-#r@Nn_x--NzX=uwl&FBZ0_)NRQoV z?VBJ%fFy3qkF5v*=aL)veCmQO373_2!+4BzL-=#=cy5!BKbQo;^>5`nSA6*P;e6+P z@KC*m<7>UHvgm%jy)S+ZSV}MYuq-?Szyy~PsV-=$(7O2qZWpl1onT+Q6|=r2_O9m~ zxRd45yxvgGT^asO3=21r^*(4vr=WjbVV9-a;e;zXZwXC;7=Wky{$Rn|3?35W`ugYu z?$IG#{E0f#qbXxr8w7zI&LqG50LmlKuqd=yZ*XifF?!XJIp?wma+??M(G1@#4iWEv z+>iziC{clA-{-k}J{Ut{LsPg8{O61PLoBJ1W%-*n{>-z84mF2V#m-iq9D{!-D z`3>NG*wLz!Y~tpYkOXqKgIvP-f>qef4amT+cFja0=(;{p$*>v! zW|^oOhJeEUqf-d867~s3R?%Tz&!`IQWp-b`@Fr_xj3G#lKg&wfs_RW=31{%Xp79H)Wp}%6B#ValZhtzP4cLrPxTG8^ z_$zfYS&qei)a+unTj*S$BtR*&cc|$t&sRV&x{sN5en}&64a4Br+&n7jkKWR=d(6lh zvMsm|bCWfy!?X<>&Y;h~AeQ9+FDQviNJ!pnf&$;O-HCG0brl{O9>!ZQ9ty) zhS~lM1lZJ^H2G%?iXjFZO;56*U?{`s!7WE#ILRgEY`b_Vz9XT!V<}u%7cfUOuwOU% zx06E*UZ}-ve;y_{0ONnczImLhTKB}-t76v$Oy{8K*5_B}Z0%a`ZcD$-smMPgyiY&L zjrrXpJnnYfg0;EJYK}D@_*MSMZbSO3?rkB@ujhUc`Vw_$b<6olwM`c^W@9${6zWsx zxa1H#$)3u_Hn7jwH5WGZ`N=1nI8shKc|FSOIeoVeOC^u*#E*^{t~bDK8h>CRpF~VaY=GHrvwC=G6G&$ondz)!HGrBmUmS zTzJs>rY|$ty!WWCo7Ov>!TXQ+ z58Pq&snJ3|@Pf7%>j>vjsp@LT!)*S&~qfBy>U#_@B;u~4UDaC!ybeva$P_^Ne-XqU?u%+vwW7Qai;84FCILRvYyB227 zWd+>!=+b^snx0vB^nlyZ3$AD#4UWY&a+QuZ1MtE9i2?23%EwY^+*F%dv!F3?63=$V zOLr>Zj~!7JE;7RCGG(Z-!i%oETaYv2S?TsUb;`83&HQGIQ>v}V)}k?C$EDURJg4lM zC|rACxmb%T+#Hl}R#+qGqY|c|;H}((J(3}PY67~7+XQnh(DJDEzljY#sjZm$YV&t# zelKLFD9~UyT0g2HOgr5*@?E&sgcnJIAln`ZM_oO~xDcM4pT&$z@TxZxgrxYlqGf6V z4wWt;E`O8tHQ_rY!nM*ziNE&IQgwWN)n61PG`n?;ggakbW-92hdGJ+3Nbr#=o$0To z@yeZFAoM)9L)(ZgVi}%Kq(Rvh1yjK$(J~V61%CBa_AkzG!C7j&L{#G%XHRQpUTu)- zm4Z06kq*Bv#+F{r1lc{DtC~)06_+eb?VuVTbU9Mjr{fk$ayQ1L#Jz9hup7M_D27Dc zG_W1B48Ob=acbNyxnrEAh7J#oFFuD#;>BJWPnkJo)-CO$1iY53V6Ml(0v5-WPHc7;Tbvlh&pOR;XL+{%nymUEpEt1n0S)1>sla#or_ z!lY{r#BR{OK4ZLqjyu0R3UB9e$Ea;zI!c1Abj9pm2YMHKgb;sIAhc2gL4KFJ zw7qUybFOl57x@DSsj*HM-ksl8*?R1E{~;^@1jpmb%AX=RWJ2L6=WIbf{k`2_p{?0)fo3XBjL(Y znhXfoAsy#huzmzDT#2(gSF zTIgEe73hps;vm+tnq9@=paME$@MVj%r|(|*9$pIE;4t9z59?DFFuTC-+slcJO8dwh zgVoW>_kyZ$kI)LV%tUvXLG!1@jSh}ZqvrDa#fCi2P@-JJU0Ptq_)_gTMQW@rAf6yL zN0>;xEC+8;SA>$7TGb8qnd2&*p<%f%V31>Ny)lReW$jV`ayhEoux!CY!r#YFi@mCiR67<7J9h(78oZ7y%I zQKuoFXt6OeXS#9sVDpO1bCTKIjr@`O`2+X@eJiemYb%PKfc%ks*^;cPXkf2QH^yFZf9!KyAftMr;IhxW5SDXOrWjg;wrm9O!B^a z5ic9=k(KkEyEZ;Zy+H(Xy$0(8Mpt@Lm})EbIO3mIxP;&qSN^2Un)P$|6ZO3WAeNAN z8Hi3C=nVZ`a^13HPb=-EcJ13JsKs$(&czRUrtVpUmoBgXe_rp3BskT>0x;wv9^6u{ zkX!Ipi;|Hp%z2R5;UqVUy6S<^U+W;ZWTk+y{M#;#XoHxrgGs(F4tG=pE`qmsyWn3z zmrf~yQ%us{P%QsTFb~!6=MN9TSf-=}Zo#Sb>Ya?YO;Av zI(QSWLtIC*e*c}{Vr#VS_li$DZy7y~03f+YJ8JZ88n!ItpePCJ#ZB2p0jW z|G%osjA*5~2I??YrnQ6$Cj#-WgI5+>SQY_&PB>J77PJzagBD!ZAlm!)f|d3^cBS%; z8!GRaC96`9DnZ_h(U=165z=X?UHncrn$76+I)zH`g@e40&gk_1kJ4zT1F=__Y6mAb zJwiKOhT7*;rz`Ch(M~I)qX;^EWeeg7Ux;>kvAMkOy6Om2fwzShfui-r4ns!01xSZx zGozqp{c5DswYpzGI#z;RhwHOpcqDjUIK>QEr(jmW_itZC{M#DQS5Uud=|03Bd+!fG z7IC1m(*-cssd8W(XgPzw5dZ5`xvo-(YeBhf8-Jn}v>d9I-mcR13U9xco9+p!z8PAX zQSLuG^11Ys%Th3uYg)?pU_#>C%~90-4(kDGH+l>Bme}45UiNK;FiwoFp!NVC)qWf- zh{aaoAWj73u0cZ76+8Sc)akCa;`>kflEw?*jk{{Dvt)9qoI%$^16Tv1znJGoAU}3J z`|u3JbU}-YeJdo*!HDGF5_}|)pX~a6hB5N!`B(SvBTrocTbz*8LMuOb_dPY3Z%}4t z$Wzb(-(e-kEyz#BuX&`gD04j+jgePi@q%}zbbX$BX9Q`CQ{oBMeQ?eGy;HCB-D~p9 zZ0gC0;0FG9e~8#V^6^kT%C;FT4xrdkUPwm+%iVgJ2K*jn>{ z4tZq9x!XV+k!LDwU@_wVT#v6jc-zd3Uyr1@iU8UKc*a9I(jJkA4I&cxTd)-If8y=` z#|us`iGF+*nWfO?!ao}7N*aO3_dOwcX_zm)@Y_aqUpfj zH;gb_F4Eo;-tCz<7u@z}xO?T@-`>4oQaXJ0_2L6Nw)U>3Hvhd>%C06M+lOTj*%juOZj8hvA=ajH~*AaopR^;%|7N#86xH)ZR^giFyTA#!tjr~-?xn%%DNWF-*L<; z>bW{o0mJfASs1Lc0=l>#DjNu*fmMAQ9>$)B?RP10_ZI=N`&??kJgjoZ(QRZoX7f9s zi*jU@?vW)p88k(Z)L2Igp-8HTyzV%=P_Kvdc|)SZvaTSL;gO=jfg7}0JMlH$`@&gR zQy2Vdc^BTb*kj(OBN)2FBM+W2V_m*Jse815iMv$F-TO5g&(lnFaH{Ovsj_o2IvC0= zCYszb`kG|&wBYS;#oWCoD{Rb7>DbYdiFT|u(rDk+W zxpN@sM%Zf_vJu}iN33Nhh-0(h=t8MoX^W(?lCOm!y#)>V-y}rm6Y1Ktx z`byBIq5xY=Ol|6U-;=`!sHN!sC3sZc_iOjQB6+11E7p$B)0}(skB0xpOUS@&b&v>rci0PYvdGc*~Y1)ftv)L2e&X6?j0o4wm?U zFAJy~x_i%h724&Z2a%nTL}HOJeFdwlPx)R%MD2<_*TVFSnSb1n9`#%x5$aGEZPN8z z$x459g@9Oos8m*3GV9F)nbEf*qNGP26ka2-IQtYG#*bTU3Gd*};K=}uV>fZx%s90b zW7xiXjjP(5A35?qq~TI=VnxWQ`s!BSUx$reucVN;l)A>!3r=v1=ib>6N_*F5#g_7C z{>q&2>ke$E;PaknACMs~B$}vFbiUqR*%s`Vo`;(>o~{Q@^Wz1iw+?vvo#|TIfRYsJ z!Wo)TE%xh8^IM}T^e#9Rdp@^0qd?F>+Yz^c+H4Z~;kA`mSvzU~(DiMcr(5|u^p@v{ z#Mnum+op`fJzSWM_q{Dl4_5-Mz~(<~oq`296n3!frl_TD*`mzduO#q5!zsbe*1wS! z#oxxmL3%dg$Z+)EM&{zxW5On3Th9KlP4LCdDt=8_V|qz3)`KNu`pzzLE~L&41g%Hc zFM%UtVF+n-z`>H%$6$SYl&aVGa&WQa^&|CHgfs%@YjP*P2yD(;hynBVU0)F>zn?Sm zR_H?KM+5)sIK3`n8XRYPl;Q)t?}x(vlVSgWgGhS zemRO-_w1NcZl%o+t1GAyNYVY>&uka@30V~CP`!@)>f1NocMc@5a$P;@+d;zSg5-@UinTNZC<(B~y}=TX7=c(q^rd`W z>Lnq=8cuo<5g9$ixl%sADR*xEvpM0aO7$jmrj_x7EhTx?x0>)DB8BNaMc2{ir*u-f zRIKF$H_v<-_*JY&pDNrCE8Ad2);H=mO?0>#GjJY1QrXz#=FI&|-Ku{nG&Oys=Fx*$ z#NQ;rhmoPu`QkDO4r3OVN!Eg)okAEj6#@OUug*o?+XZ)=SAscI%TU>s2U{&y6RPHm z{0E_I_f%7#sOFUdD<_>F-?v)6RN0j%Z+fR1Bmp{E1tylRTee?vK z8rmFeWjMJ&yxHDdEbxms#_8QfnwiPiyGGe8B?0p;IM39s6b~IdukTQ}gXF$`+s0mp zdW{K_cw`n(@SHx%cYcKduiW_GpDUmw39eCLm_p$vxt z8&km@+XbadZ5mHY;#;rAicS#eLjx z4W~djASsnqqdVAnUfj9mY9W64??)EY?;n>vG)u*m5zL!kBJP^L4*XG;f}^El)R*jE zaxKfRE2DqP7I#&hUqlx-fN4KP;@GVKy|up9O#PTstu~^$FN);i4btEy3W~L0)AN6P z5Meg27WVq+bQ1>;sdf=9rk+^H%u}NHiBI|*xf#G5X`#0z^wq|@sR}^RtJaQtn0YB% zstdD|cw5FcE4Hj=96i)!UEy*=B1$UDgGy#i1g{_1Usz}0v;m787!Nr9;WYZxBkd&k zIMLZ*rq>e{Fy96H{cnqqvzgScZR9^K$QQd9I7YBb?_*_G7hbE>_d-8;a_~akPM0&A zj;M-&ljsj;RRLOGHi*8*UPUbM{qe-qc=;%z=WgwV7nvgoeUt`M!7JCo+$_m04jm_1 z(d{R2C0(q9v5}0#>%#K)YosoMkI#y%Sg2u>Z!Ko9C0zPqD>uyVdYEZ|lXB-vCmAt= zQ_W_AKp^nXui~}0-DLQWe~OLe4w>LiH(zbGm+^P=aDqPIhO0KsEz0f+Mpm$UqGh%# z*_n`gk(T?SY_a4;W$*6GYA_t={r_t$`nhj4sy-k#Vny^>G2>l)QfCcu^03!yq1Gy73;*|U6T zOs9m@V6Haa;mFnJed$b`1v%c5tSIa#W3x>#Gf!9^g3^)~(^m_Gw~&aDYV;Kl7)S>@ z85N*622O6UiUscPqIJ&rdim+E{4#*g)rvOKGkV@|pwAZKiLq6El(D<-yNxKs?Un^RJHWPqI32|qWop<=Y~uMx8Gd* zRImA)IP+hr3G08!ov_;;&HrECQFDBo8-L^E2_mEY(A^5EIJZ;$_ErHI6(`jZ@B9y!(wd%^v1GeE z6SH6@z6jPPIDl)~#4CZ(U@%L};ANnrp_qa2-y={zPA9w2oy~9?gtEwA3j!41__yy< zw0qsu@RO+6w;`8bCFP@7uGW1&%kV%$iKPjycUG$2V`6vP@e7@n(Ur&_-uqQlr{=Zi z+V59lAFXfSDhiW{a_7B<5}ityeGMggx5H#gCfXW*H#;DsXqRIGUjGTH!Mg;LKTjRD zX)zORqKm_{!Yb^+d-lG(pA{)lD@P5mlv;D+)Nl6(OJr{Dn^T%F)_>YtHquSC_vnT8 z9(>=sM$0nWp;Gl~og1h0o(-hgat<9y?Mx4%-uf74Q6hI>>;=WB_{na_mO8<#;`d$<&bgG_}ZT;n~Xc104aM$;T3*`<3lrykI^kIhO$*7a7EU!a_ zUt>wdPqu1Qc`H5L7vrWna#Z+en@HByjHYenzkOPA%|83t3=O(I=S7X zKXNSPQ;H*FwANdeB=oqcO6!W{-$zbSyDkXNJ2)`WN7SAKt}Tt@x5Aihfpz=D3_h^q z|8FA_Q7`3Y*xU`?^LWX>^_nfp{F3yalO{}0ZZw@(PHlYZqn^wZh_F#O?}|>WI&KWn zKt(tUa65ut=*KSbHBvNi5o=L~Yt$rQns8h-{eK_jZ~t4Y>`EWkKJ)*Ydk?6lv*vGn zL0mvrP%N<0R4l9@h=_EsEU1XGtg9$3C<-E7q!VCK7L~S$igXpxReDi+0xCq8rhs$^ z5(P1V5RoJh2=Cm4W_zCB?>+DTod2G)lH_~8x6I6)d+&T^<{q-sVz0E7#;o381sRQ` zvkvE(SYbx0wr#MYzzO>RJPlBP^Gm8RTvP}7n>18>7iMlII85PMbl+}BWog^v8L?kJ zRK#QaUo;w!XptJd-SZwU7OHS{5ENsf<9Mp^@Z^g|>fw3K_5i$0IgBSsrP7$T0sRFx z^uZ7jNg$A$C|lI{1;t7j9rg0z1YK%aiUFiV_rv0e=5J7k>UIINI95rbdQR;*07S{M z-JPv}DPuDM4>^*8Yu8)&X&aQskpTaU=L_@hJ)Df7fk-J|FS~1jD78}QAu_QZk(SD~ z;J-ekTCX*Ew}I&whGI!)pv@JrbnW9ZJVaJE0zWF}sKsM=P5-a?B#k zpHJM+z9Ddwv*Kq%)`B=JV~0=sa+ey$zg0^(3*i^b?jWxX zaSP5L?MYCdvx@2xHdw9jF#b@98&x8jaU@Dpb$O6mbRejw2cj&psP6skI?~)8a7h2# zDff$38+o#d`r?AagA@;FZ7eyok!O=Ud*4pZhV3(k&G759AGur%<}<$xQSTQmVqi;p z`Z|}dpOu?=*#!)3yC26k`3^)I$!2(KdxWn#Q_ZNk+g*)uKMlZh&Z=U)edR~41-2Ds z5Wo|o=T!S<=#7X=dKqtv1uy-PNzau7<&wcx9$C%%$|;>W&xpC|PBMK4_Uo-YhK}_* zw@DN%b4yhb*{bO&Pb+YU=(tp*`EGYGUrn9lfr)Tg9?um);OEe~TJUC~=5kugdcHiR zBQiXBZno>#9&edV&iM50*2d_Ef+BXh+HB2+&xjEcdDzKqf?|4>;e0ijx~dUMUsU9q zrw@Kob;l4sdh2;RK>R)aEKmP}?FPkoW+-0P5dw+9=J_*G&i}8KMaldBw=_-&g0Q)J zQvY0#2T}gs{6PD}$vot7pv~Ak`6*TO8d6xRPE#k?#>0c^y>A;joI2#gVNSb(rZlk- zup#728BJvT$>&!S8@VMh4c29oy%6{XHd?opLDu`qrG1|sM;~DqsC+{7G^V-P{?Hqo zlmRcewET-p&uB>yC&&(Ln|@RmZhZ|cQn`xuWR;LUSP=pJmSyGI^g2s|n+fF(rMlgx zw#e15^ETc%POyaYNYOF?cG4SfaUZHHK0M8-+I~IT<60m9?@-xBb~ET+9Us!I28KiD zqwQC~OkmS8EyXI0ptT}PrmCLU5KIipuVe2;0rvKvi8M{X8Q%^#0Eqnd?d{*RHY@Ks z7m=%&rq`CJuHP23axY-SN7h@VQS2S*mPyg;d)}A5f8Bq>V_8r}=ozTI@E@w*#Qs!^ zi#@-QT)~_|PMS(Y-?j~r6_$IaEp^Z?aF;s9GtdwUA3ZJ99xtkBc54Z?Mo&BqBdRC% z+&?0a`G85bK>v0`k?ca);Pyh@1~+L$!f84pWB1?8j##^9!wE;qFy`vEuhm8n+@*ZXJ*4#znaF zG`0G$S|#|PHk2UC%p&p)@wD|jb?B8mp1>@0kMpaU?MU*jSKV3ee+5OMLWmK0xq>u$=0 zx#cAW@(2c|y#Q*>;s~D`^?_g>fJ;!f?F;$WL4TK#FSt-;>kB#kB)`bMTa{(GzUS-^ zQc$ba3zf2E)4hol-zAkwI7=C@p;$SHs^7f)B#u!_*DyaVsMg~BBf@^7ivD6QNBCIO z25?jmahdIo`MA~v1phoY2Qvgh$ev%_4XAnHt{Fw7fK94EVAZM2wbO9lDZ8I{^Mai~ zfhB19>4O`X`uQ*pkMU~-)fB;PhrfifiCITT{-fAH^vat96P4*}dHomFzq*^V9y=t`~f=OEpjpp~Dl|6e>C$Yov^tcdhu_BKFXvSP}n zn=fz_<4@ zB&=r+sC=$)GAH>wP=5o@eXV+dc_=Am-T#|cj;ZDy`V#@8(#rldJplBr@2Tng8Cpc5 zlH+8P$csMd+DzO;G5|)GTIC=v8hEm+Mq?qu1^sK7N=SmP1}ewf@^MkkXT`DZix9p! zag%0^yF4>S>`9M5e!raaX@*(o?=#)~q~tSC zxIjGz|AvpR@B=5y_I9e=XeaH=sD$dlWqh=_ z@Izp&<}|P@IvWjVuKnIi3phRaXSx6_<+n)=2e^1Vr=C}w9z5!OKhuL0L=cY40oQ-P z^zCDA52W?x^?-}eqk5nR&Ay#2Y7hHUE_t`Q8U3XHbthZl4g+oL@0lEePbACK^^@OMPe*fz$1?oFc3Dj{2=o5>De3`Dxx*Q0Y~^&!Cp+A~T&q+?pfYOcCJPH~R~vSNWzh zrpbzm{2Vr)_nfX=;S6cWzH)9z(#o86fGabN{n!3lQ%-nlo^dmF&2N?AkQs^EESaaJ z$hDl~Edicl^kY&vEkQpw=kdN5O$Wf_!kltBRbNjV9V);9- z)-ItBY4PEM@30^p1lqer1G#Qg&vqkcdQ*4?IIK3iGAPFOT&2RFFghe7iG_(37^P#S^o|kC7_61|Ez`2$KvT5N0(=h)Zv`J5jgiY3&q>5L> z;8jn-FQhw|4@GPCf&&?vH7j4pfnae;uD{g}lYp$<_p`*L1$y;KiI-DUAo9rJ%q7Z5 z*^Zc3KPzrlFgNsS|7Ggo1MHR^b>YY};J{&5^NC5_;hIPlcP9y!sg_?u_nvr-)XJ*U zE@2;s=$e-AQ>s@5S-Xaj-Ys@6&i#~FA8^>tPy1h7Jl@M;KqXlVciv#Pi$Oi1!xOQiCpRW zPF$so8b*$vO zp7ixr<`*-U`Ki3f%u8QL&FPPYD)ofw#-y*Fxej&S!e?#yi~0$bhA&5a-dJ4YGk;p4 zM#tMJwSZo_s(>7(X0`*LkLzln{bnBFMUyw~5?A>A_B-q>Ihe_XDEacJ^A!*tKXZcq z=1%-KFVPJ=OZ0Pp*xOL)_3V{bx|FJ_xnE?19L9~&AWJ&w?8VkLFk6VxQZ4bH_=%P` zs@}|R32t8#?#qJYveh)>nB;YQCYMPsd$U59CX8!=v$xsfr4m=IuesKj3^h002CGFv;?{on1mLaB!f%x*%QXDMP;`L%#;JE#Cw@U(Gb`9oMOa1Vm$iJWY>#MF1yj%c_bv~3h@`YS|zbIYc3 z{b&I?jwkXa*|{Y9yKi1D?zX;!hu8_qlkSPTc{9ZcB0+o3g>4&p7JbOHm5?yj8Vu(w z4?mB|oQrUVRUhjI5fCo6ma=F$RoyBh0gKI$ertNq4~cL+=anMV?MiP z&405$BEf-QL0fR<)X@V4lXM=x+AdG0V8gJM^r)-LuLWujO~zct6PYhkz#h$twTlT+LgHjqJ00m~dI} z$v@`1Q0Nx`OS@jH2BhZee|VHs0+xMtK>Q#PTA+-sro|+RS7K`ZMtqDdt$v0{aGTfU zzbB0#6gBpF$%GU`k#ivLTs@}tEQle7Izko3k2`b?8`<->ziJ9r%aU0t>04eXrzF*J>O%r&5t^mxe7+U#p%%Y3 zM5(dzrKE}Eh|Uey;M}F>gB_~$Yw^Uk^xcUVD?6*R;J>hGewDJNi)TuMx~CNiL10dR z3h$6TKH&U70CoU-;IMJ4>?)E9$@lf8=b5`*~Gb!Uet>`e>*; zgbrzKOft($Mz-?DS(sDCGJMEAKW>t*e+{xy9d8n-@G{gYu+gn}aG+eaWoZ1W=XINp zL-kWMxm4Os@WSYYIp$TfVuRJhl=8ngdmV2-yUrFxspY}o6}52`j4buF5#IeH#6Ms? zd1z1$iO(Hynact=ZJ6xmeLS>4jN1s^%?{=RlzPv9pf?8{$LRbVw) zUZ2rsgp{{udkcg|tX?Y?`Qq8hTH7@MgRSQ|9Srs_7H*tQ@^qkEk1wwGvKc6T9%M*Q zoVvwNZ8e_e{w)-3cq1yip?W5T0GV-s(QNejl&UEB9-Gd(KO4=viv97a(}x7D>ua zg*C3XmU%;$*wUm9Sxg5Ph*e@%1Oc0dE7@oo{cN;AL!8k57dlmax$t@-h{9cz zY&Q`$LV^HV-4(2!PJ4Po%`i2?)3P! zVuy|Z(6^0N5PtW#=0L;d2lv+S-keN9r$hCft%En?7|0d>Kd#;L7W81xxuZII$(&!XHHOKm zYDvn@wxCl(`)yjJ_XsHL;T2lTXGZiC5^)h&+d7Vd>-Gp#G&6uE61xQ+}Bf1lXw-fB4#Y@}BFY0@^~4~+tC zo{*(M8C1}2*&DF{mDKF|qOJ8-d3+L%Wws82Y8RI}U@_p-0Z`yZ1)h>ic;?_Ly=(xv+$Ec zRpL|k^0S`VgS_2(4qP`OveQznH*%s3zmmaOo_wy)=4qYj2h6EaJnj=Nl{RWPG5M*H zTAZzNRRL$R@#j1;AZC3f>38_F=&jcA2D}P40yE&OUMZ4&T}H6*81LhApn<|fw*33Z zu7-?e{-oxGt=VUHYwuRgz9zh$*b&46Vff+ex3tG6 zd{6FxiMvs`A*v@2G#M$aP1>bCN&8?1y5$wn8?x7W9?=8kN0GRx-Q{z@>Y&X&LZRdK zm8ZAe88wn%^fZ8D@&*WVblHsXrVR(f`NG!O8m`D?<@l&VGW%fniW|D0QTpUn*AokWPDO}RJl^5iW}=~a41_VwLCt< z@tHN^^r<$ktHVj7|FKk0&%}9h4&JeVX_0bCB&D{bz>Izf27jZrOHix>;23T8*=LwE zzOdvi{(XX5dgb4?o;E^m(VKZpu3LjUn>~688!Uz0TRTWu`V)@~%5kD1zA)n!2ZPl& z`U3d$fFtG+H5;ntNwy9g5ABZ+c z>FF3@mcSvzzBuRZZi=e&C;?tN~U8eR1+FnpLPfDIitZBcI#Pt4)J@h{WJ-D0Y z>8LX#28WLGb`MXy4E1!{*#1!ClkbKW%t}D0#qsBMA1L72G z{~&(CV)h9+%g5<(40yB=MTRpKy-n;^oXyENnR(BG&Qo*h-5>PQ;tvng3{t{M-d;Um zFjY^OJZ0CJM}00mRd=t7;nSrwc3r_H707ueP~^4rDbaS{sH_ zAZ=yZ0oTssU~k2_L$-Y$$~xB3;P0-LLa!YlSBQcc2h1B*uy}?c+5@Lby;n{OLWlr%vW**@0;5Da8w{`vTHO|cT0GXvu>McL~H}G1Bn~we!AvM zrIAD0Ir$rDOxXYE3V%-leML!#Go9N!a`aS>1_Vp2J+@(B^RrG>9Q|TLhjUT&lPc@t zQj>|Al4Su4kU7MPl51im+E(yH#AR=U#jloW6tfe8g)#(%by-K~84n@QTC9Y!vQ>|M z(1N!+Y5TvAWV^=Os}(!;WcS@%aQD<$jj=hLvh*uNm%r;0?LL3n4#De*?(eY%*10*l zcbR7Nk(|)y_SdoCHIcWFYA9rb4!p50<{JdzN~!b9yfbp0k8H47!fh}rki3P*J}HvZ zkr`UAydD?}Ne8m-FvVE}a31X?r^&K!yin?hbXr+ST%n$M2l;vy3R}$ukA8#jhb+J^ zCC^%lc4;r?Ec|>k=$Q|Xq-H&$z%={~qR#*E3t@#9nh%sa5X<|plNt%}l9-X$O|m?D zLA?gO*^!lXwRH7Cq2AU=iG&4&U!GSuRJc43e)cCg%RJHMVYSO`wcG@Df1N;nip4RF zQJNS*?;6%vNQWkwtXK;h_$-2(iMy5%%})Z7j3^~XT5mF&aJn9-669PHJounSc@KqA zV89q%@wP|f92|S(`0o$jEpQ@?~;Z7E@q)X}WID`PNIIc?-X$0r3yP-_+@simC$~S8GY~CZ- zSQatDk`pQmJH*(f6Jyea2Ou#4N$J6J@<5zQ%fR?l*VR=kMHR-Ez_y>VNU=`gts3m= zG$R)5FTeh#wS||o6hP#DGC~fZ(wZox-F{nafaICQA6vAK*olx@KJW>Po910L^J_inUbqQe1?-_9w5R~OENi`WD4lApn5`w|`z9 zeM1f(Gh0`AWQcWr=IX1XI~7*a*|uol#-b9JM9O%ZiZU(6!tGG#mDnbGY$Bhz{HQ5r z6Kr?Nnp`48Guv<2Na6#ZvVI9m&^Dge+ODv za^90=pN;se0(Te-#f;a(yX)Ul&VfX2L^{|l?&7p@_&dFy#2!R@_B})v>=5YfMD}aI zuoDeAieJx(V_`*ae!TLH;iJtx=a>OxSj@pCa##TxkFrQHW692m<#&3YEUYqUD*K=L$$2B%w#o_~;cDJK`lV zK88wyp=|Tz#jjOFT+S};IE>FyZYxk2F~mqHw?V*1VHkMiu`cOy`tUMxcW5Way#8(J z1yKdw4l68S_^)tNIS2ci(=7?@o)B@4gyN5{Wnr4rOOdp2rytjrUAc@f=1Orj&auhK zVUST&TeXEl!pKODe&*}vg_N5X`<@yRz20cM2p2*8P>mWKc4#NFK zUSbEw0KS51VV``f;vX*hhR86Dt`rW1b|T#1N(#U;sEl;5e~m0cT%ZgBRnXsu2z(i` zmv99|GY6ZfAs~-KQDl<_G-DcxlGZ~2&B?7ZVAV`hvqffr1x}IqRs3g|XBxNq9&9oX zE}HExAVKr`i^lT*=l&wZ<(XDG0=K4-pm|W8>>P-8Oq=l~i1v(Uuf!JmB@R|N4el(z z@M>C+rr|&UC1FUR=+p~~=Il!_39?p}#Eos2#)E6YesX36^YC(Ny? z*un<~pUlYxN8+c2X%(T={H!U|g|i8lp$qApNjY!sqN%w#;)^bD$5yN% zff;9;Wp;ju#7&O3hG+Cb@>}kN-dA@CCi=z`oSnssFfd#qp6W<)IF(S;GXFzMEF5c zhl}$%b{*0U0Q}A!IV@xA*5BGM#tk>;_7L)kAYnvu{l~IpBELab=cL2F4r~*$)Z7-Z zB%H>iYXg<0CkcvF7kN1?6lz>l5>#$+)^yt_VtwCE)bnY?60r^n%xIv^&{asqaB&88 zMeRd0(Wf803w|4ZbOn;ae4Bz?vAGI~W&xn)_GN}$Gaxt;3Mhc2578wBM!lQ&;moKx zC@&!x`R#&V{3X05oACtj1g@G#$!xg%n47A9Kv&MgMCQoe=e(Z`)+23K`}1M%;__SvD`i);_Ty{mQVPM_&9_^31-Zj+@x4e@!MX|ZZ-wNje=%G{_vFRS{F zO0!=sH;B~w_#Cp8g><=g9X0U~z>yt1}z{*0Y~S1${ZUxQX@6&TG`1+e13 zXQX)FGZt`a42DJd&NL#8_7WgIfAi&E+Jnv=w_vA@GF||sAq`95s+E9qb65xS?#lDr zk@Ap<(ApbU14BhImQz<+Zs4XGJQXQ!^x;M!H^ z&(=wv-DP`Lf)l%_YB};IxK^z>!MEl_2>aF1{whGuR4qQ0Hq@JsH3!#Tw5l!IBUteu zoS=w(pN@`-J}2@ZhaV@w+CVoWJLJ0Mq?v9(b}XdC^pstz_GoN8OnTyWLjdcFsJz2Q ztFv{9BYu(_x<*?tcx%p0VU`|kr&CSLUG?nzuwZjb1!UP<%M!e?Q{mo^t|xFUR9~n2 z*reA-xuGND{8ZnuCpII7wMA}_k!9!f6oZY=OtPh~_tYl)csUW&M{!eqck~5sN=B>r z|J7{z2c$-s7G*TSK0A}l|HdiERpCe}XuiP3up3h1H8!ZX=00y|!TmyG$`15)2PkSX zDpO;ds!iPzbwtmfRcoePZzxt1iHy9eU=Yyq8O+i!D0a}Y`JGBu zwRn`><|Sbu(Cm`lW}kH=vyuc3suK2hEt|cZ-l|<&n(zb>IY9m6?NpWD745YcD7rx7 z+EU8Ly(5z9C(^)DST=R%ZuJlZt)@`m7)d_GaM$vrREFFpfZMUF7UTkecGaXpM1rbK zpIxqpLE=6>;((pU?K9nKP6#tU9Yvc)PChE6bwM9+++Dx1W$rm6vUSID57Eg^`vb}6 z>4yyfVj7L}SQb1hzcLY^FSvee^NBxu(vCL$Lu>49w5sNL>ohWslSHVua7aS9q+{1{ z`0$$OA$Cw@&hcb*H^zm#1?; z5xqUbBPuY8bTGN(+=(f+Fo^ok1aVzPRD6LC!F6h4N_r~wwwlehOladQAW_t9ZKCn6 z;4^QnRaHhGZ}$Xb?43oGl5ZBd+rQz7jReWJk`&T=bGCJWPbs0bYBv2sl}a7fEu5}g0!A;4?FHpHoEhWENC$P;cj8HmO>ulNTRkJ+cJ--1-g>WOV&C(Y; zSYjf>10d)NTq}r9KY$0#6!3e#oKaNADDAbxbZi!EwLr<07NcY)(?^KubcSFs*uql~ zF=hMZ$#zU6Tb~X}?n$#mV=680_;=p^>PQT+-A(H}&Hur@uespS^^3fhQ83Av$cQgd zhyZ6{qSteK4+{zKukTMdN3;+nfX^|cXU@BU4L z#jyb8>Ych)I&oXLtMfx6)$5+q=p;2Dz-}X3$Gt;54k89_rdUtw6$i}(hSv9i zt#9{WQUAm~yN&I+3lmqrI3^1kJj!k4Is3c}XOm4L?bErXD zXR@h*IUcX*cL`Q8jt@8Nl@2hp^D6^%eP7k;H#a2@yVc$Xk?Ol6uQ}Ru_(1Ylm@UL4*k7~?Kb5{L zqD@JqMPd)HK8>+A;M3mJ{v=jsaQL|`NoF!UBujtex2Jm*JC=j5a;t-G#qCwn+mu94 zAFuTSo zRROC7NnzwvCtf^xECoq2`YYQECoD#To#h?KO62ECS4yWm25T30_|#~e3tQ(*R0$}} z&LE`A5M)0(O^x%5J|4a4{^sUoe3&s=i8ihr69`Tup_ZTS1UqVPv`mIN+u zg|jOHjK(ih6}5+shg{Pc37UGeeNjm(z}l*o%8MX>B%T^_X!EpuK+xddHZ0ofedUJq z(bo`mv|;f!T1)ZH=tS|z2auT}98eqK3Yg-(&Y}{|oo7A3BAy1ZL<6o=X6GX_lv@%I z@i>}BTTz?u>D;WWh%G!R^5WyTx?j7ON}8dkAB0I@$LXY$ZEI^Wfq5Fsv`@^kkAT{- z?wdu1!<7ARZI_b9=zc^l=nQP@9;4RIvn$+kz}%y)v-}c7k{C`riXhC?0PL`VcE02Y zuws_@#Oz{^A(K^L_qgUe-K3+MRq6B2%{I+}(Ji9Che zHCIZ*jAN{ugRNJ1|C|EJ@)iSow&`0$92bo+-g!E#7z%-l{L}*aDEZw@2naWRi)H#x zJ^Yk~AH3AyWr$(~Bf*XP)bne)5YrOM`T>GmD?3$_*#}eOkq%CkRc7MUztrnxe^4TNZ9jX9-CM_ z-ULQp{3H*NR)m+AJkASi-#DmTU5L-WhudVe;?$lt(E(DlE>@Her@k&GVmi%+bE_5P zAh-=uRjPgRqO7BWXIH;gqh5E$({IFK&a&sLSyOmN#Kk%g`-3o3Gb2Cit7Y~3D=pwP zB4`q;K=R#C2KeFlsi3gVnqMtz$?JQ10TSfH)E1#Mtx+Z;8iIv4^}>77)ICu^5$h}% z1NBawhsZh86^IuR;1+XBLS-rl;>21$VF&Iu08_{KB?s4msN|p!UWcP3#lt&A)$ScFBbc?nu6W)cXuCjfaEEY(9c%5+u4Wk zcu4r`IRS@|FQ>^@Oy#`Qlexj_F%d)8fXNxXEGQNrXGOZ`RL_Id$#^yG)nbEHAH}j;N3H_KE$_#Z}=>Z#s1WO!``+i(3?vgL4x=8hzmrtZf+`+{OdHou==+9L?x;I(0W8hymThuj}YpCrZWv9u}Iqjps%R7pz99dodQ;P_P}yUr}Q#|)F}Nq`OP`*5O39O_9Pc6_{@ zl@(fAtHQXZfn(paHc%F_fH4|jFuqgnQJ!u6T5Kk@JBQY}+{ouwyQg>#I+m!+|# zr8~gltp!~jL(h#FZ1oxv?;ifEA#6{JUP65G`R2YmbnZ|DEBLkFnRz;BF$x1l98+n^ zEkoj8bL>BDSFJ#Vf*_{7+IN0}R!$xNY3ivx&>O1vr}RP$mBkc}bRf^V*S*q7*E3ip%;h2Hf#I4WiS!Nzq}WoPDs>#>LwPZVavz zixaJ>{|pwl@do|68#1lH`t#J3(H|EBPC7Z zUi@pi_80_?R+aHuv)ZXy`rW5nYU;2bCWqiynV!Cq+d%x5c@3mQ`1EkQ*gWw{BDHm; ziMg@QP*HtXPL}^@+5u7#WWRnRMwFvN=+rOT;Hm;Y<#_sKNVlJ@)%lCoS(prK2BbZU z3n$>{EJPQTJU7yAJuX#1V!@f&``+AuwC@wKCzOvLm*bniqE4@(bIa$K3v1_5UY!BT z{cH3MeN`v}>E|lr`x@wKV72Mnh%RFR(51@*AoE{%MxFU8`L_^n1%OwtbC)`3K5)ro znt1|xRs(xPHbC_o`IM2?b$^2#@vqFh&DkjBK<-`irXpvX^a=%i0zukn*F{{^Mv9(V z%B%PeyQ|;RZ+%U97XudrUdHPLTU9AVV`2lVe?8%0n1=k&=$7EIVYK$xQ^>hsTz>k_ zR?T}yT2G5?_00m$4tYLd-}3&)KDUG+ z?R4Fxq40;T?^I>CYp3et)sQ19x96dvU&{7#$j>NTg5=&DMG7dP_cc6M>R>7EI2VS3 zS6P{3q|bgR2$;N(8fE?1*23Yf7)mq+n=jdqXVs)!{7UQE_m_*7z1K^H;{Y^-eiA|p z(f+XoV1;N!jXOSoLdQspW!70dVuJ_Bc z@X#q4RxyQUqFkT9sA9F?F&Sx6|K%29tro{9_qpxp`zu7x@8Bfs@YTNH``bXsBlPUf z$+V?4ahLI4wd#tH<{8;)k3HIC)m(dJ$SYB$efd_hjh-i$i=rqXA9>n5+E?Cs^mg-R z7UVr5gA*4Kf)!fyDjg~|EI+GqZm@h|RARR(ob*@=n*E6PwT5bHGd#d)KtlQUQJEG~ zb(?ir?g8LCM;rGQ*8LQ1*0qxGYwPLV8Ty%$vP~X7MDP~L)3vH)OulI}>gqge0xlX- z+*b)OU2Ew4V4il@(tpu9imQVO+I049`=7fV?=v#stjSt%R4|DY*@j+&W#+2kKd+xZ ziW}3A!a$@boj-jHSMOJ)yrVz^0zQtW1VZ%t@*qyAX7Wfv|F!1Q7P2LyIIm!JR+0GU z&8D|WL!)RIBZjC+Q?k}!PL&RcU?@a+iV>s%*eT-H_f;)@9rD}cpBOC2CTDNDrRM~8 zXt0ZL=K1#(JH!&o^Yl*x8OsQ-At3Pg>nNxqFH)iFEPZ%5EOfQX;73c9k2!EM$?SE$ zv|ml(Y*T)<@9XCTS&IKaZGide`ivs~PB)j5di)0oz;UiKYlG#;a%tayfmRWh;}Q^`&H1^C zR%NeauJM>p;<`2R&0G}5nyj?1vaY%jSiKquaKX6ywC_S25)I6!M^LdmIOKH5Z5?8W zX8p`<;VA4gSBf!ALLq&AdMK!+3&y;-K}Tn~@ZH0uBXx2aD-qRUg9ERLN78QQZ0N~_ zJDt#5H*1w?V#rWGZC1X*g(l{PH&08@A2A!(Q>uOvgzMnA(9E+xIq287my?~LS2#Jz zkt70Ibdk@zE5yV6*b6ZHNYt2?2-iMz3tKD)3;$cIgZ7NuvCEMXK9_wDGH_lgG8BMB zQR$CZVs&;}AS@kn!v~}_(zZwcMcyWraueeFIdw$7N$4YUi;}ltTEO8JqV}uG7Pg$G zt&ND8e!yfY!S3$dR`Sac==%Ri5R{_>JLLHe9_2k+g)j{cc*afM{Dn<%c74tN2N8u+ z5R7|}l4hmp%Yihqnjm3e3=RNItUv`0MH->M#Ss#=Bn@tlcxkwdC7Y(RQ@81g% zK!#$?xGoZNs0$aN<$z%S06;p8R)Ir@x7&K2*I3A3jL=suj)=g!S;QMOg|Jt0DkL>w z=B|zePYEtiJ_8d@yB%=iIuVwNTq%%Z;liVH$S4CKrUFvby?zebfzm@9DEvxQ-e!BWcc#HEwTB# zW8xG=$RnYyYDN;cJ%xs!JtZPMf8I;zBccMAsb^8Ar=?_`93jvP0ms>#O~EYD zX6l(mu;)nxG|j1piz;#*AGrTU9f=G$SE0KLBI&=;4PLCMwDyjW&JLI$O%{mpa z@q=B$>0PI>1lolSiEh99EIyV#wCd&c_TFD*Upj8Rwa7Ty>7PAa(zi~mIiB!qi~2^V zyZ=CNilNOmtHvcOuWep$aYAT!AO7bBn{A>U+%=YnneioR(1fqOusG-;jq999>h3SX z6N|L_JuQtszS!n7q&^t$RyU}f6`mY%PloS2+-cj`XuMbQ;9g1hrdQ{ijQ65%?%Uy; z@!ku8LVTa@*=l_FCne%uN`yvS1o}7t-#8!7EPeTBVE3oyQA+5#HsL}O}<#;G+b+s?lrs>0mXNV>Qct0OU}>@W&ww8IsQO5`5M2e z%h*p9(GAW1BSHDbB?>QOJ;H_=e--$rx@t665Hl3VeMOo(uQDS{!T_;nct1SEv@lg7*x91{AiB_|wPAE&zlQ{y=dM1$#5vkne)qdxAN zI_GsPAb-qXc;L9nt zbhuo`>LNacS}Ic;BD{ESx$q#o+@0=n*I%wl^o<`wZvZ}=E!pU-T!PQ5xjxVS@ zyyL?>vqQKT=-)cx2e&f5B%6M&Am)a7F%SvV`w$fYBu(V0w5@jJ=1epMB^`vnOFKb55VNx$Rr|z-YfFcJkkR z9W7}iEy~|j-a1D;%>92Oo%ica7`du!yQ;Xoez;|b)#h?rd^AE_)hQv?x!;LBT21-UL2v$ zL=sy39`qIGR!S~cZ#CNUg=w-AR@%|*Jt?hGtER>@b%w=9GT*w+TqZfg{^_^g-vOO~ z2;{zmhf@S~Y*qreV$Bg4tBPb@em__=evb9Cl4?t>o1W?tHI98W=a#`uRR5F0iPt^q#ZB(L+v)O0QlxrMhcLaigx^Rj z=d9Kiux@8zY(_`Jxiv_S z3-E-{o;Xj~ur>+f6t?gO(UOz>`pbOBaiHGiG8g21{3jmD{SW!Qdne~U`)^ezIeGsN(r8vO zZ-v*Crst4hbr-wpK+L`HmmzsafEAU61eVvao!7|)3tUdBv zrcsC8wYTA10To1Id5rY6RVNfOS21zAHxGppvkvTZNzk}q&QkPTQX#>s&kX%r)bo{| zXe!bIOS9jmn-`t4@vz-ucB6TvKox6Zz=Co2ozqlf4CdyKoH<+K^X5L0yhN^kpKs%3 zeDdRW+NyUG1M1BhGAHSa8>Cvx8HVli3N*4%A7T{T@Ue3xT16U)>*#wD->Pg_AGM zeJ61{hGRMsq+3Hi+J?-j@h@iiJOz)Qe5Uc|imLr0phgV|cTF=fx_^C5k3ELfo)37j zLPm%{tJgXSi(EU~_UqI!Q&aJ!l(oEd9keprt|X7C4xO0hANhPQ1%_|p?OYj?5KU*x zDekvl5QN=R-g)^4QDPpBku#7l!so~AR&Ml(2s}cxRq$=DM{V4b^i1ty)=upwujM`J zZk}^e&s31N4~Jna;W=>VS)o9H#agS<$&&8T%zW3~sI~&fj>q{WRGel89}gberD7+H zJ>|O^&M#~DwAM)fRfes6W#_E@CMdre^LUG6b*<{%vNuGeFJdu&7Mk$n%6;ATPC*jjq-@5SPS(5hgY#zjf@9|zEa|Md$i`*;($c5FujM<|s0j)%^Rl#I7gTuB74E*?m3<1kyL=Lxd|BWaObv1eBi5X7DG-R1t0QBj zm3CB0Nx1~w;$gQ8Zo!>gM}L>Opw)zLqOHZLt5{{rNvITh@H2;Hlv2{ckmNXe&2r53 zRnBe$_o^G3&CeZZKj2)dGWb3Kw`Gvw)S9%)dFro5w%V5N0KZOQY1~G~1P%SLY75^7 zK<=zbQEAWC7JiANgiEeP_C~<{_7 zLhTneTe1{&hO&WS=@H9F=cth|3g1CIv+yZSrzcyZigE9L;(3?$nHpwVk|@7!PEzC# zdepL1erz%fR(;o^erphgdDW>0R(IW#W6F7_8^g8nYv*R!^cT?TJvTD&SC8^~)9hu0 z`d$vd(3RM60ruCDnjUxIKKXCG^z|zKT{RCy=@LAul2dWp1zPWr9=@|%>heYs zqx!CdRn$$4W64^6r`y%sCq89T@9H(xRGbwQxKWN7%Bfn(6X4f@wPOf%HxzPIX{U9# z9IyZ6Vd}w9>8pJ@|7_s_wOFm)T)3{MdoOigwM9!7N9Qq)VRx2yx^D#G;$xqSRLK-z z_Nn5P2H&LOJLlC@6@B4viH6tftOh3|?Fm==t>+q+X`SqeQWkIdkA<=WomqCfc4OX1Q@oCV4P-*=-0iR5tr&)+^|6)nuP>66`p3h+-U~&DQR;5)KzZAuyjaDxPNu;{5 zUB0pb9^YWfw`zg4?c1y=gKblXqDArcEez#%TbFKiYSR6~!7ce|p^nv%zrJLafl~ll zjOg8YPDbKYqdabm#*hdq{^QkrIR*7ij$$qRHC^@p$XE^0O&nqk;=SLe=Zh;^#O(+p zFX$VxZCaN7mM*<|!rXL-RfB3b$JaOcl)8j8 z9$3Wsle3^E%2)|jP4>qmCDRiX6^vMK3U1sTEMr{m@@X`-bCfC&7ElX|0Qq?OUhVQX zKmH=n+6TK$%MA>oEuTcyh^xpl7qM?123)IF@f^rp*=kcn2>P~ceywl5v*ru%h|=~{ z?pV`m146f>Wl_2X5r$4o{w4E+>7S3t;=EM{4<4*MVj-99LAnx1(+FI~gGZ}*V@p<8 zTJcez#Q`XWRa zqx1GszDSrarQ|?^M?k9g6zly+18$fudT&?xkS5F&kJqjZ%d*oVq9Xz=+!N08Q67?D zI+;kW?U@)Z_*g`MayDV)4`Z-hVSa!;pSb?sZ%Eep1lOJAo0o>gCnY6$ZeT=T(EUcC zi-G(@PqzwqS?xf?2WvqISo1`61X?G-I3abD+5%$jq0O}{ePao>($)QgBEd(ROz`{o zR|RF`yFHbeJG14oS1a>zex`I4*>5dLlCkPvBS!6~lM9yEw5alh75x08fQ}&h3H5fr zK&v|JXp8KJ^Wk+G%iUy{kH9a*rKDEv5Gw&h*si`9KQedDJ)R^X4+tDUk<#1K5^%^ z$Wky;>@A1=kd8D*aljJ)Nb*3B@HY;clD2;=%chC%6coBqPW0Dd9&ggs{QPjqo!#ZZ zOTK!WW2^fXMeD(4b%JB%Mb;tp_LZ1Fi0y*W1Ypnmu5zXB=SB&rWLX z8xKtRDIj#xFTG61YH$P^qEGtHuh{p4@{bqV#+DL7PRyFR!K#&Dyt{m}gnZ||iMFq2 zCCJzK)|y$r9p1U&ZHUzaXIBxoE4Sxn^^am6KzS z^LWCAM!!p>r(0)i$Y4;^sJpZCrmFTg=byjA9BRO|(@&dvzYq>x(K%Zo!V7xd;s)O7 zTx!kh{X1WxdyxHcx!kVu%84Hw{?NG5W07h7qD7z5!LSb}?;(hkR=dlOwO_)r{4-p! z?~es}7Da5pi03#Hvs5yLB@YZMwB)?Cm!S``9#xIrwifJur*Xs2@fi+_Z!Xk1FJDn& zPPm#C-YWX)bV4TggfAhW^T6cCj|M%8p2^W48A;HBw)6qqj*~jSzmy z!A3Zk%6FB?En!zbF_8QjhyxjBfBeSAap_AWE*$c1mt1>m0Z-XL_(v)=(63PCgCyxi zg?Xn0Q~Z3H4mzFuZ$6q2X`HoiOjx>=KWGDNJoZod8VU9F_LUQFk%20lj~J4pCHCFV zb{iOiJd;`}GbUuPCUP)`Zyjes)ch5ufe`=|P=; zg5aRpSw7o!R0}dmygyh!k4XqZ<8~ctG2Y9e)wDoxewg=6@;sU^({R%)rZW%h`v1It zuOtu{(#Qcl=5>to5fO6+%;Ta)^B{s5R1R6^-dFYko;IrjX2q35i7(;2F99{Q#`!cV z&yr>ilH>|KqW?6SIIsEHT4vD1FJ5ah=I8PdnvW1o`x0)PCnit;t2xDQpA__ap83Yw=d!kcnC$K-ec;hcA~i0v3OcmxaEDY zn19*KW9@>{oY|Zs1!|~6&_ekCrIHBa4sU)TQ za27gcEMYds#r{9{#Xh8enW|KvJ=S!@^r5|n^xY4lucb|X@b9J_uSX9q$r>nT-#BAB zaVE=KZ+jwk;_LF`n`69)4(OrZ3GrR$tm%6|i29sC0{Q>7`>oyYwR-@yl)B$99oT;cleB zs+uAZKXi-?k&lV-Ymb%YgdiU_hc)xUQ|WIqYnH{0)i_aE&0+6u)vc-r=PT7DN?L4$ z=(1Z-lpkQumz(!b$7tQG8j_sa8rQ~n?X>(lS$C?cWQ#G$m|y=n=5L!J ze~7-UT|rdml(~J=5FiSa;hSKQPLu7`khRw-Nx3rPuqNdKyk1gR6D5)PlQL6uT1jE z&s0#DUq2gLM@OR$C4`j@g&LDWPVP{l+tpvhi#~t-mJCZs`9xia7+6TD^KLbuk0*Qg zlz~47$K0p5fUJVOvDqZMdIa#B(0-&5_M=OVv-!dcZZ|KJ?0OtShG=-_nx3NbdQYe;l?-+25p z1!p?4QwWjy(kUhe{E*l}CGJEUvJ>$pSCZLH6Q?YRnj8w749M11Pjd`CYZillHYkP@ zKnx8OxizIGU%@PYG8S~wQm7f>MJNW^Q#x`jGMA!{`e4)@s>si?(4{#&^C9^jz7unq z5VrssURz{*UE?V0WJp3;TlMqLR(HbQz6)>Lp_RI`r-rg6rz+LIkj}-X8zkUBdRa6S z(mH$p7g^ZvsZZW6pJD+&yl(yl7w(*oMccUfn@lVBcG-lD(uw6B3Pv zW48HjyA_CNW~TbfIUff^J24Y0m$+GeYjq8Q&4{a(>!mh7hs0f+;l1%GQ zWhk}B*nF0l|1MaT>N--!o3~x&>3eL82t1YesU(}>Wk#6@9-Q5{L)1g}-^EuoZCMr1 z4(D#*Vkrnb1bD1fDnb8I56V*CE9yTOUe6!$?%j`*OI=y95kZmP<>Y7dS$f3!79iAi z_LnZ(xsFftU7!Ck4|(aLb+c~7 zz@sN;DY}lbXPiV&`?WqdJdp?HGFdZr%$+q~O!TD~|$kOXP! zzklEP*wW*>Rg{9*iO7nw?8A@p5-GojWdUYe4 zF`oQLf8wmXM&SE>!YNe~-o88H=t+f{)#b5qB~kdEa$n`Rzss-pOIyrrH_)0?t07m( zQ1i?CPi``rPT^S@k+$fWsT+~}%bGfMo5(2MgnaBt{@!$GDq6Yo-Bt_#BJ-^*YG>44 zVJrLZJhYdgDwX#h0Xw;_bzpFer9*3cYem5=!*}tp6-x$qOa=%18=9?EEM;LW+sX$w zh-@-8zZ3l9qP#AX7;u0&N7hQ|UCh@?3#UIV+};&RGmy{R*#BZxMcK>yZ9)=;iWhEn z7&_X;71GB%!XG2xt$PFEBSa zq|s9s@eN(lhe^aRY02XlL~IhpOK{-{E>9VefT17b2(Y+uY##8i5;cupp;aayNXOpv z%I+H5OKP-=GAU{z&o!Y?%ND}${p0broFPwTsSQ?32OHJps5|fSAS8M^pKPXKxuZ~i zktmhu5`v?nHL7{O{Rczcw!~MShONxj6i|uk_Aa0JTA0eqH5BoBcY}oWCJHPeZm5~i zcDTPSPujUk>&5K1wwoqZOqhD$>M7q^W-{3SD>{*g!dMaVkW zl`0eI$K;bjf(svT(wScvg+4dMBod}%YRb!uAPbE=vkAS*326jmu~?qDVN7l=o8k{kT{B6^c~HJ92+s;K2qu~#>b}nyDbL4KLohCrk(l2gdD9OTsq-H z9OjdR(Lnj2$hqd^9ggMqw!9oDXO3FPhk4DWDJxYBp&gwzeVV$?XMXY5uSp}uH^By$ zhMjhaD#F3prcQC9{Jzf2T?}Z0-l?ZZj?G9}4GN5(z7zDtO;jmXkOWoewhh`$|q-1w8SarI$CmE(o4vJYRzx2v@}?m(86 zmN~bVE>~V6mfqPR+rRHn7&G3<{yW1X%fF>)yz^NOW~acACv)gIP3m~3M@Zjz_3H)n zf=)|tU>{DWc-RUzNV*rd5bRG+Cs>qa6Bs~uciiwYz!oMGQk}x9Mm0yOhHe2NU`RW2 z;xM$Ej%Emg*myksi5cI<`UTX0U61rHW&5R{tf(bx2W0BlG1@BIA0c3Lv;9V-jEJ>o z)#LbKb0AR0{bDs+MhHN*zdU|5KgwUsHS9otn=HBVyQ~mEwvI{u)DH zp-Z>#fr9Cuf^|@i`p!VrlGgNLE;w8QsclJnYK!*EL!RIW5rvc2oes~}VJ);r*z-}X}Tf@$4&|s^_UHUfbRT9_1n%#aY({Ti-mU`RplA> zxZ&OMkE)qdc2*QE7Y7+Da5$T|?sY17T-8@7P60cn{Du&MtWMRg0N4Iq$N2ROnt!E-dtqE|!nGfzh#;LiFG+tpLP;_nn4uVDA0%y@ ztS0l~{24X7%GwP5q)^kJ6}^;1*V8=b)f$JYtNi$oWRg?wh??f4(Z#*%@ezeA8zppcvs8CZ_#TI=3{pV3A-ZaY(3s2l{B`Y z-c|ai1FCalncduv9qZs{Tmn0O@Z{m8h@CiKd3AcY@% ziy{a233o5tVnCaC& zcm9kp;am3ZTl$)uOQQU!a5(*^eLdhUK5IS|;?$_+yA*2HGNi_h`}tiK@1P~Q;oLvh z)V_ml%t`7bV%|vNmR=_F&Z#1rsZ;ZpBlyk{rwCRXem)3z>YRoQex*b_d4wax0=C=7 zi6d%;(YzfVN$o8$S4{`5$)Cm2UW>k*#sm6@@Daae(2 z@%)&BUXzC)Wi(!;yrJ=d47{G@h2ECE$K*c6xsD|3ChfWRU>kVbeI#K#Fmpc1jZVSj zB4IAC4Gu>KiNf7eCeUz8W%&N|_?`}`LkzwvpoC#eYLs)1iP+84lj8a~O+L)0We7wW z@c~l{#D`9*zR1oW6{1M@Y-{USFCIkZeHr$#5~J`|5#OxMW&7RM372E$o{yx}HDJpc z=g|YkWeh??PS?rE9BJoE6~5svVYutr!R=Pyfu7KOFYnrIwNKAAyZxQ!Z5L$3==sSe?`F{h+eqT`K}JQ4p(HS=JHyE zzsBIlPz?ZR4+SLKQ;|MqeBWMs4+acrDhI!bj^Tu}tf%5NyoHmm5W;IIi1qA?+tzLP z!d!AsIsB*6{q&!I^1etC72d&Q7!~Mw7&l5%Zrh%tln+$h^i}$tNQ^kvm{QntNY#fH z4H7hYMBZZNPoqHMoy+hcN(uPg)POzO2$LfPUc+}WOT3E*t|k?hdVyfbZvP#mpM#up z*@{^myo_4Z3%L6MhKtRf?*3mRwz=?1P)PeE9Jh)(LD6b4&ddABE z0vTqH&2B^GbAADYofoZlOEN_z&?VNxOc&$c|25dXH4G zk5snl*;J~X&$f!??@i`E$qR8yaXn5vsdtaMf945`Y+b7X?lqD4&6NJZRT5c1rC=Dm*KtvmZIITtCC-D6Hh=dFDjad(F5`PFcj zWun$T60JAG$OS}18ijToQA;Em%I{|B^8PLZJFe-MJj&Z)PHym=jcjSXedA0+5OcoifMt09DL{(p zlKoCwHj2cVA;{Nz~k2oZ8L<#p7|!jX~j1E zhbblq3E@BWyP)HazY=j5N@enop!vTSf-J2(vOj<_l6G!HIQ1N5vy6Gq!A*b0R5p7- zRZ2p>3)>rR7#8BiQQ!&K8@ap-Tybb9VZ#{x3Ld=Y)yK!Zua#k_Wn<5LM zH$FL7Lutg4p)Qipu*KUL;Z@mom|D0E`HiFfGTz~+HDdXj4e?|Ytx}TL^n5%a73LHZ zp^@a#Q%AWrHKNlXdGy?*%$1vUb6mn7#OoICi^Au(lVM)mAvoM%`xSQ~l!TjpZSG@= zdV3Te*xW^$S@yP3`LPX&g0zg&PAvnSW6sw=fL4Med=q)WNzY|0Z-jXW!;*Bqq$lTM zfsVCqf=|w%hqjFUaEl8TE^%o{O!mqIjz!aAz^XU0V&?%M~aRK-PEkHj&34Y1L=n zLJPt-dbjUF`JGJsd~a;se6j|8`+M(?5#(_XA%uNQGBBiucf6E{V(E4~*K#F^Rm3#Lbhw^-JYeuXgtfAsz`ZV~)2h zMQAiC{1`gj;{f*LwSurNiFC36!Kcg0SCHB`D+=GWq#(juk{BjtlxZ&5Bdi4p86YqO zQXE1yFH@R2%=OAYqM9(-pPE+v?16heNeq!^OP?a@uguA(*)JC*6KV0pt&LQE_=R{p zDCG!4M(*RMsf7M#APaS*=cJ`2z@w@+$eJpV7sT3^IBmD!554@B%U zYro*|sp7V!I%x;+Y>;)c0pBZI9|auJ$s@D0&=}0`0sSxW{-sRnAQiLL(BXl7N?{k< zEn)Doz=KvTR?FZoO?d6Ci^OsM7tEy<$K+4WskB}(#P}Wo(LTfH(Rwp#t$r%z7As%j zbWgHq2UqtfI;hv*)T}+;V!oFb_QF!He3S#^UWW~V2aA6!>^q2!qIZj;_9ujW$W&HZ zOd}2)QRRk`2>U7J(*s%qVaLqELH2zO#41W-0x&gN{OCkQ>cG2^{itybd%7|YNUWz1 z022|4%4^)2WNOp2?NN3=+Ew&J${ejbG@sDMi(q$0)=IEn3&RbspF@m{hWdUt()9$bu#c_5>c=mYrN{b%Jg7QhTr~tAV zD3x4O-!n%Jg9X;={GbzQ@9{@P4wUD4jSZv2ApvC5?e!Kd;921X551xO0q9^Ekpg4! z#v|1u1uGlR&0-_n&lH=@7JO1Rzl7mBJtEw8jBa{~n8AGi*RosO^dsEC8l3A5oapkz zZXG@N^2c$fmGoxni)~0vi*+jpY`w2thq`Ra*1n-U-B>_?{(EC(Jib7@kk)#Yt@e*u92G!SA7TV?QG!;Als z{@rb~egC`AZC?)q0(c33oeQdhvU9sNC0EyZ3fjtMK7e+s?e1GU5LpAv9`RzP-9gQ` zfxqDUJDwUzaSgg&f0WOBF9ehOHi~ZV!GLyL*B-osv~%I7J<&@I=lN=|hDDs*YU58Y z5_8=;-!XR!2~5*5OF{AiUrwLiN2Nn5cy2-?Nht)<$j13wwQM!y#LWcWhI~veAOo#f z;{0^2d^;Yv>EdQJxVv<^3@D-GRB0j4oICzJ$82&@BFX)oCf~FTM^t%VSq|n6cdV`9 zJO7FQpb4n>{$a;|(L`tj>UcLxu2EpMXt9}1Pw+1IA>FILQ{*CEZGBXAxIo>pe-!UK z^1;c{$sGRMciqYVkrW%Mfuto|qo;jE&$eTL2lQH5YVd-R!Pp@GOs{HU`e8CXezTKS z{h@n3E?a@4m380@5unR{xK`ZnWFJ4h&>G@v*6VTUZOcCwbl z+}GrOmyf;@K2c=~3Ew|8WZ*`2IJULZC5+^PXsbaKF(OwCVHfui#5gh}yfy zj#kXu|2bI_B4N-``%!nWIZ)VtxlGp09sA?w8M+iv`SiU*`jm+1$2skEu=fkg|M_S8PrMWviFZe) zU!=~NN~x&}OzrQ=RYG}HM%V|ayijoO6dvOf##Oo=7bsGCGX_WW>@~b`Rl%b)Cw*8` zXAEEw2LBH`!FE&XParmndF;4R0$TE0Ta(@fa)N%(SHnLtqtzGh6CsVPcogspE#c`u zXfWttLh^bUF;stzVI&K$Zz!RhOHsm7n zpHB`JE3wEE0n8JeQA-U8GS1dTz;G7%mI)MXLz1NdNevbt3RIt3tPbe6EB@@}yVJiW zoez=MrSQV?<6ThB&#SFP;szG#`ENFJ!1ou$U)QokWPJ%-XR#d^NgxOZTlf4zn;02# z$wI~y(*NHay%2CN*8oi9^)K`aH~gn9IUo0{7HH9W^@4iiA0Q9}uIYmi2w?0T^sazN z%bNDoh_so^)q%-HFI*OsX2rmk1wW3cUG*-6&N)X8J}LpAKDt-O00d;H+utEc@2NpQIiO@i1lQnE zIjYH$DZ+^1@~?p3M^KU~m~B{=cII0Sycp-H3F7k?msbk4{HuSj)oGe6@#F`H>y(%mV(-z+Tq&PM;$W=Yj2Ay>NbYdr+O8WkLR6l23+( z!5QH8Ki_EuLROkXXeG327p#YWTgP%it$+68AfQyE?a*O=pkfnS3)BkK62~gMOs`3A zRDF+e{YwDYO*&E~A4q5fc$ zx})0UM~FS)>EQeT8}@k*2P}mRp?I^Cw+2|SMNx&H7wQ1m((ol!CW<0FqqCT0!iuXOVsr|C`mxOAw}e6I_Kg4_)VN_4sR#k|Cho2p zdp-~#uYnN3S|;}PED$0a_Q(by?kR99MxJmkSP3#;L>Jn=jVOpXXC^@9+f z2S5lRsLSbdd$&PfU8CZJGe8=0vjUaRd*mRPBm`*rbq+S-@^lt-gjpAHB#naBc>q9C zn+^SrfrfMvwev3|3$8iRP=>kFpH5c4eRbikT3l@w>$gOCFs-LkVXb9M=`rtwu3_KovNjd?Kw8a@hsiI9!6A+F6FugtsOF-=_zQma zpls)JjmpqhT{PlVK7|m8e6v7OroQ zbJo%73s~6v3!3I^`Gfx=9>Cu0ogQEj3F{H8U-;mY9Y+i7qPHp8`nojJ$_qK89D0ZJ zzP*u~QhReQUA!zC8A4>fPugga*F>bfPR3Mzf18{uO_*jLke~o#zPdgC>v0e?DFwok zLBK2H!a0HQC9TMk;mREY$3Q_$gWl+Kbp!@=!-$y3*+(Flo4;}K?yN2pbiDHiEOQPE z>^}Qwj!V={+^zrBq5vESs)EJOJ^x2Kx0e)Vp-H*xxbklcRCg}eke-j}6hFn-fqdK? zrj6MP;IX|A;+Qh1LoBu)>>3ezzrDX1R73nRY#($c%OMFr`~0}`e0c(Bm)eIdieldR zCdURjExxrmaACaj@b@^(@=>$A+``T6WdA7wt4q9nRtkLms+l%R%>cq~a0&EDsi8(B zWhWceO>hOetrk**$@~YU(#8K4(llAD*&QhbBL?qx2`cM zV|;GZB}M^6x@C+2++tmF#s**HWAbd3=>_{FSl)5{^7HzdXf1dF-ap@!uMoS*^59F> zNg<2#PnR*NXL7MTSq$YB8c-_rj#XSalhr2#%Usv>{FRe9&c)6f?LW~sa@+3vL>un@ z@P{P(`Ws4!fuX7z+&;U&tMhBh>N&Flg z)XiHWJq_P2N{Y- zS+4RGI;{XZQnxQh1~-#AmZHxyPTFZrw4+(V(%Dk&a;f5gy1gJ=Ga`T6KGL&VCp%kC zR&ACPwlML5Vn7dDIr}HBO_F#f7TUIag_s0sf!df}ESH0+ggL_uBYu13=7~ za$MjUPrudj6@>%D_w@3}8duO!q1!23!wd8=8Htke0;8a0ss@@vILZrL#W0RZTzR^M zqn0K$7?vr3*f~NA$_@!^^t_RSH6dwKv)ZslVdL~3+@#i9^-t1}5FPr-VF8LT`^;-0 zVJEyfpA)~s5PI&~*a%pCfvd5oa8y$i$+ix6S&_|=>#J~Azdl*&v!t$f=;8II5=Xyx zw$Rh+CdwtA;c(Y;HVy=BOR|3}DD?t#L>6>k44mAC#Y?fXb_4P|6m&lgUuBn8&;w@#|Vy&Sy#&hQ5Ak(=uQmyr@745JMWuMYsgS%BDFZGeMj zAw-xyxD(%e#E%SJ@rTGl$Wlstd^?5<=-%wT_ zw|}1=P#w49#983_L2(qSU0M{!nuP-g2^r~{w_;s9Hyv?6Q?e>(wI6N?5CkusAS32L8W8ReJJhJnMnnbsB6|J1!VY*! z7v|SdZfhYDoGF3Qchl>H8OD%Jgj(+s@DSUi14RE&M7A2i@15QcScRNtsyCFvhx0L>r;&NrWv31 zT#IX)vc(rfomkMg&W+2A_7%oLvRqi(wkNeUpshk4R}AC#j$ReZ!FZ<3(iMOZPp|^) zv=>IEf09IafqnG<){-vLm!{DgaOGQ~9GZ*$|9%4M9sqg*8VMXj%#wxc)o8Xvt2fFj z%;_L>&bIbStubMCyd9B_g4)XK@C* z$@t*fLDs|Ej(~!Kb81_}=hS2tf_sdUtw_{RLr^>}$}e4?S@4-6zkE%#zq2xM%rV|J z5fAE2E+0Fb?aNv|J1w6!Ynj)W@*tz8jdAA!8XIcPExfCn?cNF+U#xm%fh1G5K!5Z{ zwo=?Wo9GCqrTW{~jzmY#=+`ZfHQ8GGdP~h$G|Nf6r3pdu*7-70+F6(WTe3mDgA8t% zS_w6ai5B>-+5v!foV4PFRjiGIZQaimR^fCgze#~rNL2Ev0Sa#Tp|RjIv~?Ob;55cKGF%02&_M77 zc~~PuTVbTgGi7(t;rv8=A;S|u4V{G>Rz;jlvbUCKmKyLNGOhryibf}e6C{^-%JN%X zbJ^#Np{ek@Tf+2S)s0txA~d6+VThikfyjfs;H9h5o?hGs$p)2%QjvPmRyv#kDjLUB0%J$tV^^}<(~dH&IQ7UUxou< zrU710*kFkQddbx17oGFaIJ%Aa!!PNpT3v8Y#emt8S!1BC(dI2qtB=F`x74KPVrO5E zFqcd*CzHLMN>;|JTU6out8}LZ#mxtYR({x&Logvgj!SrKA<_dxNPGK78>;UEJd8WQ zhXMO{^*@e@)lM_5dcnxFNd+;|ZvX6AfLEVCkvTOV*|=3&L~vmPjI|nMIZ1n@-+Tz% zhZ_ILcraGCilJSV*jUsqw{Y&&XN%hPmSP$7=-z2LP|iIx1!?XUS?8D-t>tO+1CtAq z3)@x?nJD0QhHj!QE}vs~${Mx&GK_ya>l3mZ71O^wBCm0sB7_312;h<1@?@zN*L(2CZ5B6G>wQTM2nDV$P zP$Ur``oXQI&K0}t)-$zR;qSXeO=J8Bdr_D$QwxzL0A#mT4AK0(FlY{vcTsp59~(Ng z>TmAjwz7)t0N>c_a!v-{w^cfQV&v^WM3|YdAYVJ^2jGGE#uilMx+YP6^GnW&>tSC` z_tkv9l$?8PwihHdo_~@aM3ifNP5@Z_3xO^LK7Hj!T|U6T$iX9BW(I;3qo_?up(c$4 z0KsQ#b`&0iYh}ti7=PL-eWJtw5*U=(O`sfEo!M)WrP= zBbIyi``5OmHggNLa=whj z;c_i8RegPJ?zAuzl9sX0u*Ufx{14e0yc&+NrPXhv0O(skEvWqW0RZ5!PWStHe)|Ri z4!=`1`boZg`9r3;rfF5K42CJ*>MRp<)FD!-Gs8#!r-aa7&mJI4S z%*vW6iKg9m@XmIkI{;kcXKeAY3KY}kmAtgn1!%?I#S?0vQT(#tS>f0W6F`sJM?m)? z!qP&f7?iEAu7Lhh9+%%y!ktWM}F5K)G$C!t& zSeGeK-(k%U>NS*>DmeF!a8oH=+CU~qA*Q4g0B#oU)4Ua_%`?d|P(r_%YnDwt3R)0V zyj-9nWuWzP03kDSYOex#>-Q%33m7nW7HWl{@yDXXX?945fUot9fLTc7 zLrhhS&6-!t@|ahQUA_L$TVVqZRrF$}{v=%s2yW>SWr-2JY3jrB_HCn!GYZMFtw_uFoOQ{*n|^+ zfUaS-)OZ2p46;uJM$?&E5!G9s5?UU7DpW#vRmD)_t)}=lB~ft8xA||H(84B5?l^(L zfSv|M9iy!~VqoiyQc+M`r*cng&4(NV1fmGS>#vT@00i&Z%8fuFcTt$E)z_2(3c=Rg z$yzK9@SftZE0LU{-y)WWh;~P2gPW_?=(zSh5G<~WA^5hoCMqw^pVg=Hs|M9S?0VLc zFX&yvyrZX~R?ji(lI;HU$n7BsNbTjG8cAttT_X0zKR;>oFQh})(~eQF>kHOJUF%kS zRpZ~EaP2DIqITCyr{)LK1w2#$=Ot{U@}L=5d5ApVXSX4m$xORKR(W*Z=@Jp34G=@2 zQ8(bV8)T^O#@aB9j}!7;r2&_{74{SYYfcT_2ZilpOCMc7uw`IPoR+Q}VUZkw(tI7m z&VoXjR?YA&d1bv*ShH}wSGBy%`amb$4Tyxlg%uZwfh}p~OT?8Ip!>BGq(>y22ia%%K{RDr%< zN@w|(FtVsI>A0cab^wpKnyC(I#NA7#pAFq*g~KRaPwrG@j56S zLWX<%^Vquj1lE5Xk8#hS7Ge?MA9pzRp|{97v~=S%=t(`M2b%#Dv@J`P`mZ20=iq4;wnx$0 zbpB4euUyGAx}3%K_1~dz3I)%oT5D@-Pap}~k@a&-+2xOUo73~vw0!{(J!)WyT%NiK zLNv-a^CHv%`jpMrPDb^h;;stkkA(77x?a>{Pkbp9{w8(EwO-#NDHx8K#V`jGmToRM zmQ@IDy=JW;ta%FIg^04|!YL@SDsifxktcSgV#4RK2kQLDc>*bA!&F9;f`)La=4?AbFZNr`~A#_siV4}&W54}K(Q z`LHq!iWUInJMk^I^bpFovHz^V;aBN_gf;MRRVNZt{$-sW9EM#4V^fQj6|Bi0F#noL z$ghMfb@(JGzZ?KL6kn%>XFe$AN#8vv0N=<}3!y4@N>Yri_P2EsaAu$5@;Zt~EI&53 zUhJR^KEeGx5m%nycFvRQ?H~%UCm{v}1B*?7kMC zg{g`bK%p;C-F=`QOD*qr&-wQAlBAs)4J|W@dZkTkCa7TU_`P?bbAfm z43I$W-*7;q5=dBIogu&ju&fuDlJNWZ%${#KKh25s7$j!9HKs?>OcHWdp#$69CLx3I z1{7QW3qk(NihU*Afg#47N-MU=QdtG7@jZHcW1Q|tkpYiOmLsO?!6dNmV;b<7L9e=+Xd12M2EPuURoj*E2ni?Se;VbgmsUn&z3(f4s%O(K;tjG5wU&yp*S5 zJ2cO)jrlimX;hju@8h2>5F#s`C>SpBw;VK1VGAnt(`j=_$k?TZ@wp^Izf=2FfCUf# z`YCCxL6tO156-MJx7rma64d5k!~5rFybuad zblNShQ~1WjBTm>t4=pyC7pAr~VLHtl(*}hhpk%asjn*F{>s)pROdXN)_(9`(OF1N1 zlcVfT1Jp_dsv*7if6Qv0PPWtCYdFNmXoOk`tOgcjxU_`-Q%gYw*EpWYHJF|t`ZxsKx`>SL5hQ_Yxtj6R7}zZf3fCn(gjMmssrn160-2AECjVpHZ7cMs znU7o6faZZ#`hSZQz;#%h%lzZ~eDM8vfY4w{3iDZjkuQ2%SzNlHpcA&sKr&WI z&KW@{5K!sCm06H6gIJ}WN6h!Q#x#8`h2wxcT$K);dV3GV z_=|Qz75|9(g;|R7BENA?VZzjxosm)?cXp--!Lak90EBIo@X4Kygs{FceOpD>^QnQj zba!4zKCN82iU?KTgj%-)*$HvL0|51T(aw+KBM`I|)Q$mIYvPQ=LpG3QwOz{6Ayy>y z%gO*EydKVj#HfB0Go*l`sp z2otB0lTXad&`x#@Ej0`a6pfyY#4V2C95sTh-D6LtfGgPPXD&15BKlrA&d zHsipUdt4GFPf5f{%dSwxM{wx){Qv;j%Dh{)27HZjaNBwa>vEt{jA*7hB!Vk$;0;l*%E5{-<=I zpF_|KJ{I)i>z*8YkJZS6^)S1JvszkHFA#t*ClLMvbl_J>Xsc6Tvk&0h5DD~z#_57> z%NpyAB%LST+?Y!OZvwrF39|UanjFARp&w<1o;2eFBT+UbZR0b@SOD~*6D(R-G*jWI z;iY>@wUsrZRN#j%5eu_%N8@T#4G;-qlZ@JjzB|C`oUoFSdPV~!--txBFW=iI>ufJq zm=CzJ1B}bk*D{a$OAr)!wA`_1h2322EI(m?|i!#fT4<+Ze!uS*pgE3uY@|p z^_ypcTp7zg?_RtsY0)a?8~Ym!2ZZ%sAMmraj+JPw`aUQuuxMqQoQ}i-wYP!w{`&1e z?mydb1T?*40rwRRA}NXG8-d~6gX%GY!220G}TDYT*9iND)kl7;5}j-qbM;oIgD_LI4CbZtv0E#3yf<#y7#-(B*DAq3sbthZ`M~Hu!IMdppy1b zX`MHBqJlpgBXi`y!+>4-tLn4nsuXpkS*5D77<_>)lgAa(O|n5hYR$&F?`ci?U->dn z)K!?>D%n>r5jtw2(0O{2FgPO*t_|@nj;r*-XuJVo$skq-)l(5n&Kt0~k%#N8Vpz*V z(E4##ztZiM``*MqseM6+;OlowGA2QO*-sP+k|PMO{74u!R2c?*UONPAuJPeTWLY8d zLb63S)@#7zK1f3IovJ9W9VL|tz-R9U_gc&koR1(CSaT#QY_@0x@SmTm!a_&<*53G? z)29JqFk@J=|6Mi!;;e#Gpm-);5h&KGG_29=8rB4QR%gfRg+Ji#q^zfrd;T^l=f#1M z`uXI!3!cu;z!eN}Z@wCOa)m5`-r|F^E3s`runPqLIgCC-#HrtY-b`=sLsUV5vg>2Y z(-S#G-~AcRaHKXj5`aJb>0%(qVxiuPlxXy!0UG++X@X9Vmbq>9P*k?kD17et-}6!@ zeS5$?8>wga*B7`PdC#_&3La}0MDZeyuI+ivuvXf#bmHpoR(SRDDkv2D?eX&^tQZ(V z6Iz@TXQ|67EQ+Fpbey|seMQ0e6%uFb3G4tF)$F|6XLd4e(qYihb7D|O$h(;N&%IM% z4#k23B4A3MS}@aiLFqkLec{8#;CY}b^qgTLYvcOY6FPZtt+;=U>pfYi0jf(Z+Yp^0t_`ivU(_bjd&F|_O zD;$eAIv>1L0d!oA_Oy%0@oYAJB!h0si4*v;A54j8FPps zUxV6gl_rMU4;6XM7)ZzkzE7!E@e#Ve~lF?LdMfx!T z70d3A(#|az(HrZY5Gu}eU{s6ZvxE5H#|w05!$v=nMu9v|$u=t)SCR$ZExLfD-ng2V z+eOp6`zRRIR9>O)mIp`f6n4_uk4xu;?|%?K4WNIWX%9z1!r*e-jk4{t1ond!3yj8% zQ1AD%$2ROrTzR;dOtgEu65Mj6fN>@4z;>BJFEB#5(6xRQ#{^RAKTZr1C;8o@imaR9 zKpuNEQ7eP|DrsWwidzOVa`pU$?4E)Y%XPDIQZRW^GOVY}zCVtgl!AH*3$`~R%02Xe zqFS`0k<68l1d62%nU1a!e_Q*{NOWUBH_W?u4ps=|?F4Z9gDNU~TzaYD$9xh+W4O_;MEIm;jl5=Ea|ea- z$gS;oJdW{?@c~sZnuoBtOeCxz6TJamh;^6Q_}eX^l7(mYe4c{A_q>6c?*PmHJIev` zuiHPh{;)FcwJ5`g+#_fQ{YNyjOKr?JqoNtg zxNFy}zD{Vcbo$V?g;JK>vnD6b1I!}pm6rMG4{amQa|0I192>s^%+@Tac%&dS`w>ys zSHyJ9DI9$pp#)@2kjBAWN`B~yEuGk{-9>nY`5XA&V9ihEU@j=OJzno~N!xTmF3to2 zTyvA*p_TOATO4D~8w>6jGq#{G+cEAOph8~stzrY5?3`VrS`C%{xZKF{_gwyer9>LY&EzN7LW-$peip$@WTsXv8ksN(17wA5RLci>5~>6kj{%G`0*z zp~aY|GeEUG>#14yy=mcg2tE$Lq=iM6P8Z-_m+-rWXXEhC3hP{h(+ZSCP}6xf4Cf7q zEHr4AOz-O92Qs`xfe~&AfZuXpd4MU?k4K(}4$anHAVb5)6yRRaJ79bpH$bkt+PF4-jj->eD|I9lI9Zd8l@}$(0_2<`W!jAY!HITdQ=K8A-ZKD(?6PT zoS5TUT#iWlJ}tO6QSwe~gcb1&F&P;pAegy2VV}ajgo`bnAQ58)3SZqJkd(bRJaVxz zc?~7jN?fzCk6(oupd>ay{cElx_j>luy8_-^)e!stfK6rC=rP~Q7) zSOw96mcDx~5m-FGT>>h0#6nOa9tYS%3J?4O1Jrg|jy_8P!K+=))qL#bDLI6F6V`|{ z2d*Wcx8kR=R+f5`X*8GCJw*>Vp~+uR9X9w%r7xh*PX|r%BJ2mi?D3GdzE5h#cWYF|p?P#q)HHU5ZgoKVZb06w9wepU7Aj$Q z;#1!QIs{a*PeKyJN4T4h-T#zv-_07NDl< zofa#Sd8s~K7fz2g8miO)>`ln8I%a_~*imBopBFKn@CJ$GbP9GpR$BX?Oj$s*7c~m* z)54VXpn6}l?fUBmmzF(ncU;p0luE&ig*V=>0Ioy|pt2`R0=M<_S2#7kMn{G>nh|MK zpdM389WeQ+mG46b2I*Na#LfotNNZK4YpFj;Ou)~gkmbvKs}3c?23kua{VM-VWLRoDhrEkhy(RKj-cJ zkc>`3qVDK!hLP$xw$g%y1O)fhA+<;(Ld&4mIxb{zX9*XQ7QLtiRzU1iYsG_LCpdO^VPfFg_^LGx{>}7gRnWSCm~9Z96B!`Pw?P3AX@jp zTP4_)Xv&fWNgm%btxx}(GbwHK^DD;62E>(n!nX%j zre9)$BI&*hTI-x@CaB2+O9e_iU6r%hNsIr)|8?$DtC#jn?F&a=+rhSEP*%CXhtmL^ z*!62eaC@LTl(c1`8{hOJzg>2U19xzW_;A5izF6~b1ETU`CcpVzrhc{{B5N}u0T)mg zmA*>>DNP**fDShYX{x%O7Tqpm9H%rPQLghXh5svm@s!Kt3r~<=4(-8*q5F2|ERUM& z%nkC%<1V~XJK$3@XDg3kWOCiCqv(%1&{Mz-Fg^n{)d1kPH#8bt;X&&qw9!sNhLPlR zv5=EqGsndE+|1SPloz_NXhDj?>&i^my77^21j$j`)*~cb72!3(!tfe!okadC0Y9B} zyY5rt!Xo7$3$^>Kw89KPg>ykZ5NO(J693bR`E`Toml^k%P|@Mn4g8o8Vlpp16=3IP zlo46rc7UZXeKlcsdo9;h{aCskH-L}`=TVWRUf~=-=$)Zh;cHI!$fZUI`kAkk6R~4H zh~AXiF+Hy?E%*=w*gAAjEY@sUXv$M+DiqUt8yaeu4*KyPT++9X)M*0<2(DRL?hBx7 zh6+e)?P9SuP)yJcL(=eE21Q$ZW~=@`zTP{asjT@QCLur+5LgRUX{*>M3J3}ktUQW{ zE^DO(EFjVsX+lT<3oC6`R1~CGu+ju6Qj(wox>P|4NJ%tGkrEIgfdF~u-UM}@=l6a0 zk7XB|Z%HIS=S%zf60 zf3u&OU~X-oJ|NecsJGL`d#r}1ds+8)Dn409^~ND7)~dS^*MVyfxf5w*e3>qJxaD7D zpi15X+TmPWu-yZ21zteR!GJh?F#TL58=2*r9E7W;a{MQonN^B}Tp^nr!AjriU{R zF~KcJ1Mow}btCyio_y0Jum!FWr64s~6~7P`epe+#Usr9RH{Z*}1M9@X;sm_Xs9p{? z2`Z7iojb`B%Mg%WuM#FGr>fXvGc}SF8n$pmDu^WQ%Vn?8WU)FTV}MV?1k>1ExP)bZ zbJ+9JO1SO_An@rWr0VrVFkFmm7)#Eirt0BhO$LvwFGoshuS2Tx`?0bNQ=IxV`_n3& zu%Op2(gLQ2&gQYZm+upf+wbb^DbXQ3{PglZX^xgD-p}FP)O)gHu6WxbA;ZODIElKJ zxcH3tr_)BHX?cd6vy8GS^|No9-;<+i6b;+L*Qq=41d@MtOm|9H56l~OaP#HbQWi`U zmyjlRHJ=oKZC5*6P8@5~T@I*AUTLa2ZN)mAD8mh=*t8bxrv69AiD0<%@{eWBYnz8% zP=SfTEBOJ0*q$)*^72WO)NOB?dVz=s03W=V$t1;!j9(w86f^g&&w1^MH+4jU96u=R z_A89^Hxh;|qQIfv=n6oa%9q)>!Oj5291YOMBb0royjQP8$_v8fVsTbPeml(QQ_s?;pXhqnXgP`5WP^i4&C&qy}C zD$F#aTTD(-@GP1vj_;J9FcfjqBf~QbG`_lEeg#)yQKBRQk3{NfMHgwnp|=9~59Tg9 znB?=MHG)0h5SfW0KR0pBQnYdqqmh+5U9=i zNRYd+_J`sS^0{s|-sb>|}d0cyoVwSQuOjhDmt zO!s{Nnu>|9YiQXTQeM6mec`C4bCE024&Vs>d6Fn-^s>7Z zeaGM$e92$<5VI%VCT*mZVSJwvFIsvT>y{aLV(dtnA&_iMf0VwU#ggrHTp--?DCFP) z!4j&aL9`CGc+-Dg5Ti9m#&9aK2FfvI2KovbYdSMYyoF< z!t}ZXLOE1Noma9DBgK4g#%aNfUn4K{bswVfIxAHu+^H3;5Nkd>2sttUSY5N-`GNHw zbeG@Uum{RlLzW5EM~5sJp>WE5$baDjJJiQNfs^_$hG+6C!u^NN96|4jnamf$cg=Rt zGmSAXRKlKoJWGD^af;L{Xm~v{Mwl3!m5|=JF^gNwYhwvw_%!Dc-qwE_FX?@9m+Q(E zJiRzrHkR(^MemM<@|w!dT-|AMR4g{k7!I|6RY_jOD^==kt5D%V(d8{anc@VBjX;{4 ze5u22Kr-i|zcKEnE;tPK+mZ79r)T{~3LeSlg`9Lw`rs&ez8&aJPw;a?d|ED0M*C~Z z13564w3_~AwjaSZq}Bp*Jq3(<1U$o_wywD$g)1K|*T4F*M$n(@t)86Ri|!_dlR|?^ z?mQ(8&NsHx{$^lG=mq9h$+xN(D2JRvKKtU#M=K@1EivTvFtsH0X8C(ZU@+&s)QeQH zje@r3iKz5Nj?=w*xK4?P(HYozD_6(@q!>KP`NGI2eWP;jNs~8>?)Uoc9$O8D7yi77 zeIvOyGIiJU_z#0foaDp^=iFf(M;#}5-#?f*hnKW1kK_%q#Ff;Ev-Opj1$sFG6W+Y0 zR3_G8)&?;IaGoM|J$jF!&`e|utb?5ZH(j0dus?QzKZ11~3HoPM|t){*Yeg#AT)Cng#2}2yPz0mG?Zc-~AH}A9zo5l@g0_ zS$lA0o{lsbOeOOSuuC1zDy?VaFpJ^!^l=Jh9hCKh{_WgX)_*T$w^lR4hL^BdJ-2;D z2RTfxeFB86z{!6(&dV>H%=ZF*uTX;BC5rU`PUmO-J2XhZSP%*2yRQ797j~mS-_mh` zmZRV+<{cAKVM#jU zD*Tw*z*>gCKE2)TCGGP@a!|7mcEJQ~Ats2;ehxuM>J6}z|NS;}FZ!uVedd@AM%yg6 zok`=Y+Bp8y?qbht+mPM6!iI09TwDK`y%M#u)7q@|M&99F42X>4Apwq_5}j9TD#jIN zY6MB!0`E~+TbFTLxBbJ8^1)hoen?mTQk5=N-vsz@UKgJjHp5H28^v&_GlyG<2dL!gCW5aa7d5W9PA+i;(|4_8rqYUYAisZ?wfm(%7w)7VAol?KQj%B8 zB8*i?c2Q#r%)LPy*g2Fi!^!Gp<#*TNH=Ls(+;&f8$z9qk?=ztTnx9o8YS-!Qs2DN4 zWv1ZEH4%R-Z!jph>4N7S{Aq89%%T>n`l>W1Jo+A-Wssaz+%gfuwE?C4H-)zKm9cFLQXx|g!2<_INQ#J^A))!ZQNt)QL4P&b!id*g9~1(!ICxU%sajFhVv!-VZ%2I?Zd!8}` zq=Eh^S5Gy3aO)Abt47%+f|y)p@vj!)-^SFNR6nf(y}4sFWWr9~hYWusfK(AUt+U9eMfuP#JRsb+s`v^J7XS}in0$0}xXr#u0q9A+Vlqjzyf=O{sXCbF*@NAbAYCwgBJZ6ANNB01fCiVa7EP=1~I=#Y3 zxb*1af-MURUrV-^3{mbr`8rLNOBjHtRheJ~ORPVfbzA2yVLT+9LX&=8EMa}kOI;vH zeO1EmxF9a(KwW?cP=tfs{vB5ZlGWKLxr#i{73AdfM@?~3KHENB@7dTy=6B*dUtJGu z(NzS9d>n%)f^!+WDl@$=?0HlAM5j1!$yXg>Xn-Jw$bXZnJk6;hR}YR(e|9e6r;B?! z)B(e>y7`Ip%SDz3fR|GL2^+8Byc}DdMOmFWJDDzc23NN8`i`58{P=^ zA!jXK*+HFd1!jWuQRZ|#^UBLh35>haP%}~xLrH0T|M=IvJen`tV!6&>!4+Qcg{XGF zbvct5yeR=6{O-V^tDR?BY?dEExICc-FQw-?8+sM|`kZLKS9^N%NK?h`k8Fkc?C@oR zPqNDk&M-~>IpGCxe0&LZI*#PD7%|c3pb5d*FophO$vNWJhwj%F3Raw_gl36fXA!5l zsZBv$k2wFJnOZk3}dOuqkGRicOk4Q<+YjCSRLO=zvcBFSm_5#HYl(9UKEmi5|_2* z?)jnJP$zYHb8*ioL=mc2a;ERoN-KUfi$B!ndVA~0QXo>2L$p~TO9o~`ae54af8;!cSgYVW!hsJ6 zf#Sj%KbU;UUIl{`UfIlZrhY%I=OX~r>VIXGfA~4d<+cRjhUM|rT^zRitzhPC89;}0 zHJzt7Ho@`WxCZOQ{SrEJw~wV`1!5Bdw~?~PACEW{CbEZ1;~t}lrk=&Z>hLw(p%)9x zo)TXDA-{G&Z1~9pb0l3d&I7AA%c-Qf5ipzf+9!))i?ES)NW=>*%tfjJjceg%HVsgP zx1PZ5YuxdzHLs!LberNrhSW_hFgi0pFC8l2Gl{=NQH*nq8%_)PR(s_zhOEc0@6z76 zhMA@VJYw`vL4COv>z>XK6rw(NFkeNrc^ODPnTRws?)?S*z9xA z^>ohxR4+qY;lBT^G)xn7CMhz}bfGG9U&*5+c;! z5Pm*dns_1$$I!&4&BP{Lg}N%QxW8e94m$X@gX%XQ75=RgONq1e{W1qoN9)*n6acU2 zy@;o}{HjLD>62|qmCUseAqq|nXg#uA0i$z1$We@FH<)q|P~t;^q%MH$x`mxmGu{g< zbWh_=S|ce>w5B(I*5zM$*|jszjMrDOduP9#AHh#BYA(;T#Z_)-xD`Ku66Fga)N_?v zbGI8N)cEMociw=FDc@u|9~*d{as*B4`{H8vBi7tf$d=ODTmK8LJP7VTZKAr1RjcqN zj{z?I3q~>L>hg*UkjV{9Jh~=N3iD2_i}6n#P^+`28f?Swo_Lhtk-AH!=cP#qfJ*AI z2=k|}$NUgxDU*G{6=2RH=1)7QXlL>i0^w z0j@W{P@vToFv>ll!K*q?2>7e`JnBPgW_;8`c2v|Jv*;9C7&SV2eA^c{dvWq`OegUY z-vDF{$sm>*Q zmx)nB)F96HCVu!ut2Q(iF#B{p^JPs!$WS++T8Id|Qc*n!&9*HHLIR1UAUm!Hw?mX&lFdMxD@T z^Kc&`utN|YE|L7f2E6fTZ2j;o90is`}2~gUdfC2%s7y}(|i>si>A9znC{ehBN zT5a%H`oJR0#YonAwL#ftV=OQzR>1H81=Dr$;c(B34Hi#|85%vULTIaS^&~Zg3xh&y zR2s|le;Mktkb%Bb$l_(*WFjjp>Hn|KAWTT00vYW0ll#p8205%H>>jtS1eL<$|KSvL z6~m}*`EbN7^#E{PZUxnY<+?s;fB^eMP2Lc)BCWmUuw1AGL`1&btW?fW{T>0x!+vh^ zhU(%f#D&TjEe8_I_m#+_Y6?%mqeQZfl1E2!GVCa@jc>_YHF9$Dc~Xwv4M|*VThhri z1#pP6JN9cwN9~cB51YtlpuQPAaJcmNdwrI*+FbhUF{01805OlW zl#29wZ9yZ~TC7V;6jGPqVm$;~EJd>WA8?@BiDUaRuKXqfx9tF3=DJj?qC?y7;D!Pv z_o0L*P(=RS9&MNV~kh2-uhf+ca1&mAedHOx&45x#obiC5vIYwHn3&#sS19^u$_7S_!bZV*V9|uV}ORZ`^ z6b^1-Ihq+$e?3gWDkz}r92xu&TFhI&Z)SC$vKk9F#mC zuRYBtE$Ii#?E`R&;C=8<;@U^@`)pSK8U3nn7^NgoU$PM27amTn*}the7)B9xJw!V| zZK)MQnW5Y@+KUO$3l%;MBHn6htq&-;9(sZE3YvLHJQ}5ZtHihNc#0J!QmGke4 zw&y9}j%s9ZkP;kt#qaQAX2HTS7l0VwVLw3V%;cM#e}}=~S8B;OaYekZ$Zc$eTm8j{ zpuT;kr47-55;Rf@*g^Z25K4dI(ei%N?64oVEIT_W@%6g&xXxq~x z{HgUa@WXiHfC&wz@Bk72Ktdj;r(Anv;jv8&uPzL%`rT45dW=;bdz% ze*NK6e9yh-P`0qAFD6!X)?(%{*{o2!vh#OdZFdwWVN#n_ZfS74BZN!v?nwPTOS3Jr zmO^>rmqJR-D(*?kS*&bx0r6T^xY;y3`f2e*Q3MkS`F(I}6iOrGwE^H+p>*Q)k-pm? z4Mb{p;AohFS_9O)MI#kc@QY-m!gQZa!&E)zyLj)&`%2HXOFNLf1+a{GeXdz=#&7K7h4KdRx1f2`X|-Gel|#-o z33W{5#mH{&c(B$0J6!thV>9wcq|PZW+YdNSBadhUgWUkaU`N>QF}t?i@9^)q&bx{3 zLqL$e9vK@J*bI|z9$+Zfynzj*06BP(WA8m5K}khWs!@8GrV~n#2SrfYYtQUQKMH#d zK1ved=ODHB|3Bf3?d37filLPfir1jZtN8UqVzyylR7o(xoFTc^kDxGn%p_~^sjw`0 zY_;D=`JptYG&|^>8jqX^v_#v=?|AvpRVu=?G1gRFmgxHai5Bd9>fuQ^>fnR zm!x~90GoNItYpgvexso`*gho8)g@=)&qhCu2Rg+^CoT)*XuzYwcET`?i6cy*KU*cQ4>&%`kG*l7=8ryXU}saapy z4M#E?778_U=E6X1yex*`H(FtBi0j1RilIswFo2|pj8SBJviW`wo>jl5dnCtCZ??2l zUypwv-=r&zh#B@R%=ySoof?G?TPza`V*WlJztPpJV%P+F*n;6@DAPPpg_w_t6B%RV zrjxMTxWCtY{ER*?dYc?ErL~n4A=}vX)e4EnhhLnBJ41jjk{P3)HnD>caG%{T`V;U?ByC=NnO6!l-GgTCtre&V1V05%(#px`&4S;QfvLsPSgDaHAhp)Yn^&4zd z#6F*=6Y0x|C zH1p+9Zg$Wbd84#WIiaw_8K})8^>4F0FEsQp{k0EwE$MYn46Vcmnxsw_HS;z@n+~9K z3txnkJOo|0wB(Azx~teCBY^LhNL=1dQB}blhk_nUgALTy3#2KHiCBsPD?cVwl6<6K z=0xsd^+wU3RMh>ctD+ZvhSDH<=Ov0C0UD@r6XOhv^+8WcmJvM_q3;Mkx2JGJyOS#} z5@FwX3iM$Xh8eK|t<#6s_uq$iZ?$Bqu5d#(YlAe#V;?8A@!6xNi!tnEO8Zn);(`I9 zaso2Asc_K&FlQM>>N10}?DPd4zI<)`>ylNYjS`qHbVXp>2!W1rS~w#M)FnpWM!5F& zZu*#!7}hB$S9*6D(CVij*hc5fPDJg#7_0`v!9N(5f{)*9578sL!XjluSgGQoXDU@% zuOtr{8IUCccSPM$q*kae>g_~kdUi`tr+B=s7lM7+l|Ku0aMe)AQYdY|rAg`fQfyJq z=cMd)A#~np2m28*ocV{B&4bUpPr6>DhwCMN*MtR&<{BQ&(M@ zKGWCT9T**)3W}V=x(4~$&yQsp_SIb8ML=_fh=T!&v;mjpKB2xDItJDkkvWFBYqM{R zKyjk)RWidheL$J=?iy2f8#AR*1!GcK;#3;Q8m?V&`e^$aRuN{ zYRQC=>)Hz(?K$g(>}I|LVS=FqClB=g2R_pWVff&CUVJ_#^?rPPE5}2`7)H(bC?)t+WcWYZ&9;6n(^e4*Qh0lp= z@TD#uv(3z3`b*K?B`J`4Bp=bnSG#JQlBrvODe%7$=`4W-5^butEY@48Z2iTQ7?m@% ztx}#O@A`p**i4yd%IL)tl3~gd@66M@77~jkF(!MCO5=<~$_H!N1a)N#fCxN_MlHn% z<7X+c=k-Zb`|ZkP|9Z5g;$e1(+;J;9VKR}hs($SNI0WxPffo)s-=bR%wMnRM;UBEI zNRhLc4)o~A9z)9~9L_UcjO8ctN%x&A@gJ``@*L|r5iAZT`}xOnm}CAf^t5&oBkyCF!Qb5a!zBc=0l|6{h7vq?tWXY z2ihR-dyjUNKjl{K8t#MMxH^|%UAYF1O`>>K3eYWnPTJfJv=|e?vfTk&=(0M^I6FC{ zo+&A(@fvGSy+y&yQ!Umcf1pP+r9de`*s zx@*eN(XJTp55C*=R%ndmT|Y(HOBB4O60}NBK2Q{dq-QBldmU!@d>N?bBqXc9ZCHUj zXDytXY4@M)AvSd3`Z=@Pj+#Q;8U7S#MYq;(TUS`dogKdx$IpUHV>a9rZUOn3P%^@= z3z-lcH{u`x_O6$3=V5wi&yCE-GD42PcW_Eo9ix4{ntLU13w@n=aRNaR=b(m*9XqSg zKZP`S5W&-25O`Ym^j9~N!o^Y-&!M0RbW|KTfAt3Sf7B@xqtWt(F6d4LTHvL-M*>0 zJ-PXKB@!70HSLs{?Vh~(c=tsLEv6yZs(&ql__f6IXsVduYt~MOj*5qnjFA%RxmAYm zECM$M9*4z{M;(d$l=0bb%gv7K30>*s!bklx3lON1^7aV{L<4eGVkhEB z9T;@htUN}N${@e)BJ*%^rHC7+VG#mtT&KLiwad4Kwj8^lD>`<;t-79wV$K-&O??Lr~*^3$8kN%yvs7raECi~bBEg4X0 z8>$JQGLd>-z=On{V>j->{#@t2-qt%8$P0GbrnjrDyeIjlQlNk zl_(MBfa-s5OGHfgBsf3^T?jEHE1b_2wNdkOdqxL0uboOpG*h_eSxVrM15;e>goydh zP@*!y=}hHY*HecRTa7U$8Qs?gWSo2}&u6aiJr}( zbl{oU+n$H#RWYZ6-Rpn#^C*BWxX}6QMS55M&o;^ock|Q0)YUMyYN)l&;(>Xhk($NA1$`&Rnmt#sWF zoNU{hR{|@ko~V7Gw?9W{6B4Tf#DrVacacWPKy%Ff4bO>z$Coa2$&m&|2J%6|qC-j{ z|4hc?dG>;ShBXwo?lZ(qpNxa-N8M~qZ=hvfp#msE47*SgsT|42Fz(rY{PjW7=4Blx zhiEd&sfB(K&K^KeO=s3K!dy-xX&ywskOR*FUlPPgLp*C3g4!DF0*tR?1}Xv5l~`}p z*`o~E!Tbi+i7ik?;yi4@t?+SIWIg_g;1@Y!HE>zyezb=#T)`TY&s#ixw3y88*SR8+ z`D&=o2!vEOV8RGro?P7Hg?0d40xLsyzxg&HfyS|8*uXE*mFTcNS8xzHn*8M~5MQ>S zSNN0+;SY4^A~gYrNwD_KdmSI$p@A~fVgEN!v9{6;-!>I2`=!u=>he()*)MH|*zmD6 z`g-+6lWn!?zrQa{=Pd&mwt6eN3^QbEf$M{yT(@aPGjuM(7@yGG9)rVFZe&8WPsElS|WcGPeUenrkDFtCA*v8dU({juYw@dJ5#=uz?7AseSvW+qv_24TMc)->Q-bkDdk$(UNP} zv+RMK=csW6u0RhKM}AY zEU;ezQqnigByJm(eGvEG6?&8^x1J%hn>2la= zX}5u7Qi|Bew~R-XHG_pW3*q?MUJ6>U$aS+A|1y-WAwjmeowh!=vGRBM&ccD#=3MfS zx^)mU(4#-Ct891gJhE`QB};MB=h;NZO-74gu23M0E=3o@kJs_BLId7;cnZVs)I<3h3vja13yF_=D$r|khb2%CN;igkx+vkfMg z3!cOE%};>Ln(n9Jyk0Dv2?)zfOGt-X*9`>lb!E-MhNDx%3`bj|eZ;9hEQjsJ`p^qIkZp?1KFB3~~ zfoMb^s9Gf8x2k4IQ14M9;wNJ7pr`oE-fxx>59|uG7TwF@WiTn0zI_WYLT|V}cTF~g zj#oG<@>IVxT6}XjBa5GScQOk4xcqb^^zo1P5Uy#pogA1t3C%dFBdqNsSnB00maZzK z?3Fuy6@_TxKL;I_UX+u)`8GsMFHjKJnP!9>R<9?SVr;6^JD;6Q%0}5KsFP{0--0i`1BuDcZNj--A~J>0ImItu|3#$=FQzej@_x+{AmPx=%N-v*fCT8l zXW2%#fz$=;JyR@h`eBXwflWQ3BGd0_;S0u@{7O6C^q5!0sFoTacYUytf4P+tr|kCY z+t=!=sw-gF>~;$iO2+6DG5mE+M@gh66P)h$@I5qrxG5rI~GoIYOK^fcd^(e&6 zSgVm9UG3}`tZ0|F&6_=D4-B4q?~-OAqTl)zK-)496VYTH(`F&(#90IdpO&7*Od8UG zZl9Z??lIx!V!t1fKPNE?22XqVNXhSc3Iq%x!~i%F4~h7bL!gYvs;Wt2CkhX0pwlcF z*nQ#56Da)sbS=BxR@O;Z3A=amy=^e)BZ3LoZ{}#E7}>+SnS^UjA)_t3%cg@9peyBc zipKq!`*&8vG~FV{)|V>q8$zdstl8{bi`l=LODxdGBO(poVsMNp-uSEr3`!4>z50$K z89XlOOKCF_2|mrYA}BybcLn;0Y|#(d_pW@ai84ome$ zUy#p{t}^bQ6yXw2Dm2k#kQM!+>HLzquUWumn!9;b z_=R}}ht>90kM17>H^&?w99{)6v2d;Flg7}Lr+Ccah&ir&GFku1K}~0=JwI3~e9Pg| zfUeQKAS0tTk|A$UhWblOepXN0$;tn{8vj$HiDX7gahB~yH&9<`W@3^t zNiiC~QdU@}2a-efQ-A_Q*dYupkDXu;O=sU_I$Gu} zZh@We;D_^kUinY_$3~0GPo6Eu>*>Px2znwvdOq&k%({cP&JpqQU$<3c@h1+vpW?0c zGq5(dsNK-7nBWc>qewI^p@1PymT;2apL~wqNHdr&1-Qy&z4tR0^cG-qp8Fw#Se$EE zJ6i@0<&SkivpIxos0orzpcuowh3s!0Z)kn)*)6kLUK|?Z`5j-f2bNhIMz1G>o%+L0 z(RS$?;3*i82BbH*F-gcN2|v%#sPcIM1W~e7xNvw(y=s3GzJ#;M{{8$&V`r1xLM#Mg zWpHm5G+A$%W@bUdYg1z?)}6|=j$M7qL+=U)J=3DN|GUE3A_;5 z;OES!h4yW+D_bdy0-*`$8z$iP?nCmbAUk1Zcy7T_l>_${2E|qVg64G4EEPnZy|=#Q zDY^baI7V#s{7S;_xdK@Q@JO=7OQw;Vy+=LQig+FEByPzaL)Po{kueu^UUd+Sh8&O7 z#S71q8TGRc)o8AkV+G0Y`kOx#$~v5URt)Dj{j?eC#*Ig~g^u_4dO^Sf*q;}EDGY3v zA1O{h%LKm@QWG(0;dR&QeVb;?!6rab3CX^rq0*hPR`^o}k5V=U*olGZMQ{5s9aO&W zK4ncBjO~m^HaoJO^D(kJL0pd`D8@@w7M@&ZnKwQvGn+LJj9D9lYo#bOTF7_NBrfvZ zO_Dgzs!F|qd9%+)bM>+@)HYAjHqdV*uM1gr6aODE4~X*YWW{8(UJ8P=1LN!~>EXPS zp$@jb>rW&>yBZJ7zi}pQ< zZlZZQY^}Z|-g43PKJ)|wl>}t2CGABWJDsUE18ZsApP%M@fz%%Q0As^` z1GHt;+&fCKUtITgEduU#{7Nd}ARftYK*x0k&L4R78Q?qNl>~$BY!maZa9{W3Yw$J) zC10UVKP|i3N@vO~^{=_tYUx6x^o1jZM6?$#5qZD}-l3#uDzT6B(uDn$zs>Nz3sdMrVvKmwWNb(W}^Mz0N^l#Jz`UIiY|7*~YwT46h)aNEI=QN+yI%n+VQyw%c)r9O+P-~>Jmw<3GJ^hO9){pQW1LA?xAE2@Y*F?+ zUZPxuKO98Se$e8!)WR8JqbL;QcSJHbO#V=NheA>!D8~#Q#oRAlB`<`x0Qimo-aXjj zV-|Jgex{ z5KB8;(8vqFu#*lXXYwPjbV&C7>pOMehPnn@y^NH!%N++9I8ycugRzM+2UOY9kPM@n zd*s0%fEn~uLUP`KdO?!8b7&SGpasV@?S5KWBn*zicpTl-{ujpcH-r-xxwsXyub%J!FXUl&D;nCH`GRpRz)<=R}n5ah~#J+dc33+qa`t zN^=U|>GJ@^WgrU{+h~mYU48w5pMW@sX+rQoAVCye@-m>hR|We>xLOMRau~Tb&sVck z0||O@uT1;%fP2`#fSZ^-niTNI#}i~Jp5~@%{!1C0J!C4OPUI2aSO^fh(f(`9qtRO{ zu5RCcVh{Jes!0kepsq%K3T1Xh#^}oGJ23S9ORdI8`E_IB&Er%nfc|tX`mXG&&qU}| z-x-CxIHK#Om*DFU^wJ*XSc0aj0Ox9s-TjGNeFF7q?FLeW^T32(edVIH3kPG`xlY!H zK3l9abIR7>6}Z{#1`N}NP`TmWTheFtv)=?8p_}Iv)fQQ#|HdSJ{|vG{;4{c6_y73J zx@A?t0GtACFlOq=?+bhKa86a*LQ-~7uQ9JZP_%<^gQ?O74ueQO)4#e~2l~Vs<;W_h zca*Ag$C`Fz#EhOYPd51tq-Vs`f#WYjw7urgTzy!_x4}ls>Qy(!e=$wwER3L3FuBpl zKXaO{*mulaB?0girkmpB_aW0!P%t_4<=+17Jwc?613OHv09QQBe&T|aMX6DbA%kf5 zUP9`SA6zMLYE#cvyvT^LRD=~dr}qV((Br=yj=x1vz~!y71Nlz?M7_umuGu-g_&fjg z36Y8c_y-xrB?K&hESZHBvSeT*kYQX~grOA;yloDNaIT-oE$6x0r9?Pyr&G~u;d#BL zs^CS4!9~nRSUk*XC4iaU+1#0%AYUa>4nAYFOsJamy~rO{YOQcT&}DQ<2xmK?bVdPP zg{B<{FfJ?}`L*aN|EU?U-cE;iZ?7PdXWn!S--SM!dX86jT|01rkm%ARQL2=143I2q zd;JE_#yGyAPHaqKN>$&PR6|lpJ8;-XDLcO#J7lm%%i?rUsL%g%w6BlM=_{Dgm;j>J)4<<9&_9=_31m!yI-y8c-dP`f^flq4b_h{hn<>RS%e?Q zI2}l(H8No70$Kp@hF-`YpmVPV%pJ0+di;f|g9`qHOOF1L*^r?zJ2U0r%~a~yZV&yG zte~`-K+h3HvxkQlJ{f-&gO40$fMy7HK?Ft6Z0iulX_mp5U@CP#=noSjj}tXOxf>M_ zM-k9prS6MF{j>l926`Q9wQiXs>THc|L@tCN%->Do=8%D;aBAAuXsIk4kAcHoBb~ZB z@~|u6JTOOHqd1*^eMd`cn4zHorUJ6H;9IYUC?7ey>%>y}L*Rz}t}$2Nf>uRy_9upa z@qsnzyDhzG8I9i|x7rxSfn@r|HX=)NDSFpYWR*Tn9c#8#R03MhY|@l;CWlusW(}wu zSHkyRU!#5D2(=IUHKMl>6s=>I4}wlc55y#Y^G7oR2p;|sKjP{Zy2@L7eqCWEL|IU3 zBuG){Zu_tI<>;SoMnIJ3C>D(47iblsIV+bM;#Cq*V8idod^IzoMPq}{Uakr`y@f6p z{^;pKqz|4ku1-EPx8@cS@%e%bi+&@u_p956bZzu|*Y6&HP4nXw093K!ipR6}$miV~ zO;%HVqQ9YIwqBnc=q?L^(y@(Sv(pC-?jgSAQbs^+;M*XXk5ji-2$36*5moXSgX*7} ztkIIgTyZqrECL+#_!l*BnX`R`*FaK-6B8Fad-1l&o7h4{D6te0pCTr~fw&0!rLMuQ zD4e)P$PuKt6vO=blDN8FEp(2`_54Crx z6u4BHtW$43S8~lfy(+fRT1UAv4Ta-~4*>3y73JR`fv_AVsRr(KHqJScgUM<3lgm(p zm-uu(W(N#x&y*-2*Wh998eIIL;N7>*EaL}?#Gt-H3CEfxAJbFy*dLb32^yL;|I!;a zq_)Q98p4orjIgc`iutXFi~@G~0jNKkVlvZgz1#45OvlqA*L2hhtcC?(D4vHDU=k3Z z(@CGs`>cvfuzD5RuWGS>syDsU@#t7^r)~WJI%hYoeP0^Wuj#0IY7Gf3AMy>wUysYy zJB3DFU?C98GWSe0GGqNGg&A7s$Y|yN#Uc6jiwA|dxp&T%+!@X<>R!0am#eFq&KK9= z05l*h`wn$rfhfylx((+bO65<)P@WKH5^2nQzjWgc1`1I?^cqpU0cnT1q>UzawkwUf zwq8sxyeAx-xz+V)s=~Va>q_YmYGWla#~wCEpD})LDC)xZl<^CsFe{*6n%>lS8y*HO>Kxi*1^qb&tPkww$#e+7)r}0Vs;S6K616+IsXkG`I+W9u@5d*<=f}+rTN}IJkIPom zrN_cdAI^0BXYDth!D~~V%^i;1It$_3kXyYyo|nO2c6915zPi&?8n^uZx7^t;xx0gg zJC95?1W^l&hI@H;=;j78lN@V(z0~Ex6gX`$&=zat=2nAwg+_|8 za*0ntPo~gbu@&=^M9>1O*~S*YE1oMyaUswZ#gkwSS>l+0)XB%a72#eL>C~r5IxPQ1P5ZQ9D?5qNrrOw;Ntm!B&k%y46}22iOol}0JS8e#m6Es4+#HX zq*D(HGa3uz$Wrv~Mz1cJ*>J(OGHcXO$@@Y=W$$34Ilr@6c~5NpK)}hA-u^ZBE%MJ? zV;`^1^|&0EVv@ydD=UWozmv-_er@{7=J-bSGTN+FW`UZzVYp*^Psmh7Q1@?}YDd?x z83CmIXiNtZmnspQKF%y*H{t8w30J~LV~A@v53Rd@E&PKtaV3dS)_vgYlzYOeH~?hE zj&L_~+lYu6fLV)SLWz8zz5p@%H|Q}3W%}6zVh}q=YHfr75u1Z(OAz?<@Ay7p*!GYl zu>ToEmFp!m{sIZjk2&*r=NST@C z@2@+%wdwDinT3q$vv%NUA^kgd0;t7zCm8#Fe%olxHAugfq#wzFIM&a=SHv~nWGf99 zZN9Q076Gji+YXHdPb^#Fhq4ujme?DwM9~jl!rs8Cm|ByGWL;}pWrcAL`VkH1jW=vf z$FhFlw_nu@tXHY>=CIqN>rB9VSV*onwlkB*?>$c&-29Z9v#Yw>TZPMf+a37f?E;*? z-3R?pIVz!tObTgVV`20i_zkJ&50_D6)Eh;4B3IIw3e5(p`|n>xnxrT>0E1zsNvH3& zekqHQh~{XDf)vNU(d{uJg!8+}25fQP;o;$NxQSW;0V2 zL>lsG<+mga#-?U5f+nYR08=Uy=^)vB=c(Ez-EiPL2Bd^+djCDH3?@kxW0)5c_vc8^ z8xPJw6#~~7;qrho_ys`1mwI;kE}FwF(qioo+Els-tEY}_r4u{vK{dmKA(CrFTn=<< z=x89Dw8^$Y!#U|sT1le{FXbJ#dS9ZPj>YhfpR?*Q6DA0Yfm%y&xgeEOQq(QeKRbBI z7Ll(gy*^q=3`6mVc#PsN8169j_S0W_l9PuF0d4LJ4HbAklta%Q>-rQ0fwP;_8btMM21hWLmr)bcF&bx=fr^Qm+xx?Tnw{R$EcrSZUb?= z*`auKxKyCMy8vNQh=Khe(v1)emb@54LI`la8O z-j%tMU(;C5`qK9LEUAV|c>QrMp^?X4_q@G(^PxRn0OR9F(y~TpPH>({;{jT3N!M=J zbl1pUeI(n5kIL|lOa-_YGDI?8hNQ2`vk zep44{oXF|p#+pjpXMK33H(J~1tu-F|AfrT`EkaJg$I%pH0AVP-D%vQB4N58+Mge`M z(-nPcjMdYnG+o^XHGz!Gc?^P3CfpV#kOz4`Mqj{f*kX5+6hLWCob z4iFcPa3-c*-Cq_)`z26E{%Vf0Epv2N1mY%GgpQZoh0IBt7TuWl|yEB#R5C+)_ zPri&AGjQ-p=Cb|+> zY4*hEsLJ4DplfOjK7MMoSKyyDo1Z(h-tXc>L*TTIrQczDkbT@D@#(1l^pje>HF09x zTl~)ZMp}+x&0l8$n2JBp3jqay!s4w`<~@0Z;}$~?ZC6!Hy!WuCeaqCh z85(C0iBsEb%^!br3M!jP8*k_rn$dFsFrhcdn8_$gq4V^cHc9LY!Y0 zPBRF}@~-!fFOU3f`>nxInJZdaRYyH?(g9B{%4yc{8(YAbw)Q`+ER&f%ZLls77#Jz+ z@`jh3uj&>bb21yrUrE-6v9%DI#uUuFWL)BCQj4j%1XyuB5y8kK0q0!vL3z*fD8Wdq z5}cQ@@+07_Ja{TO=slB8>;}gCKVcsUm3VHSXMq0*WxERzk1p%TLWAd>^7!jR1Zui$ z4VpnIspr&0`AYh$H=^VW{v80)dT(o@*sd@;JL-l!zLJ##x5a%tNwvZvUU{4@$ zB%oVrNHx1(gp`8RPTcU43+YJ!B-cqsq)eyIPm4y+HF( zfV8LE)oWDguzqx6D4`6lszl74VRl|A1Jix{v&;UHSwsZwc z!$V+fL7k6K+u%(-aOR)>+Sb}?W`wOCo~&s^?smW?auJ#g&ZR`R(((Rf!vfe*<-k21 z>Rm)ejBG5SkFE3wbPe!SNKFND1WG8m=y}^k1^E$-uY&@B0SDYGv~TeO_7tR{OZ%)Q zD_9|Kdv}xUUVTWGJjLUnLmj?uKe~Gzt?xc@`WV)_M)+pHuovd?7(@ zssWB2>;3yfyS2ZNCI?edV=Xj& zW83N1OZF0rt`T3B;mM<;;zhgUM~K}uRDpnqZZoxIvmen4Y?A&DMgyBfMw_)qPA}5Cn)|T#pPz{B^3*e7>lGtjC*{@Pp0>MrU>xAm! z+m>P!XEwFWkIdzBZ1wA_4|KYA3xQi%;+3Ykb}fr3u-Mx24rQaH%r|mWUeV*CLCkd} z@by47{>$YAxb>N|vsU!zGk#i}<+g;1Z>ti5EdipRf~#Zs9ONF~W@CZg7U-Y1L(UUM zeF(tNq&d8U=gzd&!s#^Mo+FvCU4jSRM?#(1yk}CIA*JYX$tw`L3jwu7rv5N;`$kGZ zChWqxJS(1;*ns(f{%I90Ht(2ek=1mM0>jnnEi zS9h`=hlE{gb2HZiia=AlmL2Bth@1?u)i>et**ewtXeRaoWwQ>;Q8MHR5W3gkO3t^?z)dj^d0R z6mWGE{TI~angXxArlWTOFd4>sq3(1VU2MU8Plp$}DGmUfLl9o00Ukho&rrT8hjpbV zxj?7@0misOhdlO+1e%M2!EO5=g(W}-jkYR*c3H^PtH>s-3qW0``ee3R&S@i((>ViK z@7hZc(W#Ev4UqDINmw4=DuujxQfthv0b|fyKxXc!W92&$ky~=HXOeoqLef$4qIc!R zt!d-8RnKzd8Ch}s(jgo5?)H8$C2J{OmR$GCxinj~*=v z!;?1k^b#pC@p=k97xW-liq>GBn?sx* zc(qXKRsa3;{oroaTh_AlhXs_^5n_M)Z3TqfL8jD9FO(IQ7Ig=wY1N&4aLCYNy;>~4 z(a8j02&q{OJ$ZPgMmt%2Yit5wy*kKK>k?MSG1ZOPFKmtpOyWcZC$xYcze|+gRP{_(99k8O)I>u5mjhO|02+9=MI{20UWh^AHrBsOE zz%?v!wVIQtj>~v<$(jW)-bLGh?FH1;p8CbDx zGkT>P7=A}_uID|7pz%tdARdPjMo+v_-@)I;y`C4{!2tIFjy7+x!e$`sO&;@>0@amibgwij))5;P59^B+w(u#{DRT4XG zZAdqvgbu(u`n*6>K!a4Pql5S^C~Yae#AKfa$`)Ewdws*L`p59#rB1FqO*U zh%-pf9wI=0RCbDew;?Tv$1JUB4UP7M#$RCFd{3>+3h5ebJPA1hBzXhk`Du%==4%3& zGA6+-yPYbDgRYn4YgGAF8* z$}^uz;_Op~43UFFUBFK%&YIgrkkfepDaEapHGX&zD5LwEs{U%YkBGPOty@hH>AfU< zzb<}PiV-m$G%@$wH#>B0{6dyW!_b(SK+#$^kzY2_?k!L>%HWpG>RZB!>$MHd0>Ca~GJq(Z(y%eNnye!TN z-)9;)S)UrgLZYy7K=oD&?K>t;a&Ac)!5`;<-E(>h--YtUn^1kyHa~=0%5S&pkieB? z>9k$ik(~;hW~*Bvqk;g)Wn?84kgn6$GSU>oUyv-vzoEikEBJTDjf1CM`U2VE2C z*`qhYg}Z6WQIkIl%G`m}Ol4&qQb@9Xux#ql<9nbQCI1WQ_ZKDuA=h>6YbBDogtB(X zqvJ6Rz3n*OaANX0|q0o@*! zmZFfucU1zGpP#e#qWOpYLeO%&d9yadz~chakqBwe(^}(h0W2qQ{&cPY1Xb(!Z2s#u zFp&-K!$duO*j%fZ)GWZKk;wlaV{ZZv_4@sRHyWCX4BZmh3$0WVWi6?sQdhgRBsW`R zk14(#y z9L2@Z;CskbN{Sui9h!sg3JY|CKLm;Vr=T~}66ITsdvQC^&dlN1R5Q~SLB63xX)Nz4 z^)^IvpuG2zaUZb%N=>KKL3R1c1gA8%$o%PIN0E}E*ZS{vfP@eT1^@s65+$zN6c-o< z{psFsqc@4NGI>Ve($fK977Jw()Z>IDaUb_Uyf$^QP+0?HMFf`J=^GA77&7As#q(X3 z55~a02I0``BG4#2XPowRikQ_X)0Soja)pB*C`6wRdoglJ5!wIW-9 zbMX^YDFB;nlR|tv!<&N||AgZcGh`sj{9VSdA2RnMWYJ+}YXC4EfRyt_c|NG{%LAbB{-KOB%phNmz=-m&UWqy1Hqhowb#MRx zBSS9mbtS;p7V3wf-qaaFX1IdBcP6All*Bi-d$2|X0{1_{CqE7sgcp$mtZCUplzJD! zS=VDeiO$2`N*4Jy-!o#Ik@o@ZyuP}H`}~7?9=+N^fvU+5=Bt(4dz;jv7kE^+P}5pJ zjC`y+mhc3OyibpK{NO~L=~YBYRq(Xi6wn3;*?=%FonX#|WPt1gP15TCa1KrT`QZOy zC&JI&1>5Psw+c3aOj5Gz4LSv=ZU-KXHnt3EKCXFL6Ws|M&jX2u(@@`UivNQ6{{LAd zVTgfn-gVRLoy^-jY|ccxLfJw?e`oBam4{WY{o)Bwjn!~|!87EkPr8rC`U<9Y&v?k* z&>S;x;K_L*=+b3`nLsWg*o}}a2%tYbxqH}wKMgJF@_Ze5e`tmGU}JueEm}DoIi;7q z71(7UD$C_qez0j7=e-qPXkFh;{v-85adxZv8dsA9q`Ek=Ho2!aDW&t_eAQ_$iod7% zrulvdBio6O>%f#py$%f~6x!D2R17{589lxgZlTLo==Z3cRksEXj%HO|8?C2}0Djid z=^cI~fbeXSLJ&BB4BP~*Kx>u%)EG6ZnbyaDpGj3xGGXVK!2xi*G=Jg}V^F*6YoueE zg-eANI)H~h4c`6ozyOjmP0?Df?kE9p86t?>Ot=Km)*TR>q4@;Rl$$#-E+c~VMCzeV zxULC89bSK+COJtsT)8NWw&@1N+TA_DS?HEDx{NQv6)+(gBS4#hrgc(^vz!Nf$6LCs zx)MWo&X~*e`vfPLU>690NZ@gU*5yE_69m|Wee>@A`U7Pl6cUN-RkB@bbV%85@71#H z(i%czS_b)c52o!UA2T(Gat&~dltuiHJ*b;4bPETrvf;+4KnhYt+ih9VZ{F*ZsgS+4L%SRDA^AuyOtLk9n5+ z6hT}c3Nq$ho>g%&VbdSVR7N!#4c9!H$4O8Z)hhlr*3isHw(J^IQk=f|55U-| za@3*yhvW@V_p9Rt6@8o!-mU2(=q{ z3+=ia*QhbiLSFEB;oL944W+&1N%8)CZd7z6oc7g0f^JxVa>7>UH@!RXdh|K|>;Iak zY-hV)=ZepFO(+b7i}(5$m5Kay1iKPUxn-Y-`oaYma30h_buD6j7k2}$ez+ONXdmi4 zu>E!E9_vQ{v-_zLz$q_E4q#gy*m9blCAp!phpX09zKt@}*TJQuPB&DM+CDgFdCc=K% zSn26j)_HF6+7w_P_|3pElJzR9cJz_NJMP3o*HQV=4uKvqrhV!Fk$HOOg78;8%K9&Ng=IUuUMewzj&7tZMGvhNj~U$FTdrteEG)uT=4E zHK>D16Oc`?*A8#PC@TP%17IkrGZG%8A${;N&~o3eqy;hJi=iF~^ekUtN!RfuCsHNF zaxYU-(zJd>HE^Tv#rU-kZKu{)4K-;#_Nk*?@fZ6F<8X`89Z@gWUi0PqM&4T>NYL*k#(XPtO96mpaj@}rlXKtAD z*BE!$p|-yuP>-q>f>s7&99l(dq%H<~79FkE8``vdaY{I`Mp_rQgqHBnn7~6M=4>pZ}df!BJUp=c0F9aHKcjXL@6i^G*Gm8MW*BdgeL~V_iSV3=&_vjn5BxHcJ zi&Q3Oe%z9!k;q}0W%Xk!^=g^j+F7bZkUQ0#A4u+#!r8O?XkL|Nqy`t;;e1%F*UM94 z>wf{sUYHJ|Era4I8*k?`BJM+)?1$af7P0!;2J%pBS+Q=t0U+z?@w&cIJ07(*R-EV8 z^J~%;Ba7vJl=-}b7N*CcKF;Z2{=OeVBN{i*!bQX)wSsIAFx3);9+U+L1ul>QeyBY_lP>++Geb-;FEt%`L{s*_wQ^3Y>P$U z-z=z~qf!ZoZ-CAI+c$bgVQYr{P7B1%)XDD*#Jx~_%|q?OaC)6gUqOFA`AxlyY}E&2 z2I%U)<#kR4E=C21ph{YdCOUcgIz@~1L<5u3A_;dwpR+Jlw#6JAlsdpFjvpIX8&|?` zJv;8s=fRzLfS{DHrvcQ62_FVcz3DMn2w%+G^j@1U9>KSH?wbdmSfBK#j zMt=FwEb)7*;qmeMX#Jv%)6K&cgi?yqc-kXdC@H^TmbUFfEG28Zku#_yEb!<@B7(g> zMXLbc0hq|y7o|UWLo^-KPD%=AIGPx?vIxSYaV^v++vw>YNg#H)oA7Ux}}Z|H#(Bw6RoT2FZq{xL4F*v=%o3UB&jJuX|Jz zF^t!5BXjh0Q1$@Cq0>3Em$__5{Uysw-!Ws<8ks96|mHQ~0E}5Ti;1+E>Qojy!>$ zcaf)eN#*ujBtcfXbxjKcsJw~pdpd_EeQBL7=QST$2C%ti1$MSM3P_$z4pYNGaE%d+ z8;J>zd*5BsjgDPHlLT(6y_oFR<7hY)TvwjJBPG>xUpYn_u*JzaBNerJMH>y+I}<&3 zOe0BW7ZK;X|B0R}5Td5uam@?kD#`Wc#V=RK5SN-4*`7vK0)(1*KA1a$yoR zkO(;G1S#njsR;-DS#sg*HeqNMa!!${rXG8{to$seBj8C+S01*%`_*0^lfnn~RFvO_ z6yKN?OvU8&^1HDUo(bsfbA|q_osI9~)JmqLSlhHT2HUlDKUikBJwy~|sgSm{sG*!Y z-g+3`l&UP$kUX2}Q|+Aud6RYZ{2p35dP1iGp%kyP16{_$Fr4KO zKxoPZEx<-6*i7dVWao6xJ#S=`I46;h4!=k6tAZYov~gFBmHJ&VTo#KLww8C(a8f); zZL+m<8qJC)G~q8A`(lSJU~7XI)?lkAyqgU!6-!SvqhgQ>L+^yUYTgNl<$(6^EJPKg zCn6KRwXvgTNN5e9w5vtCGILVr9(|{TA~vsMDHdsKGf-)6zw=%?wRSK4Y#d1{Jy$=tVUUfstFRCkC{EwZk(y&n8dBrK`!#!xXiDV9x3)($UZhwiX1Pd0Sy<<$ zIeOZ3MrP_<9sC&Z&hW7s)GES~f^G#vOU#nUDxjve*uI3_>fNe+1S27S6cP3Jm_e>V zxjX+mu#E%!gA(h;z@UR}DNN=zMosodMn~ItzrSiW#SNHD_DYzhN^x#Ix-dO02lCVH z^RZ*&Dlj<_`|A3#75P>fR$~=Fp1h!m!JyQTdVt=g;%lS~tfro?MSTS7N2^GGk_JlG*7=@LeCU7)Z z_Kc~iG)&O|@M)Tq4`lbqHS^Cu zK>v{B=+l5{cCD)8E~$V|f9!N?j^*t+Jz}ncZ88+xsPF>HDCQQSC^ve+GkW7ZWLU6C zDKY<4wsYxiA!~&>0*hC{GUkIeaj&AP$n~DEw|c05=ice{2DlXckO@dUw0Hcy7lwmC zVn?Ufy_YH&j41KyUe$v^LsZ?{7`5?UqI)XS+jukhkl_MLVBAhF8WL3YC%FG$t1Y zaJL*wtF4+KC+i53*qC7prkD-$=0iM&K)pyf>Zy>uhi~v)wy0SYvmxar$&ICHV&^zE zbYxNSvUn)Cf%NliGIx?)zvZexFxFA9;oKo<7}uFt>UVqrzU`b)Vde5o$c(ugOfwYR zIdT=%eW4)jNM|!2#Gnq1f_9`YeE`=fdjC|`_BqKPr5d=pv+%1M=2bOFTY3Exet_|i zLUn$O9DDEN;o)B=Z7^gJSbguT;?HE2$x&PVTu8s^et47ii_8M=g|ZSWXD9Uyam-oY z9Y?S5GNxg(=qTb39;M?5I%ziC*w#pt>vySbqIMjcAMhbfgk%l-mKG9G_(rhk%qVi2TAw0Z&hBly@9wNs!g(oM^XHUu%e@KceVz;Z4=@2 zlxTHhF|Pz_zc`5#N1+Q{$pf+8N{4#~Wz^b8QFqlMWP=WF1KWxgjh_K;7uUJPo9cf- zqr2HS3&UJ~v#~aL9nHT(@Vn^>IsM62)I_Gl@Inq`_tXchPjR@c|k${)>ROPOc->jfK$KXuLiLy zVtOTGQX7&X{@$Gj6z_2|k;j4|hsuKGKNs|r6_Xn4KMY2bkK~`=nwk|9HYhK%t6siK zmK{-cxh|`53zS}l{%I_0iNth|pA*Nf(qELj-ou^%0`Oye2T>jPDQeK9L6s|rL_s{! zTNaZ;he4a4l@cf5^Yclw9#Pe{?$LZ&@Q!{^#<8{)H5GGpi z18C172=)sC;to0+MW9y1LXR48?zq7}Fc2vbs#;A_OB?6FYW{ys7DMg0--aH;KnH*{ zZlnC7e00Ifzs7a%^!Pl@I6r_m`NttGI`oWGlV^A&)^#Cr$01_5Anuy4MBhnUzcG8D z9}k5VrOl_}x7yZMUZ=P|m1^JsHF3rTCQ+*NzS~93H7#$RQ9o&F7AUNqh6`>pV!!zB zEWpnh<8jgRpw$!58+xBnA-LTUg*qCr>@y~Z@;s$&nV=MKKx;UGykNIuhbr?o1954> zx z6D}O)jwxht`wK;35bJ3T5<6uNd<{A8Eiri4vu0&`nRwy-#yM8K1;+7Ddee$&BjNKC zwX;(qSQ&sGo2qK&_SCsZ7Mfk%)@HKVyisoWcT){S5oiEA*uP_6{ zw<*(}h272{XooFh6$tI13q6v8&!_xTp)oO#r~)xyY}}Y>LJIv>(N4s_Tt2|X0ztzs zPN&LOVVNZCYs({d(iiU2d$#f9Oh~nKZW!<`FPPGY_!9!%i*ho{pn7=+K9mByha)Ds z>ImE2H&ja-7_}$>uC43JjR+jIC$R9bvC&5#_m!Ih`ps>5tx4qJBN}UNwpww9&Es3B zq_H;IJ$Gq9DZb6@^sZD%Tg`iEu9pQ^u*s_iUIHxG4}#!vX5E z82*`w?ibqr168cKdku^P$s!Dp!QVCjZ86SwiGxlPkWQbtZ6UuYyQXDK0$2dwpyGg5 zfQ7gOrigJzO!Ud%^I^U0I+rqz9>;pwRgPSyGzXK`3IRrl4i8Y3yfULU?q^6g7Nw7cik!{nP1*v>I(LE|Y`AsZk z8M>({^ZyyHjgc4(Ubt_~WlB8qG%>Z|i{K6UO#fLSh&k1jGYTck-!kxCatj-s$qV0f zw{#!BS#GkO4!Ge|+SfnpsCxqoy?2(!G%?1uB-W{Wtd7h0SE2N#p5qeIl$ zW@P9DYiH!A0wZU*aJ zxRRl<-88P2Z4A429_DQH#!zjHjS`)Q@;3dC(z^f~%(q+mV@*(TcRejT6oq!2$D|pp zuI4pdFwr^D899hXMz*NF_oqIn9)p7xZ;iJz8e^JHb`@>-#-{j)S?lZ@ZU`nZViS=)I}+o~mVK+G!F!w!49T(w0EQ zsgMrT&cWr%dM1+Ed(EJ>OqB;05SuxG{p1|a0#2nv zQ{VX+B5Xl+L*nYo7FD=ASfC+m{cGyXbIX|ZELyDATn9)lv)`G5&z>6Y>oOeq)6YE?(`65XcH*oQ~FM zO808BIq>Xrs9dUw_xe_m1YF#zhdaig2Zc@n2Jv%Lwr!wRcg zjeyDa+I{Y$=)$}a)Oc&lG`-Ec8zeTIlu5>SE!n$&XwQuw3n|Ep{c{UoH#vlhkr2wi zbsc+lEbnlq2OLuK31h-!!{I*9xUVj53Qi4(;QGp?W1XJPME5T{PQgyMz~8+gn-RC? zxVdnNsk!si+E!R#C?9$sO9?rEnP2?K!-h|@@xezrU|%+_KYDM#oRmsR#oX?h%4c?g z;?@EAK8xZh!@%~v7Tl;xLyH!NIGRzqH<#LFcjXd*eER~@k|3@Jur}BisLP4bpRWMud{EixajJR^BmC^JDMm`V_+ z+X&Rd83xQZgriy2%WW&qn$-g*7GzQe*=W7%A)pWL>ybmO>}Qy9i}*vCImvof+s|Sp z6clUg_g^)$?6&KG1l849TiH*bks1QaVE0})=4J|?FWTy{qk(RlvHTd+e7>}j;yiCI*7$lr(e9m&ZuebJRA8`LXw3Tp%ekr= zJP=628*~ON)f7sYc�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 diff --git a/common/dist/sprites/spritesmith-main-11.css b/common/dist/sprites/spritesmith-main-11.css index 6c97f196e0..a0ca92f018 100644 --- a/common/dist/sprites/spritesmith-main-11.css +++ b/common/dist/sprites/spritesmith-main-11.css @@ -1,1990 +1,1990 @@ -.Mount_Icon_Rat-White { +.Mount_Icon_Rat-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -82px 0px; width: 81px; height: 99px; } -.Mount_Icon_Rat-Zombie { +.Mount_Icon_Rat-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -82px -1100px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Base { +.Mount_Icon_Rat-Desert { background-image: url(spritesmith-main-11.png); background-position: -164px 0px; width: 81px; height: 99px; } -.Mount_Icon_Rock-CottonCandyBlue { +.Mount_Icon_Rat-Golden { background-image: url(spritesmith-main-11.png); background-position: 0px -100px; width: 81px; height: 99px; } -.Mount_Icon_Rock-CottonCandyPink { +.Mount_Icon_Rat-Red { background-image: url(spritesmith-main-11.png); background-position: -82px -100px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Desert { +.Mount_Icon_Rat-Shade { background-image: url(spritesmith-main-11.png); background-position: -164px -100px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Golden { +.Mount_Icon_Rat-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -246px 0px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Red { +.Mount_Icon_Rat-White { background-image: url(spritesmith-main-11.png); background-position: -246px -100px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Shade { +.Mount_Icon_Rat-Zombie { background-image: url(spritesmith-main-11.png); background-position: 0px -200px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Skeleton { +.Mount_Icon_Rock-Base { background-image: url(spritesmith-main-11.png); background-position: -82px -200px; width: 81px; height: 99px; } -.Mount_Icon_Rock-White { +.Mount_Icon_Rock-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -164px -200px; width: 81px; height: 99px; } -.Mount_Icon_Rock-Zombie { +.Mount_Icon_Rock-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -246px -200px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Base { +.Mount_Icon_Rock-Desert { background-image: url(spritesmith-main-11.png); background-position: -328px 0px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-CottonCandyBlue { +.Mount_Icon_Rock-Golden { background-image: url(spritesmith-main-11.png); background-position: -328px -100px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-CottonCandyPink { +.Mount_Icon_Rock-Red { background-image: url(spritesmith-main-11.png); background-position: -328px -200px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Desert { +.Mount_Icon_Rock-Shade { background-image: url(spritesmith-main-11.png); background-position: 0px -300px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Golden { +.Mount_Icon_Rock-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -82px -300px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Red { +.Mount_Icon_Rock-White { background-image: url(spritesmith-main-11.png); background-position: -164px -300px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Shade { +.Mount_Icon_Rock-Zombie { background-image: url(spritesmith-main-11.png); background-position: -246px -300px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Skeleton { +.Mount_Icon_Rooster-Base { background-image: url(spritesmith-main-11.png); background-position: -328px -300px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-White { +.Mount_Icon_Rooster-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -410px 0px; width: 81px; height: 99px; } -.Mount_Icon_Rooster-Zombie { +.Mount_Icon_Rooster-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -410px -100px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Base { +.Mount_Icon_Rooster-Desert { background-image: url(spritesmith-main-11.png); background-position: -410px -200px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-CottonCandyBlue { +.Mount_Icon_Rooster-Golden { background-image: url(spritesmith-main-11.png); background-position: -410px -300px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-CottonCandyPink { +.Mount_Icon_Rooster-Red { background-image: url(spritesmith-main-11.png); background-position: -492px 0px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Desert { +.Mount_Icon_Rooster-Shade { background-image: url(spritesmith-main-11.png); background-position: -492px -100px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Golden { +.Mount_Icon_Rooster-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -492px -200px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Red { +.Mount_Icon_Rooster-White { background-image: url(spritesmith-main-11.png); background-position: -492px -300px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Shade { +.Mount_Icon_Rooster-Zombie { background-image: url(spritesmith-main-11.png); background-position: 0px -400px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Skeleton { +.Mount_Icon_Sabretooth-Base { background-image: url(spritesmith-main-11.png); background-position: -82px -400px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-White { +.Mount_Icon_Sabretooth-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -164px -400px; width: 81px; height: 99px; } -.Mount_Icon_Sabretooth-Zombie { +.Mount_Icon_Sabretooth-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -246px -400px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Base { +.Mount_Icon_Sabretooth-Desert { background-image: url(spritesmith-main-11.png); background-position: -328px -400px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-CottonCandyBlue { +.Mount_Icon_Sabretooth-Golden { background-image: url(spritesmith-main-11.png); background-position: -410px -400px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-CottonCandyPink { +.Mount_Icon_Sabretooth-Red { background-image: url(spritesmith-main-11.png); background-position: -492px -400px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Desert { +.Mount_Icon_Sabretooth-Shade { background-image: url(spritesmith-main-11.png); background-position: -574px 0px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Golden { +.Mount_Icon_Sabretooth-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -574px -100px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Red { +.Mount_Icon_Sabretooth-White { background-image: url(spritesmith-main-11.png); background-position: -574px -200px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Shade { +.Mount_Icon_Sabretooth-Zombie { background-image: url(spritesmith-main-11.png); background-position: -574px -300px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Skeleton { +.Mount_Icon_Seahorse-Base { background-image: url(spritesmith-main-11.png); background-position: -574px -400px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-White { +.Mount_Icon_Seahorse-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: 0px -500px; width: 81px; height: 99px; } -.Mount_Icon_Seahorse-Zombie { +.Mount_Icon_Seahorse-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -82px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Base { +.Mount_Icon_Seahorse-Desert { background-image: url(spritesmith-main-11.png); background-position: -164px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-CottonCandyBlue { +.Mount_Icon_Seahorse-Golden { background-image: url(spritesmith-main-11.png); background-position: -246px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-CottonCandyPink { +.Mount_Icon_Seahorse-Red { background-image: url(spritesmith-main-11.png); background-position: -328px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Desert { +.Mount_Icon_Seahorse-Shade { background-image: url(spritesmith-main-11.png); background-position: -410px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Golden { +.Mount_Icon_Seahorse-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -492px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Red { +.Mount_Icon_Seahorse-White { background-image: url(spritesmith-main-11.png); background-position: -574px -500px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Shade { +.Mount_Icon_Seahorse-Zombie { background-image: url(spritesmith-main-11.png); background-position: -656px 0px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Skeleton { +.Mount_Icon_Sheep-Base { background-image: url(spritesmith-main-11.png); background-position: -656px -100px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-White { +.Mount_Icon_Sheep-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -656px -200px; width: 81px; height: 99px; } -.Mount_Icon_Sheep-Zombie { +.Mount_Icon_Sheep-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -656px -300px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Base { +.Mount_Icon_Sheep-Desert { background-image: url(spritesmith-main-11.png); background-position: -656px -400px; width: 81px; height: 99px; } -.Mount_Icon_Slime-CottonCandyBlue { +.Mount_Icon_Sheep-Golden { background-image: url(spritesmith-main-11.png); background-position: -656px -500px; width: 81px; height: 99px; } -.Mount_Icon_Slime-CottonCandyPink { +.Mount_Icon_Sheep-Red { background-image: url(spritesmith-main-11.png); background-position: 0px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Desert { +.Mount_Icon_Sheep-Shade { background-image: url(spritesmith-main-11.png); background-position: -82px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Golden { +.Mount_Icon_Sheep-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -164px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Red { +.Mount_Icon_Sheep-White { background-image: url(spritesmith-main-11.png); background-position: -246px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Shade { +.Mount_Icon_Sheep-Zombie { background-image: url(spritesmith-main-11.png); background-position: -328px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Skeleton { +.Mount_Icon_Slime-Base { background-image: url(spritesmith-main-11.png); background-position: -410px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-White { +.Mount_Icon_Slime-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -492px -600px; width: 81px; height: 99px; } -.Mount_Icon_Slime-Zombie { +.Mount_Icon_Slime-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -574px -600px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Base { +.Mount_Icon_Slime-Desert { background-image: url(spritesmith-main-11.png); background-position: -656px -600px; width: 81px; height: 99px; } -.Mount_Icon_Snail-CottonCandyBlue { +.Mount_Icon_Slime-Golden { background-image: url(spritesmith-main-11.png); background-position: -738px 0px; width: 81px; height: 99px; } -.Mount_Icon_Snail-CottonCandyPink { +.Mount_Icon_Slime-Red { background-image: url(spritesmith-main-11.png); background-position: -738px -100px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Desert { +.Mount_Icon_Slime-Shade { background-image: url(spritesmith-main-11.png); background-position: -738px -200px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Golden { +.Mount_Icon_Slime-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -738px -300px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Red { +.Mount_Icon_Slime-White { background-image: url(spritesmith-main-11.png); background-position: -738px -400px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Shade { +.Mount_Icon_Slime-Zombie { background-image: url(spritesmith-main-11.png); background-position: -738px -500px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Skeleton { +.Mount_Icon_Snail-Base { background-image: url(spritesmith-main-11.png); background-position: -738px -600px; width: 81px; height: 99px; } -.Mount_Icon_Snail-White { +.Mount_Icon_Snail-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: 0px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snail-Zombie { +.Mount_Icon_Snail-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -82px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Base { +.Mount_Icon_Snail-Desert { background-image: url(spritesmith-main-11.png); background-position: -164px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-CottonCandyBlue { +.Mount_Icon_Snail-Golden { background-image: url(spritesmith-main-11.png); background-position: -246px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-CottonCandyPink { +.Mount_Icon_Snail-Red { background-image: url(spritesmith-main-11.png); background-position: -328px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Desert { +.Mount_Icon_Snail-Shade { background-image: url(spritesmith-main-11.png); background-position: -410px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Golden { +.Mount_Icon_Snail-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -492px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Red { +.Mount_Icon_Snail-White { background-image: url(spritesmith-main-11.png); background-position: -574px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Shade { +.Mount_Icon_Snail-Zombie { background-image: url(spritesmith-main-11.png); background-position: -656px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Skeleton { +.Mount_Icon_Snake-Base { background-image: url(spritesmith-main-11.png); background-position: -738px -700px; width: 81px; height: 99px; } -.Mount_Icon_Snake-White { +.Mount_Icon_Snake-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -820px 0px; width: 81px; height: 99px; } -.Mount_Icon_Snake-Zombie { +.Mount_Icon_Snake-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -820px -100px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Base { +.Mount_Icon_Snake-Desert { background-image: url(spritesmith-main-11.png); background-position: -820px -200px; width: 81px; height: 99px; } -.Mount_Icon_Spider-CottonCandyBlue { +.Mount_Icon_Snake-Golden { background-image: url(spritesmith-main-11.png); background-position: -820px -300px; width: 81px; height: 99px; } -.Mount_Icon_Spider-CottonCandyPink { +.Mount_Icon_Snake-Red { background-image: url(spritesmith-main-11.png); background-position: -820px -400px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Desert { +.Mount_Icon_Snake-Shade { background-image: url(spritesmith-main-11.png); background-position: -820px -500px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Golden { +.Mount_Icon_Snake-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -820px -600px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Red { +.Mount_Icon_Snake-White { background-image: url(spritesmith-main-11.png); background-position: -820px -700px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Shade { +.Mount_Icon_Snake-Zombie { background-image: url(spritesmith-main-11.png); background-position: 0px -800px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Skeleton { +.Mount_Icon_Spider-Base { background-image: url(spritesmith-main-11.png); background-position: -82px -800px; width: 81px; height: 99px; } -.Mount_Icon_Spider-White { +.Mount_Icon_Spider-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -164px -800px; width: 81px; height: 99px; } -.Mount_Icon_Spider-Zombie { +.Mount_Icon_Spider-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -246px -800px; width: 81px; height: 99px; } -.Mount_Icon_TRex-Base { - background-image: url(spritesmith-main-11.png); - background-position: -984px -700px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-CottonCandyBlue { - background-image: url(spritesmith-main-11.png); - background-position: -984px -800px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-CottonCandyPink { - background-image: url(spritesmith-main-11.png); - background-position: 0px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Desert { - background-image: url(spritesmith-main-11.png); - background-position: -82px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Golden { - background-image: url(spritesmith-main-11.png); - background-position: -164px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Red { - background-image: url(spritesmith-main-11.png); - background-position: -246px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Shade { - background-image: url(spritesmith-main-11.png); - background-position: -328px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Skeleton { - background-image: url(spritesmith-main-11.png); - background-position: -410px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-White { - background-image: url(spritesmith-main-11.png); - background-position: -492px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TRex-Zombie { - background-image: url(spritesmith-main-11.png); - background-position: -574px -900px; - width: 81px; - height: 99px; -} -.Mount_Icon_TigerCub-Base { +.Mount_Icon_Spider-Desert { background-image: url(spritesmith-main-11.png); background-position: -328px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-CottonCandyBlue { +.Mount_Icon_Spider-Golden { background-image: url(spritesmith-main-11.png); background-position: -410px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-CottonCandyPink { +.Mount_Icon_Spider-Red { background-image: url(spritesmith-main-11.png); background-position: -492px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Desert { +.Mount_Icon_Spider-Shade { background-image: url(spritesmith-main-11.png); background-position: -574px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Floral { +.Mount_Icon_Spider-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -656px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Golden { +.Mount_Icon_Spider-White { background-image: url(spritesmith-main-11.png); background-position: -738px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Peppermint { +.Mount_Icon_Spider-Zombie { background-image: url(spritesmith-main-11.png); background-position: -820px -800px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Red { +.Mount_Icon_TRex-Base { background-image: url(spritesmith-main-11.png); - background-position: -902px 0px; + background-position: -410px -900px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Shade { +.Mount_Icon_TRex-CottonCandyBlue { background-image: url(spritesmith-main-11.png); - background-position: -902px -100px; + background-position: -492px -900px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Skeleton { +.Mount_Icon_TRex-CottonCandyPink { background-image: url(spritesmith-main-11.png); - background-position: -902px -200px; + background-position: -574px -900px; width: 81px; height: 99px; } -.Mount_Icon_TigerCub-Spooky { - background-image: url(spritesmith-main-11.png); - background-position: -902px -300px; - width: 81px; - height: 99px; -} -.Mount_Icon_TigerCub-White { - background-image: url(spritesmith-main-11.png); - background-position: -902px -400px; - width: 81px; - height: 99px; -} -.Mount_Icon_TigerCub-Zombie { - background-image: url(spritesmith-main-11.png); - background-position: -902px -500px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Base { - background-image: url(spritesmith-main-11.png); - background-position: -902px -600px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-CottonCandyBlue { - background-image: url(spritesmith-main-11.png); - background-position: -902px -700px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-CottonCandyPink { - background-image: url(spritesmith-main-11.png); - background-position: -902px -800px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Desert { - background-image: url(spritesmith-main-11.png); - background-position: -984px 0px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Golden { - background-image: url(spritesmith-main-11.png); - background-position: -984px -100px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Red { - background-image: url(spritesmith-main-11.png); - background-position: -984px -200px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Shade { - background-image: url(spritesmith-main-11.png); - background-position: -984px -300px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Skeleton { - background-image: url(spritesmith-main-11.png); - background-position: -984px -400px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-White { - background-image: url(spritesmith-main-11.png); - background-position: -984px -500px; - width: 81px; - height: 99px; -} -.Mount_Icon_Treeling-Zombie { - background-image: url(spritesmith-main-11.png); - background-position: -984px -600px; - width: 81px; - height: 99px; -} -.Mount_Icon_Turkey-Base { +.Mount_Icon_TRex-Desert { background-image: url(spritesmith-main-11.png); background-position: -656px -900px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Base { +.Mount_Icon_TRex-Golden { background-image: url(spritesmith-main-11.png); background-position: -738px -900px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-CottonCandyBlue { +.Mount_Icon_TRex-Red { background-image: url(spritesmith-main-11.png); background-position: -820px -900px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-CottonCandyPink { +.Mount_Icon_TRex-Shade { background-image: url(spritesmith-main-11.png); background-position: -902px -900px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Desert { +.Mount_Icon_TRex-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -984px -900px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Golden { +.Mount_Icon_TRex-White { background-image: url(spritesmith-main-11.png); background-position: -1066px 0px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Red { +.Mount_Icon_TRex-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1066px -100px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Shade { +.Mount_Icon_TigerCub-Base { + background-image: url(spritesmith-main-11.png); + background-position: -902px 0px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-CottonCandyBlue { + background-image: url(spritesmith-main-11.png); + background-position: -902px -100px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-CottonCandyPink { + background-image: url(spritesmith-main-11.png); + background-position: -902px -200px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Desert { + background-image: url(spritesmith-main-11.png); + background-position: -902px -300px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Floral { + background-image: url(spritesmith-main-11.png); + background-position: -902px -400px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Golden { + background-image: url(spritesmith-main-11.png); + background-position: -902px -500px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Peppermint { + background-image: url(spritesmith-main-11.png); + background-position: -902px -600px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Red { + background-image: url(spritesmith-main-11.png); + background-position: -902px -700px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Shade { + background-image: url(spritesmith-main-11.png); + background-position: -902px -800px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Skeleton { + background-image: url(spritesmith-main-11.png); + background-position: -984px 0px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Spooky { + background-image: url(spritesmith-main-11.png); + background-position: -984px -100px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-White { + background-image: url(spritesmith-main-11.png); + background-position: -984px -200px; + width: 81px; + height: 99px; +} +.Mount_Icon_TigerCub-Zombie { + background-image: url(spritesmith-main-11.png); + background-position: -984px -300px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Base { + background-image: url(spritesmith-main-11.png); + background-position: -984px -400px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-CottonCandyBlue { + background-image: url(spritesmith-main-11.png); + background-position: -984px -500px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-CottonCandyPink { + background-image: url(spritesmith-main-11.png); + background-position: -984px -600px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Desert { + background-image: url(spritesmith-main-11.png); + background-position: -984px -700px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Golden { + background-image: url(spritesmith-main-11.png); + background-position: -984px -800px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Red { + background-image: url(spritesmith-main-11.png); + background-position: 0px -900px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Shade { + background-image: url(spritesmith-main-11.png); + background-position: -82px -900px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Skeleton { + background-image: url(spritesmith-main-11.png); + background-position: -164px -900px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-White { + background-image: url(spritesmith-main-11.png); + background-position: -246px -900px; + width: 81px; + height: 99px; +} +.Mount_Icon_Treeling-Zombie { + background-image: url(spritesmith-main-11.png); + background-position: -328px -900px; + width: 81px; + height: 99px; +} +.Mount_Icon_Turkey-Base { background-image: url(spritesmith-main-11.png); background-position: -1066px -200px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Skeleton { +.Mount_Icon_Unicorn-Base { background-image: url(spritesmith-main-11.png); background-position: -1066px -300px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-White { +.Mount_Icon_Unicorn-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1066px -400px; width: 81px; height: 99px; } -.Mount_Icon_Unicorn-Zombie { +.Mount_Icon_Unicorn-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1066px -500px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Base { +.Mount_Icon_Unicorn-Desert { background-image: url(spritesmith-main-11.png); background-position: -1066px -600px; width: 81px; height: 99px; } -.Mount_Icon_Whale-CottonCandyBlue { +.Mount_Icon_Unicorn-Golden { background-image: url(spritesmith-main-11.png); background-position: -1066px -700px; width: 81px; height: 99px; } -.Mount_Icon_Whale-CottonCandyPink { +.Mount_Icon_Unicorn-Red { background-image: url(spritesmith-main-11.png); background-position: -1066px -800px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Desert { +.Mount_Icon_Unicorn-Shade { background-image: url(spritesmith-main-11.png); background-position: -1066px -900px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Golden { +.Mount_Icon_Unicorn-Skeleton { background-image: url(spritesmith-main-11.png); background-position: 0px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Red { +.Mount_Icon_Unicorn-White { background-image: url(spritesmith-main-11.png); background-position: -82px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Shade { +.Mount_Icon_Unicorn-Zombie { background-image: url(spritesmith-main-11.png); background-position: -164px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Skeleton { +.Mount_Icon_Whale-Base { background-image: url(spritesmith-main-11.png); background-position: -246px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Whale-White { +.Mount_Icon_Whale-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -328px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Whale-Zombie { +.Mount_Icon_Whale-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -410px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Base { +.Mount_Icon_Whale-Desert { background-image: url(spritesmith-main-11.png); background-position: -492px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-CottonCandyBlue { +.Mount_Icon_Whale-Golden { background-image: url(spritesmith-main-11.png); background-position: -574px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-CottonCandyPink { +.Mount_Icon_Whale-Red { background-image: url(spritesmith-main-11.png); background-position: -656px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Desert { +.Mount_Icon_Whale-Shade { background-image: url(spritesmith-main-11.png); background-position: -738px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Floral { +.Mount_Icon_Whale-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -820px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Golden { +.Mount_Icon_Whale-White { background-image: url(spritesmith-main-11.png); background-position: -902px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Peppermint { +.Mount_Icon_Whale-Zombie { background-image: url(spritesmith-main-11.png); background-position: -984px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Red { +.Mount_Icon_Wolf-Base { background-image: url(spritesmith-main-11.png); background-position: -1066px -1000px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Shade { +.Mount_Icon_Wolf-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1148px 0px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Skeleton { +.Mount_Icon_Wolf-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1148px -100px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Spooky { +.Mount_Icon_Wolf-Desert { background-image: url(spritesmith-main-11.png); background-position: -1148px -200px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-White { +.Mount_Icon_Wolf-Floral { background-image: url(spritesmith-main-11.png); background-position: -1148px -300px; width: 81px; height: 99px; } -.Mount_Icon_Wolf-Zombie { +.Mount_Icon_Wolf-Golden { background-image: url(spritesmith-main-11.png); background-position: -1148px -400px; width: 81px; height: 99px; } -.Pet-BearCub-Base { +.Mount_Icon_Wolf-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -1148px -500px; width: 81px; height: 99px; } -.Pet-BearCub-CottonCandyBlue { +.Mount_Icon_Wolf-Red { background-image: url(spritesmith-main-11.png); background-position: -1148px -600px; width: 81px; height: 99px; } -.Pet-BearCub-CottonCandyPink { +.Mount_Icon_Wolf-Shade { background-image: url(spritesmith-main-11.png); background-position: -1148px -700px; width: 81px; height: 99px; } -.Pet-BearCub-Desert { +.Mount_Icon_Wolf-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1148px -800px; width: 81px; height: 99px; } -.Pet-BearCub-Floral { +.Mount_Icon_Wolf-Spooky { background-image: url(spritesmith-main-11.png); background-position: -1148px -900px; width: 81px; height: 99px; } -.Pet-BearCub-Golden { +.Mount_Icon_Wolf-White { background-image: url(spritesmith-main-11.png); background-position: -1148px -1000px; width: 81px; height: 99px; } -.Pet-BearCub-Peppermint { +.Mount_Icon_Wolf-Zombie { background-image: url(spritesmith-main-11.png); background-position: 0px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-Polar { +.Pet-BearCub-Base { background-image: url(spritesmith-main-11.png); background-position: 0px 0px; width: 81px; height: 99px; } -.Pet-BearCub-Red { +.Pet-BearCub-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -164px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-Shade { +.Pet-BearCub-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -246px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-Skeleton { +.Pet-BearCub-Desert { background-image: url(spritesmith-main-11.png); background-position: -328px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-Spooky { +.Pet-BearCub-Floral { background-image: url(spritesmith-main-11.png); background-position: -410px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-White { +.Pet-BearCub-Golden { background-image: url(spritesmith-main-11.png); background-position: -492px -1100px; width: 81px; height: 99px; } -.Pet-BearCub-Zombie { +.Pet-BearCub-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -574px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Base { +.Pet-BearCub-Polar { background-image: url(spritesmith-main-11.png); background-position: -656px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-CottonCandyBlue { +.Pet-BearCub-Red { background-image: url(spritesmith-main-11.png); background-position: -738px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-CottonCandyPink { +.Pet-BearCub-Shade { background-image: url(spritesmith-main-11.png); background-position: -820px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Desert { +.Pet-BearCub-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -902px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Golden { +.Pet-BearCub-Spooky { background-image: url(spritesmith-main-11.png); background-position: -984px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Red { +.Pet-BearCub-White { background-image: url(spritesmith-main-11.png); background-position: -1066px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Shade { +.Pet-BearCub-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1148px -1100px; width: 81px; height: 99px; } -.Pet-Bunny-Skeleton { +.Pet-Bunny-Base { background-image: url(spritesmith-main-11.png); background-position: -1230px 0px; width: 81px; height: 99px; } -.Pet-Bunny-White { +.Pet-Bunny-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1230px -100px; width: 81px; height: 99px; } -.Pet-Bunny-Zombie { +.Pet-Bunny-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1230px -200px; width: 81px; height: 99px; } -.Pet-Cactus-Base { +.Pet-Bunny-Desert { background-image: url(spritesmith-main-11.png); background-position: -1230px -300px; width: 81px; height: 99px; } -.Pet-Cactus-CottonCandyBlue { +.Pet-Bunny-Golden { background-image: url(spritesmith-main-11.png); background-position: -1230px -400px; width: 81px; height: 99px; } -.Pet-Cactus-CottonCandyPink { +.Pet-Bunny-Red { background-image: url(spritesmith-main-11.png); background-position: -1230px -500px; width: 81px; height: 99px; } -.Pet-Cactus-Desert { +.Pet-Bunny-Shade { background-image: url(spritesmith-main-11.png); background-position: -1230px -600px; width: 81px; height: 99px; } -.Pet-Cactus-Floral { +.Pet-Bunny-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1230px -700px; width: 81px; height: 99px; } -.Pet-Cactus-Golden { +.Pet-Bunny-White { background-image: url(spritesmith-main-11.png); background-position: -1230px -800px; width: 81px; height: 99px; } -.Pet-Cactus-Peppermint { +.Pet-Bunny-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1230px -900px; width: 81px; height: 99px; } -.Pet-Cactus-Red { +.Pet-Cactus-Base { background-image: url(spritesmith-main-11.png); background-position: -1230px -1000px; width: 81px; height: 99px; } -.Pet-Cactus-Shade { +.Pet-Cactus-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1230px -1100px; width: 81px; height: 99px; } -.Pet-Cactus-Skeleton { +.Pet-Cactus-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: 0px -1200px; width: 81px; height: 99px; } -.Pet-Cactus-Spooky { +.Pet-Cactus-Desert { background-image: url(spritesmith-main-11.png); background-position: -82px -1200px; width: 81px; height: 99px; } -.Pet-Cactus-White { +.Pet-Cactus-Floral { background-image: url(spritesmith-main-11.png); background-position: -164px -1200px; width: 81px; height: 99px; } -.Pet-Cactus-Zombie { +.Pet-Cactus-Golden { background-image: url(spritesmith-main-11.png); background-position: -246px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Base { +.Pet-Cactus-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -328px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-CottonCandyBlue { +.Pet-Cactus-Red { background-image: url(spritesmith-main-11.png); background-position: -410px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-CottonCandyPink { +.Pet-Cactus-Shade { background-image: url(spritesmith-main-11.png); background-position: -492px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Desert { +.Pet-Cactus-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -574px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Golden { +.Pet-Cactus-Spooky { background-image: url(spritesmith-main-11.png); background-position: -656px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Red { +.Pet-Cactus-White { background-image: url(spritesmith-main-11.png); background-position: -738px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Shade { +.Pet-Cactus-Zombie { background-image: url(spritesmith-main-11.png); background-position: -820px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Skeleton { +.Pet-Cheetah-Base { background-image: url(spritesmith-main-11.png); background-position: -902px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-White { +.Pet-Cheetah-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -984px -1200px; width: 81px; height: 99px; } -.Pet-Cheetah-Zombie { +.Pet-Cheetah-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1066px -1200px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Base { +.Pet-Cheetah-Desert { background-image: url(spritesmith-main-11.png); background-position: -1148px -1200px; width: 81px; height: 99px; } -.Pet-Cuttlefish-CottonCandyBlue { +.Pet-Cheetah-Golden { background-image: url(spritesmith-main-11.png); background-position: -1230px -1200px; width: 81px; height: 99px; } -.Pet-Cuttlefish-CottonCandyPink { +.Pet-Cheetah-Red { background-image: url(spritesmith-main-11.png); background-position: -1312px 0px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Desert { +.Pet-Cheetah-Shade { background-image: url(spritesmith-main-11.png); background-position: -1312px -100px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Golden { +.Pet-Cheetah-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1312px -200px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Red { +.Pet-Cheetah-White { background-image: url(spritesmith-main-11.png); background-position: -1312px -300px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Shade { +.Pet-Cheetah-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1312px -400px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Skeleton { +.Pet-Cuttlefish-Base { background-image: url(spritesmith-main-11.png); background-position: -1312px -500px; width: 81px; height: 99px; } -.Pet-Cuttlefish-White { +.Pet-Cuttlefish-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1312px -600px; width: 81px; height: 99px; } -.Pet-Cuttlefish-Zombie { +.Pet-Cuttlefish-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1312px -700px; width: 81px; height: 99px; } -.Pet-Deer-Base { +.Pet-Cuttlefish-Desert { background-image: url(spritesmith-main-11.png); background-position: -1312px -800px; width: 81px; height: 99px; } -.Pet-Deer-CottonCandyBlue { +.Pet-Cuttlefish-Golden { background-image: url(spritesmith-main-11.png); background-position: -1312px -900px; width: 81px; height: 99px; } -.Pet-Deer-CottonCandyPink { +.Pet-Cuttlefish-Red { background-image: url(spritesmith-main-11.png); background-position: -1312px -1000px; width: 81px; height: 99px; } -.Pet-Deer-Desert { +.Pet-Cuttlefish-Shade { background-image: url(spritesmith-main-11.png); background-position: -1312px -1100px; width: 81px; height: 99px; } -.Pet-Deer-Golden { +.Pet-Cuttlefish-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1312px -1200px; width: 81px; height: 99px; } -.Pet-Deer-Red { +.Pet-Cuttlefish-White { background-image: url(spritesmith-main-11.png); background-position: -1394px 0px; width: 81px; height: 99px; } -.Pet-Deer-Shade { +.Pet-Cuttlefish-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1394px -100px; width: 81px; height: 99px; } -.Pet-Deer-Skeleton { +.Pet-Deer-Base { background-image: url(spritesmith-main-11.png); background-position: -1394px -200px; width: 81px; height: 99px; } -.Pet-Deer-White { +.Pet-Deer-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1394px -300px; width: 81px; height: 99px; } -.Pet-Deer-Zombie { +.Pet-Deer-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1394px -400px; width: 81px; height: 99px; } -.Pet-Dragon-Base { +.Pet-Deer-Desert { background-image: url(spritesmith-main-11.png); background-position: -1394px -500px; width: 81px; height: 99px; } -.Pet-Dragon-CottonCandyBlue { +.Pet-Deer-Golden { background-image: url(spritesmith-main-11.png); background-position: -1394px -600px; width: 81px; height: 99px; } -.Pet-Dragon-CottonCandyPink { +.Pet-Deer-Red { background-image: url(spritesmith-main-11.png); background-position: -1394px -700px; width: 81px; height: 99px; } -.Pet-Dragon-Desert { +.Pet-Deer-Shade { background-image: url(spritesmith-main-11.png); background-position: -1394px -800px; width: 81px; height: 99px; } -.Pet-Dragon-Floral { +.Pet-Deer-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1394px -900px; width: 81px; height: 99px; } -.Pet-Dragon-Golden { +.Pet-Deer-White { background-image: url(spritesmith-main-11.png); background-position: -1394px -1000px; width: 81px; height: 99px; } -.Pet-Dragon-Hydra { +.Pet-Deer-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1394px -1100px; width: 81px; height: 99px; } -.Pet-Dragon-Peppermint { +.Pet-Dragon-Base { background-image: url(spritesmith-main-11.png); background-position: -1394px -1200px; width: 81px; height: 99px; } -.Pet-Dragon-Red { +.Pet-Dragon-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: 0px -1300px; width: 81px; height: 99px; } -.Pet-Dragon-Shade { +.Pet-Dragon-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -82px -1300px; width: 81px; height: 99px; } -.Pet-Dragon-Skeleton { +.Pet-Dragon-Desert { background-image: url(spritesmith-main-11.png); background-position: -164px -1300px; width: 81px; height: 99px; } -.Pet-Dragon-Spooky { +.Pet-Dragon-Floral { background-image: url(spritesmith-main-11.png); background-position: -246px -1300px; width: 81px; height: 99px; } -.Pet-Dragon-White { +.Pet-Dragon-Golden { background-image: url(spritesmith-main-11.png); background-position: -328px -1300px; width: 81px; height: 99px; } -.Pet-Dragon-Zombie { +.Pet-Dragon-Hydra { background-image: url(spritesmith-main-11.png); background-position: -410px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Base { +.Pet-Dragon-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -492px -1300px; width: 81px; height: 99px; } -.Pet-Egg-CottonCandyBlue { +.Pet-Dragon-Red { background-image: url(spritesmith-main-11.png); background-position: -574px -1300px; width: 81px; height: 99px; } -.Pet-Egg-CottonCandyPink { +.Pet-Dragon-Shade { background-image: url(spritesmith-main-11.png); background-position: -656px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Desert { +.Pet-Dragon-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -738px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Golden { +.Pet-Dragon-Spooky { background-image: url(spritesmith-main-11.png); background-position: -820px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Red { +.Pet-Dragon-White { background-image: url(spritesmith-main-11.png); background-position: -902px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Shade { +.Pet-Dragon-Zombie { background-image: url(spritesmith-main-11.png); background-position: -984px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Skeleton { +.Pet-Egg-Base { background-image: url(spritesmith-main-11.png); background-position: -1066px -1300px; width: 81px; height: 99px; } -.Pet-Egg-White { +.Pet-Egg-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1148px -1300px; width: 81px; height: 99px; } -.Pet-Egg-Zombie { +.Pet-Egg-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1230px -1300px; width: 81px; height: 99px; } -.Pet-Falcon-Base { +.Pet-Egg-Desert { background-image: url(spritesmith-main-11.png); background-position: -1312px -1300px; width: 81px; height: 99px; } -.Pet-Falcon-CottonCandyBlue { +.Pet-Egg-Golden { background-image: url(spritesmith-main-11.png); background-position: -1394px -1300px; width: 81px; height: 99px; } -.Pet-Falcon-CottonCandyPink { +.Pet-Egg-Red { background-image: url(spritesmith-main-11.png); background-position: -1476px 0px; width: 81px; height: 99px; } -.Pet-Falcon-Desert { +.Pet-Egg-Shade { background-image: url(spritesmith-main-11.png); background-position: -1476px -100px; width: 81px; height: 99px; } -.Pet-Falcon-Golden { +.Pet-Egg-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1476px -200px; width: 81px; height: 99px; } -.Pet-Falcon-Red { +.Pet-Egg-White { background-image: url(spritesmith-main-11.png); background-position: -1476px -300px; width: 81px; height: 99px; } -.Pet-Falcon-Shade { +.Pet-Egg-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1476px -400px; width: 81px; height: 99px; } -.Pet-Falcon-Skeleton { +.Pet-Falcon-Base { background-image: url(spritesmith-main-11.png); background-position: -1476px -500px; width: 81px; height: 99px; } -.Pet-Falcon-White { +.Pet-Falcon-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1476px -600px; width: 81px; height: 99px; } -.Pet-Falcon-Zombie { +.Pet-Falcon-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1476px -700px; width: 81px; height: 99px; } -.Pet-FlyingPig-Base { +.Pet-Falcon-Desert { background-image: url(spritesmith-main-11.png); background-position: -1476px -800px; width: 81px; height: 99px; } -.Pet-FlyingPig-CottonCandyBlue { +.Pet-Falcon-Golden { background-image: url(spritesmith-main-11.png); background-position: -1476px -900px; width: 81px; height: 99px; } -.Pet-FlyingPig-CottonCandyPink { +.Pet-Falcon-Red { background-image: url(spritesmith-main-11.png); background-position: -1476px -1000px; width: 81px; height: 99px; } -.Pet-FlyingPig-Desert { +.Pet-Falcon-Shade { background-image: url(spritesmith-main-11.png); background-position: -1476px -1100px; width: 81px; height: 99px; } -.Pet-FlyingPig-Floral { +.Pet-Falcon-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1476px -1200px; width: 81px; height: 99px; } -.Pet-FlyingPig-Golden { +.Pet-Falcon-White { background-image: url(spritesmith-main-11.png); background-position: -1476px -1300px; width: 81px; height: 99px; } -.Pet-FlyingPig-Peppermint { +.Pet-Falcon-Zombie { background-image: url(spritesmith-main-11.png); background-position: 0px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-Red { +.Pet-FlyingPig-Base { background-image: url(spritesmith-main-11.png); background-position: -82px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-Shade { +.Pet-FlyingPig-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -164px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-Skeleton { +.Pet-FlyingPig-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -246px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-Spooky { +.Pet-FlyingPig-Desert { background-image: url(spritesmith-main-11.png); background-position: -328px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-White { +.Pet-FlyingPig-Floral { background-image: url(spritesmith-main-11.png); background-position: -410px -1400px; width: 81px; height: 99px; } -.Pet-FlyingPig-Zombie { +.Pet-FlyingPig-Golden { background-image: url(spritesmith-main-11.png); background-position: -492px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Base { +.Pet-FlyingPig-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -574px -1400px; width: 81px; height: 99px; } -.Pet-Fox-CottonCandyBlue { +.Pet-FlyingPig-Red { background-image: url(spritesmith-main-11.png); background-position: -656px -1400px; width: 81px; height: 99px; } -.Pet-Fox-CottonCandyPink { +.Pet-FlyingPig-Shade { background-image: url(spritesmith-main-11.png); background-position: -738px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Desert { +.Pet-FlyingPig-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -820px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Floral { +.Pet-FlyingPig-Spooky { background-image: url(spritesmith-main-11.png); background-position: -902px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Golden { +.Pet-FlyingPig-White { background-image: url(spritesmith-main-11.png); background-position: -984px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Peppermint { +.Pet-FlyingPig-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1066px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Red { +.Pet-Fox-Base { background-image: url(spritesmith-main-11.png); background-position: -1148px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Shade { +.Pet-Fox-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1230px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Skeleton { +.Pet-Fox-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1312px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Spooky { +.Pet-Fox-Desert { background-image: url(spritesmith-main-11.png); background-position: -1394px -1400px; width: 81px; height: 99px; } -.Pet-Fox-White { +.Pet-Fox-Floral { background-image: url(spritesmith-main-11.png); background-position: -1476px -1400px; width: 81px; height: 99px; } -.Pet-Fox-Zombie { +.Pet-Fox-Golden { background-image: url(spritesmith-main-11.png); background-position: -1558px 0px; width: 81px; height: 99px; } -.Pet-Frog-Base { +.Pet-Fox-Peppermint { background-image: url(spritesmith-main-11.png); background-position: -1558px -100px; width: 81px; height: 99px; } -.Pet-Frog-CottonCandyBlue { +.Pet-Fox-Red { background-image: url(spritesmith-main-11.png); background-position: -1558px -200px; width: 81px; height: 99px; } -.Pet-Frog-CottonCandyPink { +.Pet-Fox-Shade { background-image: url(spritesmith-main-11.png); background-position: -1558px -300px; width: 81px; height: 99px; } -.Pet-Frog-Desert { +.Pet-Fox-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1558px -400px; width: 81px; height: 99px; } -.Pet-Frog-Golden { +.Pet-Fox-Spooky { background-image: url(spritesmith-main-11.png); background-position: -1558px -500px; width: 81px; height: 99px; } -.Pet-Frog-Red { +.Pet-Fox-White { background-image: url(spritesmith-main-11.png); background-position: -1558px -600px; width: 81px; height: 99px; } -.Pet-Frog-Shade { +.Pet-Fox-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1558px -700px; width: 81px; height: 99px; } -.Pet-Frog-Skeleton { +.Pet-Frog-Base { background-image: url(spritesmith-main-11.png); background-position: -1558px -800px; width: 81px; height: 99px; } -.Pet-Frog-White { +.Pet-Frog-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1558px -900px; width: 81px; height: 99px; } -.Pet-Frog-Zombie { +.Pet-Frog-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1558px -1000px; width: 81px; height: 99px; } -.Pet-Gryphon-Base { +.Pet-Frog-Desert { background-image: url(spritesmith-main-11.png); background-position: -1558px -1100px; width: 81px; height: 99px; } -.Pet-Gryphon-CottonCandyBlue { +.Pet-Frog-Golden { background-image: url(spritesmith-main-11.png); background-position: -1558px -1200px; width: 81px; height: 99px; } -.Pet-Gryphon-CottonCandyPink { +.Pet-Frog-Red { background-image: url(spritesmith-main-11.png); background-position: -1558px -1300px; width: 81px; height: 99px; } -.Pet-Gryphon-Desert { +.Pet-Frog-Shade { background-image: url(spritesmith-main-11.png); background-position: -1558px -1400px; width: 81px; height: 99px; } -.Pet-Gryphon-Golden { +.Pet-Frog-Skeleton { background-image: url(spritesmith-main-11.png); background-position: 0px -1500px; width: 81px; height: 99px; } -.Pet-Gryphon-Red { +.Pet-Frog-White { background-image: url(spritesmith-main-11.png); background-position: -82px -1500px; width: 81px; height: 99px; } -.Pet-Gryphon-Shade { +.Pet-Frog-Zombie { background-image: url(spritesmith-main-11.png); background-position: -164px -1500px; width: 81px; height: 99px; } -.Pet-Gryphon-Skeleton { +.Pet-Gryphon-Base { background-image: url(spritesmith-main-11.png); background-position: -246px -1500px; width: 81px; height: 99px; } -.Pet-Gryphon-White { +.Pet-Gryphon-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -328px -1500px; width: 81px; height: 99px; } -.Pet-Gryphon-Zombie { +.Pet-Gryphon-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -410px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Base { +.Pet-Gryphon-Desert { background-image: url(spritesmith-main-11.png); background-position: -492px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-CottonCandyBlue { +.Pet-Gryphon-Golden { background-image: url(spritesmith-main-11.png); background-position: -574px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-CottonCandyPink { +.Pet-Gryphon-Red { background-image: url(spritesmith-main-11.png); background-position: -656px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Desert { +.Pet-Gryphon-Shade { background-image: url(spritesmith-main-11.png); background-position: -738px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Golden { +.Pet-Gryphon-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -820px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Red { +.Pet-Gryphon-White { background-image: url(spritesmith-main-11.png); background-position: -902px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Shade { +.Pet-Gryphon-Zombie { background-image: url(spritesmith-main-11.png); background-position: -984px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Skeleton { +.Pet-Hedgehog-Base { background-image: url(spritesmith-main-11.png); background-position: -1066px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-White { +.Pet-Hedgehog-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1148px -1500px; width: 81px; height: 99px; } -.Pet-Hedgehog-Zombie { +.Pet-Hedgehog-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1230px -1500px; width: 81px; height: 99px; } -.Pet-Horse-Base { +.Pet-Hedgehog-Desert { background-image: url(spritesmith-main-11.png); background-position: -1312px -1500px; width: 81px; height: 99px; } -.Pet-Horse-CottonCandyBlue { +.Pet-Hedgehog-Golden { background-image: url(spritesmith-main-11.png); background-position: -1394px -1500px; width: 81px; height: 99px; } -.Pet-Horse-CottonCandyPink { +.Pet-Hedgehog-Red { background-image: url(spritesmith-main-11.png); background-position: -1476px -1500px; width: 81px; height: 99px; } -.Pet-Horse-Desert { +.Pet-Hedgehog-Shade { background-image: url(spritesmith-main-11.png); background-position: -1558px -1500px; width: 81px; height: 99px; } -.Pet-Horse-Golden { +.Pet-Hedgehog-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1640px 0px; width: 81px; height: 99px; } -.Pet-Horse-Red { +.Pet-Hedgehog-White { background-image: url(spritesmith-main-11.png); background-position: -1640px -100px; width: 81px; height: 99px; } -.Pet-Horse-Shade { +.Pet-Hedgehog-Zombie { background-image: url(spritesmith-main-11.png); background-position: -1640px -200px; width: 81px; height: 99px; } -.Pet-Horse-Skeleton { +.Pet-Horse-Base { background-image: url(spritesmith-main-11.png); background-position: -1640px -300px; width: 81px; height: 99px; } -.Pet-Horse-White { +.Pet-Horse-CottonCandyBlue { background-image: url(spritesmith-main-11.png); background-position: -1640px -400px; width: 81px; height: 99px; } -.Pet-Horse-Zombie { +.Pet-Horse-CottonCandyPink { background-image: url(spritesmith-main-11.png); background-position: -1640px -500px; width: 81px; height: 99px; } -.Pet-JackOLantern-Base { +.Pet-Horse-Desert { background-image: url(spritesmith-main-11.png); background-position: -1640px -600px; width: 81px; height: 99px; } -.Pet-Lion-Veteran { +.Pet-Horse-Golden { background-image: url(spritesmith-main-11.png); background-position: -1640px -700px; width: 81px; height: 99px; } -.Pet-LionCub-Base { +.Pet-Horse-Red { background-image: url(spritesmith-main-11.png); background-position: -1640px -800px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyBlue { +.Pet-Horse-Shade { background-image: url(spritesmith-main-11.png); background-position: -1640px -900px; width: 81px; height: 99px; } -.Pet-LionCub-CottonCandyPink { +.Pet-Horse-Skeleton { background-image: url(spritesmith-main-11.png); background-position: -1640px -1000px; width: 81px; height: 99px; } -.Pet-LionCub-Desert { +.Pet-Horse-White { background-image: url(spritesmith-main-11.png); background-position: -1640px -1100px; width: 81px; diff --git a/common/dist/sprites/spritesmith-main-11.png b/common/dist/sprites/spritesmith-main-11.png index 8f1f71ebf3f8894b42c38996a821660174f70e54..7c23598db3cefdef189adedbbff203c410744fe3 100644 GIT binary patch literal 154718 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 literal 156933 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 YgSxAq2uuCjdd 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 Q8SwhrWv?@BS76dV0V&^Ap8x;= diff --git a/common/dist/sprites/spritesmith-main-12.css b/common/dist/sprites/spritesmith-main-12.css index f5cff687d1..41561f57c4 100644 --- a/common/dist/sprites/spritesmith-main-12.css +++ b/common/dist/sprites/spritesmith-main-12.css @@ -1,1554 +1,1596 @@ -.Pet-LionCub-Floral { +.Pet-Horse-Zombie { background-image: url(spritesmith-main-12.png); background-position: -82px 0px; width: 81px; height: 99px; } -.Pet-LionCub-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -984px -900px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Peppermint { - background-image: url(spritesmith-main-12.png); - background-position: -164px 0px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Red { - background-image: url(spritesmith-main-12.png); - background-position: 0px -100px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -82px -100px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -164px -100px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Spooky { - background-image: url(spritesmith-main-12.png); - background-position: -246px 0px; - width: 81px; - height: 99px; -} -.Pet-LionCub-White { - background-image: url(spritesmith-main-12.png); - background-position: -246px -100px; - width: 81px; - height: 99px; -} -.Pet-LionCub-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: 0px -200px; - width: 81px; - height: 99px; -} -.Pet-MagicalBee-Base { - background-image: url(spritesmith-main-12.png); - background-position: -82px -200px; - width: 81px; - height: 99px; -} -.Pet-Mammoth-Base { - background-image: url(spritesmith-main-12.png); - background-position: -164px -200px; - width: 81px; - height: 99px; -} -.Pet-MantisShrimp-Base { - background-image: url(spritesmith-main-12.png); - background-position: -246px -200px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Base { - background-image: url(spritesmith-main-12.png); - background-position: -328px 0px; - width: 81px; - height: 99px; -} -.Pet-Monkey-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -328px -100px; - width: 81px; - height: 99px; -} -.Pet-Monkey-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -328px -200px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Desert { - background-image: url(spritesmith-main-12.png); - background-position: 0px -300px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -82px -300px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Red { - background-image: url(spritesmith-main-12.png); - background-position: -164px -300px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -246px -300px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -328px -300px; - width: 81px; - height: 99px; -} -.Pet-Monkey-White { - background-image: url(spritesmith-main-12.png); - background-position: -410px 0px; - width: 81px; - height: 99px; -} -.Pet-Monkey-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -410px -100px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Base { - background-image: url(spritesmith-main-12.png); - background-position: -410px -200px; - width: 81px; - height: 99px; -} -.Pet-Octopus-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -410px -300px; - width: 81px; - height: 99px; -} -.Pet-Octopus-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -492px 0px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -492px -100px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -492px -200px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Red { - background-image: url(spritesmith-main-12.png); - background-position: -492px -300px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Shade { - background-image: url(spritesmith-main-12.png); - background-position: 0px -400px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -82px -400px; - width: 81px; - height: 99px; -} -.Pet-Octopus-White { - background-image: url(spritesmith-main-12.png); - background-position: -164px -400px; - width: 81px; - height: 99px; -} -.Pet-Octopus-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -246px -400px; - width: 81px; - height: 99px; -} -.Pet-Owl-Base { - background-image: url(spritesmith-main-12.png); - background-position: -328px -400px; - width: 81px; - height: 99px; -} -.Pet-Owl-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -410px -400px; - width: 81px; - height: 99px; -} -.Pet-Owl-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -492px -400px; - width: 81px; - height: 99px; -} -.Pet-Owl-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -574px 0px; - width: 81px; - height: 99px; -} -.Pet-Owl-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -574px -100px; - width: 81px; - height: 99px; -} -.Pet-Owl-Red { - background-image: url(spritesmith-main-12.png); - background-position: -574px -200px; - width: 81px; - height: 99px; -} -.Pet-Owl-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -574px -300px; - width: 81px; - height: 99px; -} -.Pet-Owl-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -574px -400px; - width: 81px; - height: 99px; -} -.Pet-Owl-White { - background-image: url(spritesmith-main-12.png); - background-position: 0px -500px; - width: 81px; - height: 99px; -} -.Pet-Owl-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -82px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Base { - background-image: url(spritesmith-main-12.png); - background-position: -164px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -246px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -328px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -410px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Floral { - background-image: url(spritesmith-main-12.png); - background-position: -492px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -574px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Peppermint { - background-image: url(spritesmith-main-12.png); - background-position: -656px 0px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Red { - background-image: url(spritesmith-main-12.png); - background-position: -656px -100px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -656px -200px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -656px -300px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Spooky { - background-image: url(spritesmith-main-12.png); - background-position: -656px -400px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-White { - background-image: url(spritesmith-main-12.png); - background-position: -656px -500px; - width: 81px; - height: 99px; -} -.Pet-PandaCub-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: 0px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Base { - background-image: url(spritesmith-main-12.png); - background-position: -82px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -164px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -246px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -328px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -410px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Red { - background-image: url(spritesmith-main-12.png); - background-position: -492px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -574px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -656px -600px; - width: 81px; - height: 99px; -} -.Pet-Parrot-White { - background-image: url(spritesmith-main-12.png); - background-position: -738px 0px; - width: 81px; - height: 99px; -} -.Pet-Parrot-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -738px -100px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Base { - background-image: url(spritesmith-main-12.png); - background-position: -738px -200px; - width: 81px; - height: 99px; -} -.Pet-Penguin-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -738px -300px; - width: 81px; - height: 99px; -} -.Pet-Penguin-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -738px -400px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -738px -500px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -738px -600px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Red { - background-image: url(spritesmith-main-12.png); - background-position: 0px -700px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -82px -700px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -164px -700px; - width: 81px; - height: 99px; -} -.Pet-Penguin-White { - background-image: url(spritesmith-main-12.png); - background-position: -246px -700px; - width: 81px; - height: 99px; -} -.Pet-Penguin-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -328px -700px; - width: 81px; - height: 99px; -} -.Pet-Phoenix-Base { - background-image: url(spritesmith-main-12.png); - background-position: -410px -700px; - width: 81px; - height: 99px; -} -.Pet-Rat-Base { - background-image: url(spritesmith-main-12.png); - background-position: -492px -700px; - width: 81px; - height: 99px; -} -.Pet-Rat-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -574px -700px; - width: 81px; - height: 99px; -} -.Pet-Rat-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -656px -700px; - width: 81px; - height: 99px; -} -.Pet-Rat-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -738px -700px; - width: 81px; - height: 99px; -} -.Pet-Rat-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -820px 0px; - width: 81px; - height: 99px; -} -.Pet-Rat-Red { - background-image: url(spritesmith-main-12.png); - background-position: -820px -100px; - width: 81px; - height: 99px; -} -.Pet-Rat-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -820px -200px; - width: 81px; - height: 99px; -} -.Pet-Rat-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -820px -300px; - width: 81px; - height: 99px; -} -.Pet-Rat-White { - background-image: url(spritesmith-main-12.png); - background-position: -820px -400px; - width: 81px; - height: 99px; -} -.Pet-Rat-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -820px -500px; - width: 81px; - height: 99px; -} -.Pet-Rock-Base { - background-image: url(spritesmith-main-12.png); - background-position: -820px -600px; - width: 81px; - height: 99px; -} -.Pet-Rock-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -820px -700px; - width: 81px; - height: 99px; -} -.Pet-Rock-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: 0px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -82px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -164px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Red { - background-image: url(spritesmith-main-12.png); - background-position: -246px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -328px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -410px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-White { - background-image: url(spritesmith-main-12.png); - background-position: -492px -800px; - width: 81px; - height: 99px; -} -.Pet-Rock-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -574px -800px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Base { - background-image: url(spritesmith-main-12.png); - background-position: -656px -800px; - width: 81px; - height: 99px; -} -.Pet-Rooster-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -738px -800px; - width: 81px; - height: 99px; -} -.Pet-Rooster-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -820px -800px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -902px 0px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -902px -100px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Red { - background-image: url(spritesmith-main-12.png); - background-position: -902px -200px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -902px -300px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -902px -400px; - width: 81px; - height: 99px; -} -.Pet-Rooster-White { - background-image: url(spritesmith-main-12.png); - background-position: -902px -500px; - width: 81px; - height: 99px; -} -.Pet-Rooster-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -902px -600px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Base { - background-image: url(spritesmith-main-12.png); - background-position: -902px -700px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -902px -800px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -984px 0px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -984px -100px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -984px -200px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Red { - background-image: url(spritesmith-main-12.png); - background-position: -984px -300px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -984px -400px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -984px -500px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-White { - background-image: url(spritesmith-main-12.png); - background-position: -984px -600px; - width: 81px; - height: 99px; -} -.Pet-Sabretooth-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -984px -700px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Base { - background-image: url(spritesmith-main-12.png); - background-position: -984px -800px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: 0px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -82px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -164px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -246px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Red { - background-image: url(spritesmith-main-12.png); - background-position: -328px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -410px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -492px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-White { - background-image: url(spritesmith-main-12.png); - background-position: -574px -900px; - width: 81px; - height: 99px; -} -.Pet-Seahorse-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -656px -900px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Base { - background-image: url(spritesmith-main-12.png); - background-position: -738px -900px; - width: 81px; - height: 99px; -} -.Pet-Sheep-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -820px -900px; - width: 81px; - height: 99px; -} -.Pet-Sheep-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -902px -900px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Desert { - background-image: url(spritesmith-main-12.png); - background-position: 0px 0px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -1066px 0px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Red { - background-image: url(spritesmith-main-12.png); - background-position: -1066px -100px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -1066px -200px; - width: 81px; - height: 99px; -} -.Pet-Sheep-Skeleton { +.Pet-JackOLantern-Base { background-image: url(spritesmith-main-12.png); background-position: -1066px -300px; width: 81px; height: 99px; } -.Pet-Sheep-White { +.Pet-Lion-Veteran { + background-image: url(spritesmith-main-12.png); + background-position: -164px 0px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Base { + background-image: url(spritesmith-main-12.png); + background-position: 0px -100px; + width: 81px; + height: 99px; +} +.Pet-LionCub-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -82px -100px; + width: 81px; + height: 99px; +} +.Pet-LionCub-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -164px -100px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -246px 0px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Floral { + background-image: url(spritesmith-main-12.png); + background-position: -246px -100px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Golden { + background-image: url(spritesmith-main-12.png); + background-position: 0px -200px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Peppermint { + background-image: url(spritesmith-main-12.png); + background-position: -82px -200px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Red { + background-image: url(spritesmith-main-12.png); + background-position: -164px -200px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -246px -200px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -328px 0px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Spooky { + background-image: url(spritesmith-main-12.png); + background-position: -328px -100px; + width: 81px; + height: 99px; +} +.Pet-LionCub-White { + background-image: url(spritesmith-main-12.png); + background-position: -328px -200px; + width: 81px; + height: 99px; +} +.Pet-LionCub-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: 0px -300px; + width: 81px; + height: 99px; +} +.Pet-MagicalBee-Base { + background-image: url(spritesmith-main-12.png); + background-position: -82px -300px; + width: 81px; + height: 99px; +} +.Pet-Mammoth-Base { + background-image: url(spritesmith-main-12.png); + background-position: -164px -300px; + width: 81px; + height: 99px; +} +.Pet-MantisShrimp-Base { + background-image: url(spritesmith-main-12.png); + background-position: -246px -300px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Base { + background-image: url(spritesmith-main-12.png); + background-position: -328px -300px; + width: 81px; + height: 99px; +} +.Pet-Monkey-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -410px 0px; + width: 81px; + height: 99px; +} +.Pet-Monkey-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -410px -100px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -410px -200px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -410px -300px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Red { + background-image: url(spritesmith-main-12.png); + background-position: -492px 0px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -492px -100px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -492px -200px; + width: 81px; + height: 99px; +} +.Pet-Monkey-White { + background-image: url(spritesmith-main-12.png); + background-position: -492px -300px; + width: 81px; + height: 99px; +} +.Pet-Monkey-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: 0px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Base { + background-image: url(spritesmith-main-12.png); + background-position: -82px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -164px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -246px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -328px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -410px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Red { + background-image: url(spritesmith-main-12.png); + background-position: -492px -400px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -574px 0px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -574px -100px; + width: 81px; + height: 99px; +} +.Pet-Octopus-White { + background-image: url(spritesmith-main-12.png); + background-position: -574px -200px; + width: 81px; + height: 99px; +} +.Pet-Octopus-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -574px -300px; + width: 81px; + height: 99px; +} +.Pet-Owl-Base { + background-image: url(spritesmith-main-12.png); + background-position: -574px -400px; + width: 81px; + height: 99px; +} +.Pet-Owl-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: 0px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -82px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -164px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -246px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Red { + background-image: url(spritesmith-main-12.png); + background-position: -328px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -410px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -492px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-White { + background-image: url(spritesmith-main-12.png); + background-position: -574px -500px; + width: 81px; + height: 99px; +} +.Pet-Owl-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -656px 0px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Base { + background-image: url(spritesmith-main-12.png); + background-position: -656px -100px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -656px -200px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -656px -300px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -656px -400px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Floral { + background-image: url(spritesmith-main-12.png); + background-position: -656px -500px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Golden { + background-image: url(spritesmith-main-12.png); + background-position: 0px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Peppermint { + background-image: url(spritesmith-main-12.png); + background-position: -82px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Red { + background-image: url(spritesmith-main-12.png); + background-position: -164px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -246px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -328px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Spooky { + background-image: url(spritesmith-main-12.png); + background-position: -410px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-White { + background-image: url(spritesmith-main-12.png); + background-position: -492px -600px; + width: 81px; + height: 99px; +} +.Pet-PandaCub-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -574px -600px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Base { + background-image: url(spritesmith-main-12.png); + background-position: -656px -600px; + width: 81px; + height: 99px; +} +.Pet-Parrot-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -738px 0px; + width: 81px; + height: 99px; +} +.Pet-Parrot-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -738px -100px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -738px -200px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -738px -300px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Red { + background-image: url(spritesmith-main-12.png); + background-position: -738px -400px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -738px -500px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -738px -600px; + width: 81px; + height: 99px; +} +.Pet-Parrot-White { + background-image: url(spritesmith-main-12.png); + background-position: 0px -700px; + width: 81px; + height: 99px; +} +.Pet-Parrot-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -82px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Base { + background-image: url(spritesmith-main-12.png); + background-position: -164px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -246px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -328px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -410px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -492px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Red { + background-image: url(spritesmith-main-12.png); + background-position: -574px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -656px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -738px -700px; + width: 81px; + height: 99px; +} +.Pet-Penguin-White { + background-image: url(spritesmith-main-12.png); + background-position: -820px 0px; + width: 81px; + height: 99px; +} +.Pet-Penguin-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -820px -100px; + width: 81px; + height: 99px; +} +.Pet-Phoenix-Base { + background-image: url(spritesmith-main-12.png); + background-position: -820px -200px; + width: 81px; + height: 99px; +} +.Pet-Rat-Base { + background-image: url(spritesmith-main-12.png); + background-position: -820px -300px; + width: 81px; + height: 99px; +} +.Pet-Rat-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -820px -400px; + width: 81px; + height: 99px; +} +.Pet-Rat-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -820px -500px; + width: 81px; + height: 99px; +} +.Pet-Rat-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -820px -600px; + width: 81px; + height: 99px; +} +.Pet-Rat-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -820px -700px; + width: 81px; + height: 99px; +} +.Pet-Rat-Red { + background-image: url(spritesmith-main-12.png); + background-position: 0px -800px; + width: 81px; + height: 99px; +} +.Pet-Rat-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -82px -800px; + width: 81px; + height: 99px; +} +.Pet-Rat-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -164px -800px; + width: 81px; + height: 99px; +} +.Pet-Rat-White { + background-image: url(spritesmith-main-12.png); + background-position: -246px -800px; + width: 81px; + height: 99px; +} +.Pet-Rat-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -328px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-Base { + background-image: url(spritesmith-main-12.png); + background-position: -410px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -492px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -574px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -656px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -738px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-Red { + background-image: url(spritesmith-main-12.png); + background-position: -820px -800px; + width: 81px; + height: 99px; +} +.Pet-Rock-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -902px 0px; + width: 81px; + height: 99px; +} +.Pet-Rock-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -902px -100px; + width: 81px; + height: 99px; +} +.Pet-Rock-White { + background-image: url(spritesmith-main-12.png); + background-position: -902px -200px; + width: 81px; + height: 99px; +} +.Pet-Rock-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -902px -300px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Base { + background-image: url(spritesmith-main-12.png); + background-position: -902px -400px; + width: 81px; + height: 99px; +} +.Pet-Rooster-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -902px -500px; + width: 81px; + height: 99px; +} +.Pet-Rooster-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -902px -600px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -902px -700px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -902px -800px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Red { + background-image: url(spritesmith-main-12.png); + background-position: -984px 0px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -984px -100px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -984px -200px; + width: 81px; + height: 99px; +} +.Pet-Rooster-White { + background-image: url(spritesmith-main-12.png); + background-position: -984px -300px; + width: 81px; + height: 99px; +} +.Pet-Rooster-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -984px -400px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Base { + background-image: url(spritesmith-main-12.png); + background-position: -984px -500px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -984px -600px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -984px -700px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -984px -800px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Golden { + background-image: url(spritesmith-main-12.png); + background-position: 0px -900px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Red { + background-image: url(spritesmith-main-12.png); + background-position: -82px -900px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -164px -900px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -246px -900px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-White { + background-image: url(spritesmith-main-12.png); + background-position: -328px -900px; + width: 81px; + height: 99px; +} +.Pet-Sabretooth-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -410px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Base { + background-image: url(spritesmith-main-12.png); + background-position: -492px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -574px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -656px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -738px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -820px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Red { + background-image: url(spritesmith-main-12.png); + background-position: -902px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -984px -900px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -1066px 0px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-White { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -100px; + width: 81px; + height: 99px; +} +.Pet-Seahorse-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -200px; + width: 81px; + height: 99px; +} +.Pet-Sheep-Base { + background-image: url(spritesmith-main-12.png); + background-position: 0px 0px; + width: 81px; + height: 99px; +} +.Pet-Sheep-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1066px -400px; width: 81px; height: 99px; } -.Pet-Sheep-Zombie { +.Pet-Sheep-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1066px -500px; width: 81px; height: 99px; } -.Pet-Slime-Base { +.Pet-Sheep-Desert { background-image: url(spritesmith-main-12.png); background-position: -1066px -600px; width: 81px; height: 99px; } -.Pet-Slime-CottonCandyBlue { +.Pet-Sheep-Golden { background-image: url(spritesmith-main-12.png); background-position: -1066px -700px; width: 81px; height: 99px; } -.Pet-Slime-CottonCandyPink { +.Pet-Sheep-Red { background-image: url(spritesmith-main-12.png); background-position: -1066px -800px; width: 81px; height: 99px; } -.Pet-Slime-Desert { +.Pet-Sheep-Shade { background-image: url(spritesmith-main-12.png); background-position: -1066px -900px; width: 81px; height: 99px; } -.Pet-Slime-Golden { +.Pet-Sheep-Skeleton { background-image: url(spritesmith-main-12.png); background-position: 0px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Red { +.Pet-Sheep-White { background-image: url(spritesmith-main-12.png); background-position: -82px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Shade { +.Pet-Sheep-Zombie { background-image: url(spritesmith-main-12.png); background-position: -164px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Skeleton { +.Pet-Slime-Base { background-image: url(spritesmith-main-12.png); background-position: -246px -1000px; width: 81px; height: 99px; } -.Pet-Slime-White { +.Pet-Slime-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -328px -1000px; width: 81px; height: 99px; } -.Pet-Slime-Zombie { +.Pet-Slime-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -410px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Base { +.Pet-Slime-Desert { background-image: url(spritesmith-main-12.png); background-position: -492px -1000px; width: 81px; height: 99px; } -.Pet-Snail-CottonCandyBlue { +.Pet-Slime-Golden { background-image: url(spritesmith-main-12.png); background-position: -574px -1000px; width: 81px; height: 99px; } -.Pet-Snail-CottonCandyPink { +.Pet-Slime-Red { background-image: url(spritesmith-main-12.png); background-position: -656px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Desert { +.Pet-Slime-Shade { background-image: url(spritesmith-main-12.png); background-position: -738px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Golden { +.Pet-Slime-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -820px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Red { +.Pet-Slime-White { background-image: url(spritesmith-main-12.png); background-position: -902px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Shade { +.Pet-Slime-Zombie { background-image: url(spritesmith-main-12.png); background-position: -984px -1000px; width: 81px; height: 99px; } -.Pet-Snail-Skeleton { +.Pet-Snail-Base { background-image: url(spritesmith-main-12.png); background-position: -1066px -1000px; width: 81px; height: 99px; } -.Pet-Snail-White { +.Pet-Snail-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1148px 0px; width: 81px; height: 99px; } -.Pet-Snail-Zombie { +.Pet-Snail-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1148px -100px; width: 81px; height: 99px; } -.Pet-Snake-Base { +.Pet-Snail-Desert { background-image: url(spritesmith-main-12.png); background-position: -1148px -200px; width: 81px; height: 99px; } -.Pet-Snake-CottonCandyBlue { +.Pet-Snail-Golden { background-image: url(spritesmith-main-12.png); background-position: -1148px -300px; width: 81px; height: 99px; } -.Pet-Snake-CottonCandyPink { +.Pet-Snail-Red { background-image: url(spritesmith-main-12.png); background-position: -1148px -400px; width: 81px; height: 99px; } -.Pet-Snake-Desert { +.Pet-Snail-Shade { background-image: url(spritesmith-main-12.png); background-position: -1148px -500px; width: 81px; height: 99px; } -.Pet-Snake-Golden { +.Pet-Snail-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1148px -600px; width: 81px; height: 99px; } -.Pet-Snake-Red { +.Pet-Snail-White { background-image: url(spritesmith-main-12.png); background-position: -1148px -700px; width: 81px; height: 99px; } -.Pet-Snake-Shade { +.Pet-Snail-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1148px -800px; width: 81px; height: 99px; } -.Pet-Snake-Skeleton { +.Pet-Snake-Base { background-image: url(spritesmith-main-12.png); background-position: -1148px -900px; width: 81px; height: 99px; } -.Pet-Snake-White { +.Pet-Snake-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1148px -1000px; width: 81px; height: 99px; } -.Pet-Snake-Zombie { +.Pet-Snake-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: 0px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Base { +.Pet-Snake-Desert { background-image: url(spritesmith-main-12.png); background-position: -82px -1100px; width: 81px; height: 99px; } -.Pet-Spider-CottonCandyBlue { +.Pet-Snake-Golden { background-image: url(spritesmith-main-12.png); background-position: -164px -1100px; width: 81px; height: 99px; } -.Pet-Spider-CottonCandyPink { +.Pet-Snake-Red { background-image: url(spritesmith-main-12.png); background-position: -246px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Desert { +.Pet-Snake-Shade { background-image: url(spritesmith-main-12.png); background-position: -328px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Golden { +.Pet-Snake-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -410px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Red { +.Pet-Snake-White { background-image: url(spritesmith-main-12.png); background-position: -492px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Shade { +.Pet-Snake-Zombie { background-image: url(spritesmith-main-12.png); background-position: -574px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Skeleton { +.Pet-Spider-Base { background-image: url(spritesmith-main-12.png); background-position: -656px -1100px; width: 81px; height: 99px; } -.Pet-Spider-White { +.Pet-Spider-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -738px -1100px; width: 81px; height: 99px; } -.Pet-Spider-Zombie { +.Pet-Spider-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -820px -1100px; width: 81px; height: 99px; } -.Pet-TRex-Base { - background-image: url(spritesmith-main-12.png); - background-position: -656px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -738px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: -820px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -902px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -984px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Red { - background-image: url(spritesmith-main-12.png); - background-position: -1066px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -1148px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1200px; - width: 81px; - height: 99px; -} -.Pet-TRex-White { - background-image: url(spritesmith-main-12.png); - background-position: -1312px 0px; - width: 81px; - height: 99px; -} -.Pet-TRex-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -1312px -100px; - width: 81px; - height: 99px; -} -.Pet-Tiger-Veteran { +.Pet-Spider-Desert { background-image: url(spritesmith-main-12.png); background-position: -902px -1100px; width: 81px; height: 99px; } -.Pet-TigerCub-Base { +.Pet-Spider-Golden { background-image: url(spritesmith-main-12.png); background-position: -984px -1100px; width: 81px; height: 99px; } -.Pet-TigerCub-CottonCandyBlue { +.Pet-Spider-Red { background-image: url(spritesmith-main-12.png); background-position: -1066px -1100px; width: 81px; height: 99px; } -.Pet-TigerCub-CottonCandyPink { +.Pet-Spider-Shade { background-image: url(spritesmith-main-12.png); background-position: -1148px -1100px; width: 81px; height: 99px; } -.Pet-TigerCub-Desert { +.Pet-Spider-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1230px 0px; width: 81px; height: 99px; } -.Pet-TigerCub-Floral { +.Pet-Spider-White { background-image: url(spritesmith-main-12.png); background-position: -1230px -100px; width: 81px; height: 99px; } -.Pet-TigerCub-Golden { +.Pet-Spider-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1230px -200px; width: 81px; height: 99px; } -.Pet-TigerCub-Peppermint { +.Pet-TRex-Base { background-image: url(spritesmith-main-12.png); - background-position: -1230px -300px; + background-position: -1230px -1200px; width: 81px; height: 99px; } -.Pet-TigerCub-Red { +.Pet-TRex-CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1230px -400px; + background-position: -1312px 0px; width: 81px; height: 99px; } -.Pet-TigerCub-Shade { +.Pet-TRex-CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: -1230px -500px; + background-position: -1312px -100px; width: 81px; height: 99px; } -.Pet-TigerCub-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -600px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Spooky { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -700px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-White { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -800px; - width: 81px; - height: 99px; -} -.Pet-TigerCub-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -900px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Base { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1000px; - width: 81px; - height: 99px; -} -.Pet-Treeling-CottonCandyBlue { - background-image: url(spritesmith-main-12.png); - background-position: -1230px -1100px; - width: 81px; - height: 99px; -} -.Pet-Treeling-CottonCandyPink { - background-image: url(spritesmith-main-12.png); - background-position: 0px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Desert { - background-image: url(spritesmith-main-12.png); - background-position: -82px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Golden { - background-image: url(spritesmith-main-12.png); - background-position: -164px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Red { - background-image: url(spritesmith-main-12.png); - background-position: -246px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Shade { - background-image: url(spritesmith-main-12.png); - background-position: -328px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Skeleton { - background-image: url(spritesmith-main-12.png); - background-position: -410px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-White { - background-image: url(spritesmith-main-12.png); - background-position: -492px -1200px; - width: 81px; - height: 99px; -} -.Pet-Treeling-Zombie { - background-image: url(spritesmith-main-12.png); - background-position: -574px -1200px; - width: 81px; - height: 99px; -} -.Pet-Turkey-Base { +.Pet-TRex-Desert { background-image: url(spritesmith-main-12.png); background-position: -1312px -200px; width: 81px; height: 99px; } -.Pet-Turkey-Gilded { +.Pet-TRex-Golden { background-image: url(spritesmith-main-12.png); background-position: -1312px -300px; width: 81px; height: 99px; } -.Pet-Unicorn-Base { +.Pet-TRex-Red { background-image: url(spritesmith-main-12.png); background-position: -1312px -400px; width: 81px; height: 99px; } -.Pet-Unicorn-CottonCandyBlue { +.Pet-TRex-Shade { background-image: url(spritesmith-main-12.png); background-position: -1312px -500px; width: 81px; height: 99px; } -.Pet-Unicorn-CottonCandyPink { +.Pet-TRex-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1312px -600px; width: 81px; height: 99px; } -.Pet-Unicorn-Desert { +.Pet-TRex-White { background-image: url(spritesmith-main-12.png); background-position: -1312px -700px; width: 81px; height: 99px; } -.Pet-Unicorn-Golden { +.Pet-TRex-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1312px -800px; width: 81px; height: 99px; } -.Pet-Unicorn-Red { +.Pet-Tiger-Veteran { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -300px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Base { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -400px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -500px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -600px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -700px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Floral { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -800px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -900px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Peppermint { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1000px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Red { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1100px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Shade { + background-image: url(spritesmith-main-12.png); + background-position: 0px -1200px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -82px -1200px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Spooky { + background-image: url(spritesmith-main-12.png); + background-position: -164px -1200px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-White { + background-image: url(spritesmith-main-12.png); + background-position: -246px -1200px; + width: 81px; + height: 99px; +} +.Pet-TigerCub-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -328px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Base { + background-image: url(spritesmith-main-12.png); + background-position: -410px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-CottonCandyBlue { + background-image: url(spritesmith-main-12.png); + background-position: -492px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-CottonCandyPink { + background-image: url(spritesmith-main-12.png); + background-position: -574px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Desert { + background-image: url(spritesmith-main-12.png); + background-position: -656px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Golden { + background-image: url(spritesmith-main-12.png); + background-position: -738px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Red { + background-image: url(spritesmith-main-12.png); + background-position: -820px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -902px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -984px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-White { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -1200px; + width: 81px; + height: 99px; +} +.Pet-Treeling-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -1148px -1200px; + width: 81px; + height: 99px; +} +.Pet-Turkey-Base { background-image: url(spritesmith-main-12.png); background-position: -1312px -900px; width: 81px; height: 99px; } -.Pet-Unicorn-Shade { +.Pet-Turkey-Gilded { background-image: url(spritesmith-main-12.png); background-position: -1312px -1000px; width: 81px; height: 99px; } -.Pet-Unicorn-Skeleton { +.Pet-Unicorn-Base { background-image: url(spritesmith-main-12.png); background-position: -1312px -1100px; width: 81px; height: 99px; } -.Pet-Unicorn-White { +.Pet-Unicorn-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1312px -1200px; width: 81px; height: 99px; } -.Pet-Unicorn-Zombie { +.Pet-Unicorn-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1394px 0px; width: 81px; height: 99px; } -.Pet-Whale-Base { +.Pet-Unicorn-Desert { background-image: url(spritesmith-main-12.png); background-position: -1394px -100px; width: 81px; height: 99px; } -.Pet-Whale-CottonCandyBlue { +.Pet-Unicorn-Golden { background-image: url(spritesmith-main-12.png); background-position: -1394px -200px; width: 81px; height: 99px; } -.Pet-Whale-CottonCandyPink { +.Pet-Unicorn-Red { background-image: url(spritesmith-main-12.png); background-position: -1394px -300px; width: 81px; height: 99px; } -.Pet-Whale-Desert { +.Pet-Unicorn-Shade { background-image: url(spritesmith-main-12.png); background-position: -1394px -400px; width: 81px; height: 99px; } -.Pet-Whale-Golden { +.Pet-Unicorn-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -1394px -500px; width: 81px; height: 99px; } -.Pet-Whale-Red { +.Pet-Unicorn-White { background-image: url(spritesmith-main-12.png); background-position: -1394px -600px; width: 81px; height: 99px; } -.Pet-Whale-Shade { +.Pet-Unicorn-Zombie { background-image: url(spritesmith-main-12.png); background-position: -1394px -700px; width: 81px; height: 99px; } -.Pet-Whale-Skeleton { +.Pet-Whale-Base { background-image: url(spritesmith-main-12.png); background-position: -1394px -800px; width: 81px; height: 99px; } -.Pet-Whale-White { +.Pet-Whale-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -1394px -900px; width: 81px; height: 99px; } -.Pet-Whale-Zombie { +.Pet-Whale-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -1394px -1000px; width: 81px; height: 99px; } -.Pet-Wolf-Base { +.Pet-Whale-Desert { background-image: url(spritesmith-main-12.png); background-position: -1394px -1100px; width: 81px; height: 99px; } -.Pet-Wolf-CottonCandyBlue { +.Pet-Whale-Golden { background-image: url(spritesmith-main-12.png); background-position: -1394px -1200px; width: 81px; height: 99px; } -.Pet-Wolf-CottonCandyPink { +.Pet-Whale-Red { background-image: url(spritesmith-main-12.png); background-position: 0px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Desert { +.Pet-Whale-Shade { background-image: url(spritesmith-main-12.png); background-position: -82px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Floral { +.Pet-Whale-Skeleton { background-image: url(spritesmith-main-12.png); background-position: -164px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Golden { +.Pet-Whale-White { background-image: url(spritesmith-main-12.png); background-position: -246px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Peppermint { +.Pet-Whale-Zombie { background-image: url(spritesmith-main-12.png); background-position: -328px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Red { +.Pet-Wolf-Base { background-image: url(spritesmith-main-12.png); background-position: -410px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Shade { +.Pet-Wolf-CottonCandyBlue { background-image: url(spritesmith-main-12.png); background-position: -492px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Skeleton { +.Pet-Wolf-CottonCandyPink { background-image: url(spritesmith-main-12.png); background-position: -574px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Spooky { +.Pet-Wolf-Desert { background-image: url(spritesmith-main-12.png); background-position: -656px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Veteran { +.Pet-Wolf-Floral { background-image: url(spritesmith-main-12.png); background-position: -738px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-White { +.Pet-Wolf-Golden { background-image: url(spritesmith-main-12.png); background-position: -820px -1300px; width: 81px; height: 99px; } -.Pet-Wolf-Zombie { +.Pet-Wolf-Peppermint { background-image: url(spritesmith-main-12.png); background-position: -902px -1300px; width: 81px; height: 99px; } +.Pet-Wolf-Red { + background-image: url(spritesmith-main-12.png); + background-position: -984px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-Shade { + background-image: url(spritesmith-main-12.png); + background-position: -1066px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-Skeleton { + background-image: url(spritesmith-main-12.png); + background-position: -1148px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-Spooky { + background-image: url(spritesmith-main-12.png); + background-position: -1230px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-Veteran { + background-image: url(spritesmith-main-12.png); + background-position: -1312px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-White { + background-image: url(spritesmith-main-12.png); + background-position: -1394px -1300px; + width: 81px; + height: 99px; +} +.Pet-Wolf-Zombie { + background-image: url(spritesmith-main-12.png); + background-position: -1476px 0px; + width: 81px; + height: 99px; +} .Pet_HatchingPotion_Base { background-image: url(spritesmith-main-12.png); - background-position: -1033px -1300px; + background-position: -1476px -152px; width: 48px; height: 51px; } .Pet_HatchingPotion_CottonCandyBlue { background-image: url(spritesmith-main-12.png); - background-position: -1278px -1300px; + background-position: -1476px -412px; width: 48px; height: 51px; } .Pet_HatchingPotion_CottonCandyPink { background-image: url(spritesmith-main-12.png); - background-position: -1082px -1300px; + background-position: -1476px -204px; width: 48px; height: 51px; } .Pet_HatchingPotion_Desert { background-image: url(spritesmith-main-12.png); - background-position: -1131px -1300px; + background-position: -1476px -256px; width: 48px; height: 51px; } .Pet_HatchingPotion_Floral { background-image: url(spritesmith-main-12.png); - background-position: -1180px -1300px; + background-position: -1476px -308px; width: 48px; height: 51px; } .Pet_HatchingPotion_Golden { background-image: url(spritesmith-main-12.png); - background-position: -1229px -1300px; + background-position: -1476px -360px; width: 48px; height: 51px; } .Pet_HatchingPotion_Peppermint { background-image: url(spritesmith-main-12.png); - background-position: -984px -1300px; + background-position: -1476px -100px; width: 48px; height: 51px; } .Pet_HatchingPotion_Red { background-image: url(spritesmith-main-12.png); - background-position: -1327px -1300px; + background-position: -1476px -464px; width: 48px; height: 51px; } .Pet_HatchingPotion_Shade { background-image: url(spritesmith-main-12.png); - background-position: -1376px -1300px; + background-position: -1476px -516px; width: 48px; height: 51px; } .Pet_HatchingPotion_Skeleton { background-image: url(spritesmith-main-12.png); - background-position: -1425px -1300px; + background-position: -1476px -568px; width: 48px; height: 51px; } .Pet_HatchingPotion_Spooky { background-image: url(spritesmith-main-12.png); - background-position: 0px -1400px; + background-position: -1476px -620px; width: 48px; height: 51px; } .Pet_HatchingPotion_White { background-image: url(spritesmith-main-12.png); - background-position: -49px -1400px; + background-position: -1476px -672px; width: 48px; height: 51px; } .Pet_HatchingPotion_Zombie { background-image: url(spritesmith-main-12.png); - background-position: -98px -1400px; + background-position: -1476px -724px; width: 48px; height: 51px; } diff --git a/common/dist/sprites/spritesmith-main-12.png b/common/dist/sprites/spritesmith-main-12.png index 7938d749c1d47b1013855132ce32a638fb723bb8..79349e60a771519c1d8fdcf2dec527608e8c0a73 100644 GIT binary patch literal 133905 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