I am using Express with denodeify module. I`m getting error when i try to render email template asynchronously.
.then(function () {
return denodeify(res.render)(path.resolve('verify-email'), {
name: user.displayName,
appName: config.app.title,
mail: user.email,
url: 'http://' + req.headers.host
});
})
.then(function (emailHTML) {
// code which is not executed
})
.catch(function (err) {
// [TypeError: Cannot read property 'req' of undefined]
});
I tryed use res.render without callback and it worked fine, but i need to output email template in variable.
Also i cant understand how i can debug this part of code. Thanks for help!
Whole code
function sendVerificationEmail(user, req, res) {
return denodeify(crypto.randomBytes)(20)
.then(function (buffer) {
user.verificationToken = buffer.toString('hex');
return user.save();
})
.then(function () {
user.password = undefined;
user.salt = undefined;
delete req.session.redirect_to;
return denodeify(res.render)(path.resolve('modules/users/server/templates/verify-email'), {
name: user.displayName,
appName: config.app.title,
mail: user.email,
url: 'http://' + req.headers.host + '/api/auth/verify/' + user.verificationToken + "/" + user.email
});
})
.then(function (emailHTML) {
const mailOptions = {
to: user.email,
from: config.mailer.from,
subject: 'Verify Email',
html: emailHTML
};
return denodeify(smtpTransport.sendMail)(mailOptions);
});
}
exports.updateAuthData = function (req, res, next) {
const userId = req.session.userId;
delete req.session.userId;
if (!userId) {
return res.status(401).send({
message: 'No active session'
});
}
let user;
User.findById(userId)
.then()
.then((_user) => {
user = _user;
const fields = Object.keys(req.body);
const neededFields = user.getMissingAuthFields();
const missingFields = _.difference(fields, neededFields);
if (missingFields.length !== 0) {
throw new CustomError('Missing fields: ' + missingFields.join(', '))
}
for (let field of User.requiredAuthorizationFields) {
if (!!user[field]) {
throw new CustomError('User already has ' + field);
}
}
User.requiredAuthorizationFields.forEach((field) => user[field] = req.body[field]);
return true;
})
.then(function () {
return sendVerificationEmail(user, req, res);
})
.then(() => {
res.send({
message: 'An email has been sent to the provided email, please verify your account.'
});
})
.catch(function (err) {
res.status(400).send({
message: 'Failure sending email'
});
})
};
I found the solution, we have to bind "this" variable like this
return denodeify(res.render.bind(res))(path.resolve('modules/users/server/templates/verify-email'), {
name: user.displayName,
appName: config.app.title,
mail: user.email,
url: 'http://' + req.headers.host + '/api/auth/verify/' + user.verificationToken + "/" + user.email
});
The reason of error was the following code in express "res.render" function
var app = this.req.app;
var done = callback;
var opts = options || {};
var req = this.req;
var self = this;
the right way is to use:
denodeify(res.render.bind(res))(path, options)
instead of
denodeify(res.render)(path, options)
Thanks to all for replies!
Related
I am working on Password reset functionality in NodeJS and am facing Promise.resovle undefined issue. I am getting the corrrect values from the database and able to send the actual email, but the promise in the main function doesn't return anything.
Any help would be welcome.
Here is my main route function-
router.post('/reset-password', async (req, res) => {
const { email } = req.body
const emailCheck = await db.query('SELECT EXISTS (SELECT email from tickets where email=$1)', [email])
const ifExist = Object.values(emailCheck.rows[0])[0]
if (ifExist) {
const resetPin = Math.floor(100000 + Math.random() * 900000) //random 6 digit pin with 0 not as first digit
await db.query('UPDATE tickets SET pin=$1 where email=$2', [resetPin, email])
const result = await emailProcessor(email, resetPin)
console.log(result) /////returns undefined!
if (result) {
return res.status(401).json({
status: 'success',
message: 'Pin sent to email if present in our database'
})
}
}
res.status(403).json({
status: 'error',
message: 'Pin sent to email if present in our database'
})
Here is my helper function using nodemailer-
const transporter = nodemailer.createTransport({
host: 'randomhost',
port: 587,
auth: {
user: 'abc.email',
pass: '123'
}})
const sendEmail = (info) => {
return new Promise(async (resolve, reject) => {
try {
let result = await transporter.sendMail(info);
console.log("Message sent: %s", result.messageId);
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(result));
resolve(result)
} catch (error) {
console.log(error)
}
})}
const emailProcessor = (email, pin) => {
const info = {
from: '"app" <email>',
to: email,
subject: "Reset Pin",
text: "Pin is " + pin,
html: `<b>Reset Pin</b><b>${pin}`,
}
sendEmail(info)}
emailProcessor doesn't currently have a return statement, so it implicitly returns undefined. Change it to:
const emailProcessor = (email, pin) => {
const info = {
from: '"app" <email>',
to: email,
subject: "Reset Pin",
text: "Pin is " + pin,
html: `<b>Reset Pin</b><b>${pin}`,
};
return sendEmail(info); // <------ added return
};
P.S, sendEmail does not need to use the promise constructor, new Promise. You're already using promises, so you just need to use the ones that are there, not make a new one.
const sendEmail = async (info) => {
try {
let result = await transporter.sendMail(info);
console.log("Message sent: %s", result.messageId);
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(result));
return result;
} catch (error) {
console.log(error);
}
};
I've been struggling with this issue for a day now and can't seem to figure out a way to resolve it. This is the code I'm running
Client side:
const nameInput = document.querySelector("#nameInput");
const urlInput = document.querySelector("#urlInput");
const rowAlert = document.querySelector(".alertAppend");
const divAlert = document.createElement("div");
const nameUpdate = async (e) => {
e.preventDefault();
fetch("/auth/updateName", {
method: 'POST',
headers: {
'Content-Type' : 'application/json'
},
body: JSON.stringify({
name: nameInput,
url: urlInput,
})
})
.then(function (data) {
console.log('Request success: ', data);
})
.catch(function (error) {
console.log('Request failure: ', error);
});
};
submitName.addEventListener("click", nameUpdate);
API:
router.get("/updateName", auth, async (req, res) =>{
try {
const { name, url } = req.body;
const ime = name;
const uid = req.session.passport.user;
db.User.find({ where: { id: uid } })
.on('success', function (user) {
if (user) {
user.update({
name: ime,
webhook: url
})
.success(function () {})
}
})
res.json({ message: url});
} catch (err) {
if (err) res.status(500).json({ message: "Internal Error"})
}
});
For some reason it just runs the select query and never proceeds to update the user.
Chrome console output
Debug console output
Sequelize model in case it helps:
module.exports = function (sequelize, DataTypes) {
var User = sequelize.define("User", {
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
name: {
type: DataTypes.STRING
}
})
return User;
}
The issue was in the API, it's supposed to be router.post
router.post("/updateName", auth, async (req, res) =>{
const { ime, url } = req.body;
const uid = req.session.passport.user;
console.log(ime);
db.User.findOne({where: {id: uid}})
.then(record => {
let values = {
name: ime,
webhook: url
}
record.update(values).then( updatedRecord => {
console.log(`updated record ${JSON.stringify(updatedRecord,null,2)}`)
res.status(200).json({ message: "success"});
})
}
})
.catch((error) => {
// do seomthing with the error
throw new Error(error)
})
});
You can try the following code
await db.User.update({
name: ime,
webhook: url
}, { where: { id: uid } });
When defining your model I don't see the webhook field
I have written a Firebase cloud function to sign people up into my Firebase database. I have tested with POSTMAN and the function is working correctly.
The problem I am having is that I have a function that is getting a stripe ID and then needs to return that value. I want the customer id (customer.id in my reference) to append a JSON string I have created with the users info.
This way, when the function is done it needs to write all the data to firebase and then return the same JSON string variable to my app. This all works, but I cannot get the Stripe ID to append into my JSON array and be parsed.
I have been cowering the internet to try and find a solution, and I believe my syntax is correct, but its not working. I know the function is working because the console.log is outputting the stripe ID, its just not being added to the JSON variable that is being written to Firebase.
Anyone that could explain where I am going wrong would be much appreciated. I have referenced my issue points in the code below with // for comments.
exports.myCloudFunction=
functions.https.onRequest((req, res) => {
if (req.method !== 'POST') {
return;
}
const userDataInput = req.body;
console.log('Console Body:', req.body);
admin.auth().createUser({
email: userDataInput.email,
emailVerified: false,
phoneNumber: userDataInput.mobile,
password: userDataInput.password,
displayName: userDataInput.firstname + ' ' + userDataInput.lastname,
disabled: false
})
.then(async function (userRecord) {
console.log('User record:', userRecord);
var userObject = //CONSTRUCTED JSON STRING
{
first_name: userDataInput.firstname,
last_name: userDataInput.lastname,
mobile_number: userDataInput.mobile,
email: userDataInput.email,
timestamp: admin.database.ServerValue.TIMESTAMP,
driver_profile: { isDriverApproved: false, isDriverDisabled: false, isDriverStatusPending: false, isDriver: false, isPickupModeEnabled: false },
}
stripe.customers.create({
description: 'Firebase ID: ' + userRecord.uid,
email: userRecord.email,
name: userRecord.displayName,
phone: userRecord.phoneNumber
}, async function (err, customer) {
console.log('New Stripe ID Created', customer.id); // THIS WORKS< THE customer.id is outputting
try {
return userObject[{ stripe_id: customer.id }]; // THIS IS NOT WORKING, I WANT **customer.id** TO BE PUT INTO THE **userObject** JSON variable.
}
catch (error) {
console.error(error);
return res.status(200).send('Error: ' + error);
}
});
try {
await admin.database().ref('users/' + userRecord.uid).set(userObject);
return res.status(200).send({ returnData: userObject });
}
catch (error) {
console.error(error);
return res.status(200).send('Error: ' + error);
}
})
.catch(function (error) {
console.log('Error creating new user:', error);
res.status(500).send({ returnError: error });
});
});
I think that the return it won't return anything because is a callback
exports.myCloudFunction =
functions.https.onRequest((req, res) => {
if (req.method !== 'POST') {
return;
}
const userDataInput = req.body;
console.log('Console Body:', req.body);
admin.auth().createUser({
email: userDataInput.email,
emailVerified: false,
phoneNumber: userDataInput.mobile,
password: userDataInput.password,
displayName: userDataInput.firstname + ' ' + userDataInput.lastname,
disabled: false
})
.then(async function (userRecord) {
console.log('User record:', userRecord);
var userObject = //CONSTRUCTED JSON STRING
{
first_name: userDataInput.firstname,
last_name: userDataInput.lastname,
mobile_number: userDataInput.mobile,
email: userDataInput.email,
timestamp: admin.database.ServerValue.TIMESTAMP,
driver_profile: {
isDriverApproved: false,
isDriverDisabled: false,
isDriverStatusPending: false,
isDriver: false,
isPickupModeEnabled: false
},
}
stripe.customers.create({
description: 'Firebase ID: ' + userRecord.uid,
email: userRecord.email,
name: userRecord.displayName,
phone: userRecord.phoneNumber
}, async function (err, customer) {
console.log('New Stripe ID Created', customer.id); // THIS WORKS< THE customer.id is outputting
try {
// Move your logic to the final callback
userObject["stripe_id"] = customer.id;
await admin.database().ref('users/' + userRecord.uid).set(userObject);
return res.status(200).send({returnData: userObject});
} catch (error) {
console.error(error);
return res.status(200).send('Error: ' + error);
}
});
})
.catch(function (error) {
console.log('Error creating new user:', error);
res.status(500).send({returnError: error});
});
});
I think I found the error. There is an issue with your syntax
There is a line of code that is wrong
//replace
return userObject[{ stripe_id: customer.id }];
// for this
return userObject.stripe_id = customer.id;
Note: Try to separate your code better. It's kind of hard to read
I have a promise within a selectRecipientData function that returns some user data from an api.
export async function selectRecipientData({ email }) {
engage.selectRecipientData({
listId: listId,
email: email,
returnContactLists: false,
}, function(err, result) {
if(err) {
console.log(err);
} else {
let recipient = JSON.stringify(result);
// this logs successfully
console.log('Recipient details: ' + recipient );
return recipient;
}
});
}
When I call this function within a post request. The data is logged within the promise but is undefined when returned as per below:
server.post('/api/v1/public/selectrecipientdata', async (req, res) => {
formData = req.body;
let { email } = formData;
if (!email) {
res.json({ error: 'Email is required' });
return;
}
try {
let recipientData = await selectRecipientData({ email });
// why is this undefined?
console.log('This is Undefined: '+ JSON.stringify(recipientData) );
res.json({recipientData});
} catch (err) {
res.json({ error: err.message || err.toString() });
}
});
Anyone tell me why? Thanks
You've written selectRecipientData as a callback style function, but you're calling it as an async/await style. If engage.selectRecipientData returns a promise, you could do something like:
export async function selectRecipientData({email}) {
const result=await engage.selectRecipientData({
listId: listId,
email: email,
returnContactLists: false,
});
const recipient=JSON.stringify(result);
console.log('Recipient details: ' + recipient );
return recipient;
}
Otherwise, to convert it to a promise you could do something like:
export function selectRecipientData({email}) {
return new Promise((resolve,reject)=>{
engage.selectRecipientData({
listId: listId,
email: email,
returnContactLists: false,
}, function(err, result) {
if (err) {
reject(err);
}
else {
let recipient = JSON.stringify(result);
console.log('Recipient details: ' + recipient);
resolve(recipient);
}
});
});
}
I have to send a message to many token when a node is created in my realtime database.
I use that's code, but any notification are lost (people not receive its).
exports.sendMessage = functions.database.ref('/messages/{messageId}')
.onCreate((snapshot, context) => {
const original = snapshot.val();
let msg = {
message: {
data: {
title: 'title2 test',
body: 'body2 test',
notify_type: 'chat_message',
notify_id: ((new Date()).getTime()).toString(),
},
apns: {
headers: {
'apns-priority': '10',
'apns-expiration': '0'
},
payload: {
aps: { contentAvailable: true, sound:'' },
'acme1': 'bar',
title: 'title test',
body: 'body test',
notify_type: 'chat_message',
notify_id: ((new Date()).getTime()).toString()
}
},
token: token
}
};
var query = firebase.database().ref("users");
return query.once("value")
.then(function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var user = childSnapshot.val();
var token = user.token;
var username = user.username;
msg.message.token = token;
admin.messaging().send(msg.message).then((response) => {
console.log('message sent to '+username);
}).catch((error) => {
console.log(error);
});
});
});
});
Is the "return" Promise right ? I think I have to wait all "admin.messagging() Promise, but I don't know how can I do this.
Thank you so much.
This is how you send a FCM to an array of tokens:
return Promise.all([admin.database().ref(`/users/${user}/account/tokensArray`).once('value')]).then(results => {
const tokens = results[0];
if (!tokens.hasChildren()) return null;
let payload = {
notification: {
title: 'title',
body: 'message',
icon: 'icon-192x192.png'
}
};
const tokensList = Object.keys(tokens.val());
return admin.messaging().sendToDevice(tokensList, payload);
});
You can send notifications to an array of tokens. I am using this code to send notifications
return admin.messaging().sendToDevice(deviceTokenArray, payload, options).then(response => {
console.log("Message successfully sent : " + response.successCount)
console.log("Message unsuccessfully sent : " + response.failureCount)
});
I think you can find more information here
https://firebase.google.com/docs/cloud-messaging/admin/legacy-fcm
To return a Promise for all the send actions, modify your code to this:
return query.once("value")
.then(function(snapshot) {
var allPromises = [];
snapshot.forEach(function(childSnapshot) {
var user = childSnapshot.val();
var token = user.token;
var username = user.username;
msg.message.token = token;
const promise = admin.messaging().send(msg.message).then((response) => {
console.log('message sent to '+username);
}).catch((error) => {
console.log(error);
});
allPromises.push(promise);
});
return Promise.all(allPromises);
});