firebase/angularFire querying with startAt/endAt doesn't work - angularjs

I have an app where I use FirebaseSimpleLogin, but since I have additional user data, I store it under [my-firebase].firebaseio.com/users/[username]. The snippet below shows how it's done
var User = {
create: function (authUser, username) {
users[username] = {
md5_hash: authUser.md5_hash,
username: username,
email: authUser.email,
$priority: authUser.uid
};
users.$save(username).then(function () {
setCurrentUser(username);
});
},
...
Since data for each individual user, are keyed based on username I prioritize by uid, so I can later fetch additional user data by uid.
When the firebase login event fires, I have the following handler, that is responsible for querying firebase to get the additionala user data, and store it on $rootScope.currentUser via a method setCurrentUser(username)
Here is my login event handler:
var ref = new Firebase(FIREBASE_URL + '/users');
var users = $firebase(ref);
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var query = $firebase(ref.startAt(authUser.uid).endAt(authUser.uid));
query.$on('loaded', function () {
//PROBLEM: console.log(query.$getIndex()) logs an empty array
setCurrentUser(query.$getIndex()[0]);
});
});
As soon as the login event fires, I get access to the authUser, which contains the uid of the logged in user. Then I query firebase /users by uid, using startAt/endAt to limit my results so that I'm left only with the currently logged in user.
Then, when the query data is loaded from firebase, I invoke the setCurrentUser method which stores the username of the currentUser on $rootScope.
The Problem
The query filtering using startAt/endAt does not work, I'm getting back an empty array when I console.log query.$getIndex() when I should be getting an array with the username of the currently logged-in user.
I'm using firebase 1.0.15, angularFire 0.7.1 and firebase-simple-login 1.4.1 and following this tutorial from Thinkster.io

In the end somewhere in my code I had a $save() on my user binding, to save the id of the last project they've been working on, and this was causing the $priority issue. I used $update instead, and now everything works like a charm!
Still I don't know if this is intended behavior of $save()
To clarify and provide more context, I wasn't passing any keys to the $save() method, I simply added a property on my local user reference, and saved it
$rootScope.currentUser.lastWorkingProject = projectId;
$rootScope.currentUser.$save();

Related

Storing CognitoUser in cache make him lose some properties

I'm currently using AWS Cognito in my application.
When a user first connects whit his account, Cognito returns NEW_PASSWORD_REQUIRED as a challenge, which is fine.
I want to redirect to a page where the user can set his new password, so I put the response from Auth.signIn in storage (I tried local storage, session storage and Cache from AWS Amplify) but when I get it back on the other page, it lose some properties and Auth.completeNewPassword returns the error : 'user.completeNewPasswordChallenge is not a function'
Login.js :
try {
var authPromise = Auth.signIn(this.state.email, this.state.password);
authPromise.then((result) => {
console.log(result);
if (result.challengeName === 'NEW_PASSWORD_REQUIRED') {
Cache.setItem("CognitoUser", result);
this.props.history.push("/login/newPassword");
}
else {
this.props.userHasAuthenticated(true);
this.props.history.push("/");
}
});
} catch (e) {
alert(e.message);
this.setState({ isLoading: false });
}
NewPassword.js :
try {
var user = Cache.getItem("CognitoUser");
if (user) {
await Auth.completeNewPassword(user, this.state.newPassword);
this.props.history.push("/");
Cache.removeItem("CognitoUser");
}
} catch (e) {
alert(e.message);
this.setState({ isChanging: false });
}
Any ideas ?
It's javascript so when you write to your localcache and serializes your result into the "CognitoUser" key , it's stored as a a string, which afterwards deserialized will be a plain old Object unaware of the original type before serialization.
Original cause is maybe that your "result" type may expose functions which are not serializable (if not a getter, or if a getter with arguments).
I suggest you to call and store all the data you want into separate keys and re-read them later.
Cache.setItem("CognitoUser", result);
Cache.setItem("CognitoUser-value-1", result.myFunction1("myArg1"));
Cache.setItem("CognitoUser-value-2", result.myFunction2("myArg2"));
// ..
var user = Cache.getItem("CognitoUser");
var myVal1 = Cache.getItem("CognitoUser-value-1");
var myVal2 = Cache.getItem("CognitoUser-value-2");
You can also keep one single key "CognitoUser" in your localStorage if you make all said functions serializable. For instance, extend the type of your result adding prototypes getter functions (no arguments), each calling and returning respective myFunctionX("myArgX") functions, so that they'll appear in the JSON.stringify process.
My work around,
So this problem troubled me for some time. Amplify Cache didn't seem to work and caching username and password is a bad idea, however my work around was just include the username and password in the Require-New-Password form, so I have 4 inputs instead of just newPassword & confirmPassword which now is username, oldPassword, newPassword, and confirmPassword.
https://github.com/aws-amplify/amplify-js/issues/1715#issuecomment-642733574

