Vulcan/packages/vulcan-voting/lib/modules/vote.js

163 lines
4.3 KiB
JavaScript
Raw Normal View History

2017-09-27 17:15:49 +02:00
import { registerSetting, getSetting } from 'meteor/vulcan:core';
import { createError } from 'apollo-errors';
2017-09-25 22:09:09 +02:00
import Votes from './votes/collection.js';
2017-09-27 17:15:49 +02:00
import Users from 'meteor/vulcan:users';
2017-09-25 22:09:09 +02:00
registerSetting('voting.maxVotes', 1, 'How many times a user can vote on the same document');
2016-07-19 18:08:53 +09:00
2017-09-27 17:15:49 +02:00
/*
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
*/
2017-09-25 22:09:09 +02:00
export const getVotePower = (user, operationType) => {
2017-09-27 17:15:49 +02:00
const power = voteOperations[operationType].power;
return typeof power === 'function' ? power(user) : power;
};
2017-09-27 17:15:49 +02:00
/*
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);
/*
2017-09-27 17:15:49 +02:00
Create new vote object
*/
2017-09-27 17:15:49 +02:00
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}) => {
2017-09-25 22:09:09 +02:00
const collectionName = collection.options.collectionName;
2017-09-25 22:09:09 +02:00
// make sure item and user are defined
if (!document || !user) {
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
}
2017-09-27 17:15:49 +02:00
// console.log('// voteOptimisticResponse')
// console.log('collectionName: ', collectionName)
// console.log('document:', document)
// console.log('operationType:', operationType)
2017-01-26 13:09:27 +09:00
2017-09-27 17:15:49 +02:00
// 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,
};
2017-09-25 22:09:09 +02:00
if (operationType === 'cancelVote') {
2017-09-27 17:15:49 +02:00
// subtract vote scores
newDocument.baseScore -= calculateTotalPower(document.currentUserVotes);
2017-09-25 22:09:09 +02:00
2017-09-27 17:15:49 +02:00
// clear out all votes
newDocument.currentUserVotes = [];
2017-09-25 22:09:09 +02:00
} else {
2017-01-26 13:09:27 +09:00
2017-09-27 17:15:49 +02:00
// create new vote and add it to currentUserVotes array
const vote = createVote({ documentId: document._id, collectionName, operationType, user, voteId });
newDocument.currentUserVotes = [...document.currentUserVotes, vote];
2017-09-27 17:15:49 +02:00
// increment baseScore
2017-09-25 22:09:09 +02:00
const power = getVotePower(user, operationType);
newDocument.baseScore += power;
2017-09-27 17:15:49 +02:00
}
2017-09-25 22:09:09 +02:00
2017-09-27 17:15:49 +02:00
return newDocument;
}
2017-09-25 22:09:09 +02:00
2017-09-27 17:15:49 +02:00
/*
2017-09-25 22:09:09 +02:00
2017-09-27 17:15:49 +02:00
Server-side database operation
2017-09-27 17:15:49 +02:00
*/
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();
2017-09-27 17:15:49 +02:00
if (operationType === 'cancelVote') {
// if a vote has been cancelled, delete all votes and subtract their power from base score
const scoreTotal = calculateTotalPower(userVotes);
2017-09-27 17:15:49 +02:00
// remove vote object
Votes.remove({itemId: documentId, userId: currentUser._id});
2017-09-27 17:15:49 +02:00
// update document score
collection.update({_id: documentId}, {$inc: {baseScore: -scoreTotal }});
2017-09-27 17:15:49 +02:00
} else {
2017-09-27 17:15:49 +02:00
if (userVotes.length < getSetting('voting.maxVotes')) {
2017-09-27 17:15:49 +02:00
// create vote and insert it
const vote = createVote({ documentId, collectionName: collection.options.collectionName, operationType, user: currentUser, voteId });
delete vote.__typename;
Votes.insert(vote);
2017-09-27 17:15:49 +02:00
// update document score
collection.update({_id: documentId}, {$inc: {baseScore: power }});
2017-09-27 17:15:49 +02:00
} else {
const VoteError = createError('voting.maximum_votes_reached', {message: 'voting.maximum_votes_reached'});
throw new VoteError();
}
2017-09-27 17:15:49 +02:00
}
}