linagora/openpaas-esn

View on GitHub
backend/webserver/controllers/collaborations.js

Summary

Maintainability
F
6 days
Test Coverage
'use strict';

const async = require('async');
const collaborationModule = require('../../core/collaboration');
const userDomain = require('../../core/user/domain');
const imageModule = require('../../core/image');
const { denormalize } = require('../denormalize/user');
const logger = require('../../core/logger');
const { DEFAULT_LIMIT, DEFAULT_OFFSET } = require('../../core/collaboration/constants');
const permission = collaborationModule.permission;

module.exports = {
  searchWhereMember,
  getWritable,
  getMembers,
  getInvitablePeople,
  addMembershipRequest,
  getMembershipRequests,
  join,
  leave,
  removeMembershipRequest,
  getMember,
  getAvatar
};

function transform(collaboration, user, callback) {
  if (!collaboration) {
    return callback({});
  }

  const membershipRequest = collaborationModule.member.getMembershipRequest(collaboration, user);

  if (typeof collaboration.toObject === 'function') {
    collaboration = collaboration.toObject();
  }

  collaboration.members_count = collaboration.members ? collaboration.members.length : 0;
  if (membershipRequest) {
    collaboration.membershipRequest = membershipRequest.timestamp.creation.getTime();
  }

  const userTuple = {objectType: 'user', id: user.id};

  collaborationModule.member.isMember(collaboration, userTuple, (err, membership) => {
    if (membership) {
      collaboration.member_status = 'member';
    } else {
      collaborationModule.member.isIndirectMember(collaboration, userTuple, (err, indirect) => {
        if (indirect) {
          collaboration.member_status = 'indirect';
        } else {
          collaboration.member_status = 'none';
        }
      });
    }

    permission.canWrite(collaboration, userTuple, (err, writable) => {
      collaboration.writable = writable || false;
      delete collaboration.members;
      delete collaboration.membershipRequests;

      return callback(collaboration);
    });
  });
}

function searchWhereMember(req, res) {
  collaborationModule.getCollaborationsForTuple({objectType: req.query.objectType, id: req.query.id}, (err, collaborations) => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server error', details: err.message}});
    }

    const tuple = {objectType: 'user', id: req.user._id};

    async.filter(
      collaborations,
      (collaboration, callback) => permission.canRead(collaboration, tuple, callback),
      (err, results) => {
        async.map(
          results,
          (element, callback) => transform(element, req.user, transformed => callback(null, transformed)),
          (err, results) => {
            if (err) {
              return res.status(500).json({error: {code: 500, message: 'Server error', details: err.message}});
            }

            return res.status(200).json(results);
          });
      });
  });
}

function getWritable(req, res) {
  const user = req.user;

  collaborationModule.getCollaborationsForUser(user._id, {writable: true}, (err, collaborations) => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
    }
    async.map(collaborations, (collaboration, callback) => {
      transform(collaboration, req.user, transformed => callback(null, transformed));
    }, (err, results) => res.status(200).json(results));
  });
}

function getMembers(req, res) {
  const query = {};

  if (req.query.limit) {
    const limit = parseInt(req.query.limit, 10);

    if (!isNaN(limit)) {
      query.limit = limit;
    }
  }

  if (req.query.offset) {
    const offset = parseInt(req.query.offset, 10);

    if (!isNaN(offset)) {
      query.offset = offset;
    }
  }

  if (req.query.objectTypeFilter) {
    query.objectTypeFilter = req.query.objectTypeFilter;
  }

  if (req.query.idFilter) {
    query.idFilter = req.query.idFilter;
  }

  collaborationModule.member.getMembers(req.collaboration, req.params.objectType, query, (err, members) => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.message}});
    }

    res.header('X-ESN-Items-Count', members.total_count || 0);

    function format(member) {
      if (!member || !member.member) {
        return null;
      }

      return {
        objectType: member.objectType,
        id: member.id,
        [member.objectType]: collaborationModule.memberDenormalize.denormalize(member.objectType, member.member),
        metadata: {
          timestamps: member.timestamps
        }
      };
    }

    const result = members.map(format).filter(Boolean);

    return res.status(200).json(result);
  });
}

