How can i handle multiple response in single route? - javascript

I Try to post an order. I get product's details as array of object(in that array I get product id and quantity). I'm using map function to iterate. Using id I here find all details of the product. Then I am doing before placing an order i am checking stock value and update it. When i try to send more than one object i am getting anUnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:485:11)How can i solve this warning.And Also another is is stock condition is not working. eg:`If stock is 0 then it should give res.(404) intstead i'm getting 200 ok status
//2.Create a Order
router.post('/orders', (req,res) => {
//products is a array of object we get from frontend
const { products,name,mobile,pincode,address,district,email } = req.body;
if(!products || !name || !mobile || !pincode || !address || !district || !email) return res.sendStatus(409).json({message:'Fields cannot be empty'});
//Using map method to itreate each object in array
products.map( async product => {
try {
const orderedItem = await Product.findById(product.id);
const currentOrder = new Order({
productId:orderedItem.productId,
quantity: product.quantity,
orderId: orderedItem.orderId,
name,
mobile,
pincode,
address,
district,
email
});
//Stock updating
if(orderedItem.stock <= 0){
console.log('stock less or equal to is checking...');
return res.sendStatus(404).json({message:'oops! Product Out of Stock'});
}
if(currentOrder.quantity > orderedItem.stock){
console.log('remove product condition is checking')
return res.status(404).json({message:orderedItem.name+'Please remove the product'});
} //return res.sendStatus(404)//.json({message:'Quantity you choose is not available please try using less quantity'});
orderedItem.stock = orderedItem.stock-currentOrder.quantity;
await orderedItem.save();
await currentOrder.save();
// await sendEmail(orderedItem,currentOrder);
} catch (error) {
return res.sendStatus(500).json({message:error});
}
});
res.json({
message : 'Order placed!'
});
});

There are a couple of things wrong about your code.
You are using async-await inside a arr.map() function, which will not block the main thread and your res.json({message : 'Order placed!'}) will get called before your array elements are processed. Use a 'for' loop with await to block your main thread.
Solution- https://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html
2.You are sending multiple responses to the client for a single request which is not allowed and therefore you are getting the error Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:485:11). Change your code in such a way that it only sends one response for the incoming request.
Solution- https://stackoverflow.com/a/48123354/8393975

Related

How to fulfill orders after Stripe's checkout.session.completed event?

My problem is with Stripe's metadata object having a 500 character limit. I have a working checkout flow but my only restriction is the character limit for my cart. My cartItems object has extras and customer notes I want to include for each cart Item. With that being said, the metadata limit gets to 500 characters fast. I have read on another post here, implementing websockets into my app which would let me create the order using after listening to stripes event. How would I go about this? Any other workarounds?
let endpointSecret;
endpointSecret =
"whsec_bd73383ed0fcf9cfb27bd4929af341605ad32577dfd8825e1143425b846bb3c3";
router.post("/webhook", (request, response) => {
const sig = request.headers["stripe-signature"];
let data;
let eventType;
if (endpointSecret) {
let event;
try {
event = stripe.webhooks.constructEvent(
request.rawBody,
sig,
endpointSecret
);
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return;
}
data = event.data.object;
eventType = event.type;
} else {
data = request.body.data.object;
eventType = request.body.type;
}
// Handle the event
if (eventType === "checkout.session.completed") {
stripe.customers
.retrieve(data.customer)
.then((customer) => {
console.log("customer:", customer);
console.log("data:", data);
createOrder(customer, data);
})
.catch((err) => console.log(err.message));
}
You don't typically pass all cart information in metadata, you would typically store that information in your own database and then retrieve the necessary information based on a UUID that corresponds with that data and the order ID that you set on the Checkout Session via metadata which then gets returned via the Webhook.
To give an example of what I'm recommending above --
In your database you would have something like:
Order ID
Cart Item1
Cart Item1 Description
Cart Item2...
123456
hat
red
scarf
123457
sock
blue
Then when you create your Checkout Session you just pass your Order ID as metadata. When you receive your checkout.session.completed Webhook, that metadata will indicate the Order ID so now you have all your data necessary for fulfillment and reconciliation (and you can update your database accordingly).
Also, to clear up a misconception, with Stripe's metadata you can have 50 keys each with values up to 500 characters long.

Using Lambda with Nodejs Count Some queries in Dynamo DB

After using the below to pull data from Dynamo db sucessfully
async function pullone(sessionid) {
const params = {
TableName: dynamodbTableName,
Key: {
'sessionid': sessionid
}
};
return await dynamodb.get(params).promise().then((response) => {
return response.Item
}, (error) => {
console.error('Do your custom error handling here. I am just gonna log it: ', error);
});
}
Instead of 'return response.Item' i just want to return the count instead.
I tried doing count(pullone(sessionid)) but not sure if that is even a valid method. Please assist
Not sure if I understood your question, but:
Since you're requesting data associated with a primary key, you'll get either 0 or 1 element in Item.
So, if you aim to know if "you've found something or not", you can use Number(response.Item != null) and you'll get 1 in case of "something" and 0 in case of "nothing".
If, instead, your data contains a "count" attribute, then (await pullone(sessionId)).count should work.
Otherwise, you have to query your DB (but you'll get Items (plural) in your response) and use the length() function of the Items array you'll get in the response.

How do I Collect User IDs + Retrieve Corresponding Tokens + Send a Push Notification Via Firebase Cloud Function (JS)

The Problem:
I have been unable to use Firebase (Google) Cloud Functions to collect and utilize device tokens for the cloud messaging feature.
Context:
I am a self-taught android-Java developer and have no JavaScript experience. Despite that, I believe I have code that should work and am not sure what the problem is. To my understanding, it could be one of three things:
Somehow my Firebase Realtime Database references are being called incorrectly and I am not retrieving data as expected.
I may need to use Promises to wait for all calls to be made before proceeding, however I don't really understand how I would incorporate that into the code I have.
I may be using multiple return statements incorrectly (which I am also fuzzy on).
My error message on the Firebase Realtime Database console is as follows:
#firebase/database: FIREBASE WARNING: Exception was thrown by user callback. Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16)
at Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:729:19)
at Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14)
at admin.database.ref.once.snapshot (/srv/index.js:84:12)
at onceCallback (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:4933:51)
at /srv/node_modules/#firebase/database/dist/index.node.cjs.js:4549:22
at exceptionGuard (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:698:9)
at EventList.raise (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:9684:17)
The above indicates I am not retrieving data either at all or by the time the return is called. My JavaScript function code is:
'use strict';
const admin = require('firebase-admin');
admin.initializeApp();
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushID}').onCreate((snapshot, context) => {
const valueObject = snapshot.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
sendNotificationPayload(valueObject.uid, payload);
}
index++;
});
});
});
function sendNotificationPayload(uid, payload){
admin.database()
.ref(`/User Token Data/${uid}`)
.once('value', snapshot=> {
var tokens = [];
//if(!snapshot.exists())return;
snapshot.forEach(item =>{
tokens.push(item.val())
});
admin.messaging()
.sendToDevice(tokens, payload)
.then(res => {
return console.log('Notification sent')
})
.catch(err => {
return console.log('Error in sending notification = '+err)
});
});
}
This code is mostly inspired by what was said to be a working example here from another Stack Overflow question here. I have successfully tested sending a notification to a single device by manually copying a device token into my function, so the function does run to completion. My Java code seems to be irrelevant to the problem, so I have not added it (please ask in the comments if you would like it added for further context).
What I Have Tried:
I have tried implementing promises into my code, but I don't think I was doing it properly. My main reference for this was here. I have also looked at the documentation for literally everything related to this topic, however my knowledge of JS is not sufficient to really apply barebones examples to my code.
My Firebase Realtime Database Nodes:
#1: Loop through chat members to collect user IDs:
"Chat Basics" : {
"1607801501690_TQY41wIfArhHDxEisyupZxwyHya2" : {
"Chat Users" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : true,
"TQY41wIfArhHDxEisyupZxwyHya2" : true
},
#2: Collect user tokens from collected IDs (ignore that tokens are matching):
"User Token Data" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi",
"TQY41wIfArhHDxEisyupZxwyHya2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi"
}
Conclusion:
Concrete examples would be much appreciated, especially since I am crunching right now. Thanks for your time and help!
Update:
After some more testing, it looks like the problem is definitely due to my lack of understanding of promises in two areas. Firstly, only one user is collected before the final return is called. Secondly, the final return is called before the 2nd forEach() loop can store snapshot data to an array.
For this code then, how may I modify (or rebuild) it so that it collects all keys before proceeding to retrieve token data from all keys - ultimately before returning the notification?
Just as with every question I post, I managed to figure out how to do it (tentatively) a few hours later. Below is a full example of how to send a notification to chat users based on a message sent (although it does not yet exclude the sender) to a given chat. The order of operations are as such:
User message is saved and triggers event. Relevant data the message contains are:
username, chat key, message
These are retrieved, with (username + message) as the (title + body) of the
notification respectively, and the chat key is used for user id reference.
Loop through chat user keys + collect.
Loop through array of chat user keys to collect array of device tokens.
Send notification when complete.
The code:
//Use firebase functions:log to see log
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushId}').onWrite((change, context) => {
const valueObject = change.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
let promises = [];
var tokens = [];
for(let i=0; i < msgIDs.length; i++){
let userId = msgIDs[i];
let promise = admin.database().ref(`/User Token Data/${userId}`).once('value', snapshot=> {
tokens.push(snapshot.val());
})
promises.push(promise);
}
return Promise.all(promises).then(() => {
return admin.messaging().sendToDevice(tokens, payload);
});
}
index++;
return false;
});
});
});

Issues with the Documents in Mongoose (JavaScript)

So I'm making a bot for discord but I'm having some issues with Mongoose. So what I want is basically, the user sends a message to save a Document with some of his info, but if there is already a Document with his info it will stop the process with return. So I tried this:
function main(message){
// So first user sends a message to store some data about him
let author = message.author //this is discord.js syntax, basically it returns the author of a message
let id = author.id //also discord.js syntax, returns the id from the user, in this case the author variable above
let check = logUser.findOne({userId : [id]}).exec().then(res => {
if (res) return true;
else return false;
})} // So if there is a Document with the id of the author of the message it will return true, else it returns false
if (check === true) return console.log("This User has already a Document with his info saved");
//so if the user has already a Document with his info it will return and stop the action of saving his Data
//everything from this point is basic Mongoose Syntax, to make a Document with User data
const theUser = new logUser({
_id : mongoose.Types.ObjectId(),
userName : author.username,
userId : author.id,
currency : 0
})
theUser.save()
.then(result => console.log(result))
.catch(err => console.log(err))
console.log(`User ${author.username} was stored into the database!`)
}
It fails in the if statement that checks if the user has a Document with his info already. I've tried multiple things but it doesn't work.
I think that the solution for this problem has to do with async functions but I'm not sure, and I don't know that much about async processes.
Thanks in advance!
The problem is that your treating logUser.findOne as synchronous. Perform the check in findOne callback like so:
function main(message){
// So first user sends a message to store some data about him
let author = message.author //this is discord.js syntax, basically it returns the author of a message
let id = author.id //also discord.js syntax, returns the id from the user, in this case the author variable above
logUser.findOne({userId : [id]}).exec().then(res => {
let check = Boolean(res);
if (check === true)
return console.log("This User has already a Document with his info saved");
const theUser = new logUser({
_id : mongoose.Types.ObjectId(),
userName : author.username,
userId : author.id,
currency : 0
});
theUser.save()
.then(result => {
console.log(result);
console.log(`User ${author.username} was stored into the database!`)
})
.catch(err => console.log(err))
});
}
Are you purposely wrapping the id in an array? I don't know your schema but it seems odd and may contributing to your issues. userId : [id]
You may want to consider async/await to reduce callbacks. You can also look into using a unique index to avoid multiple requests in the future. Using a unique index will throw an error when trying to save the same document twice.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
https://docs.mongodb.com/manual/core/index-unique/

Error: Can't set headers after they are sent from a forEach loop

I'm still very new to node.js,the array over here consists a list of ids. When I'm sending the response from the forEach loop, I'm getting Error: Can't set headers after they are sent. I googled it but could not understand properly
array.forEach(function(data) {
db.collection.find({
_id: mongoskin.helper.toObjectID(data)
}).toArray(function(err, data1) {
if (err) return next(err);
console.log(data1);
res.send(data1);
})
})
Because of the loop, your code is calling res.send multiple times; you can't do that
res.send is an overloaded function that can be called in multiple ways, but any way you use it, it will set headers and send a response. Think of it like an all-in-one, tries-to-be-the-smart-one function.
// not actual source code!
// just imagine res.send kinda like this
function send(body, headers, status) {
res.setHeaders(headers);
res.statusCode = status;
res.write(body);
res.end();
}
However, if you want to write a response piecewise, use the res.write method instead. When you're done, you must call res.end.
res.setHeader(myHeaders);
myArray.forEach(
//...
res.write(something);
);
res.end();
use this code,
var index = 0;
var object = [] // empty array
function find(){
if(array.length -1 >=index){
var data = array[index] ;
db.collection.find({
_id: mongoskin.helper.toObjectID(data)
}).toArray(function(err, data1) {
if (err) return next(err);
console.log(data1);
object.push(data1);
//res.send(data1);
index++;
find();
})
}else{
res.send(object);
}
}
In your case ,you are sending response data but your array is still processing .so once your response sent ,you cant send it again .
This happens when res.end() is called more then once.
The res.send() method is an easy way to respond without need to specify the type of data you are sending BUT it can be called only once.
see express docs
This is how the res.send() method ends - express source
// respond
this.end(head ? null : body);
return this;

Categories