Firebase Functions - Return after Realtime Database fetched inside User Fetch - javascript

I have a Firebase Cloud Function that is called in my app with JavaScript.
When the function is called it fetches the user data from the user ID, then fetched a record from the Realtime Database to check for a match.
This function works but is returning "null" and finishing early instead of returning the success or error message when the match is detected.
How can I make the return text be the success or error from the match and only complete once this match is decided?
exports.matchNumber = functions.https.onCall((data, context) => {
// ID String passed from the client.
const ID = data.ID;
const uid = context.auth.uid;
//Get user data
admin.auth().getUser(uid)
.then(function(userRecord) {
// Get a database reference to our posts
var db = admin.database();
var ref = db.ref("path/to/data/" + ID);
return ref.on("value", function(snapshot) {
//Fetch current phone number
var phoneORStr = (snapshot.val() && snapshot.val().phone) || "";
//Fetch the current auth user phone number
var userAuthPhoneNumber = userRecord.toJSON().phoneNumber;
//Check if they match
if (userAuthPhoneNumber === phoneORStr) {
console.log("Phone numbers match");
var updateRef = db.ref("path/to/data/" + ID);
updateRef.update({
"userID": uid
});
return {text: "Success"};
} else {
console.log("Phone numbers DO NOT match");
return {text: "Phone number does not match the one on record."};
}
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
return {text: "Error fetching current data."};
});
})
.catch(function(error) {
console.log('Error fetching user data:', error);
return {text: "Error fetching data for authenticated user."};
});
});
Thank you

