@@ -1,6 +1,7 @@
|
||||
import validator from 'validator';
|
||||
import passport from 'passport';
|
||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import cron from '../../middlewares/api-v3/cron';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../libs/api-v3/errors';
|
||||
@@ -30,7 +31,7 @@ api.registerLocal = {
|
||||
url: '/user/auth/local/register',
|
||||
handler (req, res, next) {
|
||||
let fbUser = res.locals.user; // If adding local auth to social user
|
||||
|
||||
// TODO check user doesn't have local auth
|
||||
req.checkBody({
|
||||
email: {
|
||||
notEmpty: {errorMessage: res.t('missingEmail')},
|
||||
@@ -138,6 +139,7 @@ function _loginRes (user, req, res, next) {
|
||||
api.loginLocal = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/local/login',
|
||||
middlewares: [cron],
|
||||
handler (req, res, next) {
|
||||
req.checkBody({
|
||||
username: {
|
||||
@@ -182,6 +184,7 @@ api.loginLocal = {
|
||||
api.loginSocial = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/social', // this isn't the most appropriate url but must be the same as v2
|
||||
middlewares: [cron],
|
||||
handler (req, res, next) {
|
||||
let accessToken = req.body.authResponse.access_token;
|
||||
let network = req.body.network;
|
||||
@@ -247,7 +250,7 @@ api.loginSocial = {
|
||||
api.deleteSocial = {
|
||||
method: 'DELETE',
|
||||
url: '/user/auth/social/:network',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
let network = req.params.network;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import cron from '../../middlewares/api-v3/cron';
|
||||
import { model as Tag } from '../../models/tag';
|
||||
import {
|
||||
NotFound,
|
||||
@@ -18,7 +19,7 @@ let api = {};
|
||||
api.createTag = {
|
||||
method: 'POST',
|
||||
url: '/tags',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -45,7 +46,7 @@ api.createTag = {
|
||||
api.getTags = {
|
||||
method: 'GET',
|
||||
url: '/tags',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
res.respond(200, user.tags);
|
||||
@@ -65,11 +66,11 @@ api.getTags = {
|
||||
api.getTag = {
|
||||
method: 'GET',
|
||||
url: '/tags/:tagId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
req.checkParams('taskId', res.t('tagIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) return next(validationErrors);
|
||||
@@ -93,14 +94,14 @@ api.getTag = {
|
||||
api.updateTag = {
|
||||
method: 'PUT',
|
||||
url: '/tags/:tagId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID();
|
||||
// TODO check that req.body isn't empty
|
||||
|
||||
let tagId = req.params.id;
|
||||
let tagId = req.params.tagId;
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) return next(validationErrors);
|
||||
@@ -127,9 +128,9 @@ api.updateTag = {
|
||||
* @apiSuccess {object} empty An empty object
|
||||
*/
|
||||
api.deleteTag = {
|
||||
method: 'GET',
|
||||
method: 'DELETE',
|
||||
url: '/tags/:tagId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import cron from '../../middlewares/api-v3/cron';
|
||||
import { sendTaskWebhook } from '../../libs/api-v3/webhook';
|
||||
import * as Tasks from '../../models/task';
|
||||
import {
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
BadRequest,
|
||||
} from '../../libs/api-v3/errors';
|
||||
import shared from '../../../../common';
|
||||
import Q from 'q';
|
||||
import _ from 'lodash';
|
||||
import scoreTask from '../../../../common/script/api-v3/scoreTask';
|
||||
|
||||
let api = {};
|
||||
|
||||
@@ -23,7 +27,7 @@ let api = {};
|
||||
api.createTask = {
|
||||
method: 'POST',
|
||||
url: '/tasks',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
req.checkBody('type', res.t('invalidTaskType')).notEmpty().isIn(Tasks.tasksTypes);
|
||||
|
||||
@@ -61,7 +65,7 @@ api.createTask = {
|
||||
api.getTasks = {
|
||||
method: 'GET',
|
||||
url: '/tasks',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(Tasks.tasksTypes);
|
||||
|
||||
@@ -117,7 +121,7 @@ api.getTasks = {
|
||||
api.getTask = {
|
||||
method: 'GET',
|
||||
url: '/tasks/:taskId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -151,7 +155,7 @@ api.getTask = {
|
||||
api.updateTask = {
|
||||
method: 'PUT',
|
||||
url: '/tasks/:taskId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -171,12 +175,22 @@ api.updateTask = {
|
||||
|
||||
// If checklist is updated -> replace the original one
|
||||
if (req.body.checklist) {
|
||||
delete req.body.checklist;
|
||||
task.checklist = req.body.checklist;
|
||||
delete req.body.checklist;
|
||||
}
|
||||
// TODO merge goes deep into objects, it's ok?
|
||||
// TODO also check that array and mixed fields are updated correctly without marking modified
|
||||
_.merge(task, Tasks.Task.sanitizeUpdate(req.body));
|
||||
|
||||
// If tags are updated -> replace the original ones
|
||||
if (req.body.tags) {
|
||||
task.tags = req.body.tags;
|
||||
delete req.body.tags;
|
||||
}
|
||||
|
||||
// TODO we have to convert task to an object because otherwise thigns doesn't get merged correctly, very bad for performances
|
||||
// TODO regarding comment above make sure other models with nested fields are using this trick too
|
||||
_.assign(task, _.merge(task.toObject(), Tasks.Task.sanitizeUpdate(req.body)));
|
||||
// TODO console.log(task.modifiedPaths(), task.toObject().repeat === tep)
|
||||
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
|
||||
// see https://github.com/Automattic/mongoose/issues/2749
|
||||
return task.save();
|
||||
})
|
||||
.then((savedTask) => res.respond(200, savedTask))
|
||||
@@ -184,8 +198,33 @@ api.updateTask = {
|
||||
},
|
||||
};
|
||||
|
||||
function _generateWebhookTaskData (task, direction, delta, stats, user) {
|
||||
let extendedStats = _.extend(stats, {
|
||||
toNextLevel: shared.tnl(user.stats.lvl),
|
||||
maxHealth: shared.maxHealth,
|
||||
maxMP: user._statsComputed.maxMP, // TODO refactor as method not getter
|
||||
});
|
||||
|
||||
let userData = {
|
||||
_id: user._id,
|
||||
_tmp: user._tmp,
|
||||
stats: extendedStats,
|
||||
};
|
||||
|
||||
let taskData = {
|
||||
details: task,
|
||||
direction,
|
||||
delta,
|
||||
};
|
||||
|
||||
return {
|
||||
task: taskData,
|
||||
user: userData,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /tasks/score/:taskId/:direction Score a task
|
||||
* @api {put} /tasks/:taskId/score/:direction Score a task
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName ScoreTask
|
||||
* @apiGroup Task
|
||||
@@ -197,16 +236,17 @@ api.updateTask = {
|
||||
*/
|
||||
api.scoreTask = {
|
||||
method: 'POST',
|
||||
url: 'tasks/score/:taskId/:direction',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/tasks/:taskId/score/:direction',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']);
|
||||
req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']); // TODO what about rewards? maybe separate route?
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) return next(validationErrors);
|
||||
|
||||
let user = res.locals.user;
|
||||
let direction = req.params.direction;
|
||||
|
||||
Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
@@ -214,8 +254,47 @@ api.scoreTask = {
|
||||
}).exec()
|
||||
.then((task) => {
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
let wasCompleted = task.completed;
|
||||
if (task.type === 'daily' || task.type === 'todo') {
|
||||
task.completed = direction === 'up'; // TODO move into scoreTask
|
||||
}
|
||||
|
||||
let delta = scoreTask({task, user, direction}, req);
|
||||
// Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
|
||||
if (direction === 'up') user.fns.randomDrop({task, delta}, req);
|
||||
|
||||
// If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list
|
||||
if (task.type === 'todo') {
|
||||
if (!wasCompleted && task.completed) {
|
||||
let i = user.tasksOrder.todos.indexOf(task._id);
|
||||
if (i !== -1) user.tasksOrder.todos.splice(i, 1);
|
||||
} else if (wasCompleted && !task.completed) {
|
||||
let i = user.tasksOrder.todos.indexOf(task._id);
|
||||
if (i === -1) {
|
||||
user.tasksOrder.todos.push(task._id); // TODO push at the top?
|
||||
} else { // If for some reason it hadn't been removed TODO ok?
|
||||
user.tasksOrder.todos.splice(i, 1);
|
||||
user.tasksOrder.push(task._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Q.all([
|
||||
user.save(),
|
||||
task.save(),
|
||||
]).then((results) => {
|
||||
let savedUser = results[0];
|
||||
|
||||
let userStats = savedUser.stats.toJSON();
|
||||
let resJsonData = _.extend({delta, _tmp: user._tmp}, userStats);
|
||||
res.respond(200, resJsonData);
|
||||
|
||||
sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user));
|
||||
|
||||
// TODO sync challenge
|
||||
});
|
||||
})
|
||||
.then(() => res.respond(200, {})) // TODO what to return
|
||||
.catch(next);
|
||||
},
|
||||
};
|
||||
@@ -236,7 +315,7 @@ api.scoreTask = {
|
||||
api.moveTask = {
|
||||
method: 'POST',
|
||||
url: '/tasks/move/:taskId/to/:position',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
||||
@@ -288,7 +367,7 @@ api.moveTask = {
|
||||
api.addChecklistItem = {
|
||||
method: 'POST',
|
||||
url: '/tasks/:taskId/checklist',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -306,7 +385,7 @@ api.addChecklistItem = {
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
|
||||
task.checklist.push(req.body);
|
||||
task.checklist.push(Tasks.Task.sanitizeChecklist(req.body));
|
||||
return task.save();
|
||||
})
|
||||
.then((savedTask) => res.respond(200, savedTask)) // TODO what to return
|
||||
@@ -328,7 +407,7 @@ api.addChecklistItem = {
|
||||
api.scoreCheckListItem = {
|
||||
method: 'POST',
|
||||
url: '/tasks/:taskId/checklist/:itemId/score',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -371,7 +450,7 @@ api.scoreCheckListItem = {
|
||||
api.updateChecklistItem = {
|
||||
method: 'PUT',
|
||||
url: '/tasks/:taskId/checklist/:itemId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -392,8 +471,7 @@ api.updateChecklistItem = {
|
||||
let item = _.find(task.checklist, {_id: req.params.itemId});
|
||||
if (!item) throw new NotFound(res.t('checklistItemNotFound'));
|
||||
|
||||
delete req.body.id; // Simple sanitization to prevent the ID to be changed
|
||||
_.merge(item, req.body);
|
||||
_.merge(item, Tasks.Task.sanitizeChecklist(req.body));
|
||||
return task.save();
|
||||
})
|
||||
.then((savedTask) => res.respond(200, savedTask)) // TODO what to return
|
||||
@@ -415,7 +493,7 @@ api.updateChecklistItem = {
|
||||
api.removeChecklistItem = {
|
||||
method: 'DELETE',
|
||||
url: '/tasks/:taskId/checklist/:itemId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -457,8 +535,8 @@ api.removeChecklistItem = {
|
||||
*/
|
||||
api.addTagToTask = {
|
||||
method: 'POST',
|
||||
url: '/tasks/:taskId/tags',
|
||||
middlewares: [authWithHeaders()],
|
||||
url: '/tasks/:taskId/tags/:tagId',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -477,7 +555,7 @@ api.addTagToTask = {
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
let tagId = req.params.tagId;
|
||||
|
||||
let alreadyTagged = task.tags.indexOf(tagId) === -1;
|
||||
let alreadyTagged = task.tags.indexOf(tagId) !== -1;
|
||||
if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged'));
|
||||
|
||||
task.tags.push(tagId);
|
||||
@@ -502,7 +580,7 @@ api.addTagToTask = {
|
||||
api.removeTagFromTask = {
|
||||
method: 'DELETE',
|
||||
url: '/tasks/:taskId/tags/:tagId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
@@ -519,7 +597,7 @@ api.removeTagFromTask = {
|
||||
.then((task) => {
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
let tagI = _.findIndex(task.tags, {_id: req.params.tagId});
|
||||
let tagI = task.tags.indexOf(req.params.tagId);
|
||||
if (tagI === -1) throw new NotFound(res.t('tagNotFound'));
|
||||
|
||||
task.tags.splice(tagI, 1);
|
||||
@@ -559,7 +637,7 @@ function _removeTaskTasksOrder (user, taskId) {
|
||||
api.deleteTask = {
|
||||
method: 'DELETE',
|
||||
url: '/tasks/:taskId',
|
||||
middlewares: [authWithHeaders()],
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
handler (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import cron from '../../middlewares/api-v3/cron';
|
||||
import common from '../../../../common';
|
||||
|
||||
let api = {};
|
||||
|
||||
/**
|
||||
* @api {get} /user Get the authenticated user's profile
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UserGet
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiSuccess {Object} user The user object
|
||||
*/
|
||||
api.getUser = {
|
||||
method: 'GET',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
url: '/user',
|
||||
handler (req, res) {
|
||||
let user = res.locals.user.toJSON();
|
||||
|
||||
// Remove apiToken from resonse TODO make it priavte at the user level? returned in signup/login
|
||||
delete user.apiToken;
|
||||
|
||||
// TODO move to model (maybe virtuals, maybe in toJSON)
|
||||
user.stats.toNextLevel = common.tnl(user.stats.lvl);
|
||||
user.stats.maxHealth = common.maxHealth;
|
||||
user.stats.maxMP = res.locals.user._statsComputed.maxMP;
|
||||
|
||||
return res.respond(200, user);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
@@ -0,0 +1,65 @@
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
daysSince,
|
||||
} from '../../../../common/script/cron';
|
||||
import cron from '../../../../common/script/api-v3/cron';
|
||||
import common from '../../../../common';
|
||||
import Task from '../../models/task';
|
||||
// import Group from '../../models/group';
|
||||
|
||||
// TODO check that it's usef everywhere
|
||||
export default function cronMiddleware (req, res, next) {
|
||||
let user = res.locals.user;
|
||||
let analytics = res.analytics;
|
||||
|
||||
let now = new Date();
|
||||
let daysMissed = daysSince(user.lastCron, _.defaults({now}, user.preferences));
|
||||
|
||||
if (daysMissed <= 0) return next(null, user); // TODO why are we passing user down here?
|
||||
|
||||
// Fetch active tasks (no completed todos)
|
||||
Task.find({
|
||||
userId: user._id,
|
||||
$or: [ // Exclude completed todos
|
||||
{type: 'todo', completed: false},
|
||||
{type: {$in: ['habit', 'daily', 'reward']}},
|
||||
],
|
||||
}).exec()
|
||||
.then((tasks) => {
|
||||
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
|
||||
tasks.forEach(task => tasksByType[`${task.type}s`].push(task));
|
||||
|
||||
// Run cron
|
||||
cron({user, tasks, tasksByType, now, daysMissed, analytics});
|
||||
|
||||
let ranCron = user.isModified();
|
||||
let quest = common.content.quests[user.party.quest.key];
|
||||
|
||||
// if (ranCron) res.locals.wasModified = true; // TODO remove?
|
||||
if (!ranCron) return next(null, user); // TODO why are we passing user to next?
|
||||
// TODO Group.tavernBoss(user, progress);
|
||||
if (!quest || true /* TODO remove */) return user.save(next);
|
||||
|
||||
// If user is on a quest, roll for boss & player, or handle collections
|
||||
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
|
||||
// TODO do
|
||||
/* async.waterfall([
|
||||
function(cb){
|
||||
user.save(cb); // make sure to save the cron effects
|
||||
},
|
||||
function(saved, count, cb){
|
||||
var type = quest.boss ? 'boss' : 'collect';
|
||||
Group[type+'Quest'](user,progress,cb);
|
||||
},
|
||||
function(){
|
||||
var cb = arguments[arguments.length-1];
|
||||
// User has been updated in boss-grapple, reload
|
||||
User.findById(user._id, cb);
|
||||
}
|
||||
], function(err, saved) {
|
||||
res.locals.user = saved;
|
||||
next(err,saved);
|
||||
user = progress = quest = null;
|
||||
});*/
|
||||
});
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export default function errorHandler (err, req, res, next) { // eslint-disable-l
|
||||
|
||||
// Handle mongoose validation errors
|
||||
if (err.name === 'ValidationError') {
|
||||
responseErr = new BadRequest(err.message);
|
||||
responseErr = new BadRequest(err.message); // TODO standard message? translate?
|
||||
responseErr.errors = map(err.errors, (mongooseErr) => {
|
||||
return {
|
||||
message: mongooseErr.message,
|
||||
|
||||
@@ -46,17 +46,23 @@ TaskSchema.plugin(baseModel, {
|
||||
});
|
||||
|
||||
// A list of additional fields that cannot be set on creation (but can be set on updare)
|
||||
let noCreate = ['completed'];
|
||||
let noCreate = ['completed']; // TODO completed should be removed for updates too?
|
||||
TaskSchema.statics.sanitizeCreate = function sanitizeCreate (createObj) {
|
||||
return Task.sanitize(createObj, noCreate); // eslint-disable-line no-use-before-define
|
||||
};
|
||||
|
||||
// A list of additional fields that cannot be updated (but can be set on creation)
|
||||
let noUpdate = ['_id', 'type']; // TODO should prevent changes to checlist.*.id
|
||||
let noUpdate = ['_id', 'type'];
|
||||
TaskSchema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
|
||||
return Task.sanitize(updateObj, noUpdate); // eslint-disable-line no-use-before-define
|
||||
};
|
||||
|
||||
// Sanitize checklist objects (disallowing _id)
|
||||
TaskSchema.statics.sanitizeChecklist = function sanitizeChecklist (checklistObj) {
|
||||
delete checklistObj._id;
|
||||
return checklistObj;
|
||||
};
|
||||
|
||||
export let Task = mongoose.model('Task', TaskSchema);
|
||||
|
||||
// habits and dailies shared fields
|
||||
|
||||
@@ -45,6 +45,7 @@ export let schema = new Schema({
|
||||
// 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 },
|
||||
// TODO give all this a default of 0?
|
||||
achievements: {
|
||||
originalUser: Boolean,
|
||||
habitSurveys: Number,
|
||||
@@ -65,7 +66,7 @@ export let schema = new Schema({
|
||||
quests: Schema.Types.Mixed, // TODO remove, use dictionary?
|
||||
rebirths: Number,
|
||||
rebirthLevel: Number,
|
||||
perfect: Number,
|
||||
perfect: {type: Number, default: 0},
|
||||
habitBirthdays: Number,
|
||||
valentine: Number,
|
||||
costumeContest: Boolean, // Superseded by costumeContests
|
||||
@@ -373,7 +374,7 @@ export let schema = new Schema({
|
||||
toolbarCollapsed: {type: Boolean, default: false},
|
||||
background: String,
|
||||
displayInviteToPartyWhenPartyIs1: {type: Boolean, default: true},
|
||||
webhooks: {type: Schema.Types.Mixed, default: {}},
|
||||
webhooks: {type: Schema.Types.Mixed, default: {}}, // TODO array? and proper controller... unless VersionError becomes problematic
|
||||
// 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: {
|
||||
@@ -468,7 +469,8 @@ export let schema = new Schema({
|
||||
});
|
||||
|
||||
schema.plugin(baseModel, {
|
||||
noSet: ['_id', 'apikey', 'auth.blocked', 'auth.timestamps', 'lastCron', 'auth.local.hashed_password', 'auth.local.salt', 'tasksOrder', 'tags'],
|
||||
// TODO revisit a lot of things are missing
|
||||
noSet: ['_id', 'apiToken', 'auth.blocked', 'auth.timestamps', 'lastCron', 'auth.local.hashed_password', 'auth.local.salt', 'tasksOrder', 'tags', 'stats'],
|
||||
private: ['auth.local.hashed_password', 'auth.local.salt'],
|
||||
toJSONTransform: function toJSON (doc) {
|
||||
// FIXME? Is this a reference to `doc.filters` or just disabled code? Remove?
|
||||
@@ -627,6 +629,11 @@ schema.pre('save', true, function preSaveUser (next, done) {
|
||||
}
|
||||
});
|
||||
|
||||
// TODO unit test this?
|
||||
schema.methods.isSubscribed = function isSubscribed () {
|
||||
return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion
|
||||
};
|
||||
|
||||
schema.methods.unlink = function unlink (options, cb) {
|
||||
let cid = options.cid;
|
||||
let keep = options.keep;
|
||||
|
||||
Reference in New Issue
Block a user