How to update particular id information in express js - node.js

I want to update information from by particular id from a user user collection in MongoDB. I am using ExpressJS.
Right now from my code I can only update only login user information. Being a super admin I want to update user's info by ID. What do I need to do here ?
Here now in my code , when super admin logs in he/she can only update his/her own information. I want the super admin to update user's information
router.put('/edit', checkAuth, function (req, res, next) {
if(req.userData.role === 'superadmin') {
const id = req.userData.userId;
User.findOneAndUpdate({ _id: id }, {$set: req.body}, { new: true }, (err, doc) => {
if (err) return res.send(err.message)
if (doc) return res.send(doc);
})
} else {
res.status(401).send(["Not authorized. Only super admin can update details."]);
}
});
How can I update user's information from the collection ?

You need to specify the ID of another user through the request content, in Express this can easily be achieved with a path parameter:
// Example client request: PUT /edit/507f191e810c19729de860ea
router.put('/edit/:userId', checkAuth, function (req, res, next) {
if (req.userData.role === 'superadmin') {
const id = req.params.userId;
User.findOneAndUpdate({ _id: id }, {$set: req.body}, ...);
} else { /* ... */ }
});
If changing the request path (/edit) is not an option for you, you can opt to specify the target user id through the request body instead (you will also need to update the client request to pass that id along with your new user data):
router.put('/edit', checkAuth, function (req, res, next) {
if (req.userData.role === 'superadmin') {
const { id, ...newUserData } = req.body;
User.findOneAndUpdate({ _id: id }, {$set: newUserData}, ...);
} else { /* ... */ }
});

Related

Get the ID of authenticated user with passport

I have a database set up with mongoose and the authentication works via passport (passport-local-mongoose). That works fine, however, I want to get the ID of the current authenticated user so that I can use it as a condition for updating the database. Unfortunately I only know how to check if the user is authenticated in general. Here is the rough structure of what I want to do:
app.post("/updateUser", (req, res) => {
if(req.isAuthenticated()){
Users.updateOne(
{
_id: //get identity of authenticated user
},
{
test: "works!"
},
err => { ... });
}
});
you need to take it by request from user
app.post("/updateUser", (req, res) => {if(req.isAuthenticated()){Users.updateOne({ _id: req.id }, { test: "works!"}, err => { ... }); }});

api call getting affected by another api call's validation

