diff --git a/common/script/ops/scoreTask.js b/common/script/ops/scoreTask.js index 0508462773..f052b954e0 100644 --- a/common/script/ops/scoreTask.js +++ b/common/script/ops/scoreTask.js @@ -194,6 +194,7 @@ module.exports = function scoreTask (options = {}, req = {}) { } _gainMP(user, _.max([0.25, 0.0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1)); + task.history = task.history || []; // Add history entry, even more than 1 per day task.history.push({ date: Number(new Date()), diff --git a/migrations/api_v3/challenges.js b/migrations/api_v3/challenges.js index eaec714858..6d066b3d63 100644 --- a/migrations/api_v3/challenges.js +++ b/migrations/api_v3/challenges.js @@ -129,16 +129,16 @@ function processChallenges (afterId) { oldTasks.forEach(function (oldTask) { oldTask._id = uuid.v4(); - oldTask.legacyId = oldTask.id; // store the old task id + oldTask._legacyId = oldTask.id; // store the old task id delete oldTask.id; oldTask.challenge = oldTask.challenge || {}; oldTask.challenge.id = newChallenge._id; - if (newTasksIds[oldTask.legacyId + '-' + newChallenge._id]) { + if (newTasksIds[oldTask._legacyId + '-' + newChallenge._id]) { throw new Error('duplicate :('); } else { - newTasksIds[oldTask.legacyId + '-' + newChallenge._id] = oldTask._id; + newTasksIds[oldTask._legacyId + '-' + newChallenge._id] = oldTask._id; } oldTask.tags = _.map(oldTask.tags || {}, function (tagPresent, tagId) { diff --git a/migrations/api_v3/users.js b/migrations/api_v3/users.js index e569699673..ef15d74f85 100644 --- a/migrations/api_v3/users.js +++ b/migrations/api_v3/users.js @@ -131,20 +131,20 @@ function processUsers (afterId) { oldTasks.forEach(function (oldTask) { oldTask._id = uuid.v4(); // create a new unique uuid oldTask.userId = newUser._id; - oldTask.legacyId = oldTask.id; // store the old task id + oldTask._legacyId = oldTask.id; // store the old task id delete oldTask.id; oldTask.challenge = oldTask.challenge || {}; if (oldTask.challenge.id) { if (oldTask.challenge.broken) { - oldTask.challenge.taskId = oldTask.legacyId; + oldTask.challenge.taskId = oldTask._legacyId; } else { - var newId = newTasksIds[oldTask.legacyId + '-' + oldTask.challenge.id]; + var newId = newTasksIds[oldTask._legacyId + '-' + oldTask.challenge.id]; // Challenges' tasks ids changed if (!newId && !oldTask.challenge.broken) { challengeTaskNoMatchingId++; - oldTask.challenge.taskId = oldTask.legacyId; + oldTask.challenge.taskId = oldTask._legacyId; oldTask.challenge.broken = 'CHALLENGE_TASK_NOT_FOUND'; } else { challengeTaskWithMatchingId++; @@ -173,7 +173,7 @@ function processUsers (afterId) { newUser.tasksOrder[`${oldTask.type}s`].push(oldTask._id); } - var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', 'legacyId', 'createdAt']; + var allTasksFields = ['_id', 'type', 'text', 'notes', 'tags', 'value', 'priority', 'attribute', 'challenge', 'reminders', 'userId', '_legacyId', 'createdAt']; // using mongoose models is too slow if (oldTask.type === 'habit') { oldTask = _.pick(oldTask, allTasksFields.concat(['history', 'up', 'down'])); diff --git a/website/server/controllers/api-v2/user.js b/website/server/controllers/api-v2/user.js index daaa272491..323155a5bd 100644 --- a/website/server/controllers/api-v2/user.js +++ b/website/server/controllers/api-v2/user.js @@ -1,6 +1,7 @@ var url = require('url'); var ipn = require('paypal-ipn'); var _ = require('lodash'); +var validator = require('validator'); var nconf = require('nconf'); var asyncM = require('async'); var shared = require('../../../../common'); @@ -79,6 +80,25 @@ var findTask = function(req, res) { return res.locals.user.tasks[req.params.id]; }; +function findTaskByIdOrLegacyId (user, taskId, callback) { + asyncM.waterfall([ + function (cb) { + Tasks.Task.findOne({ + _id: taskId, + userId: user._id, + }, cb); + }, + function (task, cb) { + if (task) return cb(null, task); + + Tasks.Task.findOne({ + _legacyId: taskId, + userId: user._id, + }, cb); + }, + ], callback); +} + /* API Routes --------------- @@ -98,23 +118,27 @@ api.score = function(req, res, next) { return res.json(400, {err: ":direction must be 'up' or 'down'"}); } - Tasks.Task.findOne({ - _id: id, - userId: user._id - }, function(err, task){ - if(err) return next(err); + findTaskByIdOrLegacyId(user, id, function (err, task) { + if (err) return next(err); // If exists already, score it if (!task) { // If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it // Defaults. Other defaults are handled in user.ops.addTask() - task = new Tasks.Task({ - _id: id, // TODO this might easily lead to conflicts as ids are now unique db-wide - type: body.type, - text: body.text, + var taskOptions = { + type: body.type || 'habit', + text: body.text || id, userId: user._id, notes: body.notes || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task." // TODO translate - }); + } + + if (validator.isUUID(id)) { + taskOptions._id = id; // TODO this might easily lead to conflicts as ids are now unique db-wide + } else { + taskOptions._legacyId = id; + } + + task = new Tasks.Task(taskOptions); user.tasksOrder[task.type + 's'].unshift(task._id); } @@ -187,7 +211,6 @@ api.score = function(req, res, next) { }); }); }); - }; /** @@ -206,12 +229,10 @@ api.getTasks = function(req, res, next) { * Get Task */ api.getTask = function(req, res, next) { - var user = res.locals.user; + var user = res.locals.user, + id = req.params.id; - Tasks.Task.findOne({ - userId: user._id, - _id: req.params.id, - }, function (err, task) { + findTaskByIdOrLegacyId(user, id, function (err, task) { if (err) return next(err); if (!task) return res.status(404).json({err: shared.i18n.t('messageTaskNotFound')}); res.status(200).json(task.toJSONV2()); @@ -830,13 +851,12 @@ api.deleteTask = function(req, res, next) { }; api.updateTask = function(req, res, next) { - var user = res.locals.user; + var user = res.locals.user, + id = req.params.id; + req.body = Tasks.Task.fromJSONV2(req.body); - Tasks.Task.findOne({ - _id: req.params.id, - userId: user._id - }, function(err, task) { + findTaskByIdOrLegacyId(user, id, function (err, task) { if(err) return next(err); if(!task) return res.status(404).json({err: 'Task not found.'}) diff --git a/website/server/models/task.js b/website/server/models/task.js index 27f356efa3..0116da2371 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -17,6 +17,7 @@ export let tasksTypes = ['habit', 'daily', 'todo', 'reward']; // Important // When something changes here remember to update the client side model at common/script/libs/taskDefaults export let TaskSchema = new Schema({ + _legacyId: String, // TODO Remove when v2 is deprecated type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]}, text: {type: String, required: true}, notes: {type: String, default: ''}, @@ -119,7 +120,11 @@ TaskSchema.methods.scoreChallengeTask = async function scoreChallengeTask (delta // toJSON for API v2 TaskSchema.methods.toJSONV2 = function toJSONV2 () { let toJSON = this.toJSON(); - toJSON.id = toJSON._id; + if (toJSON._legacyId) { + toJSON.id = toJSON._legacyId; + } else { + toJSON.id = toJSON._id; + } let v3Tags = this.tags;