How to find value inside array of object with mongoose? - node.js

I have events system with different role for each event (same user could be different role in different events).
I created collection of the users and this is the schema that i used:
const userSchema = new mongoose.Schema(
{
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
permissions: [{
eventId: { type: mongoose.Schema.Types.ObjectId, required: false, ref: 'Event' },
role: { type: String, required: false }
}]
},
{timestamps: true}
);
For check if the user is allowed to get this event I created middleware that need to check if the eventId is exist in the User collection under "permissions"
so this is the code that I was create:
const authorization = async (req, res, next) => {
try {
const eventId = req.params.id;
const token = req.headers.authorization.split(' ')[1]
const tokenDecoded = jwt.verify(token, process.env.JWT_SECRET);
const userId = tokenDecoded.id
console.log(userId)
const userPermissionCheck = await User.find({ _id: userId, 'permissions.eventId': { $in: eventId } } );
console.log(userPermissionCheck)
next();
} catch (error) {
res.status(401).json({ message: 'Auth failed.' })
}
}
My problem is that my find function in the authorization middleware is not working...
What the correct way to search key of object in array with mongoose?
thanks

It seems that you are on the right track from your code, but you do not need the $in operator. You should be able to do the following:
const userPermissionCheck = await User.find({ _id: userId, 'permissions.eventId': eventId });

Related

username on posts not updated after profile information update in Node.js/mongoosedb

I am using Mongoose with express.js and will like to update user information across all instances.
When user updates their profile information, it gets updated on their profile document in the mongoose database. However, their username has refused to be updated on their posts. I need their username updated across all instances including their post username. Thank you for the help
I know I am not doing something right.
Here are my codes:
//user updates profile information
router.put("/:id", async (req, res) =>{
if(req.body.userId === req.params.id){//we checked if the user id matched
if(req.body.password){
const salt = await bcrypt.genSalt(10);
req.body.password = await bcrypt.hash(req.body.password, salt);
}
try{
const updatedUser = await User.findByIdAndUpdate(req.params.id,{
$set: req.body,
}, {new: true}); //findbyidandupdate is an inbuilt method
res.status(200).json(updatedUser)
} catch(err){
res.status(500).json(err) //this handles the error if there is one from the server
}
} else{
res.status(401).json("You can only update your account!")
}
});
User updates post codes
router.put("/:id", async (req, res) =>{
//find post that will be updated
try{
const post = await Post.findById(req.params.id);
if(post.username == req.body.username )
try{
const updatedPost = await Post.findByIdAndUpdate(req.params.id,
$set: req.body
}, {new: true})//this makes it possible to see the updated post
res.status(200).json(updatedPost)
}catch(err){
res.status(401).json(err)
}
} else{
res.status(401).json("you can only update your posts")
}
}catch(err){
res.status(500).json(err)
}
});
User model
const UserSchema = new mongoose.Schema({
username:{
type: String,
required: true,
unique: true
},
email:{
type: String,
required: true,
unique: true
},
password:{
type: String,
required: true
},
profilePicture:{
type: String,
default: "",
},
}, {timestamps: true}
);
//exporting this schema
module.exports = mongoose.model("User", UserSchema); //the module name is "User"
Post model
const PostSchema = new mongoose.Schema(
{
title:{
type: String,
required: true,
unique: true
},
description:{
type: String,
required: true,
},
postPhoto:{
type: String,
required:false,
},
username:{
type: String,
required: true,
},
categories:{
type: Array,
required: false
},
}, {timestamps: true}
);
/ /exporting this schema
module.exports = mongoose.model("Post", PostSchema); //the module name is "Post"
where I used it in React.js for profile update
const handleUpdate = async (e) =>{
e.preventDefault();
dispatch({type: "UPDATE_START"})
const updatedUser = {
userId: user._id,
username,
email,
password
};
try{
const response = await axios.put("/users/" + user._id, updatedUser)
dispatch({type: "UPDATE_SUCCESS", payload: response.data})
setUpdated(true)
}catch(err){
}
}
Where I used it in React.js for post update
//this function handles the update of the post by the user
const handleUpdate = async () =>{
try{
await axios.put(`/posts/${post._id}`, {
username: user.username,
title:title,
description: description
});
// window.location.reload("/")
setUpdateMode(false)
}catch(err){
}
}

how to fetch user details from populated table in nodejs?

