问题
I'm trying to implement an upvote/downvote mechanism for comments (similar to the upvoting/downvoting mechanism found on reddit). I have a separate collection called commentReputation and the documents inside can look like this:
{
"_id" : ObjectId("5e5acb6d6034a879655c8819"),
"commentId" : ObjectId("5e5983102328a83d1a4b541f"),
"creationDate" : ISODate("2020-02-29T20:37:01.509Z"),
"upvotes" : [
ObjectId("5e5983102328a83d1a4b53e7"),
ObjectId("5e5983102328a83d1a4b53e4")
],
"downvotes" : [
ObjectId("5e5983102328a83d1a4b53e5")
]
}
In short: every comment will eventually have it's own CommentReputation document (the CommentReputation document should be created as soon as someone upvotes/downvotes a comment)
There are 2 case scenarios:
The collection is empty meaning that I need to create my very first
CommentReputationdocument with a given commentIdx. In some other part of the project I was using$setOnInsertwith{ upsert: true }but it seems (looking at the documentation) that the aggregation pipeline does not support$setOnInsertas for now. Is there another way to deal with this problem?The document is there and the actuall upvoting should occur.
a) Both
upvotesanddownvotesarrays do not contain theuserIdthat is trying to upvote thus it gets added to theupvotesarray without any further actionsb) The
upvotesarray contains theuserIdthat is trying to upvote the comment as a result theuserIdshould be REMOVED from theupvotesarray. (the user already had this comment upvoted and clicked a second time the upvote button which cancels out the upvote)c) The
downvotesarray contains theuserId. In this case theuserIdshould be removed fromdownvotesand added toupvotes
I'm trying to accomplish the above logic with the updateOne method and a aggreagtion pipeline however I'm not sure if this is even possible.
What I currently have is returning a "Unrecognized pipeline stage name: '$cond'"
const updateUpvotes = {
$cond: {
if: { $elemMatch: { upvotes: ObjectID(userId) } },
then: { $pull: { upvotes: ObjectID(userId) } },
else: { $addToSet: { upvotes: ObjectID(userId) } }
}
};
db.collection(collectionName).updateOne({
commentId: ObjectID('5e5983102328a83d1a4b541f')
}, [updateUpvotes])
Am I overthinking the whole feature? I guess the 1. problem can be solved by simply creating a CommentReputation document (with empty upvotes and downvotes at the same time the Comment document is being created.
Is there a better way of doing this? I would love to have it working inside a single query request. Maybe someone of You guys implemented a similar feature and can give me some hints on this one.
回答1:
you can do it with the following pipeline update but it requires that the upvotes and downvotes arrays exist. even if it's just empty.
var comment_id = ObjectId("5e5983102328a83d1a4b541f");
var user_id = ObjectId("5e5983102328a83d1a4b53e5");
db.commentReputation.update(
{
commentId: comment_id
},
[
{
$set: {
upvotes: {
$cond: [
{ $in: [user_id, '$upvotes'] },
{ $setDifference: ['$upvotes', [user_id]] },
{ $setUnion: ['$upvotes', [user_id]] }
]
}
}
},
{
$set: {
downvotes: {
$cond: [
{ $in: [user_id, '$downvotes'] },
{ $setDifference: ['$downvotes', [user_id]] },
'$downvotes'
]
}
}
}
]
);
来源:https://stackoverflow.com/questions/60469900/mongodb-comment-upvoting-downvoting-with-aggregation-pipeline