I am trying to add a userID to a likes field in MongoDB. This field will be an array of strings []string
. It is currently null, but that should not be an issue since was using "$set"
. I also tried manually making the fields an array and using $setOnInsert
and $addToSet
instead of $set
with no avail.
I am creating the document using this struct:
comment := entity.Comment{
PostID: postID,
Content: content,
AuthorID: authorID,
Likes: []string{},
Dislikes: []string{},
Votes: []string{},
CreatedAt: time.Now(),
}
The document in question looks like this in the database:
{"_id":{"$oid":"67de0487653132119ba14548"},"postid":"67de00a0eea2fe8f5a55e1da","content":"awesome Pizza day","authorid":"3204aa18-2006-4aa1-ad88-1b71256d8a36","createdat":{"$date":{"$numberLong":"1742603399198"}},"updatedat":{"$date":{"$numberLong":"1742603992797"}},"likes":null,"dislikes":null,"votes":null}
and right now I am trying to add a userID string to "likes":null,"dislikes":null
.
It fails with a write exception at UpdateOne. The userID
and comment.ObjectId
are not null and the comment.ObjectId
does exist in the database. There is only one comment at the moment.
func (r *CommentsRepository) ToggleLikeComment(ctx context.Context, comment *entity.Comment, userID string) error {
filter := bson.D{{Key: "_id", Value: comment.ObjectId}}
update := bson.D{}
// Check if user already disliked
dislikeFilter := bson.D{{Key: "_id", Value: comment.ObjectId}, {Key: "dislikes", Value: userID}}
dislikeCount, err := r.collection.CountDocuments(ctx, dislikeFilter)
if err != nil {
return err
}
if dislikeCount > 0 {
// Remove dislike
update = append(update, bson.E{Key: "$pull", Value: bson.D{{Key: "dislikes", Value: userID}}})
}
// Check if user already liked
likeFilter := bson.D{{Key: "_id", Value: comment.ObjectId}, {Key: "likes", Value: userID}}
likeCount, err := r.collection.CountDocuments(ctx, likeFilter)
if err != nil {
return err
}
if likeCount > 0 {
// Remove like
update = append(update, bson.E{Key: "$pull", Value: bson.D{{Key: "likes", Value: userID}}})
} else {
// Add like
update = append(update, bson.E{Key: "$set", Value: bson.D{{Key: "likes", Value: userID}}})
}
update = append(update, bson.E{Key: "$set", Value: bson.D{{Key: "updatedat", Value: time.Now()}}})
// fails to write here...
_, err = r.collection.UpdateOne(ctx, filter, bson.D{{Key: "$set", Value: update}})
return err
}
I was able to adapt the code with the help @aneroid. Here is the query that ended up working
likeFilter := bson.D{
{Key: "_id", Value: objectID},
{Key: "likes", Value: bson.D{{Key: "$ne", Value: userID}}},
}
likeUpdate := bson.A{
bson.D{{Key: "$set", Value: bson.D{
{Key: "likes", Value: bson.D{
{Key: "$cond", Value: bson.D{
{Key: "if", Value: bson.D{{Key: "$eq", Value: bson.A{"$likes", nil}}}},
{Key: "then", Value: bson.A{userID}},
{Key: "else", Value: bson.D{{Key: "$concatArrays", Value: bson.A{"$likes", bson.A{userID}}}}},
}},
}},
{Key: "updatedat", Value: time.Now()},
}}},
}
result, err := r.collection.UpdateOne(ctx, likeFilter, likeUpdate)
Just to cover it off, if likes/dislikes is null
, then you can't do a $push
, as you've noted in your question.
So in this case, you can use an update aggregation which checks for likes being null using $cond
. Additionally, since you probably don't want repeat likes by the same user, add that to the update filter.
(You can use MongoDB Compass to convert these to Golang.)
db.collection.update({
_id: ObjectId("67de0487653132119ba14548"),
likes: { $ne: "theNewUser" } // prevent repeat likes
},
[
{
$set: {
likes: {
$cond: {
if: { $eq: ["$likes", null] },
then: ["theNewUser"], // array of single user
else: { $concatArrays: ["$likes", ["theNewUser"]] } // add the new user
}
}
}
}
])
Mongo Playground to add a like
. It's got examples of docs which have likes
as: (a) null, (b) empty array, (c) array with other userIDs, (d) array with the new UserID already in it, which won't add it to the array again.
Adding a dislike
would be similar to that.
For removing a like
(or dislike), use $filter
to keep only the values which are not equal to the new user ID:
db.collection.update({
_id: ObjectId("67de0487653132119ba14548"),
likes: { $eq: "theNewUser" } // remove only if user in likes
},
[
{
$set: {
likes: {
$filter: {
input: "$likes",
cond: { $ne: ["$$this", "theNewUser"] }
}
}
}
}
])
Mongo Playground to remove a like
. This one also has examples of docs which have likes
as: (a) null, (b) empty array, (c) array with other userIDs, (d) array with the new User ID already in it and other users, (e) array with only the new User ID.
Btw, since the filters already check for the new User already in or not in the likes
array, you don't need to do the pre-like or pre-dislike check.
Obviously, it would be easier if you bulk updated all the null
likes and dislikes to be an empty array - so that $push
and $pull
would work as expected in a regular update query.