function getInvitablePeople(req, res) {
  const collaboration = req.collaboration;
  const excludeUserIds = (req.body.exclude && req.body.exclude.users) || [];
  const query = {
    offset: +req.query.offset || DEFAULT_OFFSET,
    limit: +req.query.limit || DEFAULT_LIMIT,
    search: req.query.search || null,
    not_in_collaboration: collaboration,
    excludeUserIds
  };
  const domainIds = collaboration.domain_ids.slice(0);
  const search = query.search ? userDomain.getUsersSearch : userDomain.getUsersList;

  return new Promise((resolve, reject) => {
    search(domainIds, query, (err, result) => {
      if (err) {
        return reject(err);
      }

      resolve(result);
    });
  })
  .then(result =>
    Promise.all(result.list.map(user => denormalize(user)))
      .then(denormalizedUsers => {
        res.header('X-ESN-Items-Count', result.total_count);

        return res.status(200).json(denormalizedUsers);
      })
  )
  .catch(err => {
    logger.error('Error while searching invitable people', err);

    res.status(500).json({
      error: {
        status: 500,
        message: 'Server error',
        details: 'Error while searching invitable people'
      }
    });
  });
}

function addMembershipRequest(req, res) {
  const collaboration = req.collaboration;
  const userAuthor = req.user;
  const userTargetId = req.params.user_id;
  const objectType = req.params.objectType;

  const member = collaboration.members.filter(member => (
    member.member.objectType === 'user' && member.member.id.equals(userTargetId)
  ));

  if (member.length) {
    return res.status(400).json({error: {code: 400, message: 'Bad request', details: 'User is already member'}});
  }

  function addMembership(objectType, collaboration, userAuthor, userTarget, workflow, actor) {
    collaborationModule.member.addMembershipRequest(objectType, collaboration, userAuthor, userTarget, workflow, actor, (err, collaboration) => {
      if (err) {
        return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.message}});
      }

      return transform(collaboration, userAuthor, transformed => res.status(200).json(transformed));
    });
  }

  if (req.isCollaborationManager) {
    const membershipType = req.user._id.equals(req.params.user_id) ?
      collaborationModule.member.MEMBERSHIP_TYPE_REQUEST :
      collaborationModule.member.MEMBERSHIP_TYPE_INVITATION;

    addMembership(objectType, collaboration, userAuthor, userTargetId, membershipType, 'manager');
  } else {
    addMembership(objectType, collaboration, userAuthor, userTargetId, collaborationModule.member.MEMBERSHIP_TYPE_REQUEST, 'user');
  }
}

function getMembershipRequests(req, res) {
  const collaboration = req.collaboration;

  if (!req.isCollaborationManager) {
    return res.status(403).json({error: {code: 403, message: 'Forbidden', details: 'Only collaboration managers can get requests'}});
  }

  const query = {};

  if (req.query.limit) {
    const limit = parseInt(req.query.limit, 10);

    if (!isNaN(limit)) {
      query.limit = limit;
    }
  }

  if (req.query.offset) {
    const offset = parseInt(req.query.offset, 10);

    if (!isNaN(offset)) {
      query.offset = offset;
    }
  }

  collaborationModule.member.getMembershipRequests(req.params.objectType, collaboration, query, (err, membershipRequests) => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
    }
    res.header('X-ESN-Items-Count', req.collaboration.membershipRequests ? req.collaboration.membershipRequests.length : 0);
    const result = membershipRequests.map(request => {
      if (req.query.workflow && req.query.workflow !== request.workflow) {
        return false;
      }

      const result = collaborationModule.userToMember({member: request.user, timestamp: request.timestamp});

      result.workflow = request.workflow;
      result.timestamp = request.timestamp;

      return result;
    });

    return res.status(200).json(result || []);
  });
}

function join(req, res) {
  const collaboration = req.collaboration;
  const user = req.user;
  const targetUserId = req.params.user_id;

  if (req.isCollaborationManager) {

    if (user._id.equals(targetUserId) && user._id.equals(req.collaboration.creator)) {
      return res.status(400).json({error: {code: 400, message: 'Bad request', details: 'Collaboration creator can not add himself to a collaboration'}});
    }

    if (!req.query.withoutInvite && !collaborationModule.member.getMembershipRequest(collaboration, {_id: targetUserId})) {
      return res.status(400).json({error: {code: 400, message: 'Bad request', details: 'User did not request to join collaboration'}});
    }

    collaborationModule.member.join(req.params.objectType, collaboration, user, targetUserId, 'manager', err => {
      if (err) {
        return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
      }

      collaborationModule.member.cleanMembershipRequest(collaboration, targetUserId, err => {
        if (err) {
          return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
        }

        return res.status(204).end();
      });
    });

  } else {
    if (!user._id.equals(targetUserId)) {
      return res.status(400).json({error: {code: 400, message: 'Bad request', details: 'Current user is not the target user'}});
    }

    if (req.collaboration.type !== collaborationModule.CONSTANTS.COLLABORATION_TYPES.OPEN) {
      const membershipRequest = collaborationModule.member.getMembershipRequest(collaboration, user);

      if (!membershipRequest) {
        return res.status(400).json({error: {code: 400, message: 'Bad request', details: 'User was not invited to join collaboration'}});
      }

      collaborationModule.member.join(req.params.objectType, collaboration, user, user, null, err => {
        if (err) {
          return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
        }

        collaborationModule.member.cleanMembershipRequest(collaboration, user, err => {
          if (err) {
            return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
          }

          return res.status(204).end();
        });
      });
    } else {
      collaborationModule.member.join(req.params.objectType, collaboration, user, targetUserId, 'user', err => {
        if (err) {
          return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
        }

        collaborationModule.member.cleanMembershipRequest(collaboration, user, err => {
          if (err) {
            return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
          }

          return res.status(204).end();
        });
      });
    }
  }
}

