linagora/openpaas-esn

View on GitHub
backend/core/message/index.js

Summary

Maintainability
C
1 day
Test Coverage
'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
};