sequelize findAll is not checking if current user liked a post - javascript

I have a scenario in which if you like a post, it will change
liked:false to liked:true
This liked is based if the current user liked the post. The problem is when a new user signs up, it will still show liked being true despite the new user NOT liking the post.
How would i be able to check if the current user liked the post ? I don't think my logic is somewhat right as far as checking if the current user liked the post.
I want to keep the findAll functionality, i should get all posts, not just by the current user.
Sorta like instagram, or facebook.
this is posts array
and this is how im liking a post
likePost
likePost: async (req: any, res: Response) => {
const created = await models.Likes.findOne({
where: {
userId: req.session.user.id,
resourceId: req.params.id
}
});
console.log(created);
const post = await models.Post.findOne({ where: { id: req.params.id } });
// if like not created then do this
if (!created && post) {
await models.Likes.create({
userId: req.session.user.id,
resourceId: req.params.id
}).then(() => {
post.increment("likeCounts", { by: 1 });
post.update({ liked: req.session.user.id ? true : false });
res.status(200).send({
message: "You liked this post"
});
});
// else if post does not exist
} else if (!post) {
res.status(200).send({
message: "there is not post to be liked"
});
} else {
// else if a like does exist destroy like
await models.Likes.destroy({
where: {
userId: req.session.user.id
}
}).then(() => {
post.decrement("likeCounts", { by: 1 });
res.status(200).send({
message: "You unliked this post"
});
});
}
this is how im getting the posts.
getPosts
getPosts: async (req: any, res: Response) => {
await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
{ model: models.Likes }
],
order: [["createdAt", "DESC"]],
limit: 6
}).then(posts => {
res.json(posts);
});
},
Post.js(model)
"use strict";
module.exports = (sequelize, DataTypes) => {
var Post = sequelize.define("Post", {
title: DataTypes.STRING,
postContent: DataTypes.STRING,
liked: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
likeCounts: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
}
},
authorId: DataTypes.INTEGER
}, {});
Post.associate = function (models) {
Post.belongsTo(models.User, {
as: "author",
foreignKey: "authorId",
onDelete: "CASCADE"
});
Post.hasMany(models.Likes, {
foreignKey: "resourceId",
timestamps: false,
targetKey: "id",
onDelete: "CASCADE"
});
};
return Post;
};

I believe the bug you are seeing is because you are not resolving the promises that are returned by:
post.increment("likeCounts", { by: 1 });
post.update({ liked: req.session.user.id ? true : false });
This means that the response will be send before those queries execute. The post.liked value will be set to true any time there is a user.id on the session. You may want to consider using transactions to roll back some of the earlier queries if later ones fail. I would also recommend using Promise.all() to make concurrent queries (it will be faster) and use async/await exclusively without mixing in thenables.
likePost: async (req: any, res: Response) => {
// fetch created and post at the same time
const [ created, post ] = await Promise.all([
models.Likes.findOne({
where: {
userId: req.session.user.id,
resourceId: req.params.id
}
}),
models.Post.findOne({
where: {
id: req.params.id
}
}),
]);
// no post, no updates
if (!post) {
return res.status(200).send({
message: "there is no post to be liked"
});
}
// we are going to make updates, so use a transaction, you will need to reference sequelize
let transaction;
try {
transaction = await sequelize.transaction();
if (!created && post) {
// use Promise.all() for concurrency
await Promise.all([
models.Likes.create({
userId: req.session.user.id,
resourceId: req.params.id
}, { transaction }),
post.increment("likeCounts", { by: 1, transaction }),
post.update({ liked: req.session.user.id ? true : false }, { transaction }),
]);
await transaction.commit();
return res.status(200).send({
message: "You liked this post"
});
}
await Promise.all([
models.Likes.destroy({
where: {
userId: req.session.user.id
}
}, { transaction }),
post.decrement("likeCounts", { by: 1, transaction }),
]);
await transaction.commit();
return res.status(200).send({
message: "You unliked this post"
});
} catch (err) {
if (transaction) {
await transaction.rollback();
}
console.log('There was an error', err);
return res.status(500);
}
}
To only return Likes for the current user on the getPost()
getPosts: async (req: any, res: Response) => {
const posts = await models.Post.findAll({
include: [
{ model: models.User, as: "author", attributes: ["username"] },
// limit the likes based on the logged in user
{ model: models.Likes, required: false,
where: { userId: req.session.user.id },
},
],
order: [["createdAt", "DESC"]],
limit: 6
});
return res.json(posts);
},