I have two tables in my MongoDB cluster i.e Post and User. Actually I want to retrieve the username and userID along with a post from the post table when a user posts something.
my post controller
router.getPostsList = async (req, res) => {
try {
const posts = await Post.list();
return res.status(200).json(posts);
} catch (err) {
return res.status(400).json(err);
}
};
MongoDB Post collection Image
PostSchema
const PostSchema = new mongoose.Schema(
{
text: {
type: String,
trim: true
},
slug: {
type: String,
trim: true,
lowercase: true
},
user: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
]
},
{ timestamps: true }
);

Accessing a schema inside a schema using Express Router and MongoDG

I'm trying to create a route where it takes in a parameter for a username and then displays that users information. Only thing is, the username is in the user schema from when the user signs up. The profile schema references the user schema. How do I use the username parameter in the findOne call to display the users profile data?
User schema:
const UserSchema = new Schema({
username: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
module.exports = User = mongoose.model("users", UserSchema);
Profile schema:
const ProfileSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "users"
},
name: {
type: String
},
image: {
type: String
},
bio: {
type: String
},
location: {
type: String
},
website: {
type: String
},
social: {
youtube: {
type: String
},
facebook: {
type: String
},
instagram: {
type: String
},
twitter: {
type: String
}
}
});
module.exports = User = mongoose.model("profile", ProfileSchema);
Route:
router.get("/user/:username", (req, res) => {
const errors = {};
Profile.findOne({ user: req.params.user.username })
.populate("user", "username")
.then(profile => {
if (!profile) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile);
})
.catch(err => res.status(404).json(err));
});
Please try this :
router.get("/user/:username", async (req, res) => {
const errors = {};
try {
const profile = await User.aggregate([
{ $match: { username: req.params.username } },
{ $lookup: { from: "profile", localField: "_id", foreignField: "user", as: "userProfile" } },
{ $project: { userProfile: { $arrayElemAt: ["$userProfile", 0] }, username: 1, _id:0 } }
]).exec();
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
} catch (error) {
console.log('Error in retrieving user from DB ::', error);
return res.status(404);
}
})
Try using aggregate, firstly you check-in user table for getting details of a specific username then fetch the profile details as below using lookup, if no profile found after unwind the document will not be fetched and you can check on aggregate result's length as aggregate always return an array in result :
User.aggregate([
{$match:{ username: req.params.user.username }},
{$lookup:{from:"profile",localField:"_id",foreignField:"userId",as:"profileData"}},
{$unwind:"$profileData"},
{$project:{profileData:1,username:1}}
{$limit:1}
])
.then(profile => {
if (!profile.length) {
errors.noprofile = "There is no profile for this user";
return res.status(404).json(errors);
}
res.json(profile[0]);
})
You can do it in 2 steps.
Look for users containing username in userSchema, get it's id.
Then in promise, use that id to, look for profileSchema contains.
router.get("/user/:username", (req, res) => {
users.findOne({ username: req.params.username }).then(_user=>{
profile.findOne({ user: _user._id }).populate('user').then(_profile => {
res.json(_profile);
})
})
});
This code will look for username in userSchema and look for userSchema's id in profileSchema then returns profileSchema populated with user.

How do I get the logged-in User ID when creating a post request for that user without using req.params.id?

Let's say I have an API with a /posts endpoint. Each post in my Mongoose schema has a reference to the user that created it, and each user has an array of references to the posts they created.
When posting as a logged-in user, in order to save that reference I need to send the current logged-in user ID along with the content of the post to the /posts endpoint. I would prefer not to do so through some nested query like /users/:id/posts and then send req.params.id. I would like to post directly to /posts but send the user.id in the request somehow.
User model:
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
});
Posts model:
const PostSchema = new Schema({
content: {
type: String,
required: true
},
created: {
type: Date,
default: Date.now
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
Create new post (need a way to get the user ID since it won't actually be in req.params)
exports.createPost = async function(req, res, next) {
try {
const { content } = req.body; // ideally would get user ID from here
const post = await db.Post.create({
content,
user: req.params.id
});
const postUser = await db.User.findById(req.params.id);
postUser.posts.push(post.id);
await postUser.save();
const newPost = await db.Post.findById(post.id);
const {id, created} = newPost;
return res.status(201).json({
id,
content,
created
})
}
catch(err) {
return next(err);
}
}
I know I'm probably missing something obvious, but I appreciate any suggestions.

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