The Firebase ref.on() method doesn't return a promise, so the return statements you have in there do nothing.
You're looking for ref.once(), which returns a promise, and thus will bubble up the return statements you have within it:
return ref.once("value").then(function(snapshot) {
...
As Doug pointed out, you'll also need to return the promise from the top level. So:
//Get user data
return admin.auth().getUser(uid)
.then(function(userRecord) {

Related

TypeError: snapshot.forEach is not a function - Loop through firebase database

I am struggling to understand why my function is failing when trying to loop through a snapshot from Firebase Realtime Database.
The function should read through each 'Topic', from within each 'Topic' there is an 'Articles' field which has approximately 10 articles associated with it. The function reads each article URL and scrapes the URL for the largest image on the article website.
It should then add a new field 'imageURL' to each 'Article'.
When deployed I receive the following:
TypeError: snapshot.forEach is not a function
scraper.js
exports.imageScraper = functions.database.ref("searchTrends/google")
.onUpdate((snapshot, context) => {
functions.logger.error(snapshot);
snapshot.forEach(function(trendSnapshot) {
// TrendSnapshot - Key is topic Num
// Value is topic details with list of articles
const topicNum = trendSnapshot.key;
trendSnapshot.forEach(function(innerChild) {
if (innerChild.key == "articles") {
innerChild.forEach(function(articleData) {
const articleNum = articleData.key;
const myUrl = articleData.child("url").val();
// console.log(myUrl);
const options = {
url: myUrl,
};
// console.log(options);
ogs(options, (error, results, response) => {
if (typeof results.ogImage === "undefined") {
console.log("no Image");
} else {
if (results.ogImage.url === undefined) {
return "done";
}
console.log(articleNum);
const DBRef = admin.database().ref("searchTrends/google/" +
topicNum + "/articles/" + articleNum);
DBRef.update({imageURL: results.ogImage.url});
}
});
});
return "done";
}
}).catch((error) => {
console.log("Transaction failed: ", error);
return null;
});
});
});
The error is telling you that snapshot does not have a method called forEach. It is a not a DataSnapshot object as you are expecting. It is a Change object, specifically Change<DataSnapshot> From the documentation:
For onWrite or onUpdate events, the first parameter is a Change object that contains two snapshots that represent the data state before and after the triggering event.
Also refer to the API documentation for onUpdate.

How can I return different values from a function depending on code inside an Axios promise? NodeJS - a

I have a block of code that calls an Api and saves results if there are differences or not. I would like to return different values for DATA as layed out on the code. But this is obviously not working since Its returning undefined.
let compare = (term) => {
let DATA;
//declare empty array where we will push every thinkpad computer for sale.
let arrayToStore = [];
//declare page variable, that will be the amount of pages based on the primary results
let pages;
//this is the Initial get request to calculate amount of iterations depending on result quantities.
axios.get('https://api.mercadolibre.com/sites/MLA/search?q='+ term +'&condition=used&category=MLA1652&offset=' + 0)
.then(function (response) {
//begin calculation of pages
let amount = response.data.paging.primary_results;
//since we only care about the primary results, this is fine. Since there are 50 items per page, we divide
//amount by 50, and round it up, since the last page can contain less than 50 items
pages = Math.ceil(amount / 50);
//here we begin the for loop.
for(i = 0; i < pages; i++) {
// So for each page we will do an axios request in order to get results
//Since each page is 50 as offset, then i should be multiplied by 50.
axios.get('https://api.mercadolibre.com/sites/MLA/search?q='+ term +'&condition=used&category=MLA1652&offset=' + i * 50)
.then((response) => {
const cleanUp = response.data.results.map((result) => {
let image = result.thumbnail.replace("I.jpg", "O.jpg");
return importante = {
id: result.id,
title: result.title,
price: result.price,
link: result.permalink,
image: image,
state: result.address.state_name,
city: result.address.city_name
}
});
arrayToStore.push(cleanUp);
console.log(pages, i)
if (i === pages) {
let path = ('./compare/yesterday-' + term +'.json');
if (fs.existsSync(path)) {
console.log("Loop Finished. Reading data from Yesterday")
fs.readFile('./compare/yesterday-' + term +'.json', (err, data) => {
if (err) throw err;
let rawDataFromYesterday = JSON.parse(data);
// test
//first convert both items to check to JSON strings in order to check them.
if(JSON.stringify(rawDataFromYesterday) !== JSON.stringify(arrayToStore)) {
//Then Check difference using id, otherwise it did not work. Using lodash to help.
let difference = _.differenceBy(arrayToStore[0], rawDataFromYesterday[0],'id');
fs.writeFileSync('./compare/New'+ term + '.json', JSON.stringify(difference));
//if they are different save the new file.
//Then send it via mail
console.log("different entries, wrote difference to JSON");
let newMail = mail(difference, term);
fs.writeFileSync('./compare/yesterday-' + term +'.json', JSON.stringify(arrayToStore));
DATA = {
content: difference,
message: "These were the differences, items could be new or deleted.",
info: "an email was sent, details are the following:"
}
return DATA;
} else {
console.log("no new entries, cleaning up JSON");
fs.writeFileSync('./compare/New'+ term + '.json', []);
DATA = {
content: null,
message: "There were no difference from last consultation",
info: "The file" + './compare/New'+ term + '.json' + ' was cleaned'
}
return DATA;
}
});
} else {
console.error("error");
console.log("file did not exist, writing new file");
fs.writeFileSync('./compare/yesterday-' + term +'.json', JSON.stringify(arrayToStore));
DATA = {
content: arrayToStore,
message: "There were no registries of the consultation",
info: "Writing new file to ' " + path + "'"
}
return DATA;
}
}
})
}
}).catch(err => console.log(err));
}
module.exports = compare
So I export this compare function, which I call on my app.js.
What I want is to make this compare function return the DATA object, so I can display the actual messages on the front end,
My hopes would be, putting this compare(term) function inside a route in app.js like so:
app.get("/api/compare/:term", (req, res) => {
let {term} = req.params
let data = compare(term);
res.send(data);
})
But as I said, Its returning undefined. I tried with async await, or returning the whole axios first axios call, but Im always returning undefined.
Thank you

FIrebase jQuery .on method is updating array values one by one rather than all at once

I am trying to update the values of the orders placed by users on the Corporate's page without a refresh. For this, I used the jQuery .on method. However, this returns the values in the array that I generated for the orders one by one rather than all at once. Is this just an issue with firebase or is it just my code.
Here is my code:
When I get the values:
firebase.database().ref('Orders/' + user_id).on('value', function(snapshot) {
// Check if the user has any pending orders
if (snapshot.val() === null) {
// No Pending Orders are Present
$('.order_content-parent').html(' <div class="order_content">Hooray! You have no pending orders!</div>');
} else {
// One or more pending orders are present
console.log(snapshot.val());
snapshot.forEach(function(child){
$('.order_content-parent').html(' <div class="order_content"></div>');
var order = child.val().Order;
var key = child.key;
console.log('Key is : '+key);
getOrders(key);
});
When I insert the values into the database:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
var myId = user.uid;
const orders = ['Orders: '];
$('.postData').each(function() {
var data = $(this).html();
orders.push(data);
var database = firebase.database();
database.ref('Orders/' + user_id + '/' + myId).set({
Order: orders
}, function(error) {
if (error) {
// The write failed...
alert(error);
} else {
$('.postData').html('Connecting...');
}
});
database.ref('Orders/' + myId).set({
Order: orders,
For: user_id
}, function(error) {
if (error) {
// The write failed...
alert(error);
} else {
$('.postData').html('Order Successfully Placed!');
}
});
});
} else {
// No user is signed in.
}
});
Here is my console when I print the values from the database:
Here is my database structure:
Can anyone help
Thanks in advance,
Tom
I think this is expected behaviour, as the documentation states:
The value event is called every time data is changed at the specified database reference, including changes to children.
Since your inserts are on a each loop, they get inserted one by one, triggering the .on() listener multiple times.
You could try inserting all the orders at once. Please try this approach and let me know if it works:
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("No user is signed in");
return;
}
var myId = user.uid;
var orders = [];
// Get the orders to insert first
$('.postData').each(function () {
var data = $(this).html();
orders.push(data);
});
// Then, insert them all at once
var database = firebase.database();
database.ref('Orders/' + user_id + '/' + myId).set({
Order: orders
}, function (error) {
if (error) {
// The write failed...
alert(error);
return;
}
$('.postData').html('Connecting...');
database.ref('Orders/' + myId).set({
Order: orders,
For: user_id
}, function (error) {
if (error) {
// The write failed...
alert(error);
return;
}
$('.postData').html('Order Successfully Placed!');
});
});
});

SQLITE_ERROR: no such table in Node.js

I'm writing a simple server/client to keep track of the amount of times a user has logged in. A user can create an account and have their count set to 1. Following logins will increase their count in the backend SQLITE3 database.
In the example below, I run the "add" function which correctly checks if the user exists already, then if not, adds the username, password, and 1 to the table of users.
This properly returns 1 as you can see in the output, but why is it erroring at the end? I'm not making any other calls, but it's returning a no such table error. The only call I make is console.log(UsersModel.add('kpam', '123'));, which is on the last line of the code. I tried looking into the line 72 of events.js, but it didn't really give me much. I added print statements to make it trace more obvious, but I have a feeling something is going on behind the scenes?
Basically, I'm confused why if I only called one function, and that function returns successfully, theres an error at the end of execution?
Here is the error returned:
:$ node warmup.js
Creating DB file.
making table!
adding user!
1
events.js:72
throw er; // Unhandled 'error' event
^
Error: SQLITE_ERROR: no such table: Users
:$
And here is my code:
var http = require('http');
var fs = require('fs');
var file = 'data.db';
var exists = fs.existsSync(file);
if (!exists) {
console.log("Creating DB file.");
fs.openSync(file, 'w');
}
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(file);
var UsersModel = {
// success, no errors/problems
SUCCESS: 1,
// cannot find the user/password pair in the database (for 'login' only)
ERR_BAD_CREDENTIALS: -1,
// trying to add a user that already exists (for 'add' only)
ERR_USER_EXISTS: -2,
// invalid user name (empty or longer than MAX_USERNAME_LENGTH) (for 'add'/'login')
ERR_BAD_USERNAME: -3,
// invalid password name (longer than MAX_PASSWORD_LENGTH) (for 'add')
ERR_BAD_PASSWORD: -4,
// maximum user name length
MAX_USERNAME_LENGTH: 128,
// maximum password length
MAX_PASSWORD_LENGTH: 128,
login: function(user, password) {
if (!UsersModel.userExists(user, false)) {
return UsersModel.ERR_BAD_CREDENTIALS;
}
if (!UsersModel.checkPassword(user, password)) {
return UsersModel.ERR_BAD_CREDENTIALS;
}
count = UsersModel.increaseCount(user);
return count;
},
add: function(user, password) {
if (UsersModel.userExists(user, true)) {
return UsersModel.ERR_USER_EXISTS;
}
if (!UsersModel.isValidUsername(user)) {
return UsersModel.ERR_BAD_USERNAME;
}
if (!UsersModel.isValidPassword(password)) {
return UsersModel.ERR_BAD_PASSWORD;
}
UsersModel.addUser(user, password);
return 1;
},
userExists: function(user, makeTable) {
if (!exists) {
if (makeTable) {
console.log('making table!');
db.run('CREATE TABLE Users (name TEXT, password TEXT, count INT)');
}
return false;
}
db.serialize(function() {
console.log('checking user!');
row = db.get("SELECT name FROM Users WHERE name = '" + user + "'");
});
return !(typeof(row.name) === 'undefined');
},
increaseCount: function(user) {
db.serialize(function() {
console.log('increasing count!');
count = db.get("SELECT count FROM Users WHERE name = '" + user + "'") + 1;
db.run("UPDATE Users SET count = '" + count + "' WHERE name = '" + user + "'");
return count;
});
},
addUser: function(user, password) {
count = 0;
console.log('adding user!');
db.run("INSERT INTO Users (name, password, count) VALUES ('" + user + "','" + password + "','" + 0 + "')");
},
checkPassword: function(user, password) {
db.serialize(function() {
console.log('checking pw!');
row = db.get("SELECT password FROM Users WHERE name = '" + user + "'");
});
return row.password == password;
},
isValidUsername: function(user) {
return user.length < 129;
},
isValidPassword: function(password) {
return password.length < 129;
}
}
console.log(UsersModel.add('kpam', '123'));
The db.run(...) calls are asynchronous. So they return immediately, and look like success to your code. However they're still running in the background. Because of this, the SELECT statement is likely starting to run before the CREATE TABLE completes. And that's why you get that error.
I notice that the SELECT statement is inside of a db.serialize(...) call. Unfortunately, that call only serializes statements that are directly inside its scope. All calls outside of the serialize block continue to run in parallel (this includes the INSERT statement that comes up later).
Your code needs to be restructured to use the callbacks that the node sqlite3 module relies on. Take a look at the simple example at:
https://github.com/mapbox/node-sqlite3/blob/master/examples/simple-chaining.js
Notice how the last parameter to each db operation is the name of the function to call after the operation is completed.

Node Fibers Trouble with Meteor

I'm trying to write a simple authentication backend for Meteor that authenticates against an LDAP server. I need the function registered as login handler (the input to Accounts.registerLoginHandler) to return the id the of the user just logged in.
The problem I think lies in the Fiber I've created, getUserId, and that it's not returning id like I want it to. I know it has to be in a Fiber or else meteor gets angry and throws errors. Even though the log right before the yield shows me the id isn't undefined, getUserId.run() always returns undefined.
Any help would be greatly appreciated, thanks!
Accounts.registerLoginHandler(function(loginRequest) {
console.log("In login handler");
return auth.authenticate(loginRequest.username, loginRequest.password, function(err, ldap_user) {
if (err){
// ldap authentications was failed
console.log("Login failed");
return undefined;
}
else {
// authentication was successful
console.log("Login success");
// extracting team name from ldap record
var equals = ldap_user.memberOf.indexOf("=");
var comma = ldap_user.memberOf.indexOf(",");
var team_name = ldap_user.memberOf.slice(equals+1,comma);
// add user if they don't already exist
var getUserId = Fiber( function() { // Meteor code must be ran within a fiber
var id = null;
var user = Meteor.users.findOne({username: loginRequest.username});
if(!user) {
// insert user and kick back id
id = Meteor.users.insert({username: loginRequest.username,
profile : {team : team_name}
});
console.log('no user found, creating' + id);
} else {
id = user._id;
console.log('user found, returning id' + id);
}
console.log('id: '+id);
Fiber.yield(id); // return id
});
// send logged in users if by executing the fiber
return {id: getUserId.run()};
}
});
});
I think the problem is related to instead needing to use Meteor.bindEnvironment to control the scope of the (environment) variables and fibers in use.
A good three-step tutorial on the subject is found here:
https://www.eventedmind.com/feed/nodejs-introducing-fibers
https://www.eventedmind.com/feed/meteor-dynamic-scoping-with-environment-variables
https://www.eventedmind.com/feed/meteor-what-is-meteor-bindenvironment
My take on your code would be something like this (which in a similar problem worked for me):
Accounts.registerLoginHandler(function(loginRequest) {
console.log("In login handler");
var boundAuthenticateFunction = Meteor.bindEnvironment(function(err, ldap_user) {
if (err){
// ldap authentications was failed
console.log("Login failed");
return undefined;
}
else {
// authentication was successful
console.log("Login success");
// extracting team name from ldap record
var equals = ldap_user.memberOf.indexOf("=");
var comma = ldap_user.memberOf.indexOf(",");
var team_name = ldap_user.memberOf.slice(equals+1,comma);
// add user if they don't already exist
var id = null;
var user = Meteor.users.findOne({username: loginRequest.username});
if(!user) {
// insert user and kick back id
id = Meteor.users.insert({username: loginRequest.username,
profile : {team : team_name}
});
console.log('no user found, creating' + id);
} else {
id = user._id;
console.log('user found, returning id' + id);
}
console.log('id: '+id);
return {id: id};
}
}, function(e){throw e;});
return auth.authenticate(loginRequest.username, loginRequest.password, boundAuthenticateFunction);
});
Mind, the code sample above is untested...

Categories