So by following #doublesharp help, i was able to determine if the current user liked the post or not, by using a sequelize data type VIRTUAL, along with using getDataValue
updated code
Post(model)
"use strict";
module.exports = (sequelize, DataTypes) => {
var Post = sequelize.define("Post", {
title: DataTypes.STRING,
postContent: DataTypes.STRING,
liked: {
type: DataTypes.VIRTUAL,
allowNull: false,
defaultValue: false,
get: function () {
return this.getDataValue('Likes').length ? true : false;
}
},
likeCounts: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0,
validate: {
min: 0,
}
},
authorId: DataTypes.INTEGER
}, {});
Post.associate = function (models) {
Post.belongsTo(models.User, {
as: "author",
foreignKey: "authorId",
onDelete: "CASCADE"
});
Post.hasMany(models.Likes, {
foreignKey: "resourceId",
timestamps: false,
targetKey: "id",
onDelete: "CASCADE"
});
};
return Post;
};
//# sourceMappingURL=post.js.map

Related

Pull element from array in mongodb

I'm using this schema for the USERS Collection
const usersSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
select: false,
},
phone: {
type: Number,
required: true,
},
books: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "books",
},
],
});
and when the user want to delete a book in books collection, it should be delete in the user's books document (The above array) as well.
and I use this query but i get an error
const deleteBook = async (req, res, next) => {
try {
const { id } = req.query;
const deletedProduct = await books.findByIdAndDelete(id).populate(
"author"
);
// IT WORKS AND DELETE THE BOOK IN BOOKS COLLECTION
if (!deletedProduct) {
return next(new ErrorHandler("Product Was Not Found", 404));
}
await Users.findByIdAndUpdate(
deletedProduct.author._id,
{
$pull: { books: deletedProduct._id },
},
(err, docs) => {
// ERROR IS NULL IN HERE
console.log(err);
console.log(docs);
}
);
res.status(200).json("Success");
} catch (err) {
next(new ErrorHandler(err.message, 500));
}
};
Its My query but i get this error.
"Query was already executed: users.findOneAndUpdate({ _id: new ObjectId("THE USER'S ID"
i want to say. The book will be deleted from the books collection, but stay as same in the User's books array.

Api call finds already deleted documents