not really sure if my title is correct but my problem is that I have this reset password token checker in my api that seems to get affected by another api that finds a specific user, this api has user validation.
Here is what they look like:
//get specific user
router.get('/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});
//reset-password token check
router.get('/reset-pass', async (req, res) => {
await User.findOne({
where: {
resetPasswordToken: req.body.resetPasswordToken,
resetPasswordExpires: {
[Op.gt]: Date.now()
}
}
}).then(user => {
if(!user) {
res.status(401).json({ error: 'Password reset link is invalid or has expired.'})
} else {
res.status(200).send({
username: user.username,
message: 'Password reset link Ok!'
});
}
});
});
then here is the validateToken
const validateToken = (req, res, next) => {
const accessToken = req.cookies['access-token'];
if (!accessToken)
return res.status(401).json({error: 'User not authenticated!'});
try {
const validToken = verify(accessToken, JWT_SECRET)
req.user = validToken;
if(validToken) {
req.authenticated = true;
return next();
}
} catch(err) {
res.clearCookie('access-token')
return res.status(400).json({error: err}).redirect('/');
}
};
when I comment out the get specific user api the reset password token check works. If I remove validateToken it returns null instead of giving me the username and message.
One of the things I notice is the route param "/:id", that means that literally everything would be processed by get specific user because all routes start with "/", only use params in routes with a prefix like "/user/:id" that way only the routes that starts with "/user" will execute that code.
Change your code to:
//get specific user
router.get('/user/:id', validateToken, async (req, res) => {
const id = req.params.id
const user = await User.findByPk(id);
res.json(user);
});

findOne filtering by objectId always returns undefined

Developing node.js app. For some reason this findOne call always returned undefined, even though I have verified that the req.params.id is valid and it should return data...
let event = await Event.findOne({ venue: ObjectId(req.params.id) });
I've also tried....
let event = await Event.findOne({ venue: req.params.id });
Here's part of my model definition for Event....
const EventSchema = new mongoose.Schema({
eventDate: {
type: Date
},
venue: {
type: mongoose.Schema.ObjectId,
ref: 'Venue'
},
In postman I am doing a DELETE http verb against /venues/5e0401c4a246333ca4ced332 url for your information.
Basically I'm wanting to search for the venue id in the events table. I'm seeing if this venue is being used by an event before I decide to delete it.
Here's the entire method...
// #desc Delete venue
// #route DELETE /api/v1/venues/:id
// #access Private
exports.deleteVenue = asyncHandler(async (req, res, next) => {
let venue = await Venue.findById(req.params.id);
if (!venue) {
return next(
new ErrorResponse(`No venue with the id of ${req.params.id}`, 404)
);
}
if (req.user.role !== 'manager' && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
// the following line always returns undefined even though the venue
// exists in the events table.... then it jumps to the end of the method
let event = await Event.findOne({ venue: ObjectId(req.params.id) });
if (event) {
return next(
new ErrorResponse(
`You cannot delete a venue because events are tied to it.`,
404
)
);
}
await Venue.remove();
res.status(200).json({
success: true,
data: {}
});
});
Using Compass, looking at the events collection I definitely see records using the id I submitted...
_id:5e045b6e0c38f2502440ecb7
attendees:Array
eventDate:2020-01-01T05:00:00.000+00:00
venue:5e0401c4a246333ca4ced332
status:"planning"
I did...
console.log(`id is ${req.params.id}`);
let event = await Event.findOne({ venue: req.params.id });
The results showed I was passing the correct id.... (console output)
id is 5e0401c4a246333ca4ced332
ReferenceError: Event is not defined
at C:\Projects\abc\controllers\venues.js:90:15
Any insight would be appreciated. Thanks!
It seems you are not importing your Event model.
You need to add:
const Event = require("../models/event"); //you need to change the path
After that you need to find the event like this:
Event.findOne({ venue: req.params.id }).
Also I have a few suggestions:
1-) It makes more sense to check role authorization in the beginning.
2-) First check if there is any Event for the given Venue, and if there is no events, use findByIdAndDelete method. This way we can reduce number of db access, since we eliminated Venue.findById
3-) In the case a venue has events, using a 404 status code does not seem correct.
I think 400 - Bad request is more appropriate.
4-) mongoose converts _id's, so there is no need to use ObjectId.
exports.deleteVenue = asyncHandler(async (req, res, next) => {
if (req.user.role !== "manager" && req.user.role !== "admin") {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
let event = await Event.findOne({ venue: req.params.id });
if (event) {
return next(new ErrorResponse(`You cannot delete a venue because events are tied to it.`,400));
}
let venue = await Venue.findByIdAndDelete(req.params.id);
console.log(venue);
if (!venue) {
return next(new ErrorResponse(`No venue with the id of ${req.params.id}`, 404));
}
res.status(200).json({
success: true,
data: {}
});
});
Test:
Let's say we have the following two Venue document.
{
"_id" : ObjectId("5e0471e3394d1e2b348b94aa"),
"name" : "Venue 1",
},
{
"_id" : ObjectId("5e0471eb394d1e2b348b94ab"),
"name" : "Venue 2",
}
And one Event document whose venue is Venue 1 with id 5e0471e3394d1e2b348b94aa:
{
"_id" : ObjectId("5e04727c76da213f8c9bf76a"),
"eventDate" : ISODate("2019-12-26T11:41:16.019+03:00"),
"name" : "Event 1",
"venue" : ObjectId("5e0471e3394d1e2b348b94aa")
}
When we want to delete the Venue 1, this will result with the following error, because it has an event:
You cannot delete a venue because events are tied to it. with status code 400.
Do it with findOneAndDelete of mongoose and Expressjs:
const express = require("express");
const router = express.Router();
router.delete("/:id", (req, res) => {
if (!mongoose.Types.ObjectId.isValid(req.params.id)) { //checking if id valid
return res.send("Please provide valid id");
}
var id = mongoose.Types.ObjectId(req.params.id); // passing it into a var
Event.findOneAndDelete({ venue: id }).then( res.json({ success: true }));
});
module.exports = router;
for details https://mongoosejs.com/docs/api/query.html#query_Query-findOneAndDelete
exports.deleteVenue = asyncHandler(async (req, res, next) => {
//First check role. Because it not call database
if (req.user.role !== 'manager' && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
//Next step, check event
const event = await Event.findById({ venue: ObjectId(req.params.id) });
if (event) {
return next(
new ErrorResponse(
`You cannot delete a venue because events are tied to it.`,
404
)
);
}
//Delete venue by Id
await Venue.deleteOne({ id: req.params.id }, (error, result) =>{
if(error) return next(
new ErrorResponse(`No venue with the id of ${req.params.id}`, 404)
);
res.status(200).json({
success: true,
data: {}
});
})
});

Why does "res.send" giving me an older version of a Mongoose document?

I have an express API endpoint that deletes a Post. I have attached a mongoose pre-hook that, in turn, deletes also references to the Comment model and references from the User model:
PostSchema.pre("remove", async function() {
await Comment.remove({ _postId: this._id }).exec();
await User.update({ $pull: { _posts: this._id } }).exec();
});
This hook successfully removes all references to/from Comment and User models when the Post is removed. However, when I send the User model back to the user as a response (res.send(user)), I am still getting the reference to the Post. I know it's an older version that is being sent because when I manually query the database, I see that the Post reference was actually removed.
This is how the API looks like:
app.delete(
"/api/posts/:postId",
requireAuth,
async (req, res, next) => {
try {
const post = await Post.findById(req.params.postId);
if (post._userId.equals(req.user._id)) {
await post.remove();
const user = await req.user.save();
res.send(req.user);
} else {
res.send("Error:", "This post does not belong to you");
}
} catch (err) {
next(err);
}
}
);
(Note that requireAuth is a middleware that uses passportjs to get the jwt and deserialize the user)
In requireAuth you get User from database and store it in req.user
await post.remove(); you removed Post and Post reference from User, all ok
const user = await req.user.save(); - You are saving old User (grabbed from no1.) to database. Not good. You must update User object stored in req.user before any other action. req.user still store old version of User.
In my opinion instead of
const user = await req.user.save();
You should get fresh user from database (find), assign fresh user to req.user and finaly pass it to res.send(user)
app.delete(
"/api/posts/:postId",
requireAuth,
async (req, res, next) => {
try {
const post = await Post.findById(req.params.postId);
if (post._userId.equals(req.user._id)) {
await post.remove();
const user = await User.findById(req.user._id).exec();
req.user = user;
res.send(req.user);
} else {
res.send("Error:", "This post does not belong to you");
}
} catch (err) {
next(err);
}
}
);

How to save user id value from database on server?

I have login/signup routes where I save a user to database. Every user has its own page where he can customize eveything. For example user changes his status. Status component makes ajax call to my server, and then my server tries to find a user from the curent session (I do not know how to do that) and then changes his status property in db.
I'm using React, Express, Mongoose.
I thought I coul'd save a user to my req object on /login or /signup route but this doesn't work.
api.get('/login', (req) => {
...getting data from req obj
req.user = user
...doing other things
});
The req object contains data coming in from the http call.
For example if your react app made a GET request to this url
http://localhost/user/123
and you had defined a route in your express app like this
router.get('user/:id', getUser());
then you can access this request object to get the user id in the http url param.
function getUser(req, res) {
let user_id = req.params.id
}
with that user_id you can find a user in the mongodb using mongoose like this.
function getUser(req, res) {
let user_id = req.params.id;
User.findOne({'_id': user_id}, (err, user) => {
if(err) {
return res.json(err);
}
return res.json(user);
});
}
or you can update a user object
function update(req, res) {
let user_id = req.params.id;
let avatar_url = req.body.avtar;
User.findOne({ '_id': user_id }, (err, user) => {
if (err) {
return res.json(err);
}
user.avatar = avatar_url;
user.save((err, user) => {
if(err) {
return res.json(err);
}
return res.json(user);
})
});
}
Recommend you read this to learn the basics.
http://mongoosejs.com/docs/guide.html
https://scotch.io/tutorials/build-a-restful-api-using-node-and-express-4

Resources