问题
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
CommentReputation
document with a given commentIdx
. In some other part of the project I was using$setOnInsert
with{ upsert: true }
but it seems (looking at the documentation) that the aggregation pipeline does not support$setOnInsert
as for now. Is there another way to deal with this problem?The document is there and the actuall upvoting should occur.
a) Both
upvotes
anddownvotes
arrays do not contain theuserId
that is trying to upvote thus it gets added to theupvotes
array without any further actionsb) The
upvotes
array contains theuserId
that is trying to upvote the comment as a result theuserId
should be REMOVED from theupvotes
array. (the user already had this comment upvoted and clicked a second time the upvote button which cancels out the upvote)c) The
downvotes
array contains theuserId
. In this case theuserId
should be removed fromdownvotes
and 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