ES6 return from internal function - node.js

I have some code on my back end that I am using to try and verify apple purchase receipts with the library node-iap.
I've got all the receipt to verify but I am struggling with something really simple:
Trying to get the code to return out of the iap.verifyPayment method to carry on with the code execution in the subscribe method and return a success. It just hangs within handleAppleSub and doesn't return.
export const subscribe = (req, res, next) => {
return User.findOne({
where: {
ID: req.params.ID
}
})
.then(
user =>
user
? req.body.OS == "android"
? handleAndroidSub(user, req.body.receipt)
: handleAppleSub(user, req.body.receipt)
: notFound(res)
)
.then(success(res))
.catch(next);
};
const handleAppleSub = (user, receipt) => {
var platform = "apple";
var payment = {
receipt: receipt.transactionReceipt,
productId: receipt.productId,
packageName: "com.app.test",
secret: appleSecret,
excludeOldTransactions: true
};
return iap.verifyPayment(platform, payment, function(error, response) {
user.dataValues.SubAppleReceipt = receipt.transactionReceipt;
return User.update(user.dataValues, {
where: { ID: user.dataValues.ID }
}).then(user => {
return user;
});
});

Related

Fetch in vue 3 stops backend and does nothing after working fine one time

So, my problem is when I try to login in my vue app, the backend automatically stops when I try to fetch from it an array of objects.
To be more specific.
This is my fetch "attempt" to retrieve the objects from the database.
let url = utils.url;
let requestParam = utils.globalRequestParameters;
requestParam.method = "GET";
requestParam.body = null;
if (cars.value.length == 0) {
fetch(url + "cars", requestParam).then((res) =>
res.json().then(async (res) => {
store.dispatch("Car/fetchCars", res);
fetch(url + "users", requestParam).then((users) =>
users.json().then((users) => {
for (let car of res) {
let userCar = Object.values(users).find(
(a) => a.id == car.userId
);
car.userName = userCar.lastName + " " + userCar.firstName;
}
})
);
})
);
}
And login in view Login.vue
let requestParameters = utils.globalRequestParameters;
requestParameters.method = "POST";
requestParameters.body = JSON.stringify(data);
fetch(utils.url + "login", requestParameters).then((res) => {
res.json().then((res) => {
this.mesaj = res.message;
console.log("token:" + res.token);
if (res.token) {
localStorage.setItem("token", res.token);
console.log("token:" + res.token);
console.log("id:" + res.id);
let id = res.id;
requestParameters.method = "GET";
requestParameters.body = null;
this.$store.dispatch("login", true);
fetch(utils.url + "users/" + id, requestParameters).then(
(res) => {
res.json().then((res) => {
console.log("Jos de tot");
this.$store.dispatch("User/setUser", res);
console.log(this.$store.state.User.user);
this.$router.push("/");
});
}
);
}
});
});
}
},
Note.
cars is a computed value that return store.state.cars
and utils is
let url = "http://127.0.0.1:3000/";
let globalRequestParameters = {
method: "GET",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer",
};
module.exports.globalRequestParameters = globalRequestParameters;
module.exports.url = url;
Here at the first fetch the backend stops and also the fetch it is not done.
And the backend route is
router.get('/cars', async (req, res) => {
res.json(await functions.getAllCars(req,res));
})
getAllCars = async (req, res) => {
const snapshot = await db.collection("Cars").get();
let cars = [];
snapshot.forEach((doc) => {
let car = {
id: doc.id,
userId: doc.data().userId,
manufacturer: doc.data().manufacturer,
model: doc.data().model,
color: doc.data().color,
plate: doc.data().plate,
price: doc.data().price,
description: doc.data().description
};
cars.push(car);
});
res.status(200).send(cars);
return
};
router.get("/users/:id", async (req, res) => {
res.json(await functions.getUserById(req.params.id, res));
});
getUserById = (id, res) => {
db
.collection("Users")
.doc(id)
.get()
.then((response) => {
let user = {};
user.id = response.id;
user.firstName = response.data().firstName;
user.lastName = response.data().lastName;
user.gender = response.data().gender;
user.jobTitle = response.data().jobTitle;
user.phone = response.data().phone;
user.email = response.data().email;
user.isAdmin = response.data().isAdmin;
res.status(200).send(user);
return
})
.catch((err) => {
res.status(404).send({ message: "User not found" });
return
});
};
The user is retrieved correctly, I see it in console through a console log, but the messages that I get in the terminal and console are:
*As a final note. I use vue 3, node.js version 16.13.0 and Firestore as Database. And yesterday everything was working perfectly fine on my other computer but I had to go somewhere and use my laptop. Maybe there is something about my laptop. All I did was just to install the modules for the front and back
I think this has nothing to do with Vue - it is simply the problem of your Express backend code
ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
As described here:
That particular error occurs whenever you try to send more than one response to the same request and is usually caused by improper asynchronous code.
getAllCars
getAllCars is async function with await inside - as soon as this await is hit (together with db.collection("Cars").get() call), function returns Promise which is awaited at res.json(await functions.getAllCars(req,res));
When the DB call finishes, the rest of the method is executed including res.status(200).send(cars) - this will send the cars array to the client and returns undefined (this is what simple return does) and res.json(undefined) is executed causing the error above (you are trying to send second response)
getUserById
You say that this handler works fine but I really doubt it - from what I see, this should NOT work either
You are calling it with res.json(await functions.getUserById(req.params.id, res));. To await actually doing something, the awaited function must return a Promise (either implicitly by using await inside or explicitly) or general "thenable" object. The getUserById function returns nothing (return statements inside then() or catch() does not count! ...those are different functions)
This problem can be fixed by doing return db.collection("Users").doc(id).get().then() but then you will get same error as in getAllCars case
Correct pattern
Do not use res.status(200).send() and res.json() together
For the sake of sanity (at least until you really know what you are doing) do not mix promises with async/await
async functions should return the data (do not use return without "argument")
Following code shows both Promise based and async/await style (it "pseudo code" in the sense I did not tested it but hopefully you get the idea)
router.get('/cars', async (req, res) => {
try {
const response = await functions.getAllCars()
res.status(200).json(response);
} catch() {
res.sendStatus(500)
}
})
getAllCars = async () => {
const snapshot = await db.collection("Cars").get();
let cars = [];
snapshot.forEach((doc) => {
let car = {
id: doc.id,
userId: doc.data().userId,
manufacturer: doc.data().manufacturer,
model: doc.data().model,
color: doc.data().color,
plate: doc.data().plate,
price: doc.data().price,
description: doc.data().description
};
cars.push(car);
});
// res.status(200).send(cars); //* should be handled by caller
return cars //* return the data
};
router.get("/users/:id", async (req, res) => {
functions.getUserById(req.params.id)
.then((response) => {
if(response === null)
res.status(404).json({ message: "User not found" });
else
res.status(200).json(response);
})
.catch(er) {
res.status(500).send(er.message)
}
});
getUserById = (id) => {
return db //* return the promise
.collection("Users")
.doc(id)
.get()
.then((response) => {
let user = {};
user.id = response.id;
user.firstName = response.data().firstName;
user.lastName = response.data().lastName;
user.gender = response.data().gender;
user.jobTitle = response.data().jobTitle;
user.phone = response.data().phone;
user.email = response.data().email;
user.isAdmin = response.data().isAdmin;
// res.status(200).send(user); //* should be handled by caller
return user //* return the data
})
.catch((err) => {
return null
});
};

