mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01:00
* Begin refactoring news API to return individual markdown posts * Implement simple bailey CMS * Prevented users with lvl less than 10 from seeing mana * Added in class checks and notification tests * Added getter use * Fixed class check * chore(i18n): update locales * 4.60.2 * remove tests that are no longer needed because we won't be purging private messages (#10670) Ref: this comment from paglias: https://github.com/HabitRPG/habitica/issues/7940#issuecomment-406489506 * remove .only * allow challenge leader/owner to view/join/modify challenge in private group they've left - fixes #9753 (#10606) * rename hasAccess to canJoin for challenges This is so the function won't be used accidentally for other purposes, since hasAccess could be misinterpretted. * add isLeader function for challenges * allow challenge leader to join/modify/end challenge when they're not in the private group it's in * delete duplicate test * clarify title of existing tests * add tests and adjust existing tests to reduce privileges of test users * fix lint errors * remove pointless isLeader check (it's checked in canJoin) * Correct Challenges tooltip in Guild view (#10667) * Fix new party member cannot join pending quest (#10648) * Saved sort selection into local storage for later use - fixes #10432 (#10655) * Saved sort selection into local storage for later use * Updated code to use userLocalManager module * Fix initial position item info when selecting one item after another (fixes #10077) (#10661) * Update lastMouseMoveEvent even when dragging an egg or potion. * Update lastMouseMoveEvent even when dragging a food item. * Refactor/market vue (#10601) * extract inventoryDrawer from market * show scrollbar only if needed * extract featuredItemsHeader / pinUtils * extract pageLayout * extract layoutSection / filterDropdown - fix sortByNumber * rollback sortByNumber order-fix * move equipment lists out of the layout-section (for now) * refactor sellModal * extract checkbox * extract equipment section * extract category row * revert scroll - remove sellModal item template * fix(lint): commas and semis * Created category item component (#10613) * extract filter sidebar * fix gemCount - fix raising the item count if the item wasn't previously owned * fixes #10659 * remove unneeded method * fix typo when importing component * feat(content): Forest Friends Quest Bundle * chore(sprites): compile * chore(i18n): update locales * 4.60.3 * fix(bcrypt): install fork compatible with Node 8 * chore(i18n): update locales * 4.60.4 * add swear words - TRIGGER / CONTENT WARNING: assault, slurs, swearwords, etc * add pinUtils-mixin - fixes #10682 (#10683) * chore(news): Bailey * chore(i18n): update locales * 4.60.5 * Improve rendering banner about sleeping in the inn See #10695 * Display settings in one column * Small Updates (#10701) * small updates * fix client unit test * fix uuid validation * Revert "Small Updates (#10701)" (#10702) This reverts commitdd7fa73961. * feat(event): Fall Festival 2018 * chore(sprites): compile * chore(i18n): update locales * 4.61.0 * Move inbox to its own model (#10428) * shared model for chat and inbox * disable inbox schema * inbox: use separate model * remove old code that used group.chat * add back chat field (not used) and remove old tests * remove inbox exclusions when loading user * add GET /api/v3/inbox/messages * add comment * implement DELETE /inbox/messages/:messageid in v4 * implement GET /inbox/messages in v4 and update tests * implement DELETE /api/v4/inbox/clear * fix url * fix doc * update /export/inbox.html * update other data exports * add back messages in user schema * add user.toJSONWithInbox * add compativility until migration is done * more compatibility * fix tojson called twice * add compatibility methods * fix common tests * fix v4 integration tests * v3 get user -> with inbox * start to fix tests * fix v3 integration tests * wip * wip, client use new route * update tests for members/send-private-message * tests for get user in v4 * add tests for DELETE /inbox/messages/:messageId * add tests for DELETE /inbox/clear in v4 * update docs * fix tests * initial migration * fix migration * fix migration * migration fixes * migrate api.enterCouponCode * migrate api.castSpell * migrate reset, reroll, rebirth * add routes to v4 version * fix tests * fixes * api.updateUser * remove .only * get user -> userLib * refactor inbox.vue to work with new data model * fix return message when messaging yourself * wip fix bug with new conversation * wip * fix remaining ui issues * move api.registerLocal, fixes * keep only v3 version of GET /inbox/messages * Fix API early Stat Point allocation (#10680) * Refactor hasClass check to common so it can be used in shared & server-side code * Check that user has selected class before allocating stat points * chore(event): end Ember Hatching Potions * chore(analytics): reenable navigation tracking * update bcrypt * Point achievement modal links to main site (#10709) * Animal ears after death (#10691) * Animal Ears purchasable with Gold if lost in Death * remove ears from pinned items when set is bought * standardise css and error handling for gems and coins * revert accidental new line * fix client tests * Reduce margin-bottom of checklist-item from 10px to -3px. (#10684) * chore(i18n): update locales * 4.61.1 * Position inn banner when window is resized * feat(content): Subscriber Items and Magic Potions * chore(sprites): compile * chore(i18n): update locales * 4.62.0 * Update inn banner handling * Fix banner offset on initial load * Fix minor issues. * Issue: 10660 - Fixed. Changed default to Please Enter A Value (#10718) * Issue: 10660 - Fixed. Changed default to Please Enter A Value * Issue: 10660 - Fixed/revision 2 Changed default to Enter A Value * chore(news): Bailey announcements * chore(i18n): update locales * 4.62.1 * adjust wiki link for usernameInfo string https://github.com/HabitRPG/habitica-private/issues/7#issuecomment-425405425 * raise coverage for tasks api calls (#10029) * - updates a group task - approval is required - updates a group task with checklist * add expect to test the new checklist length * - moves tasks to a specified position out of length * remove unused line * website getter tasks tests * re-add sanitizeUserChallengeTask * change config.json.example variable to be a string not a boolean * fix tests - pick the text / up/down props too * fix test - remove changes on text/up/down - revert sanitize condition - revert sanitization props * chore(i18n): update locales * 4.62.2 * chore(news): Bailey * chore(i18n): update locales * 4.62.3 * inbox: fix avatar display and order * Username announcement (#10729) * Change update username API call The call no longer requires a password and also validates the username. * Implement API call to verify username without setting it * Improve coding style * Apply username verification to registration * Update error messages * Validate display names. * Fix API early Stat Point allocation (#10680) * Refactor hasClass check to common so it can be used in shared & server-side code * Check that user has selected class before allocating stat points * chore(event): end Ember Hatching Potions * chore(analytics): reenable navigation tracking * update bcrypt * Point achievement modal links to main site (#10709) * Animal ears after death (#10691) * Animal Ears purchasable with Gold if lost in Death * remove ears from pinned items when set is bought * standardise css and error handling for gems and coins * revert accidental new line * fix client tests * Reduce margin-bottom of checklist-item from 10px to -3px. (#10684) * chore(i18n): update locales * 4.61.1 * feat(content): Subscriber Items and Magic Potions * chore(sprites): compile * chore(i18n): update locales * 4.62.0 * Display notification for users to confirm their username * fix typo * WIP(usernames): Changes to address #10694 * WIP(usernames): Further changes for #10694 * fix(usernames): don't show spurious headings * Change verify username notification to new version * Improve feedback for invalid usernames * Allow user to set their username again to confirm it * Improve validation display for usernames * Temporarily move display name validation outside of schema * Improve rendering banner about sleeping in the inn See #10695 * Display settings in one column * Position inn banner when window is resized * Update inn banner handling * Fix banner offset on initial load * Fix minor issues. * Issue: 10660 - Fixed. Changed default to Please Enter A Value (#10718) * Issue: 10660 - Fixed. Changed default to Please Enter A Value * Issue: 10660 - Fixed/revision 2 Changed default to Enter A Value * chore(news): Bailey announcements * chore(i18n): update locales * 4.62.1 * adjust wiki link for usernameInfo string https://github.com/HabitRPG/habitica-private/issues/7#issuecomment-425405425 * raise coverage for tasks api calls (#10029) * - updates a group task - approval is required - updates a group task with checklist * add expect to test the new checklist length * - moves tasks to a specified position out of length * remove unused line * website getter tasks tests * re-add sanitizeUserChallengeTask * change config.json.example variable to be a string not a boolean * fix tests - pick the text / up/down props too * fix test - remove changes on text/up/down - revert sanitize condition - revert sanitization props * Change update username API call The call no longer requires a password and also validates the username. * feat(content): Subscriber Items and Magic Potions * Re-add register call * Fix merge issue * Fix issue with setting username * Implement new alert style * Display username confirmation status in settings * Add disclaimer to change username field * validate username in settings * Allow specific fields to be focused when opening site settings * Implement requested changes. * Fix merge issue * Fix failing tests * verify username when users register with username and password * Set ID for change username notification * Disable submit button if username is invalid * Improve username confirmation handling * refactor(settings): address remaining code comments on auth form * Revert "refactor(settings): address remaining code comments on auth form" This reverts commit9b6609ad64. * Social user username (#10620) * Refactored private functions to library * Refactored social login code * Added username to social registration * Changed id library * Added new local auth check * Fixed export error. Fixed password check error * fix(settings): password not available on client * refactor(settings): more sensible placement of methods * chore(migration): script to hand out procgen usernames * fix(migration): don't give EVERYONE new names you doofus * fix(migration): limit data retrieved, be extra careful about updates * fix(migration): use missing field, not migration tag, for query * fix(migration): unused var * fix(usernames): only generate 20 characters * fix(migration): set lowerCaseUsername * fix(lint): comma * fix(lint): comma spacing * chore(i18n): update locales * 4.63.0 * chore(news): Bailey * chore(i18n): update locales * 4.63.1 * fix(usernames): various Reword invalid characters error Correct typo in slur error Remove extraneous Confirm button Reset username field if empty on blur Restore ability to add local auth to social login * fix(auth): account for new username paradigm in add-local flow * fix(auth): alert on successful addLocal * chore(i18n): update locales * 4.63.2 * fix(auth): Don't try to check existing username on new reg * 4.63.3 * feat(content): Armoire and BGs 2018/10 * chore(sprites): compile * fix(passport): use graph API v2.8 * chore(i18n): update locales * 4.64.0 * Begin refactoring news API to return individual markdown posts * Implement simple bailey CMS * remove old news markdown * Correctly display images in bailey modal * Remove need for newStuff migration * Add basic tests * Fix authentication issue * Fix tests * Update news model * add API route to get single post * remove news admin frontend code * fix lint error * Fix merge mixups * Fix lint errors * fix api call * fix lint error * Fix issues caused by merging * remove console log * Improve news display * Correctly update users notifications * Fix date display for news posts * Fix tests * remove old cache file * correctly create date * correctly create promise * Better check for existance. * Improve docs * Fix minor issues * Add method to get latest post * fix lint errors * use correct call for 404 * add comment about old newStuff field * paginate news * Fix lint errors * Remove unnecessary await * Fix broken tests * ... * correct existence check * fix database queries * change approach to cached news posts * fix tests * Change how news posts are cached * Fetch last news post at an interval * Fix typos and other small things * add new permission for modifying bailey posts * add test for ensureNewsPoster * return last news post with legacy api * Fix test * Hopefully fix test * change fields to _id * Fixes * Fixes * fix test * Fixes * make all tests pass * fix lint * id -> _id * _id -> id * remove identical tell me later route from api v4 * fix lint * user model: fix issues with newStuff * improve user#toJSONTransform * fix typo * improve newsPost.js * fix(integration tests): do not return flags.newStuff if it was not selected * fix news controller * server side fixes, start refactoring client * more client fixes * automatically set author * new stuff: show one post per user + drafts * change default border radius for modals to 8px * required fields and defaults * slit news into its own component and fix static page * noNewsPoster: move from i18n to apiError * remove unused strings * fix unit tests * update apidocs * add backward comparibility for flags.newStuff in api v3 * fix integration tests * POST news: make integration test independent of number of posts * api v3 news: render markdown * static new-stuff: add padding and fix when user not logged in * test flags.newStuff * api v3: test setting flags.newStuff on PUT /user * refactor news post cache and add tests * remove new locales file * more resilient tests * more resilient tests * refactor tests for NewsPost.updateLastNewsPost * api v4: fix tests * api v3: fix tests * can set flags.newStuff in api v4 Co-authored-by: Keith Holliday <keithrholliday@gmail.com> Co-authored-by: Sabe Jones <sabrecat@gmail.com> Co-authored-by: Alys <Alys@users.noreply.github.com> Co-authored-by: Matteo Pagliazzi <matteopagliazzi@gmail.com> Co-authored-by: Carl Vuorinen <carl.vuorinen@gmail.com> Co-authored-by: Rene Cordier <rene.cordier@gmail.com> Co-authored-by: Forrest Hatfield <github@forresthatfield.com> Co-authored-by: lucubro <88whacko@gmail.com> Co-authored-by: negue <negue@users.noreply.github.com> Co-authored-by: Alys <alice.harris@oldgods.net> Co-authored-by: J.D. Sandifer <sandifer.jd@gmail.com> Co-authored-by: Kirsty <kirsty-tortoise@users.noreply.github.com> Co-authored-by: beatscribe <rattjp@gmail.com> Co-authored-by: Phillip Thelen <phillip@habitica.com>
675 lines
23 KiB
JavaScript
675 lines
23 KiB
JavaScript
import { Schema } from 'mongoose';
|
|
import validator from 'validator';
|
|
import shared from '../../../common';
|
|
import { // eslint-disable-line import/no-cycle
|
|
getDefaultOwnedGear,
|
|
} from '../../libs/items/utils';
|
|
import { schema as PushDeviceSchema } from '../pushDevice';
|
|
import { schema as SubscriptionPlanSchema } from '../subscriptionPlan';
|
|
import { schema as TagSchema } from '../tag';
|
|
import { schema as UserNotificationSchema } from '../userNotification';
|
|
import { schema as WebhookSchema } from '../webhook';
|
|
|
|
const RESTRICTED_EMAIL_DOMAINS = Object.freeze(['habitica.com', 'habitrpg.com']);
|
|
|
|
// User schema definition
|
|
export default new Schema({
|
|
apiToken: {
|
|
$type: String,
|
|
default: shared.uuid,
|
|
},
|
|
|
|
auth: {
|
|
blocked: Boolean,
|
|
facebook: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
google: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
apple: { $type: Schema.Types.Mixed, default: () => ({}) },
|
|
local: {
|
|
email: {
|
|
$type: String,
|
|
validate: [{
|
|
validator: v => validator.isEmail(v),
|
|
message: shared.i18n.t('invalidEmail'),
|
|
}, {
|
|
validator (email) {
|
|
const lowercaseEmail = email.toLowerCase();
|
|
|
|
return RESTRICTED_EMAIL_DOMAINS.every(domain => !lowercaseEmail.endsWith(`@${domain}`));
|
|
},
|
|
message: shared.i18n.t('invalidEmailDomain', { domains: RESTRICTED_EMAIL_DOMAINS.join(', ') }),
|
|
}],
|
|
},
|
|
username: {
|
|
$type: String,
|
|
},
|
|
// Store a lowercase version of username to check for duplicates
|
|
lowerCaseUsername: String,
|
|
hashed_password: String, // eslint-disable-line camelcase
|
|
// Legacy password are hashed with SHA1, new ones with bcrypt
|
|
passwordHashMethod: {
|
|
$type: String,
|
|
enum: ['bcrypt', 'sha1'],
|
|
},
|
|
salt: String, // Salt for SHA1 encrypted passwords, not stored for bcrypt,
|
|
// Used to validate password reset codes and make sure only the most recent one can be used
|
|
passwordResetCode: String,
|
|
},
|
|
timestamps: {
|
|
created: { $type: Date, default: Date.now },
|
|
loggedin: { $type: Date, default: Date.now },
|
|
updated: { $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: { $type: Number, default: 0 },
|
|
challenges: Array,
|
|
quests: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
rebirths: Number,
|
|
rebirthLevel: Number,
|
|
perfect: { $type: Number, default: 0 },
|
|
habitBirthdays: Number,
|
|
valentine: Number,
|
|
nye: Number,
|
|
habiticaDays: Number,
|
|
greeting: Number,
|
|
thankyou: Number,
|
|
costumeContests: Number,
|
|
birthday: Number,
|
|
partyUp: Boolean,
|
|
partyOn: Boolean,
|
|
congrats: Number,
|
|
getwell: Number,
|
|
goodluck: Number,
|
|
royallyLoyal: Boolean,
|
|
joinedGuild: Boolean,
|
|
joinedChallenge: Boolean,
|
|
invitedFriend: Boolean,
|
|
lostMasterclasser: Boolean,
|
|
mindOverMatter: Boolean,
|
|
justAddWater: Boolean,
|
|
backToBasics: Boolean,
|
|
allYourBase: Boolean,
|
|
dustDevil: Boolean,
|
|
aridAuthority: Boolean,
|
|
kickstarter2019: Boolean,
|
|
monsterMagus: Boolean,
|
|
undeadUndertaker: Boolean,
|
|
primedForPainting: Boolean,
|
|
pearlyPro: Boolean,
|
|
tickledPink: Boolean,
|
|
rosyOutlook: Boolean,
|
|
bugBonanza: Boolean,
|
|
bareNecessities: Boolean,
|
|
freshwaterFriends: Boolean,
|
|
goodAsGold: Boolean,
|
|
allThatGlitters: Boolean,
|
|
// Onboarding Guide
|
|
createdTask: Boolean,
|
|
completedTask: Boolean,
|
|
hatchedPet: Boolean,
|
|
fedPet: Boolean,
|
|
purchasedEquipment: Boolean,
|
|
},
|
|
|
|
backer: {
|
|
tier: Number,
|
|
npc: String,
|
|
tokensApplied: Boolean,
|
|
},
|
|
|
|
contributor: {
|
|
// 1-9, see https://habitica.fandom.com/wiki/Contributor_Rewards
|
|
level: {
|
|
$type: Number,
|
|
min: 0,
|
|
max: 9,
|
|
},
|
|
admin: Boolean,
|
|
newsPoster: Boolean,
|
|
sudo: Boolean,
|
|
// Artisan, Friend, Blacksmith, etc
|
|
text: String,
|
|
// a markdown textarea to list their contributions + links
|
|
contributions: String,
|
|
// user can own Critical Hammer of Bug-Crushing if this has a truthy value
|
|
critical: String,
|
|
},
|
|
|
|
balance: { $type: Number, default: 0 },
|
|
|
|
purchased: {
|
|
ads: { $type: Boolean, default: false },
|
|
// eg, {skeleton: true, pumpkin: true, eb052b: true}
|
|
skin: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
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: {
|
|
$type: SubscriptionPlanSchema,
|
|
default: () => ({}),
|
|
},
|
|
},
|
|
|
|
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 },
|
|
mounts: { $type: Boolean, default: false },
|
|
inbox: { $type: Boolean, default: false },
|
|
stats: { $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 },
|
|
reorderTask: { $type: Boolean, default: false },
|
|
},
|
|
},
|
|
dropsEnabled: { $type: Boolean, default: false }, // unused
|
|
itemsEnabled: { $type: Boolean, default: false },
|
|
lastNewStuffRead: { $type: String, default: '' },
|
|
// The newStuff field was changed to be a computed property when returning the user in json,
|
|
// so that it doesn't have to be updated for each bailey post.
|
|
// See models/user/hooks#toJSONTransform
|
|
// newStuff: { $type: Boolean, default: false },
|
|
rewrite: { $type: Boolean, default: true },
|
|
classSelected: { $type: Boolean, default: false },
|
|
mathUpdates: Boolean,
|
|
rebirthEnabled: { $type: Boolean, default: false },
|
|
lastFreeRebirth: Date,
|
|
levelDrops: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
chatRevoked: Boolean,
|
|
chatShadowMuted: 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,
|
|
onboardingEmailsPhase: String, // Keep track of the latest onboarding email sent
|
|
communityGuidelinesAccepted: { $type: Boolean, default: false },
|
|
cronCount: { $type: Number, default: 0 },
|
|
welcomed: { $type: Boolean, default: false },
|
|
armoireEnabled: { $type: Boolean, default: true },
|
|
armoireOpened: { $type: Boolean, default: false },
|
|
armoireEmpty: { $type: Boolean, default: false },
|
|
cardReceived: { $type: Boolean, default: false },
|
|
warnedLowHealth: { $type: Boolean, default: false },
|
|
verifiedUsername: { $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: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => getDefaultOwnedGear(),
|
|
},
|
|
|
|
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,
|
|
congrats: { $type: Number, default: 0 },
|
|
congratsReceived: Array,
|
|
getwell: { $type: Number, default: 0 },
|
|
getwellReceived: Array,
|
|
goodluck: { $type: Number, default: 0 },
|
|
goodluckReceived: 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: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
currentPet: String, // Cactus-Desert
|
|
|
|
// eggs: {
|
|
// 'PandaCub': 0, // 0 indicates "doesn't own"
|
|
// 'Wolf': 5 // Number indicates "stacking"
|
|
// }
|
|
eggs: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
|
|
// hatchingPotions: {
|
|
// 'Desert': 0, // 0 indicates "doesn't own"
|
|
// 'CottonCandyBlue': 5 // Number indicates "stacking"
|
|
// }
|
|
hatchingPotions: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
|
|
// Food: {
|
|
// 'Watermelon': 0, // 0 indicates "doesn't own"
|
|
// 'RottenMeat': 5 // Number indicates "stacking"
|
|
// }
|
|
food: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
|
|
// mounts: {
|
|
// 'Wolf-Desert': true,
|
|
// 'PandaCub-Red': false,
|
|
// etc...
|
|
// }
|
|
mounts: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
currentMount: String,
|
|
|
|
// Quests: {
|
|
// 'boss_0': 0, // 0 indicates "doesn't own"
|
|
// 'collection_honey': 5 // Number indicates "stacking"
|
|
// }
|
|
quests: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
|
|
lastDrop: {
|
|
date: { $type: Date, default: Date.now },
|
|
count: { $type: Number, default: 0 },
|
|
},
|
|
},
|
|
|
|
lastCron: { $type: Date, default: Date.now },
|
|
_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: () => ({}),
|
|
},
|
|
|
|
challenges: [{ $type: String, ref: 'Challenge', validate: [v => validator.isUUID(v), 'Invalid uuid for user challenges.'] }],
|
|
|
|
invitations: {
|
|
// Using an array without validation because otherwise mongoose
|
|
// treat this as a subdocument and applies _id by default
|
|
// Schema is (id, name, inviter, publicGuild)
|
|
// 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: () => ({}),
|
|
},
|
|
parties: [{
|
|
id: {
|
|
$type: String,
|
|
ref: 'Group',
|
|
required: true,
|
|
validate: [v => validator.isUUID(v), 'Invalid uuid for user invitation party id.'],
|
|
},
|
|
name: {
|
|
$type: String,
|
|
required: true,
|
|
},
|
|
inviter: {
|
|
$type: String,
|
|
ref: 'User',
|
|
required: true,
|
|
validate: [v => validator.isUUID(v), 'Invalid uuid for user invitation inviter id.'],
|
|
},
|
|
}],
|
|
},
|
|
|
|
guilds: [{ $type: String, ref: 'Group', validate: [v => validator.isUUID(v), 'Invalid uuid for user guild.'] }],
|
|
|
|
party: {
|
|
_id: { $type: String, validate: [v => validator.isUUID(v), 'Invalid uuid for user party.'], 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: () => ({}),
|
|
},
|
|
collectedItems: { $type: Number, default: 0 },
|
|
},
|
|
// 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
|
|
completed: String,
|
|
// Set to true when invite is pending, set to false when quest
|
|
// invite is accepted or rejected, quest starts, or quest is cancelled
|
|
RSVPNeeded: { $type: Boolean, default: false },
|
|
},
|
|
},
|
|
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: 'rosstavoTheme', enum: ['off', ...shared.content.audioThemes] },
|
|
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 },
|
|
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 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 },
|
|
onboarding: { $type: Boolean, default: true },
|
|
majorUpdates: { $type: Boolean, default: true },
|
|
subscriptionReminders: { $type: Boolean, default: true },
|
|
},
|
|
pushNotifications: {
|
|
unsubscribeFromAll: { $type: Boolean, default: false },
|
|
newPM: { $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 },
|
|
majorUpdates: { $type: Boolean, default: true },
|
|
mentionParty: { $type: Boolean, default: true },
|
|
mentionJoinedGuild: { $type: Boolean, default: true },
|
|
mentionUnjoinedGuild: { $type: Boolean, default: true },
|
|
partyActivity: { $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 },
|
|
},
|
|
tasks: {
|
|
groupByChallenge: { $type: Boolean, default: false }, // @TODO remove? not used
|
|
confirmScoreNotes: { $type: Boolean, default: false }, // @TODO remove? not used
|
|
},
|
|
improvementCategories: {
|
|
$type: Array,
|
|
validate: categories => {
|
|
const validCategories = ['work', 'exercise', 'healthWellness', 'school', 'teams', 'chores', 'creativity'];
|
|
const isValidCategory = categories
|
|
.every(category => validCategories.indexOf(category) !== -1);
|
|
return isValidCategory;
|
|
},
|
|
},
|
|
},
|
|
profile: {
|
|
blurb: String,
|
|
imageUrl: String,
|
|
name: {
|
|
$type: String,
|
|
required: true,
|
|
trim: true,
|
|
},
|
|
},
|
|
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, min: 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 },
|
|
},
|
|
},
|
|
|
|
notifications: [UserNotificationSchema],
|
|
tags: [TagSchema],
|
|
|
|
inbox: {
|
|
// messages are stored in the Inbox collection
|
|
newMessages: { $type: Number, default: 0 },
|
|
blocks: { $type: Array, default: () => [] },
|
|
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: () => ({}),
|
|
},
|
|
pushDevices: [PushDeviceSchema],
|
|
_ABtests: {
|
|
$type: Schema.Types.Mixed,
|
|
default: () => ({}),
|
|
},
|
|
webhooks: [WebhookSchema],
|
|
loginIncentives: { $type: Number, default: 0 },
|
|
invitesSent: { $type: Number, default: 0 },
|
|
|
|
// Items manually pinned by the user
|
|
pinnedItems: [{
|
|
_id: false,
|
|
path: { $type: String },
|
|
type: { $type: String },
|
|
}],
|
|
// Ordered array of shown pinned items,
|
|
// necessary for sorting because seasonal items are not stored in pinnedItems
|
|
pinnedItemsOrder: [{ $type: String }],
|
|
// Items the user manually unpinned from the ones suggested by Habitica
|
|
unpinnedItems: [{
|
|
_id: false,
|
|
path: { $type: String },
|
|
type: { $type: String },
|
|
}],
|
|
|
|
// only visible to staff and moderators
|
|
secret: {
|
|
text: String,
|
|
},
|
|
}, {
|
|
skipVersioning: { notifications: true },
|
|
strict: true,
|
|
minimize: false, // So empty objects are returned
|
|
typeKey: '$type', // So that we can use fields named `type`
|
|
});
|