mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
commit3aba0abeddAuthor: SabreCat <sabe@habitica.com> Date: Mon Oct 2 20:51:20 2023 -0500 fix(router): use state to pass modal launch info commit541eadd319Merge:c0bb56c8c289fff49d02Author: SabreCat <sabe@habitica.com> Date: Mon Oct 2 20:12:40 2023 -0500 Merge branch 'release' into report-profile-modal commitc0bb56c8c2Author: SabreCat <sabe@habitica.com> Date: Wed Sep 27 16:15:28 2023 -0500 test(profiles): add integrations commit9b644e9ad8Author: SabreCat <sabe@habitica.com> Date: Tue Sep 26 17:17:22 2023 -0500 fix(profile): adjust margin commitbfefe5dfa9Author: SabreCat <sabe@habitica.com> Date: Tue Sep 26 17:12:24 2023 -0500 fix(profiles): moar layout fixes commit8f211ee3e2Author: SabreCat <sabe@habitica.com> Date: Mon Sep 25 17:32:04 2023 -0500 fix(profile): fix admin actions Correct "user is banned" banner Fix bouncing modal Add "Days" smart plural Fix leaky CSS on Market page Refactor some redundant functions commitb1d23ec88bMerge:ee9709a9e1a63cc84779Author: SabreCat <sabe@habitica.com> Date: Mon Sep 25 15:37:54 2023 -0500 Merge branch 'release' into report-profile-modal commitee9709a9e1Author: CuriousMagpie <eilatan@gmail.com> Date: Mon Sep 18 16:30:30 2023 -0400 WIP(profile): add banned banner, toggle switches now toggle, add "days" to Next Login Reward commitf80928a895Author: CuriousMagpie <eilatan@gmail.com> Date: Mon Sep 18 13:43:34 2023 -0400 update(node): update node modules commit1d552f7e80Author: SabreCat <sabe@habitica.com> Date: Fri Sep 15 16:52:22 2023 -0500 fix(import): remove empty import commitf55d74a95dAuthor: SabreCat <sabe@habitica.com> Date: Fri Sep 15 16:39:50 2023 -0500 refactor(profiles): remove email feature also still more visual cleanup of profile modal commit311c743284Author: SabreCat <sabe@habitica.com> Date: Fri Sep 15 15:44:56 2023 -0500 refactor(profile): remove page view commitf8632bf50dMerge:ec85159c659e25360102Author: SabreCat <sabe@habitica.com> Date: Fri Sep 15 15:23:21 2023 -0500 Merge branch 'release' into report-profile-modal commitec85159c65Author: SabreCat <sabe@habitica.com> Date: Mon Sep 11 22:53:14 2023 -0500 feat(profiles): load modal instead of page? commit9986082914Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 8 14:49:57 2023 -0400 WIP(profile): fixed a comment, woohoo commit6262a9ba0cMerge:ae2b614df2ea2b007b1aAuthor: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 8 13:40:23 2023 -0400 Merge remote-tracking branch 'origin/report-profile-modal' into report-profile-modal commitea2b007b1aAuthor: SabreCat <sabe@habitica.com> Date: Thu Sep 7 16:54:19 2023 -0500 fix(profile): focus behavior commitae2b614df2Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Sep 7 17:47:08 2023 -0400 WIP(profile): styling updates commit2e0723f1b9Author: SabreCat <sabe@habitica.com> Date: Thu Sep 7 15:37:59 2023 -0500 feat(moderation): unflag profile Also a few stylistic tweaks commitedcf8113deAuthor: SabreCat <sabe@habitica.com> Date: Wed Sep 6 16:39:02 2023 -0500 WIP(profile): dropdown draft commit0691483d63Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Sep 6 16:33:30 2023 -0400 WIP(profile): Styling and string updates commit7e9d57d10aAuthor: SabreCat <sabe@habitica.com> Date: Wed Sep 6 11:40:31 2023 -0500 feat(profile): functional dropdown buttons commita2989b2833Merge:af6575e40ce072d7c09cAuthor: SabreCat <sabe@habitica.com> Date: Wed Sep 6 10:04:57 2023 -0500 Merge branch 'release' into report-profile-modal commitaf6575e40cAuthor: CuriousMagpie <eilatan@gmail.com> Date: Wed Sep 6 11:01:05 2023 -0400 WIP(profile): comment cleanup commit7b1de37202Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Sep 5 17:22:14 2023 -0400 WIP(profile): remove shadowban tooltip commitd1177c32b9Merge:321a01b08131f821021bAuthor: CuriousMagpie <eilatan@gmail.com> Date: Tue Sep 5 17:02:40 2023 -0400 Merge branch 'sabrecat/report-profile' into report-profile-modal commit321a01b081Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 1 16:14:36 2023 -0400 WIP(profile): close button finally workinating commite143d36d28Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 1 15:52:38 2023 -0400 WIP(profile): close icon moved to profile.vue commit31f821021bMerge:a8f5e25d388957c5c009Author: SabreCat <sabe@habitica.com> Date: Fri Sep 1 14:52:31 2023 -0500 Merge branch 'report-profile-modal' into sabrecat/report-profile commit8957c5c009Merge:d340f06a220aec3866a4Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 1 15:38:12 2023 -0400 Merge remote-tracking branch 'origin/report-profile-modal' into report-profile-modal commitd340f06a22Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Sep 1 15:37:57 2023 -0400 WIP(profile): fixed user not found error commit0aec3866a4Merge:b01f323b14ac7c8e0eb6Author: Natalie <78037386+CuriousMagpie@users.noreply.github.com> Date: Fri Sep 1 15:28:58 2023 -0400 Merge branch 'HabitRPG:develop' into report-profile-modal commita8f5e25d38Author: SabreCat <sabe@habitica.com> Date: Thu Aug 31 17:02:07 2023 -0500 feat(community): basic "report profile" commitb01f323b14Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Aug 31 17:42:12 2023 -0400 WIP(profile): removed refactoring crud, located where close icon should be (profileModal.vue) commitce7d51a20cMerge:010f2299f0ac7c8e0eb6Author: SabreCat <sabe@habitica.com> Date: Thu Aug 31 14:20:37 2023 -0500 Merge branch 'release' into sabrecat/report-profile commit18b41acd94Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Aug 31 12:23:41 2023 -0400 WIP(profile): moar buttonz commit9387b3a6bcAuthor: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 30 17:21:36 2023 -0400 WIP(profile): buttons commitb3ea48c4f5Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 25 15:52:41 2023 -0400 WIP(profile): work on achievement component commita1ceb2ea75Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 25 14:39:12 2023 -0400 WIP(profile): create achievements component commit4a24d9b80bMerge:8fe263a3771e05297e96Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 23 13:14:39 2023 -0400 Merge branch 'develop' into report-profile-modal commit1e05297e96Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 23 13:12:52 2023 -0400 package updates commit8fe263a377Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 23 12:12:36 2023 -0400 update(dependencies): ran npm install to update dependencies commit190fe048a1Merge:3ea48ab5cbfa83d1a9cfAuthor: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 23 11:52:08 2023 -0400 Merge branch 'develop' into report-profile-modal commit3ea48ab5cbAuthor: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 11 17:12:31 2023 -0400 WIP(user profile): dropdown menu and toggles and colors oh my commitc301a2b460Merge:1da6af11b5647b27c55fAuthor: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 11 12:40:07 2023 -0400 Merge branch 'develop' into report-profile-modal commit1da6af11b5Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Aug 10 16:50:07 2023 -0400 WIP(user profile): moved some CSS classes out of unscoped and into the scoped section, started on toggle buttons commitdd55cbc928Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 9 15:38:46 2023 -0400 WIP(user profile): workin on the hamburger (kebab?) menu commit3834093207Author: CuriousMagpie <eilatan@gmail.com> Date: Tue Aug 8 14:14:40 2023 -0400 WIP(user profiles): working on the drop down menu commitf2be588195Author: CuriousMagpie <eilatan@gmail.com> Date: Mon Aug 7 16:10:30 2023 -0400 WIP(user profile): options menu commit010f2299f0Author: SabreCat <sabe@habitica.com> Date: Mon Aug 7 11:49:04 2023 -0500 fix(lint): eof and const commit4551dbf4b3Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Aug 4 15:34:05 2023 -0400 WIP(user profile): styling the top portion of the modal commit19a9fe3644Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Aug 3 15:06:51 2023 -0400 WIP(user profile): adding buttons commitdfdb305b1cAuthor: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 2 14:41:20 2023 -0400 WIP(user profile): layout commitded4eee693Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Aug 2 12:04:02 2023 -0400 WIP(user profile): start flex grid & tidy up CSS commitaaca48be32Author: CuriousMagpie <eilatan@gmail.com> Date: Fri Jul 28 16:44:06 2023 -0400 WIP(user profile): mostly css updates commite531985b87Author: CuriousMagpie <eilatan@gmail.com> Date: Thu Jul 27 16:49:44 2023 -0400 WIP(user profile): one infinitesimal change that's hardly worth the electricity it's made from commiteb4021fcc7Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Jul 26 16:33:05 2023 -0400 feat(content): upgrade profile page commit1b25394f3eMerge:c50cee0d888558dcc3a8Author: CuriousMagpie <eilatan@gmail.com> Date: Wed Jul 26 11:50:12 2023 -0400 Merge branch 'develop' into report-profile-modal commitc50cee0d88Author: SabreCat <sabe@habitica.com> Date: Wed Jul 12 16:32:25 2023 -0500 fix(flagging): debug params issue Also add and document the "source" body param commit55848c58beAuthor: SabreCat <sabe@habitica.com> Date: Mon Jul 10 16:24:20 2023 -0500 WIP(members): basic report a user API commitdda6180792Author: SabreCat <sabe@habitica.com> Date: Thu Jul 6 10:05:07 2023 -0500 fix(lint): remove console.info
725 lines
25 KiB
JavaScript
725 lines
25 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,
|
|
dinosaurDynasty: Boolean,
|
|
bonelessBoss: 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.'],
|
|
},
|
|
cancelledPlan: {
|
|
$type: Boolean,
|
|
},
|
|
}],
|
|
},
|
|
|
|
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: { $type: Boolean, default: false },
|
|
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 },
|
|
// not used anymore, now the current filter is saved in preferences.activeFilter
|
|
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' },
|
|
],
|
|
activeFilter: {
|
|
habit: { $type: String, default: 'all' },
|
|
daily: { $type: String, default: 'all' },
|
|
todo: { $type: String, default: 'remaining' },
|
|
reward: { $type: String, default: 'all' },
|
|
},
|
|
},
|
|
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,
|
|
},
|
|
flags: { $type: Schema.Types.Mixed },
|
|
},
|
|
stats: {
|
|
hp: { $type: Number, default: shared.maxHealth },
|
|
mp: { $type: Number, default: 10, min: 0 },
|
|
exp: { $type: Number, default: 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`
|
|
});
|