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.
Related
I am creating a e-commerce app and now I want to update and delete a review of a product using nodejs and database is mongoDB (using mongoose). This is my User Schemas and Product Schema:
const userSchema = new Schema(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true, trim: true },
password: { type: String, required: true, trim: true },
isAdmin: { type: Boolean, required: true, default: false },
},
{
timestamps: true,
}
);
const productSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
required: true,
ref: "users",
},
name: { type: String, required: true },
image: { type: String, required: true },
brand: { type: String, required: true },
category: { type: String, required: true },
description: { type: String, required: true },
reviews: [
{
name: { type: String, required: true },
rating: { type: Number, required: true },
comment: { type: String, required: true },
user: {
type: Schema.Types.ObjectId,
required: true,
ref: "users",
},
},
{
timestamps: true,
},
],
rating: { type: Number, required: true, default: 0 },
reviewNumber: { type: Number, required: true, default: 0 },
price: { type: Number, required: true, default: 0 },
countInStock: { type: Number, required: true, default: 0 },
});
I completed the create function but I don't know how to implement update and delete function, this this my create function:
//* desc Create review
//* route POST /product/:id/review
//* access Private
const createReview = async (req, res) => {
const { rating, comment } = req.body;
if (!rating || !comment) {
return res.status(400).json({
success: false,
message: "Missing infomation",
});
}
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(400).json({
success: false,
message: "Product is not found",
});
}
const review = product.reviews;
const alreadyReviewed = product.reviews.find(
(i) => i.user.toString() === req.user._id.toString()
);
if (alreadyReviewed) {
return res.status(400).json({
success: false,
message: "User have already reviewed this product",
});
}
const newReview = {
name: req.user.name,
rating: 0 || Number(rating),
comment,
user: req.user._id,
};
review.push(newReview);
product.reviewNumber = review.length;
product.rating =
review.reduce((acc, item) => item.rating + acc, 0) / review.length;
await product.save();
return res.status(200).json({
success: true,
message: "Create review successfully",
product,
});
} catch (error) {
console.log(error);
return res.status(500).json({
success: false,
message: "Internal server error",
});
}
};
So how can I implement update and delete function?
I am assuming according to your code one product can have only one review per user.
//* desc update review
//* route PUT /product/:id/review
//* access Private
const createReview = async (req, res) => {
const { rating, comment } = req.body;
if (!rating || !comment) {
return res.status(400).json({
success: false,
message: "Missing infomation",
});
}
try {
const product = await Product.findById(req.params.id);
if (!product) {
return res.status(400).json({
success: false,
message: "Product is not found",
});
}
const reviews = product.reviews;
const foundReviewIndex = product.reviews.findIndex(
(i) => i.user.toString() === req.user._id.toString()
);
if (foundReviewIndex === -1) {
return res.status(400).json({
success: false,
message: "Review not found",
});
}
reviews[foundReviewIndex].rating = rating ? Number(rating) : 0,
reviews[foundReviewIndex].comment = comment,
product.rating =
review.reduce((acc, item) => item.rating + acc, 0) / review.length;
await product.save();
return res.status(200).json({
success: true,
message: "Updated review successfully",
product,
});
} catch (error) {
console.log(error);
return res.status(500).json({
success: false,
message: "Internal server error",
});
}
};
Similarly, you can write a function for deleting a review.
I suggest you create a separate collection for reviews and keep both product and user references in the review schema.
So I'm learning mongodb & mongoose, and I'm trying to make a projects/tasks app
this is the ProjectModel:
const projectSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
},
grp: {
type: String,
required: true,
trim: true,
lowercase: true,
},
tasks: {
type: [taskSchema],
},
createdAt: { type: Date, default: Date.now },
},
{
toObject: { virtuals: true },
toJSON: { virtuals: true },
}
);
projectSchema.virtual('tasksCount').get(function () {
return this.tasks.length;
});
And when requesting the data, this runs :
getAllProjects = async (req, res) => {
try {
const query = await Project.find().select({tasks: -1});
const projects = query.;
console.log(projects);
res.status(200).send({
status: 'sucess',
data: {
projects,
},
});
} catch (error) {
res.status(400).send({
status: 'fail',
msg: `error info : ${error}`,
});
}
};
The problem is : I get this error: TypeError: Cannot read property 'length' of undefined.
Its coming from the virtual property ('tasksCount'), So it seems when I unselect the ('tasks') property the virtual one can't be calculated, I hope someone have a way to unselect 'tasks' and still be able to send 'tasksCount'.
You can follow this code
const query = await Project.find().select("-tasks");
I'm trying to post a comment on to my posts for my MERN app but I'm running into an issue where the comment (Posts.findOneAndUpdate) seems to posting the comments twice. I read a few posts on SO that described the issue to be the way mongoose handles queries but I must be missing something.
If anyone could explain what I'm doing wrong I would greatly appreciate it!
Route I'm using:
router.post('/newReply/:id', async function(req, res) {
const body = req.body
if (!body) {
return res.status(400).json({
success: false,
error: 'No text entered!',
})
}
const reply = new Replies(body)
if (!reply) {
return res.status(400).json({ success: false, error: err })
}
await Posts.findOneAndUpdate(
{ _id: req.params.id },
{
"$inc": { "replies": 1 },
"$push": { "comments": reply },
},
{
new: true
},
(err) => {
if (err) {
return res.status(404).json({
success: false,
error: err,
message: 'Post not found!',
})
}
return res.status(200).json({
success: true,
id: reply._id,
message: 'Reply created!',
reply: reply.reply,
points: reply.points,
createdAt: reply.createdAt
})
})
.catch(err => console.log(err))
})
Posts Model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
// Create Schema
const PostsSchema = new Schema({
post: {
type: String,
required: true
},
points: {
type: Number,
default: 0
},
voters: {
type: Array
},
upvotedBy: {
type: Array
},
downvotedBy: {
type: Array
},
createdAt: {
type: Date,
default: Date.now
},
replies: {
type: Number,
default: 0
},
comments: {
type: Array
},
user_id: {
type: 'string'
},
deleted: {
type: Boolean,
default: false
}
});
module.exports = Posts = mongoose.model("posts", PostsSchema);
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 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);
});