How do I get useState to render each time I update the state?

I have a level up bar, where if you click the plus button it increases the bar by 10%.
I am using useEffect to grab the skills of a specific user by filtering what I get back from mongoDB(Mongoose).
I created a refreshHandler callback function that I calling to increment refresh, so that useEffect would make a request again to my back-end and I was hoping it would render the component again.
It does this once at start, then one more time and then stops rendering. (The changes are still happening in the Database, although the level only goes up one time when clicked)
App.js
function App() {
// States
const [inputText, setInputText] = useState('');
const [skills, setSkills] = useState([]);
const [session, setSession] = useState({ auth: false, token: '', user_id: '', refresh: 0 });
var { isLoggedIn, refresh } = session;
const userAuth = (data) => {
setSession({
auth: true,
token: data.token,
user_id: data.user_id,
});
console.log('User logged in successfully!');
};
const refreshHandler = () => {
setSession({ ...session, refresh: session.refresh + 1 });
};
useEffect(() => {
console.log('USE EFFECT MOUNTED!!');
const fetchSkills = async () => {
const response = await axios.get('http://localhost:3001/api/users/fetchSkills');
console.log('response: ', response.data);
setSkills(
response.data.filter((skill) => {
if (skill.user_id == session.user_id) {
return skill;
}
})
);
};
fetchSkills();
}, [refresh]);
This is the skill.js component that I am using in my skillList component to show a list of skills.
skill.js
const Skill = ({ refreshHandler, text, level, skills, skill, setSkills, percent }) => {
const levelUpHandler = async () => {
let data = { _id: skill._id, level: skill.level, percent: skill.percent };
if (skill.percent == 90) {
data.percent = 0;
data.level += 1;
} else {
data.percent += 10;
}
const response = await axios
.put('http://localhost:3001/api/users/update-skill', data)
.then(console.log(data))
.catch((err) => console.log(err));
refreshHandler();
};
I tried using an example of useRef that I found but that ended up rendering/making the axios request in a loop.
Node.js backend - route
router.put('/update-skill', function (req, res) {
var id = req.body._id;
var update = { level: req.body.level, percent: req.body.percent };
console.log(update);
Skill.findByIdAndUpdate(id, update, { new: true }, (err, res) => {
if (err) {
console.log(err);
} else {
console.log('Success:', res);
}
});
});
MAIN GOAL:
Each time button is clicked, update the percent/level of skill in the back-end(mongoose/ mongoDB Atlas - This is working but only one request per skill, the skill goes from 0 to 10% then each consecutive click console.logs() 10% instead of going up).
Would like to see the progress bar increase each time button is clicked.
Any help would be greatly appreciated and as if more info is needed for the problem to be clear please let me know.
I figured out the solution to my problem!
Basically when I was making a put request using axios to my backend, the request was being sent but no response was given from the backend.
Diagnosed by checking the network tab and it had a pending for the request. It seems after 5 pending request the database stalls.
So to fix it I added a return statement.
router.put("/update-skill", async (req, res) => {
var id = req.body._id;
var update = { level: req.body.level, percent: req.body.percent };
try {
var responseData = await Skill.findByIdAndUpdate(id, update, { new:
true });
} catch (err) {
return res
.status(500)
.json({ error: "skill was not updated", message: err });
}
// This was the missing code I needed!
return res.json({ response: "Successful", data: responseData });
});

Save in mongodb and pass created object to a function

I'm beginner at programing and I don't know how can I do something with the mongoose save result.
In my post endpoint I would like to not save and directly return but instead of it I would like to do something with the result of save method like take the _id value of the new object created and pass to a function.
Here's what my post endpoint is doing and I would like to after saving not return but instead call a function passing the checkout object created:
router.post('/', async function(req, res) {
const { checkinId, eventId, email } = req.body;
let CheckoutTest = {
checkinId: checkinId,
eventId: eventId,
email: email,
}
const newCheckout = new Checkout(CheckoutTest);
await newCheckout.save((err, checkout) => {
if(err) {
return res.status(400)
.send(err);
}else {
return res.status(200)
.json({message: "Checkout successfully added!", checkout});
}
})
});
An elegant way to do this would be to add a try...catch block
router.post('/', async function(req, res) {
const { checkinId, eventId, email } = req.body;
let CheckoutTest = {
checkinId: checkinId,
eventId: eventId,
email: email,
}
const newCheckout = new Checkout(CheckoutTest);
try {
const newCheckoutObject = await newCheckout.save()
// Call the function that you wanted to after the save.
// You can pass in the "_id" of the object as shown here
const newData = await functionToBeCalled(newCheckoutObject._id)
return res.status(200).json({message: "Checkout successfully added!", newData});
} catch (err) {
return res.status(400).send(err);
}
}

DELETE request in redux

I'm writing a React Redux CRUD App with Node.js API. I'm struggling with DELETE part.
I'm receiving the successful delete message but nothing has changed in my database. Successful Message in Console
I just wonder why it's not deleting any data?
user.reducer :
import { userConstants } from '../_constants';
export function users(state = {}, action) {
switch (action.type) {
case userConstants.GETALL_REQUEST:
return {
loading: true
};
case userConstants.GETALL_SUCCESS:
return {
items: action.users
};
case userConstants.GETALL_FAILURE:
return {
error: action.error
};
case userConstants.DELETE_REQUEST:
// add 'deleting:true' property to user being deleted
return {
...state,
items: state.items.map(user =>
user.id === action.id
? { ...user, deleting: true }
: user
)
};
case userConstants.DELETE_SUCCESS:
// remove deleted user from state
return {
items: state.items.filter(user => user.id !== action.id)
};
case userConstants.DELETE_FAILURE:
// remove 'deleting:true' property and add 'deleteError:[error]' property to user
return {
...state,
items: state.items.map(user => {
if (user.id === action.id) {
// make copy of user without 'deleting:true' property
const { deleting, ...userCopy } = user;
// return copy of user with 'deleteError:[error]' property
return { ...userCopy, deleteError: action.error };
}
return user;
})
};
default:
return state
}
}
user_actions:
export const userService =
{
delete: _delete,
};
function _delete(id) {
const requestOptions = {
method: 'DELETE',
// headers: authHeader(),
};
return fetch(`/removeadmin/${id}` , requestOptions).then(handleResponse);
}
AdminListPage component :
delete(){
this.props.dispatch(userActions.delete(this.state.delete_user._id));
}
Also, in server-side I'm receiving a successful delete status
ServerSide Console(200)
Server_Side router:
app.delete('/removeadmin/:id', function(req, res)
{
var sent_url = url.parse(req.url, true),
qdata = sent_url.query,
sent_id = qdata.id;
console.log('id ' + sent_id);
admin.removeadmin(sent_id, function(err, user) {
if (err)
throw err;
});
Server_Side delete function:
module.exports.removeadmin = function(id, callback){
var query = { _id: id };
Admin.remove(query, callback);
};
I have deleted a user by simple fetch command in the component without redux and I have sent id in the body of delete request and it was working but with redux just successful message.
Thank you for any help
Your code in the post should work except for the fetch request the url should be prepended with the backend url so if the backend url is localhost:3000 your fetch should be:
return fetch(`http://localhost:3000/removeadmin/${id}`,
requestOptions).then(handleResponse);
and in your serverside router you can access your id param like so:
app.delete('/removeadmin/:id', function(req, res)
{
var send_id = req.params.id;
admin.removeadmin(sent_id, function(err, user) {
if (err)
throw err;
});
I have found that I made a mistake in URL. So on the server in URL I can't have access to my id and it showed me undefined.
Just I have changed these lines :
user_action:
return fetch(`/removeadmin?id=${id}` , requestOptions).then(handleResponse);
and server_side router:
app.delete('/removeadmin?:id', function(req, res){

what is the correct way to use async await with mariadb query in NodeJs?

I'm new to async/await.
I'm trying to use async and await but the query is not waiting and it happens at last and the page renders before the query so I can't get the correct answer on rendered page.
Here is my code before using async await
orderMiddleware.newOrder = function (req, res) {
var total = 0
var curr_total = 0
// get items from cart
c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, function (err, cart) {
if (err) {
console.log(err)
} else {
cart.forEach(function (item) {
// Find item from DB and check their price
c.query('select * from products where id=:id',
{ id: item.item_id },
function (err, foundItem) {
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
console.log(total)
console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
res.render('orders/new', { cart: cart, total: total })
}
})
}
However this doesn't work properly. console.log(total) happens before the query so the result is zero and it renders zero in the rendered page.
Same thing happens if I use async. Am I using it wrong?
After using async await-
orderMiddleware.newOrder = async (req, res) => {
var total = 0
var curr_total = 0
// get items from cart
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID }, async (err, cart) => {
if (err) {
console.log(err)
} else {
cart.forEach(async (item) => {
// Find item from DB and check their price
await c.query('select * from products where id=:id',
{ id: item.item_id },
async (err, foundItem) =>{
if (err) {
console.log(err)
} else {
curr_total = foundItem[0].price * item.quantity
console.log("currenttotal" + curr_total)
total += curr_total
console.log(total)
}
})
})
await console.log(total)
// await console.log(curr_total)
// Calculate total price
// Multiply all items with their quantity
await res.render('orders/new', { cart: cart, total: total })
}
})
}
I tried without using callbacks like:
var A= c.query('select * from cart where user_id=:userId',
{ userId: req.user.ID })
but then how can I get the output of the query?
console.log(A) shows different results.
You can't because the functions don't return promises. You can promisify those function using a thirty-part library (for example es6-promisify) or you can wrap those by yourself.
Once a function returns a Promise, you can await it.
For example, for the above, a solution could be the following:
const execQuery = (sql, params) => new Promise((resolve, reject) => {
query(sql, params, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
const logCartItem = async (userId) => {
try {
const items = await execQuery('select * from cart where user_id=:userId', { userId });
items.forEach(console.log);
} catch (error) {
console.error(error);
}
};
Assuming you're using the node-mariasql package. Short answer is you can't use async/await because the package does not support Promises.
With node-mariasql it's easy to use promisify
const util = require('util')
const asyncQuery = util.promisify(c.query);
const rows = await asyncQuery.call(c, 'SELECT product FROM products WHERE id = :id', { id }, { useArray: false, metaData: false })

Resources