mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 22:27:26 +01:00
start upgrading eslint
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import mongoose from 'mongoose';
|
||||
import validator from 'validator';
|
||||
import baseModel from '../libs/baseModel';
|
||||
import _ from 'lodash';
|
||||
import { TaskQueue } from 'cwait';
|
||||
import baseModel from '../libs/baseModel';
|
||||
import * as Tasks from './task';
|
||||
import { model as User } from './user';
|
||||
import {
|
||||
@@ -11,33 +12,36 @@ import { removeFromArray } from '../libs/collectionManipulators';
|
||||
import shared from '../../common';
|
||||
import { sendTxn as txnEmail } from '../libs/email';
|
||||
import { sendNotification as sendPushNotification } from '../libs/pushNotifications';
|
||||
import { TaskQueue } from 'cwait';
|
||||
import { syncableAttrs, setNextDue } from '../libs/taskManager';
|
||||
|
||||
const Schema = mongoose.Schema;
|
||||
const { Schema } = mongoose;
|
||||
|
||||
const MIN_SHORTNAME_SIZE_FOR_CHALLENGES = shared.constants.MIN_SHORTNAME_SIZE_FOR_CHALLENGES;
|
||||
const MAX_SUMMARY_SIZE_FOR_CHALLENGES = shared.constants.MAX_SUMMARY_SIZE_FOR_CHALLENGES;
|
||||
const { MIN_SHORTNAME_SIZE_FOR_CHALLENGES } = shared.constants;
|
||||
const { MAX_SUMMARY_SIZE_FOR_CHALLENGES } = shared.constants;
|
||||
|
||||
let schema = new Schema({
|
||||
name: {$type: String, required: true},
|
||||
shortName: {$type: String, required: true, minlength: MIN_SHORTNAME_SIZE_FOR_CHALLENGES},
|
||||
summary: {$type: String, maxlength: MAX_SUMMARY_SIZE_FOR_CHALLENGES},
|
||||
const schema = new Schema({
|
||||
name: { $type: String, required: true },
|
||||
shortName: { $type: String, required: true, minlength: MIN_SHORTNAME_SIZE_FOR_CHALLENGES },
|
||||
summary: { $type: String, maxlength: MAX_SUMMARY_SIZE_FOR_CHALLENGES },
|
||||
description: String,
|
||||
official: {$type: Boolean, default: false},
|
||||
official: { $type: Boolean, default: false },
|
||||
tasksOrder: {
|
||||
habits: [{$type: String, ref: 'Task'}],
|
||||
dailys: [{$type: String, ref: 'Task'}],
|
||||
todos: [{$type: String, ref: 'Task'}],
|
||||
rewards: [{$type: String, ref: 'Task'}],
|
||||
habits: [{ $type: String, ref: 'Task' }],
|
||||
dailys: [{ $type: String, ref: 'Task' }],
|
||||
todos: [{ $type: String, ref: 'Task' }],
|
||||
rewards: [{ $type: String, ref: 'Task' }],
|
||||
},
|
||||
leader: {$type: String, ref: 'User', validate: [v => validator.isUUID(v), 'Invalid uuid.'], required: true},
|
||||
group: {$type: String, ref: 'Group', validate: [v => validator.isUUID(v), 'Invalid uuid.'], required: true},
|
||||
memberCount: {$type: Number, default: 0},
|
||||
prize: {$type: Number, default: 0, min: 0},
|
||||
leader: {
|
||||
$type: String, ref: 'User', validate: [v => validator.isUUID(v), 'Invalid uuid.'], required: true,
|
||||
},
|
||||
group: {
|
||||
$type: String, ref: 'Group', validate: [v => validator.isUUID(v), 'Invalid uuid.'], required: true,
|
||||
},
|
||||
memberCount: { $type: Number, default: 0 },
|
||||
prize: { $type: Number, default: 0, min: 0 },
|
||||
categories: [{
|
||||
slug: {$type: String},
|
||||
name: {$type: String},
|
||||
slug: { $type: String },
|
||||
name: { $type: String },
|
||||
}],
|
||||
}, {
|
||||
strict: true,
|
||||
@@ -50,7 +54,7 @@ schema.plugin(baseModel, {
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
schema.pre('init', function ensureSummaryIsFetched (chal) {
|
||||
schema.pre('init', chal => {
|
||||
// The Vue website makes the summary be mandatory for all new challenges, but the
|
||||
// Angular website did not, and the API does not yet for backwards-compatibilty.
|
||||
// When any challenge without a summary is fetched from the database, this code
|
||||
@@ -62,7 +66,7 @@ schema.pre('init', function ensureSummaryIsFetched (chal) {
|
||||
});
|
||||
|
||||
// A list of additional fields that cannot be updated (but can be set on creation)
|
||||
let noUpdate = ['group', 'leader', 'official', 'shortName', 'prize'];
|
||||
const noUpdate = ['group', 'leader', 'official', 'shortName', 'prize'];
|
||||
schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
|
||||
return this.sanitize(updateObj, noUpdate);
|
||||
};
|
||||
@@ -99,7 +103,7 @@ schema.methods.canView = function canViewChallenge (user, group) {
|
||||
// Sync challenge to user, including tasks and tags.
|
||||
// Used when user joins the challenge or to force sync.
|
||||
schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
||||
let challenge = this;
|
||||
const challenge = this;
|
||||
challenge.shortName = challenge.shortName || challenge.name;
|
||||
|
||||
// Add challenge to user.challenges
|
||||
@@ -109,8 +113,8 @@ schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
||||
user.challenges = user.challenges.concat([challenge._id]);
|
||||
}
|
||||
// Sync tags
|
||||
let userTags = user.tags;
|
||||
let i = _.findIndex(userTags, {id: challenge._id});
|
||||
const userTags = user.tags;
|
||||
const i = _.findIndex(userTags, { id: challenge._id });
|
||||
|
||||
if (i !== -1) {
|
||||
if (userTags[i].name !== challenge.shortName) {
|
||||
@@ -126,10 +130,10 @@ schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
||||
});
|
||||
}
|
||||
|
||||
let [challengeTasks, userTasks] = await Promise.all([
|
||||
const [challengeTasks, userTasks] = await Promise.all([
|
||||
// Find original challenge tasks
|
||||
Tasks.Task.find({
|
||||
userId: {$exists: false},
|
||||
userId: { $exists: false },
|
||||
'challenge.id': challenge._id,
|
||||
}).exec(),
|
||||
// Find user's tasks linked to this challenge
|
||||
@@ -139,21 +143,21 @@ schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
||||
}).exec(),
|
||||
]);
|
||||
|
||||
let toSave = []; // An array of things to save
|
||||
const toSave = []; // An array of things to save
|
||||
|
||||
challengeTasks.forEach(chalTask => {
|
||||
let matchingTask = _.find(userTasks, userTask => userTask.challenge.taskId === chalTask._id);
|
||||
|
||||
if (!matchingTask) { // If the task is new, create it
|
||||
matchingTask = new Tasks[chalTask.type](Tasks.Task.sanitize(syncableAttrs(chalTask)));
|
||||
matchingTask.challenge = {taskId: chalTask._id, id: challenge._id, shortName: challenge.shortName};
|
||||
matchingTask.challenge = { taskId: chalTask._id, id: challenge._id, shortName: challenge.shortName };
|
||||
matchingTask.userId = user._id;
|
||||
user.tasksOrder[`${chalTask.type}s`].push(matchingTask._id);
|
||||
setNextDue(matchingTask, user);
|
||||
} else {
|
||||
_.merge(matchingTask, syncableAttrs(chalTask));
|
||||
// Make sure the task is in user.tasksOrder
|
||||
let orderList = user.tasksOrder[`${chalTask.type}s`];
|
||||
const orderList = user.tasksOrder[`${chalTask.type}s`];
|
||||
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
||||
}
|
||||
|
||||
@@ -175,23 +179,23 @@ schema.methods.syncToUser = async function syncChallengeToUser (user) {
|
||||
};
|
||||
|
||||
async function _fetchMembersIds (challengeId) {
|
||||
return (await User.find({challenges: {$in: [challengeId]}}).select('_id').lean().exec()).map(member => member._id);
|
||||
return (await User.find({ challenges: { $in: [challengeId] } }).select('_id').lean().exec()).map(member => member._id);
|
||||
}
|
||||
|
||||
async function _addTaskFn (challenge, tasks, memberId) {
|
||||
let updateTasksOrderQ = {$push: {}};
|
||||
let toSave = [];
|
||||
const updateTasksOrderQ = { $push: {} };
|
||||
const toSave = [];
|
||||
|
||||
tasks.forEach(chalTask => {
|
||||
let userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(syncableAttrs(chalTask)));
|
||||
userTask.challenge = {taskId: chalTask._id, id: challenge._id, shortName: challenge.shortName};
|
||||
const userTask = new Tasks[chalTask.type](Tasks.Task.sanitize(syncableAttrs(chalTask)));
|
||||
userTask.challenge = { taskId: chalTask._id, id: challenge._id, shortName: challenge.shortName };
|
||||
userTask.userId = memberId;
|
||||
|
||||
// We want to sync the notes and tags when the task is first added to the challenge
|
||||
userTask.notes = chalTask.notes;
|
||||
userTask.tags.push(challenge._id);
|
||||
|
||||
let tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`];
|
||||
const tasksOrderList = updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`];
|
||||
if (!tasksOrderList) {
|
||||
updateTasksOrderQ.$push[`tasksOrder.${chalTask.type}s`] = {
|
||||
$position: 0, // unshift
|
||||
@@ -207,60 +211,58 @@ async function _addTaskFn (challenge, tasks, memberId) {
|
||||
});
|
||||
|
||||
// Update the user
|
||||
toSave.unshift(User.update({_id: memberId}, updateTasksOrderQ).exec());
|
||||
toSave.unshift(User.update({ _id: memberId }, updateTasksOrderQ).exec());
|
||||
return await Promise.all(toSave);
|
||||
}
|
||||
|
||||
// Add a new task to challenge members
|
||||
schema.methods.addTasks = async function challengeAddTasks (tasks) {
|
||||
let challenge = this;
|
||||
let membersIds = await _fetchMembersIds(challenge._id);
|
||||
const challenge = this;
|
||||
const membersIds = await _fetchMembersIds(challenge._id);
|
||||
|
||||
let queue = new TaskQueue(Promise, 25); // process only 5 users concurrently
|
||||
const queue = new TaskQueue(Promise, 25); // process only 5 users concurrently
|
||||
|
||||
await Promise.all(membersIds.map(queue.wrap((memberId) => {
|
||||
return _addTaskFn(challenge, tasks, memberId);
|
||||
})));
|
||||
await Promise.all(membersIds.map(queue.wrap(memberId => _addTaskFn(challenge, tasks, memberId))));
|
||||
};
|
||||
|
||||
// Sync updated task to challenge members
|
||||
schema.methods.updateTask = async function challengeUpdateTask (task) {
|
||||
let challenge = this;
|
||||
const challenge = this;
|
||||
|
||||
let updateCmd = {$set: {}};
|
||||
const updateCmd = { $set: {} };
|
||||
|
||||
let syncableTask = syncableAttrs(task);
|
||||
for (let key in syncableTask) {
|
||||
const syncableTask = syncableAttrs(task);
|
||||
for (const key in syncableTask) {
|
||||
updateCmd.$set[key] = syncableTask[key];
|
||||
}
|
||||
|
||||
let taskSchema = Tasks[task.type];
|
||||
const taskSchema = Tasks[task.type];
|
||||
// Updating instead of loading and saving for performances, risks becoming a problem if we introduce more complexity in tasks
|
||||
await taskSchema.update({
|
||||
userId: {$exists: true},
|
||||
userId: { $exists: true },
|
||||
'challenge.id': challenge.id,
|
||||
'challenge.taskId': task._id,
|
||||
}, updateCmd, {multi: true}).exec();
|
||||
}, updateCmd, { multi: true }).exec();
|
||||
};
|
||||
|
||||
// Remove a task from challenge members
|
||||
schema.methods.removeTask = async function challengeRemoveTask (task) {
|
||||
let challenge = this;
|
||||
const challenge = this;
|
||||
|
||||
// Set the task as broken
|
||||
await Tasks.Task.update({
|
||||
userId: {$exists: true},
|
||||
userId: { $exists: true },
|
||||
'challenge.id': challenge.id,
|
||||
'challenge.taskId': task._id,
|
||||
}, {
|
||||
$set: {'challenge.broken': 'TASK_DELETED'},
|
||||
}, {multi: true}).exec();
|
||||
$set: { 'challenge.broken': 'TASK_DELETED' },
|
||||
}, { multi: true }).exec();
|
||||
};
|
||||
|
||||
// Unlink challenges tasks (and the challenge itself) from user. TODO rename to 'leave'
|
||||
schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep, saveUser = true) {
|
||||
let challengeId = this._id;
|
||||
let findQuery = {
|
||||
const challengeId = this._id;
|
||||
const findQuery = {
|
||||
userId: user._id,
|
||||
'challenge.id': challengeId,
|
||||
};
|
||||
@@ -270,8 +272,8 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep, sa
|
||||
|
||||
if (keep === 'keep-all') {
|
||||
await Tasks.Task.update(findQuery, {
|
||||
$set: {challenge: {}},
|
||||
}, {multi: true}).exec();
|
||||
$set: { challenge: {} },
|
||||
}, { multi: true }).exec();
|
||||
|
||||
const promises = [this.save()];
|
||||
|
||||
@@ -280,45 +282,44 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep, sa
|
||||
if (saveUser) promises.push(user.save());
|
||||
|
||||
return Promise.all(promises);
|
||||
} else { // keep = 'remove-all'
|
||||
let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec();
|
||||
let taskPromises = tasks.map(task => {
|
||||
// Remove task from user.tasksOrder and delete them
|
||||
if (task.type !== 'todo' || !task.completed) {
|
||||
removeFromArray(user.tasksOrder[`${task.type}s`], task._id);
|
||||
}
|
||||
} // keep = 'remove-all'
|
||||
const tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec();
|
||||
const taskPromises = tasks.map(task => {
|
||||
// Remove task from user.tasksOrder and delete them
|
||||
if (task.type !== 'todo' || !task.completed) {
|
||||
removeFromArray(user.tasksOrder[`${task.type}s`], task._id);
|
||||
}
|
||||
|
||||
return task.remove();
|
||||
});
|
||||
user.markModified('tasksOrder');
|
||||
taskPromises.push(this.save());
|
||||
return task.remove();
|
||||
});
|
||||
user.markModified('tasksOrder');
|
||||
taskPromises.push(this.save());
|
||||
|
||||
// When multiple tasks are being unlinked at the same time,
|
||||
// save the user once outside of this function
|
||||
if (saveUser) taskPromises.push(user.save());
|
||||
// When multiple tasks are being unlinked at the same time,
|
||||
// save the user once outside of this function
|
||||
if (saveUser) taskPromises.push(user.save());
|
||||
|
||||
return Promise.all(taskPromises);
|
||||
}
|
||||
return Promise.all(taskPromises);
|
||||
};
|
||||
|
||||
// TODO everything here should be moved to a worker
|
||||
// actually even for a worker it's probably just too big and will kill mongo, figure out something else
|
||||
schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
let challenge = this;
|
||||
const challenge = this;
|
||||
|
||||
let winner = broken.winner;
|
||||
let brokenReason = broken.broken;
|
||||
const { winner } = broken;
|
||||
const brokenReason = broken.broken;
|
||||
|
||||
// Delete the challenge
|
||||
await this.model('Challenge').remove({_id: challenge._id}).exec();
|
||||
await this.model('Challenge').remove({ _id: challenge._id }).exec();
|
||||
|
||||
// Refund the leader if the challenge is deleted (no winner chosen)
|
||||
if (brokenReason === 'CHALLENGE_DELETED') {
|
||||
await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec();
|
||||
await User.update({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } }).exec();
|
||||
}
|
||||
|
||||
// Update the challengeCount on the group
|
||||
await Group.update({_id: challenge.group}, {$inc: {challengeCount: -1}}).exec();
|
||||
await Group.update({ _id: challenge.group }, { $inc: { challengeCount: -1 } }).exec();
|
||||
|
||||
// Award prize to winner and notify
|
||||
if (winner) {
|
||||
@@ -328,18 +329,18 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
// reimburse the leader
|
||||
const winnerCanGetGems = await winner.canGetGems();
|
||||
if (!winnerCanGetGems) {
|
||||
await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec();
|
||||
await User.update({ _id: challenge.leader }, { $inc: { balance: challenge.prize / 4 } }).exec();
|
||||
} else {
|
||||
winner.balance += challenge.prize / 4;
|
||||
}
|
||||
|
||||
winner.addNotification('WON_CHALLENGE');
|
||||
|
||||
let savedWinner = await winner.save();
|
||||
const savedWinner = await winner.save();
|
||||
|
||||
if (savedWinner.preferences.emailNotifications.wonChallenge !== false) {
|
||||
txnEmail(savedWinner, 'won-challenge', [
|
||||
{name: 'CHALLENGE_NAME', content: challenge.name},
|
||||
{ name: 'CHALLENGE_NAME', content: challenge.name },
|
||||
]);
|
||||
}
|
||||
if (savedWinner.preferences.pushNotifications.wonChallenge !== false) {
|
||||
@@ -353,17 +354,17 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
}
|
||||
|
||||
// Run some operations in the background withouth blocking the thread
|
||||
let backgroundTasks = [
|
||||
const backgroundTasks = [
|
||||
// And it's tasks
|
||||
Tasks.Task.remove({'challenge.id': challenge._id, userId: {$exists: false}}).exec(),
|
||||
Tasks.Task.remove({ 'challenge.id': challenge._id, userId: { $exists: false } }).exec(),
|
||||
// Set the challenge tag to non-challenge status and remove the challenge from the user's challenges
|
||||
User.update({
|
||||
challenges: challenge._id,
|
||||
'tags.id': challenge._id,
|
||||
}, {
|
||||
$set: {'tags.$.challenge': false},
|
||||
$pull: {challenges: challenge._id},
|
||||
}, {multi: true}).exec(),
|
||||
$set: { 'tags.$.challenge': false },
|
||||
$pull: { challenges: challenge._id },
|
||||
}, { multi: true }).exec(),
|
||||
// Break users' tasks
|
||||
Tasks.Task.update({
|
||||
'challenge.id': challenge._id,
|
||||
@@ -372,10 +373,10 @@ schema.methods.closeChal = async function closeChal (broken = {}) {
|
||||
'challenge.broken': brokenReason,
|
||||
'challenge.winner': winner && winner.profile.name,
|
||||
},
|
||||
}, {multi: true}).exec(),
|
||||
}, { multi: true }).exec(),
|
||||
];
|
||||
|
||||
Promise.all(backgroundTasks);
|
||||
};
|
||||
|
||||
export let model = mongoose.model('Challenge', schema);
|
||||
export const model = mongoose.model('Challenge', schema);
|
||||
|
||||
Reference in New Issue
Block a user