Firebase Authentication - Migrate users from SQL to Firebase - javascript

I am rewriting a website of mine, to JavaScript in combination with Firebase. So far, I love Firebase, but now I am on a part where I need to migrate all of my old users to the Firebase system.
All the users have custom extra settings and i wan't to take down the server where it's on now (€ 103, p/m). So I need it all to be copied. As far as I know, it needs to be done one by one (createUserWithEmailAndPassword). On the server, I make a JSON object and on the new site I do:
$.get("/alljsonfromalink", function (rdata) {
var $jData = jQuery.parseJSON(rdata);
var i = 0;
for (var user in $jData) {
whenYouFeelLikIt(user, $jData);
}
});
function whenYouFeelLikIt(i, $jData) {
setTimeout(function() {
firebase.auth().signInWithEmailAndPassword($jData[i].email, RandomPassword)
.then(function(){
console.log("Succes Login");
//if exist i set extra settings
setusersettings($jData[i], $jData[i].email);
})
.catch(function(error) {
var errorCode = error.code;
console.log(errorCode);
if(errorCode == "auth/user-not-found") {
firebase.auth().createUserWithEmailAndPassword($jData[i].email, RandomPassword).then(function () {
console.log("Created: " + $jData[i].email);
});
}
});
},2000 * (i));
}
It works, but even with a 2-second timeout, I got after 20 or 30 inserts:
firebase.js:73 Uncaught Error: We have blocked all requests from this device due to unusual activity. Try again later.
Anyone has an idea how to work around this?
--Update--
JamieB suggested to use .fetchProvidersForEmail() So now I use
firebase.auth().fetchProvidersForEmail($jData[i].email)
.then(function(succes){
console.log(succes.length);
if(succes.length == 0) {
// the createUserWithEmailAndPassword() with timeout
create(i, $jData);
}
})

FIREBASE REFERENCE indicates that createUserWithEmailAndPassword(email, password) returns firebase.Promise containing non-null firebase.User. So, it returns a promise. These can be pushed into an array that can be handed to the .all Promise method. You can use the below function to add the username and photoURL to the Auth subsystem where they will be stored.
Any additional user properties can be store under a users node where each child id is the UID of the user. The script at the bottom shows an example of using Promise.all.
function registerPasswordUser(email,displayName,password,photoURL){
var user = null;
//NULLIFY EMPTY ARGUMENTS
for (var i = 0; i < arguments.length; i++) {
arguments[i] = arguments[i] ? arguments[i] : null;
}
auth.createUserWithEmailAndPassword(email, password)
.then(function () {
user = auth.currentUser;
user.sendEmailVerification();
})
.then(function () {
user.updateProfile({
displayName: displayName,
photoURL: photoURL
});
})
.catch(function(error) {
console.log(error.message);
});
console.log('Validation link was sent to ' + email + '.');
}
...an example of Promise.all:...
function loadMeetings(city,state) {
return ref.child('states').child(state).child(city).once('value').then(function(snapshot) {
var reads = [];
snapshot.forEach(function(childSnapshot) {
var id = childSnapshot.key();
var promise = ref.child('meetings').child(id).once('value').then(function(snap) {
return snap.val();
}, function(error) {
// The Promise was rejected.
console.error(error);
});
reads.push(promise);
});
return Promise.all(reads);
}, function(error) {
// The Promise was rejected.
console.error(error);
}).then(function(values) {
//for each snapshot do something
});
}

Related

How can I add a setTimeOut() to run a function and insert data multiple times?

