mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
commit 00affb306655a543f5d29b3af6361e686b577a97 Author: SabreCat <sabe@habitica.com> Date: Tue May 2 09:47:25 2023 -0500 fix(tests): account for invite limit changes commit 47661117f9fd661b8bc8f63b7cc7c8d5f8fa0fd7 Author: SabreCat <sabe@habitica.com> Date: Mon May 1 17:39:29 2023 -0500 fix(lfp): final polish commit 6a1e5af1db0dd90be3ced7e223f53c9183a206f5 Merge: 728ed2ddad9e0777bb42Author: SabreCat <sabe@habitica.com> Date: Mon May 1 16:54:12 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 728ed2ddad7f0962d28f1ab0a271e3555b19296c Author: SabreCat <sabe@habitica.com> Date: Thu Apr 27 16:51:06 2023 -0500 fix(lfp): loading layout and page visit event commit 8a56ab329bff922e05963e3ef78fbc26ff273924 Author: SabreCat <sabe@habitica.com> Date: Wed Apr 26 16:54:46 2023 -0500 fix(faq): copy and style updates commit 6fd00d7f30150a1802e5a37edbb914ef120caf9a Author: SabreCat <sabe@habitica.com> Date: Fri Apr 21 17:12:52 2023 -0500 feat(lfp): fixes, analytics, FAQ commit 4b5d7304ad7cfc5f72320b23456ed2898e53caac Author: SabreCat <sabe@habitica.com> Date: Mon Apr 17 15:13:03 2023 -0500 fix(lfp): smol tweaks commit 9a5476a2558eb17a603f4aae1b5b2d35773be8b4 Author: SabreCat <sabe@habitica.com> Date: Thu Apr 13 16:03:33 2023 -0500 feat(lfp): refresh button commit aa58f5018469f38a9a9d31c3bffa26bb88a8c672 Merge: bbb03d006ec8adf20804Author: SabreCat <sabe@habitica.com> Date: Tue Apr 11 17:44:56 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit bbb03d006e8b122bb7206bdc778a31de422167bb Author: SabreCat <sabe@habitica.com> Date: Tue Apr 4 18:30:50 2023 -0500 fix(lint): whitespace and const commit 23683ad29a4cce0b0da061ad6c030982034c0a9c Author: SabreCat <sabe@habitica.com> Date: Tue Apr 4 17:02:57 2023 -0500 chore(LFP): add analytics also re-fix loading state commit 4477d84f5266c87f5583368029b72153f00f0568 Author: SabreCat <sabe@habitica.com> Date: Mon Apr 3 16:24:26 2023 -0500 fix(LFP): address issues with loading commit bdc5154f24bb5e50963376c3c0c9cc73c0b05ccc Merge: 81923eef6f229ed46425Author: SabreCat <sabe@habitica.com> Date: Mon Apr 3 15:58:12 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 81923eef6f0c627d079475a28f9d93d8e4628934 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 30 16:44:49 2023 -0500 feat(LFP): release candidate commit fe1f8939fc6b09d36cfaf0b6e5838df04e41009d Author: SabreCat <sabe@habitica.com> Date: Wed Mar 29 17:35:54 2023 -0500 WIP(LFP): fixes commit afc361f5a9f806cbd814ad910d1274e3a6609efd Merge: d6b5cbdebc7ede3acd01Author: SabreCat <sabe@habitica.com> Date: Wed Mar 29 16:24:39 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit d6b5cbdebc2829e9325ea57fb5deeccc128c1635 Author: SabreCat <sabe@habitica.com> Date: Tue Mar 28 16:13:18 2023 -0500 WIP(LFP): change copy, add close X, fix API response commit 4274a4625862351ef0ecf33c8a3249ca5ebec7cb Author: SabreCat <sabe@habitica.com> Date: Mon Mar 27 17:07:36 2023 -0500 fix(LFP): layout, unset when stopping commit 95abfcfa5f13c9cce6385206947a47f85b76d11d Merge: 4a360eedd853c536b525Author: SabreCat <sabe@habitica.com> Date: Mon Mar 27 16:32:46 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 4a360eedd8b9cf41d3a0fe7a4cfaa72c5bd7bd26 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 23 16:54:49 2023 -0500 feat(LFP): completed style and infinite scroll commit bbc439d9d03c9631a450236eb33af66f0428fa50 Author: SabreCat <sabe@habitica.com> Date: Tue Mar 21 10:40:26 2023 -0500 WIP(LFP): nicer layout, buffs fix commit 1658688597456663477ab19da61ae1b9bc85cf2a Merge: 664507434f027e61a93eAuthor: SabreCat <sabe@habitica.com> Date: Tue Mar 21 09:29:02 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 664507434f2f76e6bf3b61cdc9e3daddb81204af Author: SabreCat <sabe@habitica.com> Date: Fri Mar 17 17:13:08 2023 -0500 WIP(LFP): API and client adjustments commit 2f0b6f2517f9e2d634cb23ee303cfb4542e998ce Merge: 0db1704325a16098ccdaAuthor: SabreCat <sabe@habitica.com> Date: Fri Mar 17 16:45:13 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 0db1704325c3555f0b5d9c8d1dfc44177e90c093 Author: SabreCat <sabe@habitica.com> Date: Mon Mar 13 14:48:10 2023 -0500 fix(modal): scrollbar for squashed viewports commit 733c35192e0a4e31e1bebfdd7488cfc1f7587f36 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 9 12:51:02 2023 -0600 WIP(party): seekers functional rough commit d4b854410b557db26eec6e6a26b6d174c02cee3a Merge: 7fe919825a0b6b967753Author: SabreCat <sabe@habitica.com> Date: Thu Mar 9 10:07:28 2023 -0600 Merge branch 'release' into sabrecat/party-seeking commit 7fe919825abfb6d518cb93b91f5997d3831bd0b5 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 2 14:40:09 2023 -0600 feat(party): several adjustments to seeking feature commit c93900efcf925f7aaa4c4cb56b4451f19adfb1b3 Author: SabreCat <sabe@habitica.com> Date: Wed Mar 1 20:37:11 2023 -0600 feat(party): initial Seeking API commit 8bb784daeceb14c23992a6f3af1054a900fc26c1 Merge: e19a661a21f327795761Author: SabreCat <sabe@habitica.com> Date: Wed Mar 1 18:58:20 2023 -0600 Merge branch 'release' into sabrecat/party-seeking commit e19a661a2163a50307a286379bffb44201ed392e Author: SabreCat <sabe@habitica.com> Date: Fri Feb 24 15:51:42 2023 -0600 WIP(parties): add seeking flag and modal toggle
712 lines
24 KiB
JavaScript
712 lines
24 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, 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,
|
|
boneCollector: Boolean,
|
|
skeletonCrew: Boolean,
|
|
seeingRed: Boolean,
|
|
redLetterDay: Boolean,
|
|
legendaryBestiary: Boolean,
|
|
seasonalSpecialist: Boolean,
|
|
violetsAreBlue: Boolean,
|
|
wildBlueYonder: Boolean,
|
|
domesticated: Boolean,
|
|
shadyCustomer: Boolean,
|
|
shadeOfItAll: Boolean,
|
|
zodiacZookeeper: Boolean,
|
|
birdsOfAFeather: Boolean,
|
|
reptacularRumble: Boolean,
|
|
woodlandWizard: Boolean,
|
|
boneToPick: Boolean,
|
|
polarPro: Boolean,
|
|
plantParent: Boolean,
|
|
// Onboarding Guide
|
|
createdTask: Boolean,
|
|
completedTask: Boolean,
|
|
hatchedPet: Boolean,
|
|
fedPet: Boolean,
|
|
purchasedEquipment: Boolean,
|
|
groupsBeta2022: 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,
|
|
// 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,
|
|
},
|
|
permissions: {
|
|
fullAccess: Boolean, // esentially what was previously contributor.admin. Can do everything
|
|
news: Boolean,
|
|
userSupport: Boolean, // access User Support feature in Admin Panel
|
|
challengeAdmin: Boolean, // Can manage and administrate challenges
|
|
moderator: Boolean, // Can ban, flag users and manage social spaces
|
|
coupons: Boolean, // Can generate and request coupons
|
|
},
|
|
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 },
|
|
groupPlans: { $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 },
|
|
thirdPartyTools: { $type: Date },
|
|
},
|
|
|
|
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
|
|
// Lock property to avoid double subscription. Not strictly private because we query on it
|
|
_subSignature: { $type: String, default: 'NOT_RUNNING' },
|
|
|
|
// {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 https://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 },
|
|
},
|
|
seeking: Date,
|
|
},
|
|
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
|
|
mirrorGroupTasks: [
|
|
{ $type: String, validate: [v => validator.isUUID(v), 'Invalid group UUID.'], ref: 'Group' },
|
|
],
|
|
},
|
|
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, min: 0 },
|
|
exp: { $type: Number, default: 0, min: 0 },
|
|
gp: { $type: Number, default: 0, min: 0 },
|
|
lvl: {
|
|
$type: Number,
|
|
default: 1,
|
|
min: 1,
|
|
max: shared.constants.MAX_LEVEL_HARD_CAP,
|
|
},
|
|
|
|
// Class System
|
|
class: {
|
|
$type: String, enum: ['warrior', 'rogue', 'wizard', 'healer'], default: 'warrior', required: true,
|
|
},
|
|
points: { $type: Number, default: 0, min: 0 },
|
|
str: { $type: Number, default: 0, min: 0 },
|
|
con: { $type: Number, default: 0, min: 0 },
|
|
int: { $type: Number, default: 0, min: 0 },
|
|
per: { $type: Number, default: 0, min: 0 },
|
|
buffs: {
|
|
str: { $type: Number, default: 0, min: 0 },
|
|
int: { $type: Number, default: 0, min: 0 },
|
|
per: { $type: Number, default: 0, min: 0 },
|
|
con: { $type: Number, default: 0, min: 0 },
|
|
stealth: { $type: Number, default: 0, min: 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`
|
|
});
|