Files
habitica/src/models/challenge.js
Tyler Renelle ee1cd3c05d [#1977] APIv2 WIP - start of a framework where operations are shared
between client & server. If the op is called on the client, it updates
the user & then POSTs to the server with op of the same name. If called
on server, it updates the user and user.save()s
2013-12-11 21:42:57 -07:00

122 lines
3.7 KiB
JavaScript

var mongoose = require("mongoose");
var Schema = mongoose.Schema;
var shared = require('habitrpg-shared');
var _ = require('lodash');
var TaskSchemas = require('./task');
var Group = require('./group').model;
var ChallengeSchema = new Schema({
_id: {type: String, 'default': shared.uuid},
name: String,
shortName: String,
description: String,
habits: [TaskSchemas.HabitSchema],
dailys: [TaskSchemas.DailySchema],
todos: [TaskSchemas.TodoSchema],
rewards: [TaskSchemas.RewardSchema],
leader: {type: String, ref: 'User'},
group: {type: String, ref: 'Group'},
timestamp: {type: Date, 'default': Date.now},
members: [{type: String, ref: 'User'}],
memberCount: {type: Number, 'default': 0},
prize: {type: Number, 'default': 0}
});
ChallengeSchema.virtual('tasks').get(function () {
var tasks = this.habits.concat(this.dailys).concat(this.todos).concat(this.rewards);
var tasks = _.object(_.pluck(tasks,'id'), tasks);
return tasks;
});
// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate()
// @see https://github.com/LearnBoost/mongoose/issues/964
ChallengeSchema.pre('save', function(next){
this.memberCount = _.size(this.members);
next()
})
ChallengeSchema.methods.toJSON = function(){
var doc = this.toObject();
doc.memberCount = doc.members ? _.size(doc.members) : doc.memberCount; // @see pre('save') comment above
doc._isMember = this._isMember;
return doc;
}
// --------------
// Syncing logic
// --------------
function syncableAttrs(task) {
var t = (task.toObject) ? task.toObject() : task; // lodash doesn't seem to like _.omit on EmbeddedDocument
// only sync/compare important attrs
var omitAttrs = 'history tags completed streak'.split(' ');
if (t.type != 'reward') omitAttrs.push('value');
return _.omit(t, omitAttrs);
}
/**
* Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
*/
function comparableData(obj) {
return (
_.chain(obj.habits.concat(obj.dailys).concat(obj.todos).concat(obj.rewards))
.sortBy('id') // we don't want to update if they're sort-order is different
.transform(function(result, task){
result.push(syncableAttrs(task));
}))
.toString(); // for comparing arrays easily
}
ChallengeSchema.methods.isOutdated = function(newData) {
return comparableData(this) !== comparableData(newData);
}
/**
* Syncs all new tasks, deleted tasks, etc to the user object
* @param user
* @return nothing, user is modified directly. REMEMBER to save the user!
*/
ChallengeSchema.methods.syncToUser = function(user, cb) {
if (!user) return;
var self = this;
self.shortName = self.shortName || self.name;
// Sync tags
var tags = user.tags || [];
var i = _.findIndex(tags, {id: self._id})
if (~i) {
if (tags[i].name !== self.shortName) {
// update the name - it's been changed since
user.tags[i].name = self.shortName;
}
} else {
user.tags.push({
id: self._id,
name: self.shortName,
challenge: true
});
}
// Sync new tasks and updated tasks
_.each(self.tasks, function(task){
var list = user[task.type+'s'];
var userTask = user.tasks[task.id] || (list.push(syncableAttrs(task)), list[list.length-1]);
userTask.challenge = {id:self._id};
userTask.tags = userTask.tags || {};
userTask.tags[self._id] = true;
_.merge(userTask, syncableAttrs(task));
})
// Flag deleted tasks as "broken"
_.each(user.tasks, function(task){
if (task.challenge && task.challenge.id==self._id && !self.tasks[task.id]) {
task.challenge.broken = 'TASK_DELETED';
}
})
user.save(cb);
};
module.exports.schema = ChallengeSchema;
module.exports.model = mongoose.model("Challenge", ChallengeSchema);