A few days ago I did a project and I had some problems, which where solved in this question, let me try to resume it.
I need to insert multiple objects into a DB in SQLServer, for that, I did a function that loops another function, which opens a connection, inserts and closes the connection, then, repeats it over and over again.
It worked fine, till today that was tested in a collegue PC, in the server of the job, I get this error:
Error: Requests can only be made in the LoggedIn state, not the LoggedInSendingInitialSql state
Error: Requests can only be made in the LoggedIn state, not the SentLogin7WithStandardLogin state
Here's the code we tested (the same in my last question), it works in my PC, but not in the other:
var config = {
...
};
function insertOffice(index) {
var connection = new Connection(config);
connection.on("connect", function (err) {
console.log("Successful connection");
});
connection.connect();
let url = `https://api.openweathermap.org/data/2.5/weather?lat=${offices[index].latjson}&lon=${offices[index].lonjson}&appid=${api_key}&units=metric&lang=sp`;
fetch(url)
.then((response) => { return response.json(); })
.then(function (data) {
var myObject = {
Id_Oficina: offices[index].IdOficina,
...
};
const request = new Request(
"EXEC USP_BI_CSL_insert_reg_RegistroTemperaturaXidOdicina #IdOficina, ...",
function (err) {
if (err) {
console.log("Couldnt insert data (" + index + "), " + err);
} else {
console.log("Data with ID: " + myObject.Id_Oficina +" inserted succesfully(" + index + ").")
}
}
);
request.addParameter("IdOficina", TYPES.SmallInt, myObject.Id_Oficina);
...
request.on("row", function (columns) {
columns.forEach(function (column) {
if (column.value === null) {
console.log("NULL");
} else {
console.log("Product id of inserted item is " + column.value);
}
});
});
request.on("requestCompleted", function () {
connection.close();
});
connection.execSql(request);
});
}
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
functionLooper();
So, I thought it would be a good idea to use a setTimeOut, to:
Run functionLooper().
Open connection, insert and close.
Wait a few seconds.
Repeat.
So, I changed to this:
setTimeout(functionLooper, 2000);
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
It works, but, as you can see, only waits when I first run it, so tried to make a function that runs setTimeout(functionLooper, 2000); like functionLooper() does, but it didn't work either.
function TimerLooper() {
for (let i = 0; i < offices.length; i++) {
setTimeout(functionLooper, 500);
}
}
function functionLooper() {
for (let i = 0; i < offices.length; i++) {
let response = insertOffice(i);
}
}
TimerLooper();
This shows me this error:
Error: Validation failed for parameter 'Descripcion'. No collation was set by the server for the current connection.
file:///...:/.../.../node_modules/node-fetch/src/index.js:95
reject(new FetchError(request to ${request.url} failed, reason: ${error.message}, 'system', error));
^ FetchError: request to https://api.openweathermap.org/data/2.5/weather?lat=XX&lon=XX&appid=XX&units=metric&lang=sp failed, reason: connect ETIMEDOUT X.X.X.X:X
So, I have some questions
How can I use properly setTimeOut? I did this function based on what I watch here in SO, but I just can't get it and I don't know what I'm doing wrong.
Why it works in my PC and the other don't? Do we have to change some kind of config or something?
Using setTimeOut, is the correct way to solve this problem? if not, what would you suggest me?
Could you do something like:
//edit: not disconnect but end
connection.on("end", function(){
functionLopper(index++)
})
function functionLooper(i) {
if(i<offices.length) insertOffice(i)
}
Edit: according to tidious doc
There is an end event emitted on connection.close()
Event: 'end'
function () { }
The connection has ended. This may be as a result of the client calling close(), the server closing the connection, or a network error.
My suggestion from above
var config = {
...
};
function insertOffice(index) {
var connection = new Connection(config);
connection.on("connect", function (err) {
console.log("Successful connection");
});
connection.connect();
let url = `...`;
fetch(url)
.then((response) => { return response.json(); })
.then(function (data) {
...
});
connection.on("end", function(){
functionLopper(index++)
})
}
function functionLooper(i) {
if(i<offices.length) insertOffice(i)
}
``

Broken promise cloud firestore function

