mirror of
https://github.com/vale981/Vulcan
synced 2025-03-05 09:31:43 -05:00
Big voting refactor
This commit is contained in:
parent
7bfa4afba3
commit
071a0fd720
14 changed files with 320 additions and 206 deletions
|
@ -1,6 +1,6 @@
|
|||
accounts-base@1.3.1
|
||||
accounts-password@1.4.0
|
||||
allow-deny@1.0.6
|
||||
allow-deny@1.0.9
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.19.4
|
||||
babel-runtime@1.0.1
|
||||
|
|
|
@ -47,8 +47,8 @@ class Vote extends PureComponent {
|
|||
this.props.flash(this.context.intl.formatMessage({id: 'users.please_log_in'}));
|
||||
// this.stopLoading();
|
||||
} else {
|
||||
const voteType = hasUpvoted(user, document) ? 'cancelUpvote' : 'upvote';
|
||||
this.props.vote({document, voteType, collection, currentUser: this.props.currentUser}).then(result => {
|
||||
const operationType = this.props.document.currentUserVotes.length ? 'cancelVote' : 'upvote';
|
||||
this.props.vote({document, operationType, collection, currentUser: this.props.currentUser}).then(result => {
|
||||
// this.stopLoading();
|
||||
});
|
||||
}
|
||||
|
@ -77,6 +77,9 @@ class Vote extends PureComponent {
|
|||
{this.state.loading ? <Components.Icon name="spinner" /> : <Components.Icon name="upvote" /> }
|
||||
<div className="sr-only">Upvote</div>
|
||||
<div className="vote-count">{this.props.document.baseScore || 0}</div>
|
||||
<div>{this.props.document.currentUserVotes ? this.props.document.currentUserVotes.map(vote =>
|
||||
<p key={vote._id}>{vote.votedAt.toString()}, {vote.power}</p>
|
||||
) : null}</div>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -6,19 +6,20 @@ Categories parameter
|
|||
|
||||
import { addCallback, getSetting, registerSetting, getFragment, runQuery } from 'meteor/vulcan:core';
|
||||
import gql from 'graphql-tag';
|
||||
import Categories from './collection.js';
|
||||
|
||||
registerSetting('forum.categoriesFilter', 'union', 'Display posts belonging to all (“intersection”) or at least one of (“union”) the selected categories');
|
||||
|
||||
// Category Posts Parameters
|
||||
// Add a 'categories' property to terms which can be used to filter *all* existing Posts views.
|
||||
async function PostsCategoryParameter(parameters, terms, apolloClient) {
|
||||
function PostsCategoryParameter(parameters, terms, apolloClient) {
|
||||
|
||||
// get category slugs
|
||||
const cat = terms.cat || terms['cat[]'];
|
||||
const categoriesSlugs = Array.isArray(cat) ? cat : [cat];
|
||||
let allCategories = [];
|
||||
|
||||
if (cat.length) {
|
||||
if (cat && cat.length) {
|
||||
|
||||
// get all categories
|
||||
// note: specify all arguments, see https://github.com/apollographql/apollo-client/issues/2051
|
||||
|
@ -38,9 +39,11 @@ async function PostsCategoryParameter(parameters, terms, apolloClient) {
|
|||
variables: {terms: {limit: 0, itemsPerPage: 0}}
|
||||
}).CategoriesList;
|
||||
} else {
|
||||
// TODO: figure out how to make this async without messing up withList on the client
|
||||
// get categories through GraphQL API using runQuery
|
||||
const results = await runQuery(query);
|
||||
allCategories = results.data.CategoriesList;
|
||||
// const results = await runQuery(query);
|
||||
// allCategories = results.data.CategoriesList;
|
||||
allCategories = Categories.find().fetch();
|
||||
}
|
||||
|
||||
// get corresponding category ids
|
||||
|
|
|
@ -26,15 +26,5 @@ registerFragment(`
|
|||
}
|
||||
}
|
||||
# vulcan:voting
|
||||
upvoters {
|
||||
_id
|
||||
}
|
||||
downvoters {
|
||||
_id
|
||||
}
|
||||
#upvotes
|
||||
#downvotes
|
||||
#baseScore
|
||||
#score
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -31,14 +31,9 @@ registerFragment(`
|
|||
...UsersMinimumInfo
|
||||
}
|
||||
# voting
|
||||
upvoters {
|
||||
_id
|
||||
currentUserVotes{
|
||||
...VoteFragment
|
||||
}
|
||||
downvoters {
|
||||
_id
|
||||
}
|
||||
upvotes
|
||||
downvotes
|
||||
baseScore
|
||||
score
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ Package.onUse(function (api) {
|
|||
'check',
|
||||
'http',
|
||||
'email',
|
||||
'random',
|
||||
'ecmascript@0.8.2',
|
||||
'service-configuration',
|
||||
'shell-server@0.2.4',
|
||||
|
|
|
@ -1,25 +1,22 @@
|
|||
import React, { PropTypes, Component } from 'react';
|
||||
import { graphql } from 'react-apollo';
|
||||
import gql from 'graphql-tag';
|
||||
import { operateOnItem } from '../modules/vote.js';
|
||||
import { voteOnItem } from '../modules/vote.js';
|
||||
import { VoteableCollections } from '../modules/make_voteable.js';
|
||||
|
||||
const withVote = component => {
|
||||
|
||||
return graphql(gql`
|
||||
mutation vote($documentId: String, $voteType: String, $collectionName: String) {
|
||||
vote(documentId: $documentId, voteType: $voteType, collectionName: $collectionName) {
|
||||
mutation vote($documentId: String, $operationType: String, $collectionName: String) {
|
||||
vote(documentId: $documentId, operationType: $operationType, collectionName: $collectionName) {
|
||||
${VoteableCollections.map(collection => `
|
||||
... on ${collection.typeName} {
|
||||
__typename
|
||||
_id
|
||||
upvotes
|
||||
upvoters {
|
||||
_id
|
||||
}
|
||||
downvotes
|
||||
downvoters {
|
||||
currentUserVotes{
|
||||
_id
|
||||
voteType
|
||||
power
|
||||
}
|
||||
baseScore
|
||||
}
|
||||
|
@ -28,19 +25,19 @@ const withVote = component => {
|
|||
}
|
||||
`, {
|
||||
props: ({ownProps, mutate}) => ({
|
||||
vote: ({document, voteType, collection, currentUser}) => {
|
||||
const voteResult = operateOnItem(collection, document, currentUser, voteType, true);
|
||||
vote: ({document, operationType, collection, currentUser}) => {
|
||||
|
||||
const voteResult = voteOnItem(collection, document, currentUser, operationType, true);
|
||||
|
||||
return mutate({
|
||||
variables: {
|
||||
documentId: document._id,
|
||||
voteType,
|
||||
operationType,
|
||||
collectionName: collection._name,
|
||||
},
|
||||
optimisticResponse: {
|
||||
__typename: 'Mutation',
|
||||
vote: {
|
||||
...voteResult,
|
||||
},
|
||||
vote: voteResult.document,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
9
packages/vulcan-voting/lib/modules/fragments.js
Normal file
9
packages/vulcan-voting/lib/modules/fragments.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { registerFragment } from 'meteor/vulcan:core';
|
||||
|
||||
registerFragment(`
|
||||
fragment VoteFragment on Vote {
|
||||
_id
|
||||
voteType
|
||||
power
|
||||
}
|
||||
`);
|
|
@ -1,7 +1,9 @@
|
|||
import './custom_fields.js';
|
||||
import './permissions.js';
|
||||
import './fragments.js';
|
||||
|
||||
export { default as Votes } from './votes/collection.js';
|
||||
export * from './make_voteable.js';
|
||||
export {default as withVote} from '../containers/withVote.js';
|
||||
export { default as withVote } from '../containers/withVote.js';
|
||||
export * from './helpers.js';
|
||||
export * from './vote.js';
|
||||
|
|
|
@ -6,83 +6,95 @@ export const makeVoteable = collection => {
|
|||
|
||||
collection.addField([
|
||||
/**
|
||||
How many upvotes the document has received
|
||||
The current user's votes on the document, if they exists
|
||||
*/
|
||||
{
|
||||
fieldName: 'upvotes',
|
||||
fieldName: 'currentUserVotes',
|
||||
fieldSchema: {
|
||||
type: Number,
|
||||
type: Array,
|
||||
optional: true,
|
||||
defaultValue: 0,
|
||||
viewableBy: ['guests'],
|
||||
resolveAs: {
|
||||
type: '[Vote]',
|
||||
resolver: async (document, args, { Users, Votes, currentUser }) => {
|
||||
const votes = Votes.find({userId: currentUser._id, itemId: document._id}).fetch();
|
||||
console.log('// currentUserVotes')
|
||||
console.log('// currentUser._id', currentUser._id)
|
||||
console.log('// document._id', document._id)
|
||||
console.log('// votes', votes)
|
||||
if (!votes.length) return null;
|
||||
return votes;
|
||||
// return Users.restrictViewableFields(currentUser, Votes, votes);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'currentUserVotes.$',
|
||||
fieldSchema: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
}
|
||||
},
|
||||
/**
|
||||
All votes on the document
|
||||
*/
|
||||
{
|
||||
fieldName: 'allVotes',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
resolveAs: {
|
||||
type: 'Vote',
|
||||
resolver: async (document, args, { Users, Votes, currentUser }) => {
|
||||
const votes = Votes.find({itemId: document._id}).fetch();
|
||||
if (!votes.length) return null;
|
||||
return votes;
|
||||
// return Users.restrictViewableFields(currentUser, Votes, votes);
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'allVotes.$',
|
||||
fieldSchema: {
|
||||
type: Object,
|
||||
optional: true,
|
||||
}
|
||||
},
|
||||
/**
|
||||
An array containing the `_id`s of the document's upvoters
|
||||
*/
|
||||
{
|
||||
fieldName: 'upvoters',
|
||||
fieldName: 'voters',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
resolveAs: {
|
||||
fieldName: 'upvoters',
|
||||
type: '[User]',
|
||||
resolver: async (document, args, {currentUser, Users}) => {
|
||||
if (!document.upvoters) return [];
|
||||
const upvoters = await Users.loader.loadMany(document.upvoters);
|
||||
return Users.restrictViewableFields(currentUser, Users, upvoters);
|
||||
const votes = Votes.find({itemId: document._id}).fetch();
|
||||
const votersIds = _.pluck(votes, 'userId');
|
||||
const voters = Users.find({_id: {$in: votersIds}});
|
||||
return voters;
|
||||
// if (!document.upvoters) return [];
|
||||
// const upvoters = await Users.loader.loadMany(document.upvoters);
|
||||
// return Users.restrictViewableFields(currentUser, Users, upvoters);
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'upvoters.$',
|
||||
fieldName: 'voters.$',
|
||||
fieldSchema: {
|
||||
type: String,
|
||||
optional: true
|
||||
}
|
||||
},
|
||||
/**
|
||||
How many downvotes the document has received
|
||||
*/
|
||||
{
|
||||
fieldName: 'downvotes',
|
||||
fieldSchema: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
defaultValue: 0,
|
||||
viewableBy: ['guests'],
|
||||
}
|
||||
},
|
||||
/**
|
||||
An array containing the `_id`s of the document's downvoters
|
||||
*/
|
||||
{
|
||||
fieldName: 'downvoters',
|
||||
fieldSchema: {
|
||||
type: Array,
|
||||
optional: true,
|
||||
viewableBy: ['guests'],
|
||||
resolveAs: {
|
||||
fieldName: 'downvoters',
|
||||
type: '[User]',
|
||||
resolver: async (document, args, {currentUser, Users}) => {
|
||||
if (!document.downvoters) return [];
|
||||
const downvoters = await Users.loader.loadMany(document.downvoters);
|
||||
return Users.restrictViewableFields(currentUser, Users, downvoters);
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
fieldName: 'downvoters.$',
|
||||
fieldSchema: {
|
||||
type: String,
|
||||
optional: true,
|
||||
}
|
||||
},
|
||||
/**
|
||||
The document's base score (not factoring in the document's age)
|
||||
*/
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import Users from 'meteor/vulcan:users';
|
||||
import { hasUpvoted, hasDownvoted } from './helpers.js';
|
||||
import { runCallbacks, runCallbacksAsync } from 'meteor/vulcan:core';
|
||||
import { runCallbacks, runCallbacksAsync, registerSetting, getSetting } from 'meteor/vulcan:core';
|
||||
import update from 'immutability-helper';
|
||||
import Votes from './votes/collection.js';
|
||||
|
||||
registerSetting('voting.maxVotes', 1, 'How many times a user can vote on the same document');
|
||||
|
||||
// The equation to determine voting power. Defaults to returning 1 for everybody
|
||||
export const getVotePower = function (user) {
|
||||
return 1;
|
||||
export const getVotePower = (user, operationType) => {
|
||||
return operationType === 'upvote' ? 1 : -1;
|
||||
};
|
||||
|
||||
const keepVoteProperties = item => _.pick(item, '__typename', '_id', 'upvoters', 'downvoters', 'upvotes', 'downvotes', 'baseScore');
|
||||
|
@ -15,114 +18,111 @@ const keepVoteProperties = item => _.pick(item, '__typename', '_id', 'upvoters',
|
|||
Runs all the operation and returns an objects without affecting the db.
|
||||
|
||||
*/
|
||||
export const operateOnItem = function (collection, originalItem, user, operation, isClient = false) {
|
||||
export const voteOnItem = function (collection, document, user, operationType = 'upvote') {
|
||||
|
||||
user = typeof user === "undefined" ? Meteor.user() : user;
|
||||
const collectionName = collection.options.collectionName;
|
||||
let result = {};
|
||||
|
||||
let item = {
|
||||
upvotes: 0,
|
||||
downvotes: 0,
|
||||
upvoters: [],
|
||||
downvoters: [],
|
||||
baseScore: 0,
|
||||
...originalItem,
|
||||
}; // we do not want to affect the original item directly
|
||||
|
||||
const votePower = getVotePower(user);
|
||||
const hasUpvotedItem = hasUpvoted(user, item);
|
||||
const hasDownvotedItem = hasDownvoted(user, item);
|
||||
const collectionName = collection._name;
|
||||
const canDo = Users.canDo(user, `${collectionName}.${operation}`);
|
||||
|
||||
// console.log('// operateOnItem')
|
||||
// console.log('isClient: ', isClient)
|
||||
// console.log('collection: ', collectionName)
|
||||
// console.log('operation: ', operation)
|
||||
// console.log('item: ', item)
|
||||
// console.log('user: ', user)
|
||||
// console.log('hasUpvotedItem: ', hasUpvotedItem)
|
||||
// console.log('hasDownvotedItem: ', hasDownvotedItem)
|
||||
// console.log('canDo: ', canDo)
|
||||
|
||||
// make sure item and user are defined, and user can perform the operation
|
||||
if (
|
||||
!item ||
|
||||
!user ||
|
||||
!canDo ||
|
||||
operation === "upvote" && hasUpvotedItem ||
|
||||
operation === "downvote" && hasDownvotedItem ||
|
||||
operation === "cancelUpvote" && !hasUpvotedItem ||
|
||||
operation === "cancelDownvote" && !hasDownvotedItem
|
||||
) {
|
||||
throw new Error(`Cannot perform operation "${collectionName}.${operation}"`);
|
||||
// make sure item and user are defined
|
||||
if (!document || !user) {
|
||||
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
|
||||
}
|
||||
|
||||
// ------------------------------ Sync Callbacks ------------------------------ //
|
||||
|
||||
item = runCallbacks(operation, item, user, operation, isClient);
|
||||
|
||||
/*
|
||||
|
||||
voters arrays have different structures on client and server:
|
||||
|
||||
- client: [{__typename: "User", _id: 'foo123'}]
|
||||
- server: ['foo123']
|
||||
First, handle vote cancellation.
|
||||
Just remove last vote and subtract its power from the base score
|
||||
|
||||
*/
|
||||
if (operationType === 'cancelVote') {
|
||||
|
||||
const voter = isClient ? {__typename: "User", _id: user._id} : user._id;
|
||||
const filterFunction = isClient ? u => u._id !== user._id : u => u !== user._id;
|
||||
// create a "lite" version of the document that only contains relevant fields
|
||||
const newDocument = {
|
||||
_id: document._id,
|
||||
currentUserVotes: document.currentUserVotes || [],
|
||||
// voters: document.voters || [],
|
||||
baseScore: document.baseScore || 0,
|
||||
__typename: collection.options.typeName,
|
||||
}; // we do not want to affect the original item directly
|
||||
|
||||
switch (operation) {
|
||||
// if document has votes
|
||||
if (newDocument.currentUserVotes.length) {
|
||||
// remove one vote
|
||||
const cancelledVote = _.last(newDocument.currentUserVotes);
|
||||
newDocument.currentUserVotes = _.initial(newDocument.currentUserVotes);
|
||||
result.vote = cancelledVote;
|
||||
|
||||
case "upvote":
|
||||
if (hasDownvotedItem) {
|
||||
item = operateOnItem(collection, item, user, "cancelDownvote", isClient);
|
||||
}
|
||||
// update base score
|
||||
newDocument.baseScore -= cancelledVote.power;
|
||||
}
|
||||
|
||||
item = update(item, {
|
||||
upvoters: {$push: [voter]},
|
||||
upvotes: {$set: item.upvotes + 1},
|
||||
baseScore: {$set: item.baseScore + votePower},
|
||||
});
|
||||
// console.log('// voteOnItem')
|
||||
// console.log('collection: ', collectionName)
|
||||
// console.log('document:', document)
|
||||
// console.log('newDocument:', newDocument)
|
||||
|
||||
break;
|
||||
result.document = newDocument;
|
||||
|
||||
case "downvote":
|
||||
if (hasUpvotedItem) {
|
||||
item = operateOnItem(collection, item, user, "cancelUpvote", isClient);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
|
||||
item = update(item, {
|
||||
downvoters: {$push: [voter]},
|
||||
downvotes: {$set: item.downvotes + 1},
|
||||
baseScore: {$set: item.baseScore - votePower},
|
||||
});
|
||||
Next, handle all other vote types (upvote, downvote, etc.)
|
||||
|
||||
*/
|
||||
|
||||
break;
|
||||
const power = getVotePower(user, operationType);
|
||||
|
||||
case "cancelUpvote":
|
||||
item = update(item, {
|
||||
upvoters: {$set: item.upvoters.filter(filterFunction)},
|
||||
upvotes: {$set: item.upvotes - 1},
|
||||
baseScore: {$set: item.baseScore - votePower},
|
||||
});
|
||||
break;
|
||||
// create vote object
|
||||
const vote = {
|
||||
_id: Random.id(),
|
||||
itemId: document._id,
|
||||
collectionName,
|
||||
userId: user._id,
|
||||
voteType: operationType,
|
||||
power,
|
||||
votedAt: new Date(),
|
||||
__typename: 'Vote'
|
||||
};
|
||||
|
||||
case "cancelDownvote":
|
||||
// create a "lite" version of the document that only contains relevant fields
|
||||
const currentUserVotes = document.currentUserVotes || [];
|
||||
const newDocument = {
|
||||
_id: document._id,
|
||||
currentUserVotes: [...currentUserVotes, vote],
|
||||
// voters: document.voters || [],
|
||||
baseScore: document.baseScore || 0,
|
||||
__typename: collection.options.typeName,
|
||||
}; // we do not want to affect the original item directly
|
||||
|
||||
item = update(item, {
|
||||
downvoters: {$set: item.downvoters.filter(filterFunction)},
|
||||
downvotes: {$set: item.downvotes - 1},
|
||||
baseScore: {$set: item.baseScore + votePower},
|
||||
});
|
||||
// update score
|
||||
newDocument.baseScore += power;
|
||||
|
||||
break;
|
||||
// console.log('// voteOnItem')
|
||||
// console.log('collection: ', collectionName)
|
||||
// console.log('document:', document)
|
||||
// console.log('newDocument:', newDocument)
|
||||
|
||||
// make sure item and user are defined, and user can perform the operation
|
||||
if (newDocument.currentUserVotes.length > getSetting('voting.maxVotes')) {
|
||||
throw new Error(`Cannot perform operation '${collectionName}.${operationType}'`);
|
||||
}
|
||||
|
||||
// ------------------------------ Sync Callbacks ------------------------------ //
|
||||
|
||||
// item = runCallbacks(operation, item, user, operation, isClient);
|
||||
|
||||
result = {
|
||||
document: newDocument,
|
||||
vote
|
||||
};
|
||||
}
|
||||
|
||||
// console.log('new item', item);
|
||||
return result;
|
||||
|
||||
};
|
||||
|
||||
export const cancelVote = function (collection, document, user, voteType = 'vote') {
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -130,14 +130,14 @@ export const operateOnItem = function (collection, originalItem, user, operation
|
|||
Call operateOnItem, update the db with the result, run callbacks.
|
||||
|
||||
*/
|
||||
export const mutateItem = function (collection, originalItem, user, operation) {
|
||||
const newItem = operateOnItem(collection, originalItem, user, operation, false);
|
||||
newItem.inactive = false;
|
||||
// export const mutateItem = function (collection, originalItem, user, operation) {
|
||||
// const newItem = operateOnItem(collection, originalItem, user, operation, false);
|
||||
// newItem.inactive = false;
|
||||
|
||||
collection.update({_id: newItem._id}, newItem, {bypassCollection2:true});
|
||||
// collection.update({_id: newItem._id}, newItem, {bypassCollection2:true});
|
||||
|
||||
// --------------------- Server-Side Async Callbacks --------------------- //
|
||||
runCallbacksAsync(operation+".async", newItem, user, collection, operation);
|
||||
// // --------------------- Server-Side Async Callbacks --------------------- //
|
||||
// runCallbacksAsync(operation+'.async', newItem, user, collection, operation);
|
||||
|
||||
return newItem;
|
||||
}
|
||||
// return newItem;
|
||||
// }
|
||||
|
|
19
packages/vulcan-voting/lib/modules/votes/collection.js
Normal file
19
packages/vulcan-voting/lib/modules/votes/collection.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { createCollection, getDefaultResolvers, getDefaultMutations } from 'meteor/vulcan:core';
|
||||
import schema from './schema.js';
|
||||
|
||||
const Votes = createCollection({
|
||||
|
||||
collectionName: 'Votes',
|
||||
|
||||
typeName: 'Vote',
|
||||
|
||||
schema,
|
||||
|
||||
// resolvers: getDefaultResolvers('Votes'),
|
||||
|
||||
// mutations: getDefaultMutations('Votes'),
|
||||
|
||||
});
|
||||
|
||||
|
||||
export default Votes;
|
55
packages/vulcan-voting/lib/modules/votes/schema.js
Normal file
55
packages/vulcan-voting/lib/modules/votes/schema.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
const schema = {
|
||||
|
||||
_id: {
|
||||
type: String,
|
||||
viewableBy: ['guests'],
|
||||
},
|
||||
|
||||
/**
|
||||
The id of the document that was voted on
|
||||
*/
|
||||
itemId: {
|
||||
type: String
|
||||
},
|
||||
|
||||
/**
|
||||
The id of the document that was voted on
|
||||
*/
|
||||
collectionName: {
|
||||
type: String
|
||||
},
|
||||
|
||||
/**
|
||||
The id of the user that voted
|
||||
*/
|
||||
userId: {
|
||||
type: String
|
||||
},
|
||||
|
||||
/**
|
||||
An optional vote type (for Facebook-style reactions)
|
||||
*/
|
||||
voteType: {
|
||||
type: String,
|
||||
optional: true
|
||||
},
|
||||
|
||||
/**
|
||||
The vote power (e.g. 1 = upvote, -1 = downvote, or any other value)
|
||||
*/
|
||||
power: {
|
||||
type: Number,
|
||||
optional: true
|
||||
},
|
||||
|
||||
/**
|
||||
The vote timestamp
|
||||
*/
|
||||
votedAt: {
|
||||
type: Date,
|
||||
optional: true
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default schema;
|
|
@ -1,20 +1,13 @@
|
|||
import { addCallback, addGraphQLSchema, addGraphQLResolvers, addGraphQLMutation, Utils } from 'meteor/vulcan:core';
|
||||
import { mutateItem } from '../modules/vote.js';
|
||||
import { addCallback, addGraphQLSchema, addGraphQLResolvers, addGraphQLMutation, Utils, registerSetting, getSetting } from 'meteor/vulcan:core';
|
||||
import { voteOnItem } from '../modules/vote.js';
|
||||
import { VoteableCollections } from '../modules/make_voteable.js';
|
||||
import { createError } from 'apollo-errors';
|
||||
import Votes from '../modules/votes/collection.js';
|
||||
|
||||
|
||||
function CreateVoteableUnionType() {
|
||||
const voteSchema = `
|
||||
type Vote {
|
||||
itemId: String
|
||||
power: Float
|
||||
votedAt: String
|
||||
}
|
||||
|
||||
${VoteableCollections.length ? `union Voteable = ${VoteableCollections.map(collection => collection.typeName).join(' | ')}` : ''}
|
||||
`;
|
||||
|
||||
addGraphQLSchema(voteSchema);
|
||||
const voteableSchema = VoteableCollections.length ? `union Voteable = ${VoteableCollections.map(collection => collection.typeName).join(' | ')}` : '';
|
||||
addGraphQLSchema(voteableSchema);
|
||||
return {}
|
||||
}
|
||||
addCallback('graphql.init.before', CreateVoteableUnionType);
|
||||
|
@ -30,20 +23,55 @@ const resolverMap = {
|
|||
|
||||
addGraphQLResolvers(resolverMap);
|
||||
|
||||
addGraphQLMutation('vote(documentId: String, voteType: String, collectionName: String) : Voteable');
|
||||
addGraphQLMutation('vote(documentId: String, operationType: String, collectionName: String) : Voteable');
|
||||
|
||||
const voteResolver = {
|
||||
Mutation: {
|
||||
vote(root, {documentId, voteType, collectionName}, context) {
|
||||
async vote(root, {documentId, operationType, collectionName}, context) {
|
||||
|
||||
const { currentUser } = context;
|
||||
const collection = context[Utils.capitalize(collectionName)];
|
||||
const document = collection.findOne(documentId);
|
||||
|
||||
if (context.Users.canDo(context.currentUser, `${collectionName.toLowerCase()}.${voteType}`)) {
|
||||
|
||||
// query for document being voted on
|
||||
const document = await collection.queryOne(documentId, {
|
||||
fragmentText: `
|
||||
fragment DocumentVoteFragment on ${collection.typeName} {
|
||||
__typename
|
||||
_id
|
||||
currentUserVotes{
|
||||
_id
|
||||
voteType
|
||||
power
|
||||
}
|
||||
baseScore
|
||||
}
|
||||
`,
|
||||
context
|
||||
});
|
||||
|
||||
if (context.Users.canDo(currentUser, `${collectionName.toLowerCase()}.${operationType}`)) {
|
||||
|
||||
// put document through voteOnItem and get result
|
||||
const voteResult = voteOnItem(collection, document, currentUser, operationType);
|
||||
|
||||
const mutatedDocument = mutateItem(collection, document, context.currentUser, voteType, false);
|
||||
mutatedDocument.__typename = collection.typeName;
|
||||
return mutatedDocument;
|
||||
// get new version of document
|
||||
const newDocument = voteResult.document;
|
||||
newDocument.__typename = collection.typeName;
|
||||
|
||||
// get created or cancelled vote
|
||||
const vote = voteResult.vote;
|
||||
|
||||
if (operationType === 'cancelVote' && vote) {
|
||||
// if a vote has been cancelled, delete it
|
||||
Votes.remove(vote._id);
|
||||
} else {
|
||||
// if a vote has been created, insert it
|
||||
delete vote.__typename;
|
||||
Votes.insert(vote);
|
||||
}
|
||||
|
||||
// in any case, return the document that was voted on
|
||||
return newDocument;
|
||||
|
||||
} else {
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue