How to wait for firebase cloud function to complete before calling next function? (AngularFire) - google-cloud-functions

I have a sign-up function submit() that calls a signup()function in my authentication service, which is basically AngularFirebase createUserWithEmailAndPassword(...) . When it has been completed, another function updateUserDocument from my data service is called. This function looks for the user document in Firebase Firestore. That document is created by a cloud function, functions.auth.user().onCreate(user.createProfile) and is triggered when an auth object is created.
Using a cloud function is a convenient way to set all the initial user properties. The cloud function also makes it simple for me to implement sign-up with providers such as Google and Facebook.
However, the problem is that the cloud function does not have time to complete before the updateUserDocument function gets called and this results in it not finding the document in Firestore.
QUESTION: What is a good way to wait for the cloud function to complete before running updateUserDocument. Can I emit some kind of event from the cloud function, and if so, how do I listen to it client-side?
Submit function (in /sign-up.ts)
submit() {
if (!this.signUpForm.valid) {
return;
}
const {displayName, email, password } = this.signUpForm.value;
this.auth.signUp(email, password).pipe(
switchMap(({ user: { uid } }) => this.dataService.updateUserDocument({ uid, email, displayName: displayName })),
this.toast.observe({
success: 'Congrats! You are all signed up',
loading: 'Signing up...',
error: ({ message }) => `${message}`
})
).subscribe(() => {
this.router.navigate(['/create-profile']);
this.openVerticallyCentered('Welcome!')
});
}
Cloud function (in functions/src/user/index.ts)
export const createProfile = async (userRecord: any) => {
const uid = userRecord.uid;
const email = userRecord.email;
const photoURL = userRecord.photoUrl || '';
const displayName = userRecord.displayName || '';
const firstName = userRecord.firstName || '';
const lastName = userRecord.lastName || '';
const country = userRecord.country || '';
const dateOfBirth = userRecord.dateOfBirth || '';
const address = userRecord.address || '';
const suburb = userRecord.suburb || '';
const state = userRecord.state || '';
const postCode = userRecord.postCode || '';
const homePhone = userRecord.homePhone || '';
const mobilePhone = userRecord.mobilePhone || '';
const memNum = userRecord.memNum || '';
const memDueDate = userRecord.memDueDate || '';
const timestamp = FieldValue.serverTimestamp();
const memType = userRecord.memType || '';
const memStatus = userRecord.memStatus || '';
const isAdmin = userRecord.isAdmin || false
//const newUserRef = db.doc(`users/${uid}`)
// Convert any date to timestamp for consistency
try {
return await db
.collection(`users`)
.doc(userRecord.uid)
.set({
uid: uid,
email: email,
photoURL: photoURL,
displayName: displayName,
firstName: firstName,
lastName: lastName,
country: country,
dateOfBirth: dateOfBirth,
address: address,
suburb: suburb,
state: state,
postCode: postCode,
homePhone: homePhone,
mobilePhone: mobilePhone,
memNum: memNum,
memType: memType,
memStatus: memStatus,
memDueDate: memDueDate,
lastLoginDate: timestamp,
joined: timestamp,
updated: timestamp,
isAdmin: isAdmin,
});
} catch (message) {
return console.error(message);
}
};
index file for exporting function to firebase function (/functions/user/index)
exports.authOnCreate = functions.auth.user().onCreate(user.createProfile);

Instead of relying of a trigger in the backend, I would use a cloud functions (maybe a callable function) to do that logic.
In the "signup" cloud function I would do both things, create the account with createUserWithEmailAndPassword and set the profile in updateUserDocument.

Related

server responding with error but the data goes to database expressjs