function leave(req, res) {
  const collaboration = req.collaboration;
  const user = req.user;
  const targetUserId = req.params.user_id;

  collaborationModule.member.leave(req.params.objectType, collaboration, user, targetUserId, err => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.details}});
    }

    return res.status(204).end();
  });
}

function removeMembershipRequest(req, res) {
  if (!req.isCollaborationManager && !req.user._id.equals(req.params.user_id)) {
    return res.status(403).json({error: {code: 403, message: 'Forbidden', details: 'Current user is not the target user'}});
  }

  if (!req.collaboration.membershipRequests || !('filter' in req.collaboration.membershipRequests)) {
    return res.status(204).end();
  }

  const memberships = req.collaboration.membershipRequests.filter(mr => mr.user.equals(req.params.user_id));

  if (!memberships.length) {
    return res.status(204).end();
  }
  const membership = memberships[0];

  function onResponse(err) {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.message}});
    }
    res.status(204).end();
  }

  /*
   *      workflow   |   isCollaborationManager |  What does it mean ?
   *      -----------------------------------------------------------
   *      INVITATION |           yes            | manager cancel the invitation of the user
   *      INVITATION |            no            | attendee declines the invitation
   *      REQUEST    |           yes            | manager refuses the user's request to enter the collaboration
   *      REQUEST    |            no            | user cancels her request to enter the commity
   */

  if (req.isCollaborationManager) {
    if (membership.workflow === collaborationModule.member.MEMBERSHIP_TYPE_INVITATION) {
      collaborationModule.member.cancelMembershipInvitation(req.params.objectType, req.collaboration, membership, req.user, onResponse);
    } else {
      collaborationModule.member.refuseMembershipRequest(req.params.objectType, req.collaboration, membership, req.user, onResponse);
    }
  } else if (membership.workflow === collaborationModule.member.MEMBERSHIP_TYPE_INVITATION) {
    collaborationModule.member.declineMembershipInvitation(req.params.objectType, req.collaboration, membership, req.user, onResponse);
  } else {
    collaborationModule.member.cancelMembershipRequest(req.params.objectType, req.collaboration, membership, req.user, onResponse);
  }
}

function getMember(req, res) {
  const collaboration = req.collaboration;

  collaborationModule.member.isMember(collaboration, {objectType: 'user', id: req.params.user_id}, (err, result) => {
    if (err) {
      return res.status(500).json({error: {code: 500, message: 'Server Error', details: err.message}});
    }

    if (result) {
      return res.status(200).end();
    }

    return res.status(404).end();
  });
}

function getAvatar(req, res) {
  if (!req.collaboration) {
    return res.status(404).json({error: 404, message: 'Not found', details: 'Community not found'});
  }

  if (!req.collaboration.avatar) {
    return res.redirect('/images/collaboration.png');
  }

  imageModule.getAvatar(req.collaboration.avatar, req.query.format, (err, fileStoreMeta, readable) => {
    if (err) {
      logger.warn('Can not get collaboration avatar : %s', err.message);

      return res.redirect('/images/collaboration.png');
    }

    if (!readable) {
      logger.warn('Can not retrieve avatar stream for collaboration %s', req.collaboration._id);

      return res.redirect('/images/collaboration.png');
    }

    if (req.headers['if-modified-since'] && Number(new Date(req.headers['if-modified-since']).setMilliseconds(0)) === Number(fileStoreMeta.uploadDate.setMilliseconds(0))) {
      return res.status(304).end();
    }

    res.header('Last-Modified', fileStoreMeta.uploadDate);
    res.status(200);

    return readable.pipe(res);
  });
}