node/express duplicate variable issue - javascript

I've got some checkuser middleware that stores the user entry when a JWT is verified. However, when I include it in my routes and try to console.log(res.locals.user.username) I get the username logged twice. When I'm trying to store this username in some JSON, its creating a seperate JSON with {username: ___} that is causing issues in Mongoose. Help would be appreciated, thanks.
const checkUser = async (req, res, next) => {
const token = req.cookies.jwt
if (token) {
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, async (err, decodedToken) => {
if (err) {
res.locals.user = null
console.log(err.message)
next()
}
else {
const user = await User.findById(decodedToken.id)
res.locals.user = user
next()
}
})
}
else {
res.locals.user = null
next()
}
}
app.post('*', checkUser) //all routes
app.post('/newuser', requireAuth, async (req, res) => {
let user = res.locals.user
console.log(res.locals.user.username)
let user_req_body = req.body
let starter_workout = {}
starter_workout.username = user.username
user_req_body.username = user.username
if (user_req_body.FitnessMode == 'cardio') {
starter_workout.workout = cardio
starter_workout.workout_name = 'Default Cardio'
}
else if (user_req_body.FitnessMode == 'hypertrophy') {
starter_workout.workout = hypertrophy
starter_workout.workout_name = 'Default Hypertrophy'
}
else if (user_req_body.FitnessMode == 'powerlifting') {
starter_workout.workout = powerlifting
starter_workout.workout_name = 'Default Powerlifting'
}
else if (user_req_body.FitnessMode == 'calisthenics') {
starter_workout.workout = calisthenics
starter_workout.workout_name = 'Default Calisthenics'
}
const user_info = new userInfo(user_req_body)
const workout_info = new routines(starter_workout)
/*
await user_info.save()
.then(resp => console.log(resp))
.catch(err => console.log(err))
await workout_info.save()
.then(resp => console.log(resp))
.catch(err => console.log(err))
This code will send duplicated data to MongoDB. Also worth noting that this happens only with app.use(express.json()). I guess thats where I would need help with some work-around. Thank you.

If console.log(res.locals.user.username) inside of this:
app.post('/newuser', ...)
is outputting twice, then that's because your server is getting two posts requests to /newuser. A common reason for that is if this comes from a <form> in your web page that you are also sending an ajax call from javascript in the page. If you don't properly use e.preventDefault() to prevent the default post that the browser does automatically, then you will get duplicate post requests. To look into that more, we'd have to see how the /newuser request comes from the web page.

Related

Put and delete method is showing 404 in postman

0
I have been banging my head the long hours trying to figure why my PUT and DELETE request does not work. It returns a 404 not found response. My POST and GET all work fine.
I use chrome postman
app.put('api/courses/:id', (req, res) => {
const course = courses.find(c => c.id === parseInt(req.params.id));
if (!course) return res.status(404).send('This course with the given id was not found');
const { error } = validateCourse(req.body);
if (error)
return res.status(400).send(error.details[0].message);
course.name = req.body.name;
res.send(course);
});
app.delete('api/courses/:id', (req, res) => {
const course = courses.find(c => c.id === parseInt(req.params.id));
if (!course) return res.status(404).send('this course with the given ID is not valid');
const index = courses.indexOf(course);
courses.splice(index, 1)
res.send(course);
})
function validateCourse(course) {
const schema = {
name: Joi.string().min(3).required()
};
return Joi.validate(course, schema);
}
I am trying to create a simple api in Node.js. The http method is not working
Add a leading / to the route definitions:
app.put('/api/courses/:id', ...);
There is no such thing as relative routes on the server.

Unhandled Rejection (SyntaxError): Unexpected end of JSON input with Get request fetch

I am trying to do a MVC architecture tutorial from Codecademy's website: https://www.codecademy.com/article/mvc-architecture-for-full-stack-app
I finished the tutorial but when I run everything, I get this error:
It seems that what I'm returning is not valid JSON. So I think the problem is that the endpoint may be causing the error. But I'm not too sure. Here is the code where the error is triggered:
src/utils/index.js:
export const fetchExpenses = async (date) => {
const selectDate = new Date(date).getTime() || new Date().getTime();
const res = await fetch(`/api/expense/list/${selectDate}`);
console.log('result',res);
return res.json();
};
Here is the code from app.js in the "view" portion of my code:
useEffect(() => {
// update view from model w/ controller
fetchExpenses().then((res) => setExpenses(res));
}, []);
It seems the problem is the communication between the view and the controller. When I create an expense, it actually is updated in the database:
Any ideas why this error is happening?
Edit 1:
Here is the network response when I try to create a new expense in my application. So it seems that when I create a new expense, the fetchExpenses() is automatically called to display a list of current expenses.
this the raw response I get from fetchExpenses() :
Edit 2:
Here is what the header shows from the response:
The endpoint is causing the error, but I'm not sure why. Here is the endpoint:
export const createExpense = async (data) => {
const res = await fetch(`/api/expense/create`, {
method: 'POST',
body: data,
});
return resHandler(res, 201);
};
and here is resHandler() which createExpense() returns:
export const resHandler = async (res, status) => {
if (res.status === status) {
return null;
}
const data = await res.json();
if (data && data.emptyFields) {
return data.emptyFields;
}
return null;
};
Here is the code from the controller when an expense is created:
exports.create = (req, res) => {
const form = new formidable.IncomingForm();
form.keepExtensions = true;
form.parse(req, async (err, fields) => {
const { title, price, category, essential, created_at } = fields;
// check for all fields
if (fieldValidator(fields)) {
return res.status(400).json(fieldValidator(fields));
}
try {
const newExpense = await pool.query(
'INSERT INTO expenses (title, price, category, essential, created_at) VALUES ($1, $2, $3, $4, $5)',
[title, price, category, essential, created_at]
);
return res.status(201).send(`User added: ${newExpense.rows}`);
} catch (error) {
return res.status(400).json({
error,
});
}
});
};
Edit 3
Here is the route /api/expense/list/{dateTime}:
const express = require('express');
const router = express.Router();
const { create, expenseById,
read, update, remove, expenseByDate } = require('../controllers');
router.get('/expense/list/:expenseDate', expenseByDate, read);
module.exports = router;
And here is my controllers.js that deal with the route above:
exports.expenseByDate = async (req, res, next, date) => {
try {
const expenseQuery = await pool.query(
'SELECT * FROM expenses WHERE created_at BETWEEN $1 AND $2',
[
startOfDay(new Date(Number(date))).toISOString(),
endOfDay(new Date(Number(date))).toISOString(),
]
);
const expenseList = expenseQuery.rows;
req.expense =
expenseList.length > 0
? expenseList
: `No expenses were found on this date.`;
return next();
} catch (error) {
return res.status(400).json({
error,
});
}
};
exports.read = (req, res) => res.json(req.expense);
The reason you are getting an Unhandled Rejection (SyntaxError): Unexpected end of JSON input error is because your client app is expecting a JSON response and the express app /api/expense/list/{dateTime} route is not returning valid JSON.
The app is not returning valid JSON because the expenseByDate controller callback has an incorrect function signature so it is not getting called.
exports.expenseByDate = async (req, res, next, date) => <-- "date" is not a valid parameter.
This leads the read controller to return an undefined value to the json response.
exports.read = (req, res) => res.json(req.expense); <-- req.expense is undefined.
res.json(undefined) ultimately returns an empty response to the client which can't be parsed and thus an error is thrown.
Solution
You can fix this error by correcting the expenseByDate controller to have a valid function signature by removing the fourth method parameter. To access a route parameter you should use req.params.
exports.expenseByDate = async (req, res, next, date) => {
const date = req.params.expenseDate;
...
}

How do I set specific roles to redirect from specific routes with Custom Claims?

I am using NodeJS, Express and plain vanilla javascript/html. Not React or anything else.
With firebase I made it to when the user registers, they will automatically be called a customer (on the server-side). As shown:
server.js
app.post('/register', (req,res) => {
let {first_name, last_name, email, uid} = req.body;
db.collection('users').doc(uid).set(req.body)
.then(data => {
res.json({
uid: req.body.uid,
first_name: req.body.first_name,
last_name: req.body.last_name,
email: req.body.email,
seller: req.body.seller
})
})
admin.auth()
.setCustomUserClaims(uid, {
type: "customer",
})
.then(() => console.log('done'))
})
But now, I would like to make this route to where it will redirect if the type is a customer.
if(idToken.claim.type === 'customer') {redirect('/') }
app.get('/seller', (req,res) => {
res.sendFile(path.join(staticPath, "seller.html"));
})
So I thought, what if I were to get the Token from the user and the type as soon as they log in, and send it back to the client. This will work.
login.js
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
fetch('/getMyClaims', {
method: 'post',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({uid: user.uid,
idToken: idTokenResult.claims.type}),
})
.then(() => res.json)
.catch(err => console.log(err));
});
and now my server.js now includes:
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
and this is where I get stuck, because I am trying to find out how can I call the results of '/getMyClaims' to redirect a user, if they are a customer and are trying to access the '/seller' URL.
I did read the documents as given https://firebase.google.com/docs/auth/admin/custom-claims, but it does not really show how to re-route if claim has a specific type in the backend.
I've figured things out after hours of this!
server.js
var block;
var blockware = (req,res,next) => {
if(block == true || block == undefined){
console.log("deny access", block);
return res.sendStatus(401);
}
console.log("allow",block);
next();
}
app.post('/getMyClaims', async(req,res) => {
let {uid,idToken} = req.body;
if(idToken === 'customer'){
block = true;
} else if(idToken === 'admin'){
block = false;
} else {
block = true;
}
admin.auth()
.getUser(uid)
.then((userRecord) => console.log(userRecord))
})
app.get(['/seller', '/products', '/'], blockware, (req,res) => {
res.sendFile(path.join(staticPath, ""));
})
So now if user has a customer type claim then they are blocked from accessing seller. Otherwise, admin can access seller.
Even when user is logged out since it is automatically set to true, everyone will be blocked from it.
referenced this: express.js - single routing handler for multiple routes in a single line

Send Map in express res.send

I'm working on a game with a friend and we need to send a Map with some stuff in it, but express only sends the user {} instead of the actual Map. The problem is at sending it and not the code itself, console.log'ging it does return the Map.
Code:
router.get("/list", async (req, res) => {
try {
const users = await userCollection.find();
accessedListEmbed(req);
let userData = new Map();
users.forEach((user) => userData.set(user.userName, user.status));
res.send(userData);
console.log(userData);
} catch (error) {
res.send("unknown");
}
});
Generally, you can only send serializable values over the network. Maps aren't serializable:
const map = new Map();
map.set('key', 'value');
console.log(JSON.stringify(map));
Either send an array of arrays that can be converted into a Map on the client side, or use another data structure, like a plain object. For example:
router.get("/list", async (req, res) => {
try {
const users = await userCollection.find();
accessedListEmbed(req);
const userDataArr = [];
users.forEach((user) => {
userDataArr.push([user.userName, user.status]);
});
res.json(userDataArr); // make sure to use .json
} catch (error) {
// send JSON in the case of an error too so it can be predictably parsed
res.json({ error: error.message });
}
});
Then on the client-side:
fetch(..)
.then(res => res.json())
.then((result) => {
if ('error' in result) {
// do something with result.error and return
}
const userDataMap = new Map(result);
// ...
Or something along those lines.

How to send message in DELETE route - express.js

I want to show message after deleting user but I don't know how to do it. I tried to create req.session properties and then use them but they are not available in GET route. Do you know how to fix this code?
router.get("/", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
let currentMessage = req.session.message;
let currentState = req.session.state;
req.session.message = undefined;
req.session.state = undefined;
console.log(currentState, currentMessage); //undefined
user.getAll()
.then(result => {
res.render("users", {
name: req.user,
users: result,
msg: currentMessage,
state: currentState
})
})
});
// delete route
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
req.session.message = "Some message!"
req.session.state = true;
})
});
// jquery
function ajaxDelete(ev, url) {
ev.preventDefault();
$.ajax({
url: url,
type: "DELETE"
});
}
delBtn.click(function(e) {
var user = $(this).data("user");
ajaxDelete(e, "/users/delete/" + user);
window.location.href = "/users";
})
Use res parameter, and make a variable called message
const message= 'MyMessage';
then
res.json ({message}) // es6 feature
output
{"message":"myMessage"}
In your scenario, as far as I understand you want to send the JSON in response. You can use this code
router.delete("/delete/:id", mid.isExpired, mid.isLoggedIn, mid.isAdmin, (req, res) => {
user.del(req.params.id)
.then(() => {
var response = { message : "Some message!",
state : true };
return res.json(response);
})
});
the keyword 'return' is as per your requirement
router and session are middleware to any nodeJs App,If the router is added before session like this:
app.use(router)
app.use(session(...));
Then the session middleware won't get called for any requests that get handled by router.
Hence change the order of adding router and session middleware,like this
app.use(session(...));
app.use(router)

Categories