I have a form for an ecom website with two button one for card payment and the second is for cash on delivery , when I send the data from the frontend the data goes to the database despite the network it shows me 2 error the first one is the 'no data to load' and the second one is 'unable to order2' but I didn't know where the fault came from (I'm using knex as package)
const handleOrderCOD = (req,res, db) => {
const {firstname, lastname, email, mobilenumber, adress, city, size, quantity} = req.body;
if(!firstname || !lastname || !email || !mobilenumber || !adress || !city)
{return res.status(400).json('no data to load')};
db.insert({
firstname: firstname,
lastname:lastname,
email:email,
mobilenumber:mobilenumber,
adress:adress,
city:city,
size:size,
quantity:quantity,
joined: new Date()
})
.into('orders')
.then(user =>{
if(user){
return res.json(user[0])
} else{
res.status(400).json('unable to order1')
}
})
.catch(err=>res.status(400).json('unable to order2'))
}
const handleOrderCP = (req,res, db) => {
const {firstname, lastname, email, mobilenumber, adress, city, size, quantity} = req.body;
if(!firstname || !lastname || !email || !mobilenumber || !adress || !city)
{return res.status(400).json('no data to load')};
db.insert({
firstname: firstname,
lastname:lastname,
email:email,
mobilenumber:mobilenumber,
adress:adress,
city:city,
size:size,
quantity:quantity,
joined: new Date()
})
.into('orderscp')
.then(user =>{
console.log(user)
if(user){
return res.json(user[0])
} else{
res.status(400).json('unable to order1')
}
})
.catch(err=>res.status(400).json('unable to order2'))
}
module.exports={
handleOrderCOD,
handleOrderCP
}

Strange nodejs behaviour when logging in a user

The problem is that it shows that it is successfully logged in (201) without the redirect code, but with it, it shows a 302 error and the email_address is undefined.
What could be the problem here? I still can't come to a conclusion.
The problem may be in the order of the code I guess?
const login = async (req, res, next) => {
const { email_address, password, user_email, user_password}: { email_address: string, password: string, user_email: string, user_password: string } = req.body;
try {
const userWithDetails = 'SELECT * FROM users WHERE email_address = user_email AND password = user_password'; //w form info
if (userWithDetails) {
req.session.loggedin = true; //true
req.session.email_address = email_address; //undefined
console.log(req.session.email_address)
// return res.redirect('./index.html')
}
res.status(201).send('Succesfully signed in');
// res.status(403).send('Password is not correct');
} catch(error) {
res.status(404).send(`User with email ${email_address} not found!`);
}
await next;
};
NEW CODE ***
const login = async (req, res, next) => {
const { email_address, password}: { email_address: string, password: string} = req.body;
const userWithDetails = 'SELECT * FROM users WHERE email_address = ?';
return con.query(userWithDetails, email_address, (err, results) => {
if (err) {
console.error(err);
}
const user = results.find(emailObj => emailObj.email_address === email_address);
if (results && results.length && user.email_address) {
req.session.loggedin = true;
req.session.email_address = email_address;
const matchPassword: boolean = bcrypt.compareSync(password, user.password);
if (matchPassword) {
const token = jwt.sign({ user }, 'aaaa', { expiresIn: '1h'});
res.status(200).send({message: 'Logged in', token: token});
} else {
res.status(403).send('Password is not correct');
}
} else {
res.status(404).send(`User with email ${email_address} not found!`);
}
});
await next;
}
You don't execute your sql query at any point.
You just say :
query = 'select blabla'
if(query){...}
Of course this will always be true. You want to run the query on your database.
Also in your query you don't properly use the variables, see string formatting :
let my_var = `SELECT xxx from xxx where username = '${username}'`
Also please sanitize the parameters to prevent SQL Injection...

Node loop insert with mySQL and Mongodb

