Vulcan/packages/vulcan-voting/lib/modules/vote.js
2017-09-27 17:15:49 +02:00

163 lines
No EOL
4.3 KiB
JavaScript

import { registerSetting, getSetting } from 'meteor/vulcan:core';
import { createError } from 'apollo-errors';
import Votes from './votes/collection.js';
import Users from 'meteor/vulcan:users';
registerSetting('voting.maxVotes', 1, 'How many times a user can vote on the same document');
/*
Define voting operations
*/
export const voteOperations = {
'upvote': {
power: 1,
// TODO: refactor voteOptimisticResponse and performVoteOperation code
// into extensible, action-specific objects
// clientOperation: () => {
// },
// serverOperation: () => {
// }
},
'downvote': {
power: -1
},
'adminUpvote': {
power: user => Users.isAdmin(user) ? 5 : 1
},
'cancelVote': {
}
}
/*
Determine a user's voting power for a given operation.
If power is a function, call it on user
*/
export const getVotePower = (user, operationType) => {
const power = voteOperations[operationType].power;
return typeof power === 'function' ? power(user) : power;
};
/*
Calculate total power of all a user's votes on a document
*/
export const calculateTotalPower = userVotes => _.pluck(userVotes, 'power').reduce((a, b) => a + b, 0);
/*
Create new vote object
*/
export const createVote = ({ documentId, collectionName, operationType, user, voteId }) => ({
_id: voteId,
itemId: documentId,
collectionName,
userId: user._id,
voteType: operationType,
power: getVotePower(user, operationType),
votedAt: new Date(),
__typename: 'Vote'
});
/*
Optimistic response for votes
*/
export const voteOptimisticResponse = ({collection, document, user, operationType = 'upvote', voteId}) => {
const collectionName = collection.options.collectionName;
// make sure item and user are defined
if (!document || !user) {
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
}
// console.log('// voteOptimisticResponse')
// console.log('collectionName: ', collectionName)
// console.log('document:', document)
// console.log('operationType:', operationType)
// create a "lite" version of the document that only contains relevant fields
// we do not want to affect the original item directly
const newDocument = {
_id: document._id,
baseScore: document.baseScore || 0,
__typename: collection.options.typeName,
};
if (operationType === 'cancelVote') {
// subtract vote scores
newDocument.baseScore -= calculateTotalPower(document.currentUserVotes);
// clear out all votes
newDocument.currentUserVotes = [];
} else {
// create new vote and add it to currentUserVotes array
const vote = createVote({ documentId: document._id, collectionName, operationType, user, voteId });
newDocument.currentUserVotes = [...document.currentUserVotes, vote];
// increment baseScore
const power = getVotePower(user, operationType);
newDocument.baseScore += power;
}
return newDocument;
}
/*
Server-side database operation
*/
export const performVoteOperation = ({documentId, operationType, collection, voteId, currentUser}) => {
// console.log('// performVoteMutation')
// console.log('operationType: ', operationType)
// console.log('collectionName: ', collectionName)
// console.log('// document: ', collection.findOne(documentId))
const power = getVotePower(currentUser, operationType);
const userVotes = Votes.find({itemId: documentId, userId: currentUser._id}).fetch();
if (operationType === 'cancelVote') {
// if a vote has been cancelled, delete all votes and subtract their power from base score
const scoreTotal = calculateTotalPower(userVotes);
// remove vote object
Votes.remove({itemId: documentId, userId: currentUser._id});
// update document score
collection.update({_id: documentId}, {$inc: {baseScore: -scoreTotal }});
} else {
if (userVotes.length < getSetting('voting.maxVotes')) {
// create vote and insert it
const vote = createVote({ documentId, collectionName: collection.options.collectionName, operationType, user: currentUser, voteId });
delete vote.__typename;
Votes.insert(vote);
// update document score
collection.update({_id: documentId}, {$inc: {baseScore: power }});
} else {
const VoteError = createError('voting.maximum_votes_reached', {message: 'voting.maximum_votes_reached'});
throw new VoteError();
}
}
}