I am somewhat new to this, so if I am on the completely wrong track feel free to let me know.
I have the following post schema.
var Post = new Schema( {
description: {
type: String,
default: '',
required: 'Please type a description',
trim: true
},
likeCount: {
type: Number,
default: 0
},
url: {
type: String,
default: '',
required: 'Unable to find photo',
trim: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User',
required: 'Unable to verify user'
},
comments: {
type: [Comment]
},
//Dynamically added values
hasLiked: {
type: Boolean
}
});
And the following Like schema
var Like = new Schema({
created: {
type: Date,
default: Date.now
},
user: {
type: Schema.ObjectId,
ref: 'User'
},
post: {
type: Schema.ObjectId,
ref: 'Post'
}
});
When I show a user a list of posts I need to indicate whether they have previously "liked" a post, so I am trying to pull the posts, then iterate through them to determine if the person has liked it and update the value in the Post. I'm not getting any errors, but it's also not updating the hasLiked value. I put the hasLiked value into my Mongoose model because I can't just add a value on the fly before returning my results. I don't store an actual value for that in the DB because it would obviously be different for every person that viewed the post.
exports.list = function(req, res) {
Post.find().sort('-created').populate('user', 'displayName')
.exec(function (err, posts) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
for (var i = 0; i < posts.length; i++) {
Like.find({ 'post': posts[i]._id, 'user': req.user.id }).exec(function (err, like) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
if (like.length == 0)
posts[i].hasLiked = false;
else
posts[i].hasLiked = true;
}
});
}
res.jsonp(posts);
}
});
};
Node is async language. So your mistake here is that when you query to find if the user liked the post:
Like.find({ 'post': posts[i]._id, 'user': req.user.id }).exec(function (err, like)
the answer will return after you return the answer to the client. In other words, line res.jsonp(posts); performed before the answer from mongo returned and enters to the callback. Thats why it isn't working for you.
To handle with async methods, I suggest you to use a third-party library, such as async or q.
Here is one solution for you with Q library:
var Q = require('q');
var promises = [];
posts.forEach(function(post) {
promise = Q(Like.find({ 'post': post._id, 'user': req.user.id }).exec())
.then(
function(like) {
if (like.length == 0)
post.hasLiked = false;
else
post.hasLiked = true;
}
}
,function(err) {
//handle error
});
})
Q.all(promises)
.then(function() {
return res.jsonp(posts);
});
Related
I'm a newbie in to node.js and mongodb. Im using express for rest-api.
I'm trying to create an API where user can post and like. I'm able to create post API and the issue is with like API where the user can click the like button and it will be counted and when he clicks the like button again it will take as unlike and update the same in the backend server (express and mongodb) in my case.
Schema Definition
module.exports = mongoose.model('posts', new Schema({
post: { type: String, required: true },
date: { type: Date, required: true, default: moment.now() },
author: { type: String, required: true },
authorId: {type: String, required: true},
likes: {type: Object, required: true, default: []},
likesCount: { type: Number, required: true, default: 0},
dislikes: { type: Object, required: true, default: [] },
dislikesCount: { type: Number, required: true, default: 0 },
comments: { type: Object, required: true, default: [] },
commentsCount: { type: Number, required: true, default: 0 }
}, { timestamps : { createdAt: 'created_at' , updatedAt: 'updated_at' } }));
Code:
router.post('/post/likes', function(req, res) {
if (!_.isEmpty(req.body.postId) && !_.isEmpty(req.body.user) && !_.isEmpty(req.body.userId)) {
//Finding the post by Id
Posts.findById(req.body.postId, function(err, posts) {
if (err) {
return res.send(500).json({
success: false,
msg: err
});
}
if (posts !== null) {
var user = {
userId: req.body.userId,
user: req.body.user
};
//Returns the matched obj if true
//Returns undefined if false
var alreadyLiked = _.find(posts.likes, function(obj) {
if (obj.userId === user.userId && obj.user === user.user) {
return true;
} else {
return false;
}
});
if (alreadyLiked === undefined) {
posts.likes.push(user);
posts.likesCount = posts.likes.length;
var updatedPost = new Posts(posts);
updatedPost.save(function(err, data) {
if (err) {
return err;
}
res.status(200).send({
success: true,
data: data,
message: 'You have liked the post!'
});
});
} else {
//Removing the already liked user object from posts.likes
posts.likes = _.without(posts.likes, _.findWhere(posts.likes, user));
posts.likesCount = posts.likes.length;
posts.markModified('likesCount');
var reupdated = new Posts(posts);
reupdated.save(function(err, data) {
if (err) {
return err;
}
res.status(200).send({
success: true,
data: data,
message: 'You have unliked the post!'
});
});
}
} else {
res.status(200).send({
success: false,
message: 'No post found!'
});
}
});
} else {
res.status(400).send({
success: false,
message: 'Bad request value'
});
}
});
The issue is when I like the post for first time it works perfect and returns the expected response.
img: liked-response-img
when i unlike the post by calling the same API it returns some unexpected result. The user who unliked is removed from the likes property but the count still remains '1' as shown in img below. I can't figure out why? Can someone please point out where and what I'm doing wrong. Thanks in advance!
img: unliked-response-img
how about just making posts.save() or posts.save().exec() instead of creating a new "copied" post and saving that.
I have a Torrent item, it has subdocument array named '_replies' to saved user comments, and every comment also include subdocument array '_replies' to saved user reply, this is my all schema define:
var CommentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
comment: {
type: String,
default: '',
trim: true
},
_replies: [this],
createdat: {
type: Date,
default: Date.now
},
editedby: {
type: String,
default: '',
trim: true
},
editedat: {
type: Date,
default: ''
}
});
var TorrentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
torrent_filename: {
type: String,
default: '',
trim: true,
required: 'filename cannot be blank'
},
torrent_title: {
type: String,
default: '',
trim: true,
required: 'title cannot be blank'
},
_replies: [CommentSchema]
});
mongoose.model('Torrent', TorrentSchema);
mongoose.model('Comment', CommentSchema);
the first level comment of torrent update/delete fine, the code of server controller likes below:
exports.update = function (req, res) {
var torrent = req.torrent;
torrent._replies.forEach(function (r) {
if (r._id.equals(req.params.commentId)) {
r.comment = req.body.comment;
r.editedat = Date.now();
r.editedby = req.user.displayName;
torrent.save(function (err) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(torrent); //return data is Correct, and save to mongo is Correct
}
});
}
});
};
but when i used Alike function to update/delete _replies._replies, it can return Correct json of torrent to response, Unfortunate, the save to mongo not fine, the code:
exports.SubUpdate = function (req, res) {
var torrent = req.torrent;
torrent._replies.forEach(function (r) {
if (r._id.equals(req.params.commentId)) {
r._replies.forEach(function (s) {
if (s._id.equals(req.params.subCommentId)) {
s.comment = req.body.comment;
s.editedat = Date.now();
s.editedby = req.user.displayName;
torrent.save(function (err) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(torrent);//return data is Correct, but save to mongo is incorrect
}
});
}
});
}
});
};
also, i can delete first level comment, but can not delete second level comment reply, all the json data of torrent is correct, only not save to mongo.
what can i do more?
I already solve it, i add this code before .save().
torrent.markModified('_replies');
it work fine!
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
I have two Schemas:
var ProgramSchema = new Schema({
active: Boolean,
name: String,
...
});
var UserSchema = new Schema({
username: String,
email: { type: String, lowercase: true },
...
partnerships: [{
program: { type: Schema.Types.ObjectId, ref: 'Program' },
status: { type: Number, default: 0 },
log: [{
status: { type: Number },
time: { type: Date, default: Date.now() },
comment: { type: String },
user: { type: Schema.Types.ObjectId, ref: 'User' }
}]
}]
});
Now I want to get all Program docs, but also append 'status' to each doc, to return if the program is already in a partnership with the logged in user.
My solution looks like this:
Program.find({active: true}, 'name owner image user.payments', function (err, p) {
if(err) { return handleError(res, err); }
})
.sort({_id: -1})
.exec(function(err, programs){
if(err) { return handleError(res, err); }
programs = _.map(programs, function(program){
var partner = _.find(req.user.partnerships, { program: program._id });
var status = 0;
if(partner){
status = partner.status;
}
program['partnership'] = status;
return program;
});
res.json(200, programs);
});
The req.user object contains all information about the logged in user, including the partnerships array.
To get this solution to work, I have to append
partnership: Schema.Types.Mixed
to the ProgramSchema.
This looks a bit messy and thats why I am asking for help. What do you think?
When you want to freely modify the result of a Mongoose query, add lean() to the query chain so that the docs (programs in this case) are plain JavaScript objects instead of Mongoose doc instances.
Program.find({active: true}, 'name owner image user.payments')
.lean() // <= Here
.sort({_id: -1})
.exec(function(err, programs){ ...
Then you can remove partnership from your schema definition. Your query will also execute faster.
This is my LinkSchema:
var LinkSchema = new Schema({
user: ObjectId,
text: {
type: String,
validate: [required,"Text is required"],
index: {unique: true}
},
body: {
type: String,
validate: [required, 'Body is required'],
index: { unique: true }
},
createdAt: {
type: Date,
'default': Date.now
}
});
This is my getLink:
LinkSchema.statics.getLink = function(apiKey,fn){
var query = link.find('link.user.apiKey': apiKey);
query.exec(function (err, links) {
if (err) return handleError(err);
res.send(items);
});
}
Error:
Unexpected Token':' -> var query = link.find('link.user.apiKey': apiKey);
I suppose I am doing the find() of mongoosejs wrong. How do I fix this?
You can simply do this:
var Link = db.model('Link', LinkSchema);
Link.find({}, function(err, results) {
// res.send(results); for example.
});
The first argument of the find function is the query. For example, if you want to search all Link with body equals to blablabla:
Link.find({body: 'blablabla'}, function(err, results) {
// res.send(results); for example.
});