I am trying to retrieve the document for specific ids in the following cloud function. The loop (see "Block Get Major") for retrieving that information is running, but the logs show that it is executing AFTER the function returns data.
I am certain this is because this creates a new promise, and that I need to tie them together.
I've looked through a lot of the posts on here and elsewhere and am just not getting it.
It looks like I am creating a broken/dropped promise. I get the necessary data, with the exception of menteeMajor, which is undefined until later in the log files).
exports.getAllMatchesMCR = functions.https.onCall((data, context) => {
var dbRef = db.collection("matches");
var dbPromise = dbRef.get();
// return the main promise
return dbPromise.then(function(querySnapshot) {
var results = [];
var idx = 0;
querySnapshot.forEach(function(doc) {
// push promise from get into results
var matchObj = {
mentorName: "",
mentorEmployer: "",
mentees: []
}
var mentor = doc.data();
mentor.mentorID = doc.id;
matchObj.mentorName = mentor.mentorID;
matchObj.mentees = mentor.mentees;
for (var curIDX in matchObj.mentees) {
matchInfoObj = {};
matchInfoObj.mentorID = matchObj.mentorID;
matchInfoObj.mentorName = matchObj.mentorName;
matchInfoObj.menteeName = matchObj.mentees[curIDX];
// Block Get Major --->
var menteeRef = db.collection('users').doc(matchObj.mentees[curIDX]);
var getDoc = menteeRef.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
} else {
var userInfo = {};
userInfo = doc.data();
matchInfoObj.menteeMajor = userInfo.major;
// console.log('user Major:', matchInfoObj.menteeMajor);
return userInfo.major;
}
})
.catch(err => {
// console.log('Error getting document', err);
return ("No Mentee Doc")
});
console.log("in menteeInfo: ", getDoc.data());
matchInfoObj.menteeMajor = getDoc.data(); // Block Get Major <---
if (typeof something === "undefined") {
console.log('After BLOCK Major is UNDEFINED:', matchInfoObj.menteeName);
} else {
console.log('After BLOCK :', matchInfoObj.menteeMajor);
}
results.push(matchInfoObj)
}
});
// dbPromise.then() resolves to a single promise that resolves
// once all results have resolved
return Promise.all(results)
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
});
Here are two screen shots for the logs that show the order based on console.log output. Keep in mind that the sort of these log records is NEWEST at the top. Result 1 shows the start, from deployment of function and it's start, Result 2 shows the messages from the broken promise almost two minutes later.
The plain text version of what I'm trying to do is:
1. collection "Users" contains information on Mentees and Mentors
2. collection "Matches" is a doc of Mentees (array) for each Mentor, or One to Many).
3. Display one row for EACH mentor/mentee connection. I have the ids, but I need to get the names and other information for both mentees and mentors.
Plain old Result file shows that I am getting the rows I need with the Mentor ID, and the Mentee ID (labeled Mentees, that will change)
Can someone please show me the way to chain this promise?

Know when jqXHRs of an array are all completed

I'm trying to run some code once all the jqXHR elements of an array are completed (have either succeeded or failed).
You can see the full code here: http://jsfiddle.net/Lkjcrdtz/4/
Basically I'm expecting the always hook from here:
$.when
.apply(undefined, reqs)
.always(function(data) {
console.log('ALL ALWAYS', data);
});
to run when all the requests that were piled up there have either succeeded or failed. Currently, you can observe in the console that ALL ALWAYS is logged earlier.
A simple solution for modern browsers would be to use the newer fetch() API along with Promise.all()
var makeReq = function(url, pos) {
var finalUrl = url + pos;
// intentionally make this request a failed one
if (pos % 2 === 0) {
finalUrl = "https://jsonplaceholder.typicode.com/423423rfvzdsv";
}
return fetch(finalUrl).then(function(resp) {
console.log('Request for user #', pos);
// if successful request return data promise otherwise return something
// that can be used to filter out in final results
return resp.ok ? resp.json() : {error: true, status: resp.status, id: pos }
})
};
// mock API
var theUrl = "https://jsonplaceholder.typicode.com/users/";
var reqs = [];
for (var i = 1; i <= 5; i++) {
reqs.push(makeReq(theUrl, i));
}
Promise.all(reqs).then(function(results) {
console.log('---- ALL DONE ----')
// filter out good requests
results.forEach(function(o) {
if (o.error) {
console.log(`No results for user #${o.id}`);
} else {
console.log(`User #${o.id} name = ${o.name}`);
}
})
})