Return a firebase database reference as an array in a cloud function

Below is the firebase database reference returned as an array in my AngularJS scope:
var ref = firebase.database().ref("users").child(user.uid).child("week1");
$firebaseArray(ref);
However when I tried writing the same code in my index.js file for a database Cloud Function, there was an error message:
ReferenceError: $firebaseArray is not defined at /user_code/index.js:22:18
Is there a way to make a Firebase reference ref return as an array in my index.js Cloud Functions file since $firebaseArray is not defined outside the AngularJS scope?
Below is an illustration of the database:
users: {
user uid (generated by push) : {
deviceToken : "tokenID",
name : "Display Name"
},
anotherUserID : {
deviceToken : "tokenID",
name : "Display Name"
},
Players: {
player1: John,
Player2: ken,
}
Is there a way for a change in the Players database node to trigger a function in the users node (for each user):
exports.update = functions.database.ref('/Player')
.onWrite(event=>{
console.log(event.data);
var ref = admin.database().ref('/users/'+ user.uid+ '/week1');
ref.set(10);
return;
});
My issue was accessing the user.uid (created by the push() method) for each user.
In Cloud Functions, you can use the Firebase Admin SDK to save and retrieve data from the database. To initialize the Admin SDK, you can use environment configuration:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
You can then attach a value listener to your database reference, utilizing and returning a JavaScript Promise to keep the function alive and chain your logic:
const ref = admin.database().ref("/users").child(user.uid).child("week1");
return ref.once("value").then(snapshot => {
const values = snapshot.val();
// Do something with the snapshot
});
The values variable here will be assigned an array of the values at the location (see val()), so could be used in place of $firebaseArray.
If you need to obtain a reference to the node that triggered your database function, you can use the event parameter from the function definition:
let ref = event.data.ref;
This is useful if you need to obtain the specific location in the database that triggered your function. The event.data variable is a DeltaSnapshot instance for database functions.
Similarly, you can use event.params to obtain any of the variables specified in a database trigger definition, for example {uid} in the below definition can be obtain using event.params.uid:
exports.example = functions.database.ref('/users/{uid}')
.onWrite(event => {
const uid = event.params.uid;
// ...
});
The method you take here depends on what work you need your function to perform, how it's triggered and what variables or values you need access to.
Remember that your function can freely read and write data anywhere in the database using the Admin SDK too.
In your newest example, it would be hard to match a node at /players to /users because there isn't a value with the user's UID. So you would need to change the /players child nodes to include further data, something like:
players : {
player1 : {
name: "John",
uid: "anotherUserID"
}
}
You could then extract this uid using event.data.child('uid').val() from your function (where your function is triggered by children under this node using /players/{playerId}):
exports.update = functions.database.ref('/players/{playerId}')
.onWrite(event=>{
console.log(event.data);
const uid = event.data.child('uid').val();
var ref = admin.database().ref('/users/' + uid + '/week1');
ref.set(10);
return;
});

Using passport-facebook without Mongoose User (No Mongo in the MEAN stack)

I'm very new to the MEAN stack, and this might seem to be very naive or wrong approach, but I want to ask that when we authenticate using passport-facebook strategy, using the following code:
var FacebookStrategy = require('passport-facebook').Strategy;
var User = require('../models/user');
var fbConfig = require('../fb.js');
module.exports = function(passport) {
passport.use('facebook', new FacebookStrategy({
clientID : fbConfig.appID,
clientSecret : fbConfig.appSecret,
callbackURL : fbConfig.callbackUrl
},
// facebook will send back the tokens and profile
function(access_token, refresh_token, profile, done) {
console.log('profile', profile);
// asynchronous
process.nextTick(function() {
// find the user in the database based on their facebook id
User.findOne({ 'id' : profile.id }, function(err, user) {
// if there is an error, stop everything and return that
// ie an error connecting to the database
if (err)
return done(err);
// if the user is found, then log them in
if (user) {
return done(null, user); // user found, return that user
} else {
// if there is no user found with that facebook id, create them
var newUser = new User();
// set all of the facebook information in our user model
newUser.fb.id = profile.id; // set the users facebook id
newUser.fb.access_token = access_token; // we will save the token that facebook provides to the user
newUser.fb.firstName = profile.name.givenName;
newUser.fb.lastName = profile.name.familyName; // look at the passport user profile to see how names are returned
//newUser.fb.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
// save our user to the database
newUser.save(function(err) {
if (err)
throw err;
// if successful, return the new user
return done(null, newUser);
});
}
});
});
}));
};
I don't need to store the user information in any data store. I want to store the token only for the time the user is logged into my web application, basically I don't have the need to use Mongo, because all the data that will be displayed in the web application will come from Facebook api, for example the posts for a profile, the number of likes on a particular posts etc. I don't need to have a backend as such, because if I store the data in any data store such as Mongo, the next time the user login then the data will be stale (in a way the Facebook api is kind of my backend), and I also want that the updates for information on any posts done on Facebook should be updated realtime on my web application for e.g. if someone likes a post on the actual Facebook page the number of likes on my web application should also be updated in realtime, so it seems unnecessary to first bring the data from the Facebook SDK and then store it in Mongo, why not just give it to the controller and from there the view can present the data. If my approach is wrong please do correct me.
So basically every time the user logs in an access token is created and used for that session, when the user logs out the access token is destroyed and so completely eliminates the need for storing the token and any data that is brought in using the Facebook SDK.
Replace the function call
User.findOne({ 'id' : profile.id }, function(err, user) {
With facebook sdk authentication call and return the user object when it's validated.
return done(null, user);
Please refer...
https://github.com/jaredhanson/passport-facebook
you need to create a new user template in the model folder. I have created the following: user.js
var facebook = module.exports.facebook = {
id : String,
token : String,
email : String,
name : String
}
and then change the passport.serializeUser and passport.deserializeUser functions.
passport.serializeUser(function(user, done) {
done(null, user.facebook.id);
});
// used to deserialize the user
//passport.deserializeUser(function(id, done) {
passport.deserializeUser(function(id, done) {
done(null, { id: User.facebook.id, token: User.facebook.token, name: User.facebook.name, email: User.facebook.email})
});
then the function: process.nextTick(function() {} replace the content by this code :
var newUser = User;
// set all of the facebook information in our user model
newUser.facebook.id = profile.id; // set the users facebook id
newUser.facebook.token = token; // we will save the token that facebook provides to the user
newUser.facebook.name = profile.name.givenName + ' ' + profile.name.familyName; // look at the passport user profile to see how names are returned
newUser.facebook.email = profile.emails[0].value; // facebook can return multiple emails so we'll take the first
return done(null, newUser);
add the line profileFields: ['id', 'displayName', 'photos', 'emails', 'name'] in function passport.use(new FacebookStrategy({}
change the profile.ejs file by removing the local information div and changing the properties <% = user.facebook.id%> to <% = user.id%> and so on in the others.

Restangular use save()

I'm playing with Restangular and I want to make a call to .save() to update or create an entity. From Restangular github page I can see that it is possible to update or create a new account. But if there are no accounts on the server I get method does not exists. (firstAccount is undefined)
Restangular.all('accounts').getList().then(function(accounts) {
var firstAccount = accounts[0];
firstAccount.title = "New title"
// PUT /accounts/123. Save will do POST or PUT accordingly
firstAccount.save();
});
My question is how do I make firstAccount a restangular object that will go to the correct url (POST /accounts) when I call firstAccount.save() if there are no accounts in the response?
If you are trying to create a new element, you may restangularize it:
var account = {
title: 'New Title'
};
var restangularAccount = Restangular.restangularizeElement(null, account, 'accounts');
restangularAccount.save();
That will do an HTTP POST to /accounts

AngularFire: Getting the ID of Users Data from email in SimpleLogin

I'm trying to implement some simple using registration using Firebase through AngularFire and Angular.js. I'm using the SimpleLogin tool to manage the users. I can create users just fine.
var firebaseRef = new Firebase(FIREBASE_URL);
var simpleLogin = $firebaseSimpleLogin(firebaseRef);
var firebaseUsersRef = new Firebase(FIREBASE_URL + 'users');
var firebaseUsers = $firebase(firebaseUsersRef);
var myObject = {
register: function(user) {
var myDate = new Date().getTime();
return simpleLogin.$createUser(
user.email, user.password)
.then(function(regUser) {
var userInfo = {
date: myDate,
md5: regUser.md5_hash,
firstname: user.firstname,
lastname: user.lastname,
email: user.email
}
firebaseUsers.$push(userInfo).then(function(ref) {
userInfo.uid = ref.name();
$rootScope.currentUser = userInfo;
});
}); //push user
}, //register
Works like a charm. In order to get at this information when the user logs in, I've tried implementing an event handler on the $rootscope. I would like it to search through the uid that I stored and then get me record with the right user information.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var query = $firebase(firebaseRef.startAt(authUser.uid).endAt(authUser.uid));
console.log(query);
$location.path('/meetings');
});
In order to use startAt and endAt, do I have to establish $priority. When I try, I get an error stating that I can't have any special characters. So that never works. I don't really care about how this data stored, I just want to get the index of the data so that I can retrieve the right user.
By using $push you tell Firebase to generate a key for you.
This is great for collections where you normally access all children at the same time. But that is not the case for your user info: you want to access the info for the current user.
So instead of using $push to add your user's info, I would use the uid of the user.
In the regular Firebase JavaScript API this can be accomplish with:
firebaseUsersRef.child(reguser.uid).set(userInfo);
The equivalent in AngularFire probably uses $set, but I don't think you have any need for that in your $createUser callback.
Update
It looks like you're trying to add your info to the existing user node that Firebase creates for you. This is the example from that from the Firebase documentation on storing user data:
myRef.child('users').child(user.uid).set({
displayName: user.displayName,
provider: user.provider,
provider_id: user.id
});
You can see that they do access the user's node using child(user.uid) similar to what I proposed.
Lessons
Two relatively small mistakes here as far as I can see:
when you use push/$push, you let Firebase generate the node name for you. In cases where there already is a globally unique ID (such as the uid of a user), you're often better off using that as the node name.
If you know the name of the node you want to retrieve, you don't need a query. You can simply access the node as ref.child(user.uid).
Thanks to Frank, I was able to figure out the right way to do this. In order to make my own users object searchable, I can use the uid from the simpleLogin object. So my register function works like this:
var firebaseRef = new Firebase(FIREBASE_URL);
var simpleLogin = $firebaseSimpleLogin(firebaseRef);
var myObject = {
register: function(user) {
var myDate = new Date().getTime();
return simpleLogin.$createUser(user.email, user.password)
.then(function(regUser) {
var userInfo = {
date: myDate,
md5: regUser.md5_hash,
firstname: user.firstname,
lastname: user.lastname,
email: user.email
}
firebaseUsers.$set(regUser.uid, userInfo);
}); //add user
}, //register
} //my Object
Using set instead of push, I can store the uid from the registered user into the object and then pass along what I want to add as the second parameter. My database will now have the users organized by uid, which can be accessed via a url.
Then, when users log in, Firebase will throw up a login event along with the authenticated user, which I can catch and use to add the current user to the $rootScope that's accessible throughout my application.
$rootScope.$on('$firebaseSimpleLogin:login', function (e, authUser) {
var ref = new Firebase(FIREBASE_URL + '/users/' + authUser.uid);
var user = $firebase(ref).$asObject();
user.$loaded().then(function() {
$rootScope.currentUser = user;
});
$location.path('/meetings');
});
Thanks for pointing me in the right direction Frank.

Resources