How to obtain object id and also how to update - node.js

I am trying to obtain the object id for any article already in db so that I can validate that the article exists before comments are made.
The issue is on the router (/blog/article/comment). I cannot get the article object id from /blog/article/:postid. I want to pass this id to articleId like this:
articleId: req.params.postid
I have also tried:
articleId: req.article._id
model structure: comment.js
var mongoose = require('mongoose');
var CommentSchema = new mongoose.Schema({
content: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
articleId: { type: mongoose.Schema.Types.ObjectId, ref:'Article' },
dateCommented: { type: Date, default : Date.now }
});
Article model: article.js
var ArticleSchema = new mongoose.Schema({
category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category' },
commentId:{type: mongoose.Schema.Types.ObjectId, ref:'Comment'},
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User'},
blog: [{
topic: { type: String, unique: false, lowercase: true },
body: { type: String, unique: false, lowercase: true },
tags: [ 'first', 'mongodb', 'express'],
created: Date,
modified: { type : Date, default : Date.now },
state: { type: String, unique: false, lowercase: true }
}]
});
main.js
router.param('postid', function(req, res, next, id) {
if (id.length !=24) return next(new Error ('The post id is not having the correct length'));
//articleId: req.param('postid'),
Article.findOne({ _id: ObjectId(id)}, function(err, article) {
if (err) return next(new Error('Make sure you provided correct post id'));
req.article = article;
next();
});
});
router.get('/blog/article/:postid', function (req, res, next) {
Article.findById({ _id: req.params.postid }, function (err, article) {
if (err) return next(err);
res.render('main/publishedArticle', {
article: article
});
});
});
router.post('/blog/article/comment', function(req, res, next) {
async.waterfall([
function(callback) {
var comment = new Comment({
articleId: req.params.postid,
content: req.body.content,
user: req.user._id
});
comment.save(function(err) {
if (err) return next (err);
req.flash('success', 'Thank you for your comment');
callback(err, comment);
});
},
function(comment) {
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
}
]);
});
Another issue I have is how to update the commentId for each comment in the Article
Article.update({_id : comment.articleId }, { $set: { commentId: {} }}, function(err, updated)

Since the /blog/article/comment route is a post request. Just submit your articleId in the body of that request. You'll have to send it up from the client. You can access it with req.body.articleID (If that is what you call the variable).
See here for more info on POST requests in node.
For your second question:
Within your article schema you have commentId, That is a single record. What you want is an array of comments. Something like this:
comments: [{type: mongoose.Schema.Types.ObjectId, ref:'Comment'}]
Then within your code...
...
function(comment) {
//comment should contain all the comments
//Grab the article
Article.findOne({ _id: comment.articleId}, function(err, article){
//Go through all the comments in 'comment' compare them with the ones in artcle.comments.
//The ones that aren't already in the article object get put into newComments...
var newComments = [];
Article.update({ _id: comment.articleId }, { $addToSet: { comments: newComments } }, function(err, updated) {
if (updated) {
res.redirect('/')
}
});
});
}
...
I didn't fully implement the code, but it should get you off to the right start.
addToSet Documentation
Some more examples of add to set

Related

How to delete comment that is nested in Post schema with mongoose and nodejs?

I want to be able to delete comment that is inside my Post model.
This is my Schema for Post model:
const PostSchema = new Schema({
userID: {
type: Schema.Types.ObjectId,
ref: 'user'
},
content: {
type: String,
required: true
},
registration_date: {
type: Date,
default: Date.now
},
likes: [
{
type: Schema.Types.ObjectId,
ref: "user"
}
],
comments: [
{
text: String,
userID: {
type: Schema.Types.ObjectId,
ref: 'user'
}
}
]
})
And I have this route:
router.delete('/comment/:id/:comment_id', auth, async (req, res) => {
const postId = req.params.id
const commentId = req.params.comment_id
}
comments in post looks like this:
comments: [
{
_id: 5f1df4cf5fd7d83ec0a8afd8,
text: 'comment 1',
userID: 5efb2296ca33ba3d981398ff
},
{
_id: 5f1df4d35fd7d83ec0a8afd9,
text: 'commnet 2',
userID: 5efb2296ca33ba3d981398ff
}
]
I want to delete comment, and don't know how to do it. Does anyone have idea how to do it?
First we find the post by findByIdAndUpdate then we delete the comment using $pull from the array of comments.
router.delete("/comment/:id/:comment_/id", async function (req, res) {
try {
const post = await Post.findByIdAndUpdate(
req.params.id,
{
$pull: { comments: {_id:req.params.comment_id}},
},
{ new: true }
);
if (!post) {
return res.status(400).send("Post not found");
}
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});

Mongoose not deleting subdocument

My delete route is
const id = req.body.id;
const postId = req.body.postId;
if (mongoose.Types.ObjectId.isValid(id)) {
Comment.findByIdAndRemove({ _id: id }, (err, cRes) => {
if (err) return err;
Post.findOneAndUpdate({ _id: postId }, {
$pull: {
Comments: {
_id: id
}
}
}, (err, doc, res) => {
if (err) console.log(err);
res.redirect(req.get('referer'));
});
});
}
And the problem is that it does delete the comment from the Comment table but it doesn't delete the comment to the related Post, why is that?
PostSchema
var PostSchema = new mongoose.Schema({
Author: String,
Title: String,
Description: String,
Comments: [{
type: mongoose.Schema.Types.ObjectId, ref: 'Comment'
}],
Tags: [{
type: mongoose.Schema.Types.String, ref: 'Tag'
}],
CreatedOn: Date,
LastEditOn: Date
});
CommentSchema
var CommentSchema = new mongoose.Schema({
_postId: {
type: String,
ref: 'Post'
},
Author: String,
Description: String,
CreatedOn: Date,
LastEditBy: Date
});
no need to put _id during pull because you haven't mentioned any key in Comments of Post collection.
if (mongoose.Types.ObjectId.isValid(id)) {
Comment.findByIdAndRemove({ _id: id }, (err, cRes) => {
if (err) return err;
Post.update({ _id: postId }, {
$pull: {
Comments: id
}
}, (err, doc, res) => {
if (err) console.log(err);
res.redirect(req.get('referer'));
});
});
}
If you define _id in post schema like
Comments: [{
_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }
}]
then your query could have worked.

How to trigger a function whenever a mongoose document is updated

I have a user model schema in mongoose which contains a list of friends and groups and stats info like so...
var user = new Schema({
email: { type: String, required: true, unique: true },
password: { type: String, required: true, select: false },
roles: [{ type: String, required: true }],
friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
groups: [{ type: Schema.Types.ObjectId, ref: 'Group' }],
stats : {
nbrFriends: { type: Number, required: false },
nbrGroups: { type: Number, required: false }
}
}, {
timestamps: true
});
I need to update the users stats whenever a change is made to the friends or groups fields to contain the new number of friends or groups etc. For example, when the following function is called on a user:
var addGroup = function(user, group, cb) {
user.groups.push(group);
User.findOneAndUpdate({ _id: user._id }, { $set: { groups: user.groups }}, { new: true }, function(err, savedResult) {
if(err) {
return cb(err);
}
console.log('updated user: ' + JSON.stringify(savedResult));
return cb(null, savedResult);
});
};
How could I make sure the stats is automatically updated to contain the new number of groups the user has? It seems like a middleware function would be the best approach here. I tried the following but this never seems to get called...
user.pre('save', function(next) {
var newStats = {
nbrGroups: this.groups.length,
nbrPatients: this.friends.length
};
this.stats = newStats;
this.save(function(err, result) {
if(err) {
console.log('error saving: ' + err);
} else {
console.log('saved');
}
next();
});
});
You need to use the middleware a.k.a. hooks:
Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions.
See the docs:
http://mongoosejs.com/docs/middleware.html
From version 3.6, you can use change streams.
Like:
const Users = require('./models/users.js')
var filter = [{
$match: {
$and: [{
$or:[
{ "updateDescription.updatedFields.friends": { $exists: true } },
{ "updateDescription.updatedFields.groups": { $exists: true } },
]
{ operationType: "update" }]
}
}];
var options = { fullDocument: 'updateLookup' };
let userStream = Users.watch(filter,options)
userStream.on('change',next=>{
//Something useful!
})
You should update with vanilla JS and then save the document updated to trigger the pre-save hooks.
See Mongoose docs
If you have many keys to update you could loop through the keys in the body and update one by one.
const user = await User.findById(id);
Object.keys(req.body).forEach(key => {
user[key] = req.body[key];
}
const saved = await user.save();

Issues With Mongoose $push

I really just need a second set of eyes here. I am using the Mongoose npm to create a new entry in my MongoDB. Then I am using that new entry in a few functions in the Async npm.
The issue that I am having is that I am getting the first three console logs, "hitter", "create", and "req.body.campaign_id" but nothing past that. I think it has to do with my $push in the first findByIdAndUpdate. Please see my code and schema below.
Code! See async parallel "campaign" function
Bid.create(req.body, function(err, bid){
console.log('create')
async.parallel({
campaign: function(done) {
console.log(req.body.campaign_id)
Camapaign.findByIdAndUpdate(req.body.campaign_id, {
$push: { bids: bid._id }
}, {
safe: true,
upsert: true
}, function(err, campaign){
console.log('camp', 2)
if(err) {
console.log(err)
done(err)
} else {
done(null, campaign)
}
});
},
user: function(done) {
console.log('user', 1)
User.findByIdAndUpdate(req.body.user_id, {
$push: {'bids': bid._id }
}, {
safe: true,
upsert: true
}, function(err, bid){
console.log('user', 2)
if(err) {
done(err)
} else {
done(null, bid)
}
});
}
}, function(err, response){
console.log('response')
if(err) {
console.log(err)
} else {
res.status(200).send(response);
}
});
})
Campaign Schema
var campaignSchema = new mongoose.Schema({
title:String,
imgUrl:[String],
shortDesc: { type: String, set: shortenDesc },
longDesc:String,
duration: Number,
price: Number,
desired_price: Number,
bids: [{ type: mongoose.Schema.Types.ObjectId, ref: 'bidSchema' }],
owner_id: { type: mongoose.Schema.Types.ObjectId, ref: 'userSchema' }
});
User Schema
var schema = new mongoose.Schema({
name: String,
email: {
type: String
},
password: {
type: String
},
salt: {
type: String
},
twitter: {
id: String,
username: String,
token: String,
tokenSecret: String
},
facebook: {
id: String
},
google: {
id: String
},
campaigns: [campaignSchema],
bids: [{type: mongoose.Schema.Types.ObjectId, ref: 'bidSchema'}]
});
Please let me know if you need to see anything else. All help is appreciated.
Thanks!
You are doing Camapaign.findByIdAndUpdate are you sure Camapaign isn't mispelled there? Shouldn't it be Campaign?

Handle schema references on mongoose

I'm trying to define a simple RESTful API using Node.js, mongoose and restify. The goal is to have users which can comment on profiles of others users. For this I have a comment endpoint that receives a text, the author and the target of the comment (other user).
I want to reference users so I defined next schemas:
User schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var UserSchema = new Schema({
"username": { type: String, unique: true, required: true },
"password": { type: String, required: true },
"comments": [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
});
mongoose.model('User', UserSchema);
Comment schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var CommentSchema = new Schema({
date: { type: Date, default: Date.now },
text: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
target: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});
mongoose.model('Comment', CommentSchema);
I also have this controller (just showing createComment function):
exports.createComment = function(req, res, next) {
var authorId, targetId;
User.findOne({ _id: req.params.authorId}, function(err, author) {
if (author) {
User.findOne({ _id: req.params.targetId}, function(err, target) {
if (target) {
var comment = new Comment();
comment.text = req.params.text;
comment.author = author._id;
comment.target = target._id;
comment.save(function(err, comment) {
if (err) {
res.status(500);
res.json({
type: false,
data: 'Error occurred: ' + err
});
} else {
res.json({
type: true,
data: comment
});
}
});
} else {
res.json({
type: false,
data: 'User ' + req.params.authorId + ' not found'
});
}
});
} else {
res.json({
type: false,
data: 'User ' + req.params.targetId + ' not found'
});
}
});
};
So, I have three questions:
Why do I need to check if the user received exists? I would like to receive only the id and store it but I have to do two more queries to check it myself.
What I have to do to store in User only comments where that user is the target? solved in the edited code
How can I simplify this code? Is a pain to have async queries executed in order. I would like to have generic errors and not to have to handle each one.
EDIT: I've simplified the code using validations on the schema:
Comment schema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var User = mongoose.model('User');
var CommentSchema = new Schema({
date: { type: Date, default: Date.now },
text: { type: String, required: true },
author: { type: Schema.Types.ObjectId, ref: "User", required: true },
target: { type: Schema.Types.ObjectId, ref: "User", required: true }
});
CommentSchema.path('author').validate(function(value, respond) {
User.findOne({ _id: value}, function(err, user) {
respond(!err && user);
});
}, 'Author doesn\'t exists');
CommentSchema.path('target').validate(function(value, respond) {
User.findOne({ _id: value}, function(err, user) {
respond(!err && user);
});
}, 'Target user doesn\'t exists');
mongoose.model('Comment', CommentSchema);
Controller:
exports.createComment = function(req, res, next) {
var comment = new Comment(req.body);
comment.save(function(err, comment) {
if (err) {
res.status(500);
res.json({
type: false,
data: 'Error occurred: ' + err
});
} else {
User.findOne({ _id: comment.target }, function(err, user) {
user.comments.push(comment);
user.save();
});
res.json({
type: true,
data: comment
});
}
});
};
The problem with this is that now I have to use _id on queries (I would like to use a custom id) and I'm doing three queries every time I want save a comment (2 for validation and one more to store the comment). Is there a better way to to this?
You can use the select option of query in mongo to select only the _id field of the user, like this:
User.findOne({_id:req.params.authorId}).select({_id:1}).exec(function(err,user) {})
After you pull the userTarget from mongo, you need to add the comment._id to his list of comments, and save him:
target.comments.push(comment._id);
target.save(function(err, targetAfterSaved) {})
read about async or q, they are my favorites Libraries to handle with async functions. For handle with errors like you want, you can add some listeners - here is the documentation from restify site.
Hope you understand, if you need any help let me know

Resources