How to chain promises within nested for loops?

var verifyEmail = function (thisEmail){
return new Promise(
function (resolve, reject) {
quickemailverification.verify(thisEmail, function (err, response) {
// Print response object
console.log(response.body);
if (response.body["success"] == "true"){
var validity = response.body["result"];
if (validity == "valid"){
console.log("Email Valid!");
resolve(validity);
} else {
console.log("Email Invalid!")
resolve(validity);
}
} else {
var reason = new Error("API unsuccessful");
reject(reason);
}
});
}
);
};
var saveValidity = function (validity){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
var state = true;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
console.log("Email ("+thisEmail+") verfication could not be saved" + error);
}
console.log("Email verification saved: " +thisEmail);
});
}
}
);
};
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
var snap = snapshot.val();
keys = Object.keys(snap);
for (var i = 0; i < 100; i++){
var emails = snap[keys[i]]["emails"];
if (emails){
for (var x = 0; x<emails.length; x++){
var thisEmail = emails[x]["email"];
var emailVerified = emails[x]["verified"];
if (emailVerified != true || emailVerified != false){
verifyEmail
.then(saveValidity)
.then(function (fulfilled) {
console.log(fulfilled);
})
.catch(function (error){
console.log(error.message);
});
}
}
}
}
});
Above is the code I put together. I'm not all too convinced that it will work. I'm new to promises, so I'm trying to understand how to do this right.
The verifyEmail function should take in the email address from the firebase query in the third chunk of the code. The saveValidity function should take on the validity response from verifyEmail.
But, what I'm also worried about the nested for loop I have in the firebase query block. I'm looping through each user to validate their emails, but each user sometimes also has multiple emails. I'm worried that it will loop on to the next user before finishing checking all the emails of the previous user.
I'm also not sure if I can pass data into the promise functions the way I did.
Could definitely use some help here. Really trying hard to understand how this works.
First, you need to fix saveValidity() to always resolve or reject the promise and to pass in the other variables key and thisEmail that it references:
const saveValidity = function (validity, key, thisEmail){
return new Promise(
function (resolve, reject){
if (validity == "valid"){
let state = true;
admin.database().ref("/users_unverified/"+key+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error("Email ("+thisEmail+") verfication could not be saved" + error));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
} else {
state = false;
admin.database().ref("/users_unverified/"+keys[i]+"/emails/"+x+"/verified/").set(state, function(error) {
if (error) {
let msg = "Email ("+thisEmail+") verfication could not be saved" + error;
console.log(msg);
reject(new Error(msg));
} else {
resolve("Email verification saved: " +thisEmail);
}
});
}
}
);
};
Then, several changes are made to your main loop:
I assume we can run all the verifyEmail() calls in parallel since they don't appear to have anything to do with one another.
Change verifyEmail.then(...) to verifyEmail(thisEmail).then(...)` to actually call the function
Collect all the verifyEmail() promises in an array
Call Promise.all() on the array of promises to monitor when they are all done
Return value from .then() so we get the returned values in Promise.all()
rethrow in .catch() so promise stays rejected and will filter back to Promise.all(). You could eat errors here if you want to ignore them and continue with others.
Switch from var to let
Change from != to !== since it looks like your explicitly looking for a true or false value and don't want type casting.
Pass in the variables that saveValidity() needs.
Change logic when comparing emailVerified because what you had before was always true and thus probably not the right logic. I think what you want is to know when emailVerified is not yet set to true or to false which means you have to use &&, not ||.
Compare outer for loop with keys.length, not hard-coded value of 100.
And, here's the resulting code for the main nested for loop:
admin.database().ref("/users_unverified/").once('value').then(function(snapshot) {
let snap = snapshot.val();
let keys = Object.keys(snap);
let promises = [];
for (let i = 0; i < keys.length; i++){
let key = keys[i];
let emails = snap[key]["emails"];
if (emails){
for (let x = 0; x < emails.length; x++) {
let currentKey = key;
let thisEmail = emails[x]["email"];
let emailVerified = emails[x]["verified"];
if (emailVerified !== true && emailVerified !== false){
promises.push(verifyEmail(thisEmail).then(validity => {
return saveValidity(validity, currentKey, thisEmail);
}).then(function (fulfilled) {
console.log(fulfilled);
return fulfilled; // after logging return value so it stays the resolved value
}).catch(function (error) {
console.log(error.message);
throw error; // rethrow so promise stays rejected
}));
}
}
}
}
return Promise.all(promises);
}).then(results => {
// all results done here
}).catch(err => {
// error here
});
If ES2017 is available in your case, you can just use the keywords await and async to do that directly. Following is an example:
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
And you can read more about async/await here.
If you want to do that without async/await to achieve better browser compatibility, you can use Babel to do the pre-compile.
If you really want a lightwight implementation, you can use a function named chainPromiseThunks, or chain for short. This chain function accepts an Array of Thunks of Promises, And returns a new Thunk of Promise, Following is an one-line-implementation of chain:
const chain = thunks => thunks.reduce((r, a) => () => r().then(a));
And here is a usage demo:
const echo = x =>
new Promise(function(resolve) {
return setTimeout((function() {
console.log(x);
return resolve(x);
}), 1000);
})
;
const pThunks = [1,2,3,4,5].map(i => () => echo(i));
chain(pThunks)();

how to chain multiple promises into a single transaction in Firebase

A few months back Firebase implemented promises in the database.
In that blog post there is an example of using a single transaction with promises:
var article;
var articleRef = ref.child('blogposts').child(id);
articleRef.once('value').then(function(snapshot) {
article = snapshot.val();
return articleRef.child('readCount').transaction(function(current) {
return (current || 0) + 1;
});
}).then(function(readCountTxn) {
renderBlog({
article: article,
readCount: readCountTxn.snapshot.val()
});
}, function(error) {
console.error(error);
});
Is it possible to chain multiple promises into a single transaction to be able to erase data only if everything can be erased?
Do you actually need a transaction or just an atomic operation? You can execute atomic operations on multiple paths simultaneously that will fail if something goes wrong with one of them (ie erase which is a write operation):
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
var updatedUserData = {};
updatedUserData["user/posts/location1"] = null;
updatedUserData["user/blogs/location2"] = null;
// Do a deep-path update
ref.update(updatedUserData).then(function() {
//yay
}, function(error) {
console.log("Error updating data:", error);
});
Transactions on the other hand are typically used for atomic data modifications where concurrent updates could cause an inconsistent data state. Imagine an upvote counter:
// user 1
upvotesRef.transaction(function (current_value) {
return (current_value || 0) + 1;
});
// user 2
upvotesRef.transaction(function (current_value) {
return (current_value || 0) + 1;
});
// upvotesRef will eventually be consistent with value += 2
Without transactions, you are not guaranteed this consistency:
// user 1
ref.on("value", function(snapshot) {
console.log(snapshot.val()); // oops, could be the same value as user 2!
ref.set(snapshot.val() + 1);
});
// user 2
ref.on("value", function(snapshot) {
console.log(snapshot.val()); // oops, could be the same value as user 1!
ref.set(snapshot.val() + 1);
});
More info here

Categories

Resources