I've two endpoints: one that delete product document and one that retrieve the document.
After I delete the document throught by Id, the GET api call return me already the document even if it's deleted and It's not present on MongoDb.
Response of DELETE call returns { deletedCount: 1 }
Here code of GET product:
exports.getSingleProduct = (req, res, next) => {
let id = req.params.id;
Products.findById(id).populate({ path: 'internalColor' }).then(result => {
if(result && result.visible == true) {
res.status(200).json(result)
} else {
res.status(404).json({
message: 'product_not_found',
id: id
})
}
}).catch(err => {
res.status(404).json({
message: 'error_operation: ' + err,
id: id
})
});
}
Here code of DELETE product:
exports.deleteDefinallySingleProduct = (req, res, next) => {
let id = req.params.id;
console.log(id)
Products.deleteOne({ id: id }).then(result => {
if(result) {
res.status(200).json({
message: 'deleted_product'
});
}
}).catch(err => {
res.status(404).json({
message: 'error_operation: ' + err
})
})
}
Products Model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const options = {
timestamps: true
}
const productSchema = new Schema({
name: {
type: String,
required: true
},
description: {
type: String,
required: true
},
price: {
type: Number,
required: true
},
externalUrl: {
type: String,
required: true
},
imgUrl: {
type: String,
required: true
},
brand: {
type: String,
required: true
},
visible: {
type: Boolean,
required: true
}
}, options);
const Product = mongoose.model('Products', productSchema);
module.exports = Product;
I think the error that you are facing is caused by a typo in your code.
exports.deleteDefinallySingleProduct = (req, res, next) => {
...
Products.deleteOne({ id: id }).then(result => {
if(result) {
// this code will run always
console.log(result) // will show { n: 0, ok: 1, deletedCount: 0 },
// That is, this section of code will run always despite of delete count being 0 or more due to the request to be excecuted successfully.
...
}
The correct implementation is here:
exports.deleteDefinallySingleProduct = (req, res, next) => {
...
Products.deleteOne({ _id: id }).then(result => {
...
}
This is because by default mongooose use _id representing the document id, unless create a custom id in the schema which you didn't do.

MongoDB only updates partially

My model has "id", "liked", "likedBy" and "matched" fields.
I can update my database and add id according to my hypotethical likes; it stores target's id to my current user's liked field, current user's id to target's likedBy field.
I'm trying to achieve, if a user has both liked and likedBy id matching then put liked id to my matched field on both users, but I can't for some reason. It just doesn't care if statement there.
Any ideas why?
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
}
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
//check matching
if (user.likedBy === user.liked) {
user.matched.unshift({ user: req.user.id });
}
await user.save();
await loggedUser.save();
res.status(200).send("Liked!");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
My Schema:
const mongoose = require("mongoose");
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
picture: {
data: Buffer,
contentType: String,
},
age: {
type: Number,
required: true,
},
gender: {
type: String,
required: true,
},
job: {
type: String,
required: true,
},
desc: {
type: String,
default: "Hasn't written anything yet.",
},
liked: [{}],
likedBy: [{}],
matched: [{}],
});
module.exports = User = mongoose.model("user", UserSchema);
I found the mistake I made.
I'm trying to compare objects, which isn't possible really. I got index of my array then extracted the data I need and stored it into value1 & value2.
I found my mistake the moment I console.log'ed my conditions as below:
if(console.log(user.liked) === console.log(user.likedBy)){
...}
Working version:
//like user by using it's id. update it to liked
app.put("/like/:id", auth, async (req, res) => {
try {
const user = await User.findById(req.params.id);
const loggedUser = await User.findById(req.user.id).select("-password");
//check if it is already liked
if (
user.likedBy.filter((like) => like.user.toString() === req.user.id)
.length > 0
) {
return res.status(400).json({ msg: "Already Liked" });
} else {
user.likedBy.unshift({ user: req.user.id });
loggedUser.liked.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
const value1 = user.likedBy[0].user;
const value2 = user.liked[0].user;
if (value1 === value2) {
user.matched.unshift({ user: req.user.id });
loggedUser.matched.unshift({ user: req.params.id });
await user.save();
await loggedUser.save();
res.status(200).send("Liked & Matched!");
} else {
res.status(200).send("Liked!");
}
}
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});

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');
});

get user information from with sequilize in rest API

I am creating a REST API with nodejs and sequelize and I have 2 tables:
Users table
Friends table
With
/api/friends
I get all my friends list (stored in my Friends table) but I don't know how to get their name (from the User table).
This is my request to get my friends list:
models.Friend.findAll({
where: {
$or: [{
UserID: userFound.id
},
{
idFriend: userFound.id
}],
status : "active"
}
})
In the picture I show you the Users table and Friends table
How can I get the name of friend in my request ?
UPDATE
this is my user model
'use strict';
module.exports = (sequelize, DataTypes) => {
var User = sequelize.define('User', {
email: DataTypes.STRING,
username: DataTypes.STRING,
password: DataTypes.STRING,
isAdmin: DataTypes.BOOLEAN,
isOut: DataTypes.BOOLEAN,
bio: DataTypes.STRING
}, {});
User.associate = function(models) {
// associations can be defined here
models.User.hasMany(models.Message)
models.User.hasMany(models.Friend)
};
return User;
};
and this is my friends models
'use strict';
module.exports = (sequelize, DataTypes) => {
var Friend = sequelize.define('Friend', {
UserID: DataTypes.INTEGER,
idFriend: DataTypes.INTEGER,
status: DataTypes.STRING
}, {});
Friend.associate = function(models) {
// associations can be defined here
models.Message.belongsTo(models.User, {
foreignKey:{
allowNull: false
}
})
};
return Friend;
};
and my get friends function
showFriend: function (req, res) {
var headerAuth = req.headers['authorization'];
var UserId = jwtUtils.getUserId(headerAuth);
// Params
asyncLib.waterfall([
function (done) {
models.User.findOne({
where: { id: UserId }
})
.then(function (userFound) {
done(null, userFound);
})
.catch(function (err) {
return res.status(500).json({ 'error': 'unable to verify user' });
});
},
function (userFound, TargetFound, done) {
models.Friend.findAll({
where: {
$or: [{
UserID: userFound.id
},
{
idFriend: userFound.id
}],
status : "active"
}
})
.then(function (friends) {
if (friends) {
res.status(200).json(friends);
} else {
res.status(404).json({ "error": "no friends found" });
}
})
.catch(function (err) {
return res.status(500).json({ 'error': 'cannot find Friend' })
})
}
], function (newFriend) {
if (newFriend) {
return res.status(201).json({
'newFriend': newFriend.id
})
} else {
return res.status(500).json({ 'error': 'cannot add Friendss' })
}
});
},
thanks
If Friend and Users are associated, then you have to include them in your query:
models.Friend.findAll({
where: {
$or: [
{ UserID: userFound.id },
{ idFriend: userFound.id }
],
status : "active"
},
include: [{
model: models.User
}]
})
Then you should be able to do something like:
const friends = models.Friend.findAll({ ... })
friends.forEach((friend) => {
/* depends on your Naming Strategy, I'm assuming
`Users` will load as property 'User' on `Friends`,
it depends on how your models and associations are defined
*/
console.log(friend.User.username)
})

Categories

Resources