I have a form with one field that allows user to enter multiple developer id via comma delimited (ab1234,bc5678).
Once the form is submitted I perform the following tasks:
Get the the project
Loop through array of developer IDs to get their full name using mySQL
update the project using MongoDB
I'm new and sure this this is possible, The codes I have below is not working for me. Can someone please let me know if the codes below is even close.
const mongoose = require('mongoose'
const mysql = require('mysql');
// Create mySQL connection
const mySQLdb = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'projects'
});
const Project = mongoose.model('project');
router.post('/developerSave', async (req, res) => {
let devList = req.body.dev_ids,
devIdArr = devList.split(','),
rData = {};
// get project
const project = await Project.findById(req.body.projectID);
mySQLdb.connect();
for(var i=0, len=devIdArr.length; i < len; i++) {
let sql = `SELECT CONCAT(first_name, ' ', last_name) as full_name FROM users WHERE id= '${devIdArr[i]}'`;
mySQLdb.query(sql, function (err, results) {
if (err) throw err;
let newDev = {
userId: devIdArr[i],
fullName: results[0].full_name
}
project.developers.unshift(newDev);
await project.save();
});
}
mySQLdb.end();
rData.success = true;
rData.msg = 'Developer was added successfully.';
res.status(200).json(rData);
});
The reason you are seeing this is because your await project.save(); is inside the callback function. Your main function will not wait for all the callbacks to complete and close the db connection. Lets look at the example below
const myCallback = (param, callback) => {
setTimeout(() => {
console.log('callback function', param);
callback();
}, 1000)
}
const myAsync = async () => {
console.log('inside async');
const result = await axios.get('http://google.com/');
for (let i = 0; i < 10; i++) {
myCallback(i, () => {
console.log('this is the actual callback function');
});
}
const result2 = await axios.get('http://bing.com/');
console.log('after second call');
}
myAsync();
The output of this is
inside async
after second call
callback function 0
this is the actual callback function
...
As you can see, the after second call is printed before the callback functions.
To solve this problem, you can wrap your callback function in a promise and resolve that once save is complete.

Update only some attributes of user in MySQL using Nodejs

