How to Remove more than one documents from mongodb - node.js

I am trying to deleteFeature meanwhile i want all the comments related to that feature deleted but i don't know how to do it.
my deleteFeature method -
exports.deleteFeature = (req, res) => {
try {
const { slug } = req.params;
Feature.findOne({ slug: slug.toLowerCase() }).exec((err, feature) => {
if (err) {
return res.status(400).json({
error: errorHandler(err),
});
}
console.log("Test");
Comment.deleteMany({ _id: feature._id });
console.log("chest");
feature.remove();
console.log("Best");
return res.json({
message: "Your Feature has been Deleted Successfully",
});
});
} catch (error) {
return res.status(400).json({
error: error,
});
}
};
I have this on comment model -
feature: {
type: ObjectId,
ref: "Feature",
required: true,
},
So when i delete a feature, i want to delete all the comments containing that feature's _id on that feature field

Change
Comment.deleteMany({ _id: feature._id });
to
Comment.deleteMany({ feature: feature._id });

Related

How to update a text within a nested model - MERN stack

I have just started learning the MERN stack and I am having trouble updating a text within a model with Express/Node. I tried to look for help and accessed Update a model within a model
How to Nest Models within a Model but they weren't quite what I was looking for.
I am working with 2 models, with the comments model embedded within the cats models like so. This is the comment model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const commentSchema = new Schema(
{
user_id: { type: String, required: true },
cat_id: { type: String, required: true },
text: {
type: String,
min: [3, "Comment cannot be too short"],
},
email: { type: String, required: true },
},
{ timestamps: true }
);
const Comment = mongoose.model("Comment", commentSchema);
module.exports = Comment;
And this comment model is within the cat models
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const Comment = require("./comments.js");
const catSchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
min: [2, "Cat name minimum of 2 characters."],
},
description: { type: String, required: true },
image: { type: String },
gender: { type: String, required: true },
cage: { type: String, required: true },
adoptable: { type: String, required: true },
comments: [Comment.schema],
},
{ timestamps: true }
);
const Cat = mongoose.model("Cat", catSchema);
module.exports = Cat;
In my controller, when I update a comment, I need to update the respective comment inside the cat model too, but I'm not able to do so. I tried targeting the particular cat, foundCat, and I can't access the comment with foundCat.comments.id(req.params.id)
Strangely enough, when I console log "foundCat.comments.id", it tells me that this is a function? So I don't know why I can't access and update that text...
Here is my code for updating the comment: Note! The part with issue is located at the end, look for "Cat.findOne"
// For updating comment
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
// req.body exists, so find the comment by id and then update
Comment.findOne({ _id: req.params.id }, (err, comment) => {
if (err) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
console.log(req.body);
// update the comment details
comment.text = req.body.text;
// save the updated comment
comment
.save()
.then(() => {
// return json response if successful
return res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
})
.catch((error) => {
return res.status(404).json({
error,
message: "Comment not updated!",
});
});
// now update the comment entry for the cat too
Cat.findOne({ _id: comment.cat_id }, (err, foundCat) => {
console.log("This doesnt work", foundCat.comments.id(req.params.id));
foundCat.save((err, updatedCat) => {
console.log(updatedCat);
});
});
});
};
Example of the comments within a cat:
You should update the cat instance once the comment has been fetched.
Try to change your code like this (using async wait):
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: 'You must provide a body to update',
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: 'Comment not found!',
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
const otherCatComments = cat.comments.filter((c) => c._id !== comment._id);
cat.comments = [...otherCatComments, comment];
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: 'Comment updated!',
});
} catch (err) {
res.status(404).json({
error,
message: 'Comment not updated!',
});
}
};
Luca, thank you! That was extremely helpful and I can see the appended comment added to the cats comment array. Now the only thing is the cats.comment.filter isn’t quite working as intended, as the otherCatsComments still includes all the comments. I had to do a little digging in the code and I managed to console log the id, which returns “_id: new ObjectId("617d57719e815e39f6049452"),”
I tried changing it to
const otherCatComments = cat.comments.filter((c) => c._id !== `new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== ` new ObjectId("${comment._id}")`);
const otherCatComments = cat.comments.filter((c) => c._id !== `ObjectId("${comment._id}")`);
But they all don’t seem to work, so I had to do a deep de-bugging and turns out my code is off for some things! I’ll just add them here in case anyone bumps into this issue in the future.
First off, my comment id was different from the comment id within my cats model. For reference, here is my create comment model (I modified it to use the async/await + try/catch block as recommended by Luca:
const createComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a comment",
});
}
try {
// req.body exists, so make a new comment
const comment = new Comment(req.body);
await comment.save();
// now add comment to cat
Cat.findById(req.params.id, (err, foundCat) => {
// Append the comment to the cat
foundCat.comments.push(comment);
foundCat.save();
});
// somehow, if the new comment doesn't exist, return error
if (!comment) {
return res.status(400).json({ success: false, error: err });
}
// success!
res.status(201).json({
success: true,
id: comment._id,
message: "Comment created!",
});
} catch (err) {
return res.status(400).json({
err,
message: "Comment not created!",
});
}
};
Note the part where I add the comments in the cat:
At first it was
foundCat.comments.push(req.body);
but this would generate a comment id in the cat that would be different from the comment id in the comment. so req.body is changed to comment.
Once that was fixed, I tried the original code by Luca, but it still didn’t work. My workaround was to not use the filter, and just delete the old comment and then add in the new comment.
Code here:
const updateComment = async (req, res) => {
// if there is no req.body, return error
if (!req.body) {
return res.status(400).json({
success: false,
error: "You must provide a body to update",
});
}
try {
// req.body exists, so find the comment by id and then update
const comment = await Comment.findById(req.params.id);
if (!comment) {
return res.status(404).json({
err,
message: "Comment not found!",
});
}
// update the comment details
comment.text = req.body.text;
// save the updated comment
await comment.save();
// now update the comment entry for the cat too
const cat = await Cat.findById(comment.cat_id);
// remove the old, non-updated comment first
cat.comments.id(comment._id).remove();
// now add in the updated comment
cat.comments.push(comment);
await cat.save();
res.status(200).json({
success: true,
id: comment._id,
message: "Comment updated!",
});
} catch (err) {
res.status(404).json({
error,
message: "Comment not updated!",
});
}
};

How to update object in array of object of a document in mongoose?

My User Schema is like this
{
_id:ObjectId("6e9465528a15ba6")
name: 'XYZ',
email: 'abc#gmail.com',
transactions: [
{
_id:ObjectId("5e946557a5128a15ba6"),
date: 2020-04-09T06:00:30.000Z,
type: 'type1',
category: 'category1',
description: 'some desc',
}
]
}
I want to update some fields of transaction with specific id. But not happening.
I tried the solution answered to
Mongoose, update values in array of objects this question.
May be my _id is of type ObjectId and id coming from my request is String?
So how can I solve this problem?
My code is like this but still getiing error user.transactions._id is not function
app.post('/api/update', function (req, res) {
const {
id,
email,
date,
type,
category,
description
} = req.body;
User.findOne({email}, function (err, user) {
if (err) {
console.error(err);
res.status(500)
.json({
error: 'Internal error please try again'
});
} else if (!user) {
res.status(401)
.json({
error: 'Incorrect email or password'
});
} else {
const objectId = mongoose.Types.ObjectId(id);
let transaction = user.transactions._id(objectId);
transaction.date = date;
transaction.type = type;
transaction.category = category;
transaction.description = description;
user.save((err, data) => {
if (err) return res.send(err);
return res.sendStatus(200);
});
}
});
});
fortunately I had to do something similar recently, so I suggest you to have a look at this page from Mongoose docs in the case I miss something, but basically you have to find the document first and then update its array of objects.
I'd try something like this:
User.findOne({ _id: your_user_id }, (err, data) => {
if (err) return console.log(err);
if (data) {
//Mongoose has a special method (id) for subdocuments
var transaction = data.transactions.id(your_transaction_id);
date: new Date(),
type: 'type2',
category: 'category2',
description: 'whatever',
//data.save() saves everything, document and subdocument
data.save((err, data) => {
if (err) return console.log(err);
done(null, data);
});
}

How to save the user document when creating a new post?

So, I have figured out the previous problems and just need to populate the user document with posts. Currently user document looks like this:
{
"posts": [],
"_id": "5e75cf827ef14514f69c6714",
"username": "dio",
"email": "dio123#gmail.com",
"password": "$2b$10$fwV.KaZG.5tjtmMxQ9NNE.7.XAh6pzLFgf85z9BpPVOgFguR2inGO",
"createdAt": "2020-03-21T08:25:38.459Z",
"updatedAt": "2020-03-21T08:25:38.459Z",
"__v": 0
}
So, I did this while creating the post to be able to populate it later.
newPost: (req, res) => {
const data = {
title: req.body.title,
content: req.body.content,
user: req.user.userId
}
Post.create(data, (err, newPost) => {
console.log(data, "data")
if (err) {
return res.status(500).json({ error: err })
} else if (!newPost) {
return res.status(400).json({ message: "No Post found" })
} else if (newPost) {
User.findById(req.user.userId, (err, user) => {
user.Posts = user.Posts.concat(newPost._id)
return res.status(200).json({ newPost, user })
})
}
})
}
After doing this when i return user from the above return statement, it looks like this:
{
posts: [ 5e75d89fa048e321f704453b ],
_id: 5e75cf827ef14514f69c6714,
username: 'dio',
email: 'dio123#gmail.com',
password: '$2b$10$fwV.KaZG.5tjtmMxQ9NNE.7.XAh6pzLFgf85z9BpPVOgFguR2inGO',
createdAt: 2020-03-21T08:25:38.459Z,
updatedAt: 2020-03-21T08:25:38.459Z,
__v: 0
}
Everytime I create a new post, I exprect the posts array to contain the objectIDs of the posts that the user has just created, but it's only pushing the latest post's objectId. Why does it not remembering the previous ones?
Also, I want to get the user's posts:
getUserPosts: async (req, res) => {
try {
const user = await User.findById(req.params.id).populate("posts");
if (!user) {
return res.status(400).json({ error: "No user" });
}
return res.status(200).json({ userPosts: user.posts });
} catch (err) {
return res.status(500).json({ error: "Server error" });
}
}
Since, the user document saved in the database has empty array of posts I am not able to populate it. Please help.
After you add the new post's id to the user's posts array, you need to save the user:
Post.create(data, (err, newPost) => {
console.log(data, "data");
if (err) {
return res.status(500).json({ error: err });
} else if (!newPost) {
return res.status(400).json({ message: "No Post found" });
} else if (newPost) {
User.findById(req.user.userId, (err, user) => {
user.posts.push(newPost._id);
user
.save()
.then(() => {
return res.status(200).json({ newPost, user });
})
.catch(err => {
return res.status(500).json({ error: err });
console.log(err);
});
});
}
});
As I remember in your previous questions, the name of the field for posts was posts not Posts in the user schema, so the following line is important, also we use push method instead of concat:
user.posts.push(newPost._id);
After this, we just need to save the user with save method, since save method returns a promise I added then catch blocks.
SuleymanSah's answer is correct. However, be aware of the safer version:
User.update(
{ _id: req.user.userId },
{ $push: { userPosts: newPost._id } },
done
);
The reason behind this is that a modification might occur on the user between the find and the save whereas the $push is an atomic operation.

Mongoose post Query Middleware hook trigger twice

I'm using mongoose Model.findOneAndupdate() to find and update my document and there is a post hook on my model schema for which i'm trying to update another document.
The issue i'm facing is post hook is being triggered twice.
My model:
const mongoose = require('mongoose')
const componentSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
component: {
type: String,
required: true
},
message: {
type: String
},
bodyJson: {
type: mongoose.Schema.Types.Mixed
},
question: {
type: String
}
})
componentSchema.post('findOneAndUpdate', function (result) {
console.log('came here')
})
module.exports = mongoose.model('Component', componentSchema)
In my server log i see that came here logged is twice.
update:
try {
await Component.findOneAndUpdate(query, req.body, { new: true }, function (error, doc) {
if (doc) {
return res.status(200).json({ data: doc })
} else if (error) {
return res.status(400).json({ errors: error.message })
} else res.status(404).json({ errors: 'Not found' })
})
} catch (error) {
logger.error('error while updating order: ' + error)
return res.status(400).json({ errors: error.message })
}
moongoose version i'm using is 5.8.11
You are using both await and callback at the same time. This causes the middleware trigger 2 times. Only one of them must be used.
Use either callback:
Component.findOneAndUpdate(query, req.body, { new: true }, function(
error,
doc
) {
if (err) {
return res.status(400).json({ errors: error.message }); //500 status code may be better
} else {
if (doc) {
return res.status(200).json({ data: doc });
} else {
res.status(404).json({ errors: "Not found" });
}
}
});
Or await:
try {
const doc = await Component.findOneAndUpdate(query, req.body, { new: true });
if (doc) {
return res.status(200).json({ data: doc });
} else {
res.status(404).json({ errors: "Not found" });
}
} catch (error) {
logger.error("error while updating order: " + error);
return res.status(400).json({ errors: error.message });
}

search queries in nodejs and mongodb and populated data

i have a web app that's written in nodejs and mongodb, i have the following two models
var TeacherSchema = new Schema({
school_id:[{type: Schema.Types.ObjectId, ref: 'School'}],
name: String,
subjects: [{type: Schema.Types.ObjectId, ref: 'Subject'}],
});
var SubjectSchema = new Schema({
title : String,
school_id:[{type: Schema.Types.ObjectId, ref: 'School'}]
});
i wrote an api that searches throw the teacher or subjects
router.get("/field-teacher-subject", function (req, res) {
var school_id= req.query.schoolId;
Subject.find(school_id:'school_id,function (err, subjects) {
if (err) {
console.log(err);
res.json({status: "error", message: err.message});
} else {
var sub_array=[];
for(var q in subjects){
sub_array.push(subjects[q]._id);
}
Teacher.find({subjects:{$in :sub_array }},{first_name:true, father_name:true, last_name : true, subjects:true}).populate('subjects')
.exec(function(tech) {
console.log("hello: ");
var subjeto = [];
if(tech){
for(var p in tech){
subjeto.push(tech[p].subjects);
}
}
res.json({status: "success", message: "subjects returned",
items: tech});
}).catch(function(err){
if(err){
res.json({status:"error",
message:"error occurred"+err.message});
return;
}
});
}
}).limit(parseInt(req.query.max));
});
THIS RETURNS null when i search for a name,
what is the best way to solve this
Hard to know what you are asking but your code has few errors. Let's clean up your code, shall we?
router.get("/field-teacher-subject", function (req, res) {
// get subjects
Subject
.find({ school_id: req.query.schoolId }) // 1st argument is an object
.limit(parseInt(req.query.max)) // should go before
.exec(function (err, subjects) { // use .exec()
if (err) {
console.log(err);
return res.json({ status: "error", message: err.message });
}
// get subject IDs
var sub_array = subjects.map(function (subject) { return subject._id; });
// get teachers assigned to subjects
Teacher
.find({ subjects: { $in: sub_array }})
.select('first_name father_name last_name subjects')
.populate('subjects')
.exec(function(err, teachers) { // 1st argument is an error
if (err) {
console.log(err);
return res.json({status: "error", message: err.message });
}
var subjeto = teachers.map(function (teacher) { return teacher.subjects; });
res.json({status: "success", message: "subjects returned", items: teachers });
});
});
});
Useful links:
See 3rd example in doc on how to use .limit() and .exec().
.map()
You tried to use .exec() like .then() and .catch() in your second query

Resources