mongodb/mongoose findOneandUpdate how to get index and delete object - javascript

So I am having event object, which have comments, and comments have likes array.
What I currently can do is to add like to comments array of event object.
My schema looks similar to this:
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
comments: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
]
}
]
}
And my current add like to comments function looks like this:
commentLike: async (req, res) => {
console.log('working', req.params.id, req.params.idas, req.params.commentID);
Events.findOneAndUpdate(
{ _id: req.params.idas, comments: { $elemMatch: { _id: req.params.commentID } } },
{ $push: { 'comments.$.likes': { user: req.params.id } } },
(result) => {
res.json(result);
}
);
}
Params: idas- event._id, commentID: comment id, id: user._id
The problem is that i can add endless likes since, I have no logical operation to check if user already liked it, and im really strugling, in this findoneandupdate function to do that. But thats on problem, another thing what I want to do is unlike comment, and Im having atrouble at figuring it out, on how to get user index from likes array so i can slice that index out, currently my function is looking like this:
deleteLike: async (req, res) => {
console.log('working', req.params.id, req.params.idas, req.params.commentID);
Events.findOneAndUpdate(
{ _id: req.params.idas, comments: { $elemMatch: { _id: req.params.commentID } } },
{
$push: {
'comments.$.likes': {
$each: [],
$slice: 0 //there instead of 0 should be user index
}
}
},
(result) => {
res.json(result);
}
);
}
On this function im also using findoneandupdate function, which is probably not a good idea? Was trying to use findandremove, but it removes entire event object.

So i managed to to it, by using pull operator.
Working delete comment like fuction
deleteLike: async (req, res) => {
console.log('working', req.params.id, req.params.idas, req.params.commentID);
Events.findOneAndUpdate(
{ _id: req.params.idas, comments: { $elemMatch: { _id: req.params.commentID } } },
{
$pull: { 'comments.$.likes': { user: req.params.id } }
},
(result) => {
res.json(result);
}
);
}
};

Related

mongodb having multiple conditions for $nin and data coming from the client

I'm trying to do a query based on some data but apparently, $nin is not reading the returned values from the function.
My goal is to not show all the users I've followed on the recommended section where there is a get request to receive all the users registered on the db.
I do have the following users on the function, however when I try to do the query, its not working and I've tried for hours fixing that problem.
At username: { $nin: [allFollowers.forEach((following) => following.username)] => Doing this, doesnt work, however when I put strings from the list of following users such as 'user1', 'user2', it works. My api updates on the client and I dont see those two users I follow on the recommended section.
I'd appreciate if you could help me.
exports.recommendedUsers = async function (req, res) {
// all following users in an array
const { followingUsers } = req.body;
/* console.log(followingUsers) =>
[
{
username: 'user1',
avatar: '//www.gravatar.com/avatar/c76fa83b3saddasdas2c04a59d6e063918badbf53?s=200&r=pg&d=mm'
},
{
username: 'user2',
avatar: '//www.gravatar.com/avatar/3758e369b058b393541asdasda4d0e8a1d57402?s=200&r=pg&d=mm'
},
{
username: 'uiser3',
avatar: 'https://static-cdn.jtvnw.net/jtv_user_pictures/bobross-profile_image-0b9dd16cascad7a9bb16b5-70x70.jpeg'
},
{
username: 'user4',
avatar: 'https://static-cdn.jtvnw.net/jtv_user_pictures/82b63a01-628f-4c81-9b05-dd3a501asdasd1fdda-profile_image-70x70.png'
},
{
username: 'user5',
avatar: '//www.gravatar.com/avatar/93cd495a412a1b2asdadabe9b9c72bc246e271?s=200&r=pg&d=mm'
}
] */
let allFollowers = [];
let following = req.body.followingUsers.forEach((follow) =>
allFollowers.push(JSON.stringify(follow.username))
);
console.log(`this is all followers: ${allFollowers}`);
try {
const user = User.find(
{
_id: { $ne: req.user.id },
username: {
$nin: [allFollowers.forEach((following) => following.username)], // not working
},
},
function (err, users) {
let userMap = {};
users.forEach(function (user) {
userMap[user._id] = user;
});
const arrayData = Object.values(userMap);
return res.json(arrayData);
}
).select('-password');
} catch (e) {
console.log(e.message);
}
};
You are using foreach function, that is wrong:
username: {
$nin: [allFollowers.forEach((following) => following.username)],
}
The return value of foreach is undefined, use map function.
try {
const user = User.find(
{
_id: { $ne: req.user.id },
username: {
$nin: allFollowers.map((following) => following.username),
},
},
function (err, users) {
let userMap = {};
users.forEach(function (user) {
userMap[user._id] = user;
});
const arrayData = Object.values(userMap);
return res.json(arrayData);
}
).select('-password');
} catch (e) {
console.log(e.message);
}

How to retreive an object from an array of Objects in mongodb given a objectid

I have an array of reviews, I want to retrieve only a review from an array of objects inside a schema.
Here is my schema:
const sellerSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
reviews: [
{
by: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
unique: true,
},
title: {
type: String,
},
message: {
type: String,
},
rating: Number,
imagesUri: [{ String }],
timestamp: {
type: Date,
default: Date.now,
},
},
],
});
How can I get a single review from the array if 'by' will be req.user._id. I have the previous code, but it is not working to retrieve the review only that satisfies the query.
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const review = await Seller.findOne(
{ _id: seller_id },
{ reviews: { $elemMatch: { by: req.user._id } } } //get the review that matches to the user_id
);
res.status(200).send(review);
} catch (err) {
//sends the status code with error code message to the user
res.status(502).send({
error: "Error retreiving review.",
});
}
This retrieves the whole seller document, but I just want to retrieve the object review with the given a user_id === by: ObjectID
give it a try
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const review = await Seller.findOne(
{ _id: seller_id , "reviews.by": req.user._id}, { "reviews.$": 1 }
);
res.status(200).send(review);
}
Try to cast also the user _id as ObjectId and unify your conditions into a single object:
try {
const seller_id = mongoose.Types.ObjectId(req.params._id);
const user_id = mongoose.Types.ObjectId(req.user._id);
const review = await Seller.findOne({
_id: seller_id,
reviews: { $elemMatch: { by: user_id } },
});
res.status(200).send(review);
} catch (err) {
//sends the status code with error code message to the user
res.status(502).send({
error: 'Error retreiving review.',
});
}