I have a put route which can be used to update the user. Everything works fine unless the user will only provide only some params instead of all. How I can fix this? Are there some "simple" solutions for this problem? Because if the user only update his email everything else will be inserted empty..
const id: number = req.params.id;
const password: string = req.body.password;
const email: string = req.body.email;
const lastname: string = req.body.lastname;
const firstname: string = req.body.firstname;
const phoneNumber: string = req.body.phoneNumber;
const permissionID: number = req.body.permissionID;
const imageUrl: string = String(imagePath);
const passwordHash = bcrypt.hashSync(password, 10);
const insertData: [string, string, string, string, string, string, number, number] = [email, passwordHash, phoneNumber, firstname, lastname, imageUrl, permissionID, id];
const query = `UPDATE Users SET email = ?, password = ?, phone_number = ?, first_name = ?, last_name = ?, image_url = ?, permission_id = ? WHERE user_id = ?;`;
connection.query(query, insertData, (err: MysqlError | null) => {
if (!err) {
res.status(200);
res.json( { "Message": "Successfull user was updated" } );
} else {
res.status(500);
res.json( { "Database Error ": err.message } );
}
});
Okay I wrote something I hope this post will help someone. First of course it's possible to save the complete user data model in the client and to resend the complete data to the server. But why should I do this? I don't think this is effecient. If the user just want to change his lastname why I should send the whole payload...Anyway this is the way I solve it.
First I define my possible data I will receive if the user will update some attributes.
enum Validate {
password = 'password',
email = 'email',
firstname = 'first_name',
lastname = 'last_name',
phoneNumber = 'phone_number',
permissionID = 'permission_id'
}
So my function will check the received params and will return the insertData and query. As I'm using password hashing it will check as well if the user wants to update his password.
function updateParams(body: {}, options: [Validate], callBack: (insertData: string[], query: string) => void) {
const insertData: string[] = [];
let query = "";
for (const index in options) {
if (!(body[`${options[index]}`] === '' || body[`${options[index]}`] === undefined || body[`${options[index]}`] === null)) {
query += `${options[index]} = ?, `;
// If user will update password hash it
`${options[index]}` === 'password' ? insertData.push(bcrypt.hashSync(body[`${options[index]}`], 10)) : insertData.push(body[`${options[index]}`]);
}
}
callBack(insertData, query.slice(0, -2));
}
For the next step I'm using promises because there are some if/else statements. The user has the possibilities to just update his picture for example.
const updateUser = (req, res, insertData, query) => {
const promise = new Promise((resolve, reject) => {
let endQuery = '';
if (req.file) {
image.uploadImageToStorage(req.file)
.then((imagePath) => {
if (Object.keys(req.body).length === 0) {
endQuery = `UPDATE Users SET image_url = ? WHERE user_id = ?;`;
insertData.push(String(imagePath));
insertData.push(req.params.id);
resolve([endQuery, insertData]);
} else {
endQuery = `UPDATE Users SET ${query}, image_url = ? WHERE user_id = ?;`;
insertData.push(String(imagePath));
insertData.push(req.params.id);
resolve([endQuery, insertData]);
}
}).catch((error) => {
reject(error.message );
});
} else {
endQuery = `UPDATE Users SET ${query} WHERE user_id = ?;`;
insertData.push(req.params.id);
resolve([endQuery, insertData]);
}
});
return promise;
};
Now I can just use my route.
app.put('/api/v1/users/:id', image.multerMiddleware.single('image'), (req, res) => {
if (((Object.keys(req.body).length !== 0) || req.file) && !isNaN(req.params.id)) {
updateParams(req.body, [Validate.password, Validate.email, Validate.lastname, Validate.firstname, Validate.phoneNumber, Validate.permissionID], (insertData, query) => {
updateUser(req, res, insertData, query)
.then((result) => {
connection.query(result[0], result[1], (err: MysqlError | null) => {
if (!err) {
res.status(200);
res.json({ "Message": "Successfull user was updated" });
} else {
res.status(500);
res.json({ "Database Error ": err.message });
}
});
}).catch((error) => {
res.status(500);
res.json({ "Error ": error.message });
});
});
} else {
res.status(400);
res.json({ "Error": "Please provide the correct paramaters" });
}
});
So now
The user can update only some params
The user can update some params and his picture
The user can update only his picture
It work's fine now.
What I do for when someone is editing a user (or other type of data) is that I retrieve the entire data for the user and show it on the editing form. Then when they make the updates, I send all the data up. This way when I do the SQL update, it will re-save the unchanged data as well as the changed data.
Your other option is a series of conditionals which add to the update statement based off what fields are sent in to update.
You either set only those values that were provided, or, if you really insist on updating all columns (why not the PK while you're at it) you qould query them first.

Nodejs Request values

So I have a nodejs server and I am trying to make comparisons to the req values. Firstly, here is my code:
app.post('/', function(req, res) {
firstName = req.body.firstName;
lastName = req.body.lastName;
message = req.body.message;
token = req.body.token;
user = {name: firstName + " " + lastName, token: token};
selectedUser = req.body.selectedUser;
users.push(user);
console.log(user.name);
if (req.body.isAndroid === true) {
sendToAndroid(); //add message parameter
} else {
sendToios(); //add message parameter
}
});
app.listen(8080, function() {
console.log('running on port 8080');
});
//GCM
function sendToAndroid() {
var message = new gcm.Message();
var tokenLocation;
//API Server Key
var sender = new gcm.Sender('AIzaSyD-B3EG1xpMh6YhwBKfLMyw0GIQKWfGgZM');
//console.log(message);
// Value the payload data to send...
message.addData({
title: 'Hello',
body: 'Message From: ' + user.name + ': ' + message,
msgcnt: 1,
timeToLive: 3000
});
// At least one reg id required
if (registrationToken.indexOf(token) == -1) {
registrationToken.push(token);
tokenLocation = registrationToken.indexOf(token);
} else {
tokenLocation = registrationToken.indexOf(token);
}
if (users.indexOf(user.name) == -1) {
console.log("user destination not found");
} else {
var userTokenArray = [];
userTokenArray.push(user.token);
sender.send(message, { registrationTokens: userTokenArray } , function (err, response) {
if(err) console.error(err);
else console.log(response);
});
userTokenArray.pop();
}
}
And here is my problem when outputting to see what the value is:
running on port 8080
undefined undefined
user destination not found
What I am trying to do is put the registered users into an array of users that each element has a full name and token. Then in the Android function, it will check to see what value value is selected and then push a notification to the selectedUser via their token. I am so confused on how to compare the "strings" or whatever they are. I am using nodejs express with body-parser.