backend/core/message/index.js
'use strict';
var mongoose = require('mongoose');
var async = require('async');
var attachments = require('./attachments');
var emailMessageModule = require('./email');
var whatsupMessageModule = require('./whatsup');
var pollMessageModule = require('./poll');
var pubsub = require('../').pubsub.local;
var role = require('./role');
var CONSTANTS = require('./constants');
var objectTypeToSchemaName = {
email: 'EmailMessage',
whatsup: 'Whatsup',
poll: 'PollMessage'
};
var type = {
email: emailMessageModule,
whatsup: whatsupMessageModule,
poll: pollMessageModule
};
function getModel(objectType) {
var modelName = objectTypeToSchemaName[objectType];
if (!modelName) {
throw new Error('Unkown object type: ', objectType);
}
return mongoose.model(modelName);
}
function getInstance(objectType, message) {
var Model;
Model = getModel(objectType);
return new Model(message);
}
function registerMessageType(objectType, schemaName, messageModule) {
objectTypeToSchemaName[objectType] = schemaName;
if (messageModule) {
type[objectType] = messageModule;
}
}
function collectAuthors(messages, authorsMap) {
var authors = authorsMap || Object.create(null);
messages.forEach(function(message) {
if (message.author in authors) {
authors[message.author].push(message);
} else {
authors[message.author] = [message];
}
if (message.responses) {
collectAuthors(message.responses, authors);
}
});
return authors;
}
function applyAuthors(authorsMap, authors) {
authors.forEach(function(author) {
authorsMap[author._id].forEach(function(message) {
message.author = author;
});
});
}
function get(uuid, callback) {
var whatsupModel = mongoose.model('Whatsup');
var ouuid = new mongoose.Types.ObjectId(uuid);
whatsupModel.collection.findOne({_id: ouuid}, function(err, doc) {
if (err) {
return callback(err);
}
if (!doc) {
return callback(new Error('Document not found (id = ' + uuid + ')'));
}
var Model, instance;
try {
Model = getModel(doc.objectType);
} catch (e) {
return callback(e);
}
instance = new Model();
instance.init(doc);
return callback(null, instance);
});
}
function getWithAuthors(uuid, callback) {
return get(uuid, function(err, instance) {
if (err) { return callback(err); }
var doc = instance.toObject();
var userModel = mongoose.model('User');
var authorsMap = collectAuthors([doc]);
var ids = Object.keys(authorsMap).map(function(id) {
return mongoose.Types.ObjectId(id);
});
return userModel.find({ _id: {$in: ids } }).exec(function(err, authors) {
if (err) { return callback(err); }
applyAuthors(authorsMap, authors);
return callback(null, doc);
});
});
}
function copy(id, sharerId, resource, target, callback) {
function getOriginal(callback) {
mongoose.connection.db.collection(CONSTANTS.MESSAGES_COLLECTION, function(err, collection) {
var query = {
$or: [
{_id: mongoose.Types.ObjectId(id)},
{'responses._id': mongoose.Types.ObjectId(id)}
]
};
collection.findOne(query, function(err, message) {
if (!message) {
return callback(null, null);
}
var targetMessage = null;
if (message._id.equals(id)) {
targetMessage = message;
} else {
targetMessage = message.responses.filter(function(response) {
return response._id.equals(id);
})
.pop();
}
return callback(null, {root: message, target: targetMessage});
});
});
}
function update(original, callback) {
var copyOf = original.target.copyOf || {};
copyOf.target = original.target.copyOf ? original.target.copyOf.target || [] : [];
copyOf.target = copyOf.target.concat(target);
if (original.root._id.equals(id)) {
return getModel(original.target.objectType).updateOne({_id: original.target._id}, {$set: {copyOf: copyOf} }, callback);
} else {
return getModel(original.target.objectType).updateOne({_id: original.root._id, 'responses._id': mongoose.Types.ObjectId(id)}, {$set: {'responses.$.copyOf': copyOf} }, callback);
}
}
function doCopy(original, callback) {
var copy = original.target;
copy._id = new mongoose.Types.ObjectId();
copy.copyOf = {
origin: {
resource: resource,
message: mongoose.Types.ObjectId(id),
sharer: mongoose.Types.ObjectId(sharerId),
timestamps: {
creation: new Date()
}
}
};
copy.shares = target;
delete copy.responses;
var instance = getInstance(copy.objectType, copy);
instance.save(callback);
}
getOriginal(function(err, original) {
if (err) {
return callback(err);
}
if (!original) {
return callback(null, null);
}
async.parallel([
update.bind(null, original),
doCopy.bind(null, original)
], function(err, result) {
if (err) {
return callback(err);
}
return callback(err, result[1][0]);
});
});
}
function findByIds(ids, callback) {
var whatsupModel = mongoose.model('Whatsup');
var formattedIds = ids.map(function(id) {
return mongoose.Types.ObjectId(id);
});
var query = {
$or: [
{_id: { $in: formattedIds }},
{'responses._id': { $in: formattedIds }}
]
};
function getMessage(message) {
if (typeof message.toObject === 'function') {
message = message.toObject();
}
if (ids.indexOf(String(message._id)) >= 0) {
return message;
}
var result = message.responses.filter(function(response) {
return ids.indexOf(String(response._id)) >= 0;
}).pop();
if (result) {
result.shares = message.shares;
}
return result;
}
whatsupModel.find(query).exec(function(err, foundMessages) {
if (err) {
return callback(err);
}
var messages = foundMessages.map(getMessage);
var authorsMap = collectAuthors(messages);
var ids = Object.keys(authorsMap).map(function(id) {
return mongoose.Types.ObjectId(id);
});
var userModel = mongoose.model('User');
var query = { _id: { $in: ids} };
userModel.find(query).exec(function(err, authors) {
if (err) { return callback(err); }
applyAuthors(authorsMap, authors);
return callback(null, messages);
});
});
}
function addNewComment(message, inReplyTo, callback) {
get(inReplyTo._id, function(err, parent) {
if (err) {
return callback(err);
}
parent.responses.push(message);
parent.save(function(err, parent) {
var topic = pubsub.topic('message:comment');
if (err) {
return callback(err);
}
message.inReplyTo = inReplyTo;
topic.publish(message);
callback(null, message, parent);
});
});
}
function setAttachmentsReferences(message, callback) {
if (!message) {
return callback(new Error('Message is required'));
}
if (!message.attachments || message.attachments.length === 0) {
return callback();
}
var failures = [];
async.each(message.attachments, function(attachment, done) {
attachments.setMessageReference(attachment, message, function(err) {
if (err) {
failures.push(attachment);
}
return done();
});
}, function() {
if (failures.length > 1) {
return callback(new Error('Fail to set references for attachments', failures));
}
return callback();
});
}
function specificModelCheckForObjectType(objectType, messageModel, messageTargets, callback) {
if (!objectType || !type[objectType] || !type[objectType].checkModel) {
return callback();
} else {
return type[objectType].checkModel(messageModel, messageTargets, callback);
}
}
function typeSpecificReplyPermission(message, user, replyData, callback) {
var objectType = message.objectType;
if (!objectType || !type[objectType] || !type[objectType].checkReplyPermission) {
return callback(null, true);
} else {
return type[objectType].checkReplyPermission(message, user, replyData, callback);
}
}
function filterReadableResponses(message, user, callback) {
filterReadableResponsesFromObjectType(message, user, function(err, result) {
if (err) {
return callback(err);
}
filterReadableResponsesFromStatus(result, user, callback);
});
}
function filterReadableResponsesFromStatus(message, user, callback) {
var responses = [];
async.each(message.responses, function(response, done) {
canReadMessageFromStatus(response, user, function(err, result) {
if (!err && result) {
responses.push(response);
}
done();
});
}, function() {
message.responses = responses;
return callback(null, message);
});
}
function filterReadableResponsesFromObjectType(message, user, callback) {
var objectType = message.objectType;
if (!objectType || !type[objectType] || !type[objectType].filterReadableResponses) {
return callback(null, message);
} else {
return type[objectType].filterReadableResponses(message, user, callback);
}
}
function canReadMessageFromStatus(message, user, callback) {
role.canReadMessage(message, user).then(function(result) {
callback(null, result);
}, function() {
callback(null, false);
});
}
module.exports = {
type: type,
permission: require('./permission'),
role: role,
attachments: attachments,
like: require('./like'),
get: getWithAuthors,
dryGet: get,
copy: copy,
getModel: getModel,
getInstance: getInstance,
registerMessageType: registerMessageType,
addNewComment: addNewComment,
findByIds: findByIds,
setAttachmentsReferences: setAttachmentsReferences,
specificModelCheckForObjectType: specificModelCheckForObjectType,
typeSpecificReplyPermission: typeSpecificReplyPermission,
filterReadableResponses: filterReadableResponses,
filterReadableResponsesFromObjectType: filterReadableResponsesFromObjectType,
filterReadableResponsesFromStatus: filterReadableResponsesFromStatus,
canReadMessageFromStatus: canReadMessageFromStatus
};