Mongoose & Express: How to properly Remove, Create & Store data that are reference

The first problem I am having is that whenever I try to delete the Comment, I also try to find the index of that specific comment inside post.comments as well as inside user.comments, it consistently returns -1, the reason why I am trying to find it, is so that I can splice it from the comments array that user and post do have.
The second problem I am having is that whenever I create a comment, I try to store it in the comments array that user and post have, but it stores it only as a string, although I think it is supposed to be stored as an object right?, So I can access it later by populating?
I have been struggling for days now being very frustrated why it does not work. Please help me!
Below will be my two routes, for deleting and creating comments, and my Schemas, Thank You for all the help!
Creating Comments
// #route POST api/posts/comment/:id
// #desc Comment on a post
// #access Private
router.post(
'/comment/:id',
[
auth,
[
check('text', 'Text is required')
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id)
const newComment = {
text: req.body.text,
post: post._id,
user: req.user.id
};
const comment = new Comment(newComment);
post.comments.unshift(comment._id);
user.comments.unshift(comment._id)
console.log(user.comments);
console.log(post.comments);
console.log(comment)
await post.save();
await comment.save();
await user.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
}
);
Deleting comments
// #route DELETE api/posts/comment/:id/:comment_id
// #desc Delete comment
// #access Private
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
try {
const post = await Post.findById(req.params.id);
const user = await User.findById(req.user.id);
// Pull out comment by finding it through its id
const comment = await Comment.findById(req.params.comment_id);
// Make sure comment exists
if (!comment) {
return res.status(404).json({ msg: 'Post do not have this comment' });
}
// Check user
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: 'User not authorized' });
}
// Get The value to be removed
const postCommentIndex = post.comments.findIndex(postComment => postComment === comment._id);
const userCommentIndex = user.comments.findIndex(userComment => userComment === comment._id);
console.log(`This is the post comment index ${postCommentIndex}`);
console.log(`This is the user comment index ${userCommentIndex}`);
post.comments.splice(postCommentIndex, 1);
user.comments.splice(userCommentIndex, 1);
// save user and post
await post.save();
await user.save();
await comment.remove();
// resend the comments that belongs to that post
res.json(post.comments);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
});
Schemas:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
posts: [{type: mongoose.Schema.Types.ObjectId, ref: "Post"}],
comments: [{type: mongoose.Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', UserSchema);
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
text: {
type: String,
required: true
},
likes: [
{
user: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}
],
dislikes: [
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
}
}
],
comments: [{type: Schema.Types.ObjectId, ref: "Comment"}],
date: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('Post', PostSchema);
const mongoose = require("mongoose")
const Schema = mongoose.Schema;
const CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
post: {
type: Schema.Types.ObjectId,
ref: "Post"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = mongoose.model("Comment", CommentSchema);
I think you need to redesign your schemas in a simpler way, there are too many references between the models, and this causes issues, for example you have 5 db access when you want to create a comment, and 6 db access when you want to delete a comment.
I would create the user schema like this removing the posts and comment references, but later when we want to access the posts from users, I set up virtual populate.
const UserSchema = new Schema(
{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
avatar: {
type: String
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
UserSchema.virtual("posts", {
ref: "Post",
localField: "_id",
foreignField: "user"
});
And in the posts schema, I removed the comments references.
(For simplicity I removed likes and dislikes fields.)
const PostSchema = new Schema(
{
user: {
type: Schema.Types.ObjectId,
ref: "User"
},
text: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
},
{
toJSON: { virtuals: true }
}
);
PostSchema.virtual("comments", {
ref: "Comment",
localField: "_id",
foreignField: "post"
});
Comment schema can stay as it is.
Now to add a comment to a post, we only need 2 db access, one for checking if post exists, and one for creating the post.
router.post(
"/comment/:id",
[
auth,
[
check("text", "Text is required")
.not()
.isEmpty()
]
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
try {
const post = await Post.findById(req.params.id);
if (!post) {
return res.status(404).json({ msg: "Post not found" });
}
let comment = new Comment({
text: req.body.text,
post: req.params.id,
user: req.user.id
});
comment = await comment.save();
res.json(comment);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
}
);
Let's say we have these 2 users:
{
"_id" : ObjectId("5e216d74e7138b638cac040d"),
"name" : "user1"
}
{
"_id" : ObjectId("5e217192d204a26834d013e8"),
"name" : "user2"
}
User1 with _id:"5e216d74e7138b638cac040d" has this post.
{
"_id": "5e2170e7d204a26834d013e6",
"user": "5e216d74e7138b638cac040d",
"text": "Post 1",
"date": "2020-01-17T08:31:35.699Z",
"__v": 0,
"id": "5e2170e7d204a26834d013e6"
}
Let's say user2 with _id:"5e217192d204a26834d013e8" commented on this post two times like this:
{
"_id" : ObjectId("5e2172a4957c02689c9840d6"),
"text" : "User2 commented on user1 post1",
"post" : ObjectId("5e2170e7d204a26834d013e6"),
"user" : ObjectId("5e217192d204a26834d013e8"),
"date" : ISODate("2020-01-17T11:39:00.396+03:00"),
"__v" : 0
},
{
"_id": "5e21730d468bbb7ce8060ace",
"text": "User2 commented again on user1 post1",
"post": "5e2170e7d204a26834d013e6",
"user": "5e217192d204a26834d013e8",
"date": "2020-01-17T08:40:45.997Z",
"__v": 0
}
To remove a comment we can use the following route, as you see we decreased the db access from 6 to 3, and code is shorter and cleaner.
router.delete("/comment/:id/:comment_id", auth, async (req, res) => {
try {
const comment = await Comment.findById(req.params.comment_id);
if (!comment) {
return res.status(404).json({ msg: "Post do not have this comment" });
}
if (comment.user.toString() !== req.user.id) {
return res.status(401).json({ msg: "User not authorized" });
}
await comment.remove();
// resend the comments that belongs to that post
const postComments = await Comment.find({ post: req.params.id });
res.json(postComments);
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
Now you may ask, how will access the posts from an user? Since we setup virtual populate in our user schema, we can populate the posts like this:
router.get("/users/:id/posts", async (req, res) => {
const result = await User.findById(req.params.id).populate("posts");
res.send(result);
});
You can try this code snipet :
Comment.deleteOne({
_id: comment.id
}, function (err) {
if (err) {
console.log(err);
return res.send(err.message);
}
res.send('success');
});

Count number documents from collection inside Array

I'm trying to count the number of documents inside an array from a collection.
Below you can see the Schema.
MongoDB Schema
What I want to count is each type of expenseType but since I have this value inside an array I don't know how to build a query to get this value.
The final result should be:
Water: 2 | Wifi: 1
And when I add new Water should be Water:3 and so on.
Below I show what I'm trying to do, but received an error
router.get("/getExpense", ensureAuthenticated, (req, res) => {
House.aggregate(
{
$match: {
userID: req.user.id,
expensesHouse: { $elemMatch: { status: "Public" } }
}
}
{ $group: { _id: "$Water", price: { $sum: 1 } } }
).then(house => {
console.log(res.json({ house }));
});
});
The res.json is because I send a JSON with the values and fetching to build a chart.
This is the fetch I'm doing.
getData();
async function getData() {
const res = await fetch("/houses/getExpense", {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
}
})
.then(res => res.json())
.then(data => {
console.log(data);
});
}
You're looking for $unwind. It creates intermediary entries that you can group on.
House.aggregate(
{
$match: {
userID: req.user.id,
expensesHouse: { $elemMatch: { status: "Public" } }
}
},
{
$unwind: '$expensesHouse',
},
{ $group: { _id: "$expensesHouse.expenseType", price: { $sum: 1 } } }
).then(house => {
console.log(res.json({ house }));
});
I've also fixed the last group id.

Populate Query Options with Async Waterfall

I'm trying mongoose populate query options but i don't know why the query options doesn't work.
I have user schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema(
{
username: { type: String, required: true },
email: { type: String },
name: { type: String },
address: { type: String }
},
{ timestamps: true }
);
module.exports = mongoose.model('User', UserSchema);
and feed schema:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const FeedSchema = new Schema(
{
user: { type: Schema.ObjectId, ref: 'User' },
notes: { type: String, required: true },
trx_date: { type: Date },
status: { type: Boolean, Default: true }
},
{ timestamps: true }
);
FeedSchema.set('toObject', { getters: true });
module.exports = mongoose.model('Feed', FeedSchema);
I want to find all feed by user id, i used async waterfall like the following code:
async.waterfall([
function(callback) {
User
.findOne({ 'username': username })
.exec((err, result) => {
if (result) {
callback(null, result);
} else {
callback(err);
}
});
},
function(userid, callback) {
// find user's feed
Feed
.find({})
// .populate('user', {_id: userid._id}) <== this one also doesn't work
.populate({
path: 'user',
match: { '_id': { $in: userid._id } }
})
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
With above code, i got all feeds, and it seems like the query option do not work at all, did i doing it wrong ?
Any help would be appreciate.
Not sure why you are looking to match "after" population when the value of _id is what is already stored in the "user" property "before" you even populate.
As such it's really just a simple "query" condition to .find() instead:
async.waterfall([
(callback) =>
User.findOne({ 'username': username }).exec(callback),
(user, callback) => {
if (!user) callback(new Error('not found')); // throw here if not found
// find user's feed
Feed
.find({ user: user._id })
.populate('user')
.exec(callback);
}
], function(err, docs) {
if (err) {
return next(err);
}
console.log(docs);
});
Keeping in mind of course that the .findOne() is returning the whole document, so you just want the _id property in the new query. Also note that the "juggling" in the initial waterfall function is not necessary. If there is an error then it will "fast fail" to the end callback, or otherwise pass through the result where it is not. Delate "not found" to the next method instead.
Of course this really is not necessary since "Promises" have been around for some time and you really should be using them:
User.findOne({ "username": username })
.then( user => Feed.find({ "user": user._id }).populate('user') )
.then( feeds => /* do something */ )
.catch(err => /* do something with any error */)
Or indeed using $lookup where you MongoDB supports it:
User.aggregate([
{ "$match": { "username": username } },
{ "$lookup": {
"from": Feed.collection.name,
"localField": "_id",
"foreignField": "user",
"as": "feeds"
}}
]).then( user => /* User with feeds in array /* )
Which is a bit different in output, and you could actually change it to look the same with a bit of manipulation, but this should give you the general idea.
Importantly is generally better to let the server do the join rather than issue multiple requests, which increases latency at the very least.

Categories

Resources