How to use a variable in firebase.setValue (nativescript-firebase plugin)? - firebase

I want to store unique user ids provided by Firebase, in my database.
Here is my code:
var uid = result.uid;
console.log(uid);
// Prints the unique user id
// Create User structure in FB
firebase.setValue(
'Users',
{uid: true}
);
This creates the following in FB:
Users
uid: true
uid is simply that, just a string that says uid. Am I doing something incorrect?
(docs for reference)

You'll need to use [] accessor to use the value of the uid as a key/property name:
var obj = {};
obj[uid] = true;
firebase.setValue('Users', obj);

Related

firebase firestore adding new document inside a transaction - transaction.add is not a function

I was assuming that it was possible to do something like:
transaction.add(collectionRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
But apparently it is not:
transaction.add is not a function
The above message is displayed inside the chrome console.
I see that we can use the set method of the transaction to add a new document transactionally. see: https://firebase.google.com/docs/firestore/manage-data/transactions
The thing is if I use set instead of add(which is not supported anyways), the id of the document should be created by me manually, firestore won't create it.
see: https://firebase.google.com/docs/firestore/manage-data/add-data
Do you see any downside of this not having an add method that generates the id for you automatically?
For example, is it possible that the id generated by the firestore itself is somehow optimized considering various concerns including performance?
Which library/method do you use to create your document IDs in react-native while using transaction.set?
Thanks
If you want to generate a unique ID for later use in creating a document in a transaction, all you have to do is use CollectionReference.doc() with no parameters to generate a DocumentReference which you can set() later in a transaction.
(What you're proposing in your answer is way more work for the same effect.)
// Create a reference to a document that doesn't exist yet, it has a random id
const newDocRef = db.collection('coll').doc();
// Then, later in a transaction:
transaction.set(newDocRef, { ... });
after some more digging I found in the source code of the firestore itself the below class/method for id generation:
export class AutoId {
static newId(): string {
// Alphanumeric characters
const chars =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
}
see: https://github.com/firebase/firebase-js-sdk/blob/73a586c92afe3f39a844b2be86086fddb6877bb7/packages/firestore/src/util/misc.ts#L36
I extracted the method (except the assert statement) and put it inside a method in my code. Then I used the set method of the transaction as below:
generateFirestoreId(){
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let autoId = '';
for (let i = 0; i < 20; i++) {
autoId += chars.charAt(Math.floor(Math.random() * chars.length));
}
//assert(autoId.length === 20, 'Invalid auto ID: ' + autoId);
return autoId;
}
then,
newDocRef = db.collection("PARENTCOLL").doc(PARENTDOCID).collection('SUBCOLL').doc(this.generateFirestoreId());
transaction.set(newDocRef,{
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
Since I am using the same algo for the id generation as the firestore itself I feel better.
Hope this helps/guides someone.
Cheers.
Based on the answer from Doug Stevenson, this is how I got it worked with #angular/fire:
// Create a reference to a document and provide it a random id, e.g. by using uuidv4
const newDocRef = this.db.collection('coll').doc(uuidv4()).ref;
// In the transaction:
transaction.set(newDocRef, { ... });
To complete Stefan's answer. For those using Angularfire, earlier to version 5.2 using CollectionReference.doc() results in an error "CollectionReference.doc() requires its first argument to be of type non-empty string".
This workaround worked for me:
const id = this.afs.createId();
const ref = this.afs.collection(this.collectionRef).doc(id);
transaction.set(ref, { ... });
Credit: https://github.com/angular/angularfire/issues/1974#issuecomment-448449448
I'd like to add an answer solving the id problem. There's no need to generate your own ids. The documentReference is updated after the transaction.set() is called, so in order to access the Firestore's id you need to just do the following:
const docRef = collectionRef.doc();
const result = await transaction.set(docRef, input);
const id = docRef.id;
First of all, firestore transaction object has 4 (get,set,update,delete) methods and doesnt has "add" method. However, the "set" method can be used instead.
import { collection,doc,runTransaction } from "firebase/firestore";
On the other hand documentReference must be created for "set" method.
Steps :
1-) collection method create a collectionReference object.
const collectionRef = collection(FirebaseDb,"[colpath]");
2-) doc method create a documentReference object with unique random id for specified collectionReference.
const documentRef = doc(collectionRef);
3-) add operation can be performed with the transaction set method
try {
await runTransaction(FirebaseDb,async (transaction) => {
await transaction.set(documentRef, {
uid: userId,
name: name,
fsTimestamp: firebase.firestore.Timestamp.now(),
});
})
} catch (e) {
console.error("Error : ", e);
}

Firebase get value from database

The question is: How to take the uid row when you only know the user his username? for example, you only know 'senneken' and you want to know the uid of 'senneken'
Extra information:
My user database looks like this
I want to add friends to users. I can check if the user exists for them by doing
searchButton.addEventListener('click', function (event) {
event.preventDefault();
username = searchUsername.value;
var ref = firebase.database().ref('users').orderByChild("username").equalTo(username).once("value", snapshot => {
const userData = snapshot.val();
if (userData) {
console.log("Username " + username + " was found");
} else {
console.log("No user found");
}
But now I want to add the users UID in my database
And I can add the friends username by doing
addFriendButton.addEventListener('click', function (event) {
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
var ref = firebase.database().ref("users").child(user.uid).child("friends").push({
username: username
})
}
});
});
Because I use push there is always a random ID generated under friends but I would like to take the UID from the user that I want to add and put that under my friends (instead of the random UID)
In this case the collection of friends seems like a set: each specific UID can either be in there, or it cannot be in there. It cannot be in there more than once, and order seems to not matter. The solution is to not use a push ID, but model it as a set like this:
friends
uid1
uid2: true
uid3: true
This way you can simply set a user as a friend with:
firebase.database().ref("friends").child(user.uid).child(username).set(true)
You might notice that I also turned the collections of friends into a top-level collection. Nesting information about friends under other profile information about a user is an anti-pattern, which makes it hard to secure data, leads to downloading more data than is needed, and in general is not recommended by Firebase experts.

Cloud Functions for Firebase: how to get authenticated user in a database trigger [duplicate]

In the example below, is there a way to get the uid of the user who wrote to /messages/{pushId}/original?
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
console.log('Uppercasing', event.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return event.data.ref.parent.child('uppercase').set(uppercase);
});
UPDATED ANSWER (v1.0.0+):
As noted in #Bery's answer above, version 1.0.0 of the Firebase Functions SDK introduced a new context.auth object which contains the authentication state such as uid. See "New properties for user auth information" for more details.
ORIGINAL ANSWER (pre v1.0.0):
Yes, this is technically possible, although it is not currently documented. The uid is stored with the event.auth object. When a Database Cloud Function is triggered from an admin situation (for example, from the Firebase Console data viewer or from an Admin SDK), the value of event.auth is:
{
"admin": true
}
When a Database Cloud Function is triggered from an unauthenticated reference, the value of event.data is:
{
"admin": false
}
And finally, when a Database Cloud Function is triggered from an authed, but not admin, reference, the format of event.auth is:
{
"admin": false,
"variable": {
"provider": "<PROVIDER>",
"provider_id": "<PROVIDER>",
"user_id": "<UID>",
"token": {
// Decoded auth token claims such as sub, aud, iat, exp, etc.
},
"uid": "<UID>"
}
}
Given the information above, your best bet to get the uid of the user who triggered the event is to do the following:
exports.someFunction = functions.database.ref('/some/path')
.onWrite(event => {
var isAdmin = event.auth.admin;
var uid = event.auth.variable ? event.auth.variable.uid : null;
// ...
});
Just note that in the code above, uid would be null even if isAdmin is true. Your exact code depends on your use case.
WARNING: This is currently undocumented behavior, so I'll give my usual caveat of "undocumented features may be changed at any point in the future without notice and even in non-major releases."
Ever since Firebase functions reached version 1.0, this behavior is no longer undocumented but has sligtly changed. Be sure to read the docs.
Context has been added to cloud functions and you can use it like this
exports.dbWrite = functions.database.ref('/path/with/{id}').onWrite((data, context) => {
const authVar = context.auth; // Auth information for the user.
const authType = context.authType; // Permissions level for the user.
const pathId = context.params.id; // The ID in the Path.
const eventId = context.eventId; // A unique event ID.
const timestamp = context.timestamp; // The timestamp at which the event happened.
const eventType = context.eventType; // The type of the event that triggered this function.
const resource = context.resource; // The resource which triggered the event.
// ...
});

Is Firebase Database searchable using objects instead of references, with AngularFire $firebaseArray $keyAt(recordOrIndex)?

After a user logs in using $firebaseAuth, Google sends the user's displayName, email, and photoURL. I then want to look up the user's account in my Firebase database. I can't use $getRecord(key) because Google doesn't tell me the user's key. It appears that I should use $keyAt(recordOrIndex), and then use $getRecord(key). $keyAt(recordOrIndex) works fine with an index. $keyAt(recordOrIndex) works fine with a record that I retrieved with $getRecord(key). I can't get $keyAt(recordOrIndex) to work with an object that I made from the user data that Google returned using $firebaseAuth.
I tried both the complete object (displayName, email, photoURL) and an object consisting of only the email address. The latter is what I would prefer to use. Neither worked.
app.controller('LoginModalInstanceCtrl', ['$scope', '$location', '$uibModalInstance', '$firebaseArray', '$firebaseObject', '$firebaseAuth', function($scope, $location, $uibModalInstance, $firebaseArray, $firebaseObject, $firebaseAuth) {
// Create Firebase3 reference
var ref = firebase.database().ref();
// Set up Firebase Auth
$scope.authObj = $firebaseAuth();
var authData = $scope.authObj.$getAuth();
$scope.authData = authData;
// Google OAuth login handler
$scope.loginGoogle = function() {
$scope.authData = null;
$scope.error = null;
$scope.authObj.$signInWithPopup("google")
.then(function(authData) {
$scope.authData = authData;
console.log(authData);
console.log("Your displayName is:", authData.user.displayName);
console.log("Your email is:", authData.user.email);
console.log("Your photoURL is:", authData.user.photoURL);
var record = {
displayName: authData.user.displayName,
email: authData.user.email,
photoURL: authData.user.photoURL
};
var emailObject = {
email: authData.user.email
};
// look up account
var users = $firebaseArray(ref.child('users'));
users.$loaded()
.then(function() {
console.log("Array loaded!");
var key1 = users.$keyAt(1);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var record = users.$getRecord(key1);
console.log(record); // Object with displayName, email, and photoURL
var key1 = users.$keyAt(record);
console.log(key1); // -Khi6OxAo339ye6xoG3i
var objectKey = users.$keyAt(object);
console.log(objectKey); // null
var emailKey = users.$keyAt(emailObject);
console.log(emailKey); // null
});
$uibModalInstance.close(); // close modal window
$location.path('/languagetwo/'); // return to the homepage
}).catch(function(error) {
console.error("Authentication failed:", error);
});
};
Should I use $firebaseObject instead of $firebaseArray:
var user = $firebaseObject(ref.child('users').child( SOMETHING HERE? ));
The answer appears to be no, you can't search Firebase Database using AngularFire. (Maybe AngularFire 2 has search, I didn't look.) What I did instead was to use "plain vanilla" Firebase:
var users = firebase.database().ref('users');
users.orderByChild('email').equalTo(authData.user.email).once('value').then(function(snapshot) {
console.log(snapshot.val());
});
The first line sets up the Firebase ref and is the similar to as before, except that I'm going straight to the users array, instead of using $FirebaseArray to get to the users array.
The second line is a completely different syntax. First, you have to specify the order that you want the returned object to be in. Yes, it returns an object, not an array. I tried snapshot.val().length() and found that it's not an array. What orderByChild('email') does is to access the 'email' property of the objects in the 'users' array.
Next we do the query. equalTo(authData.user.email) returns only the objects in which the email address from $FirebaseAuth equals the email address in our 'users' array.
Next, once('value') creates a promise and waits for the async data. I tried using on() but couldn't get it to work, too many arguments or something. once() requires an argument, which can be value, child_added, child_changed, child_removed, or child_moved. The value argument is for getting data from a location without changing the child nodes.
We can then set up our then promise fulfillment. You can call the returned data anything. Here it's called snapshot.
Lastly snapshot.val() provides the data from the database, looking just like it does in the Firebase Console.

How to fetch a list of 'FirebaseUser' programatically? [duplicate]

I'm working on a firebase+angularjs app and I'm using the simple email and password authentication and it's working properly.
I'm just wondering if I can add extra user data on the user table which is being used by firebase email+password auth, like I want to add billing info and other details concerning the user without creating extra node/table on firebase to store these extra data.
Firebase stores the email/password users in a separate location, that you don't have direct access to. You cannot expand the data in this location.
Since many application developers want to access the user data in their application code, it is a common practice to store all users under a /users node inside the application database itself. The disadvantage is that you have to do this yourself. But the positive side of this is that you can store any extra information if you want.
See the Firebase guide on storing user data for sample code. From there:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.onAuth(function(authData) {
if (authData && isNewUser) {
// save the user's profile into Firebase so we can list users,
// use them in Security and Firebase Rules, and show profiles
ref.child("users").child(authData.uid).set({
provider: authData.provider,
name: getName(authData)
});
}
});
NOTE: This method only works if you are using Firebase Admin SDK and you need to have end point on your server to manage custom tokens
Firebase Admin SDK has an option to create custom tokens with additional claims object, which can contain arbitrary data. This might be useful to store some user related info, like whether the user is premium user or not.
Additional claims data is accessible using auth object.
example
var uid = "some-uid"; //this can be existing user UID
var additionalClaims = {
premiumAccount: true,
some-user-property: 'some-value'
};
admin.auth().createCustomToken(uid, additionalClaims)
.then(function(customToken) {
// Send token back to client
})
.catch(function(error) {
console.log("Error creating custom token:", error);
});
additionalClaims are also accessible in Firebase security rules.
for more info read Firebase Custom Tokens
A Firebase User has a fixed set of basic properties—a unique ID, a primary email address, a name and a photo URL—stored in the project's user database, that can be updated by the user (iOS, Android, web). You cannot add other properties to the Firebase User object directly; instead, you can store the additional properties in your Firebase Realtime Database.
Firebase has a fixed set of user properties which can be updated but not added on to.
However you can add small amounts of data with the help of serialization and deserialization using JSON.stringify() and JSON.parse()
And then use any one of the unused properties to store the string
either in DisplayName, or photoURL property.
Keep in mind the data that can be added has to be small in size and stored as a string.
And this can be only possible with using the method in the FIREBASE SDK and not the angularfire as illustrated below
var user = firebase.auth().currentUser;
user.updateProfile({
displayName: "Jane Q. User",
photoURL: "https://example.com/jane-q-user/profile.jpg"
}).then(function() {
// Update successful.
}, function(error) {
// An error happened.
});
You could store more json like data in the photoURL or displaYName variable in the form of string here.
My answer is not angular related but I searched quiet a bit to find out how to do it using Polymer and Polymerfire so I add this answer to help people get it done faster than i did.
I had to add a separate node to db as Frank van Puffelen mentioned.
Imports:
<link rel="import" href="../bower_components/polymerfire/firebase-app.html">
<link rel="import" href="../bower_components/polymerfire/firebase-auth.html">
<link rel="import" href="../bower_components/polymerfire/firebase-document.html">
Then place anywhere in your app a <firebase-app> component:
<firebase-app
name="yourAppName"
api-key= "{{yourApi}}"
auth-domain= "{{yourAuthDomain}}"
database-url= "{{yourDbUrl}}"
>
</firebase-app>
After that you will need to use <firebase-auth> and <firebase-document>:
Template :
<firebase-auth
id="auth"
app-name="yourAppName"
signed-in="{{signedIn}}"
user="{{user}}">
</firebase-auth>
<firebase-document
id="document"
app-name="yourAppName"
path="{{usersPath}}" // e.g "/users"
data="{{userDocument}}">
</firebase-document>
Script:
this._register = function(){
var formValid = this.querySelector('#register-form').validate();
var auth = this.querySelector('#auth');
if(formValid && this.passWordsIdentic){
//The actual registration
auth.createUserWithEmailAndPassword(this.email, this.password).then(function(user){
console.log('auth user registration succes');
//Example values
this.userDocument.uid = user.uid;
this.userDocument.email = user.email;
this.userDocument.firstName = this.firstName;
this.userDocument.lastName = this.lastName;
this.userDocument.userName = this.userName;
this.$.document.save(this.usersPath).then(() => {
console.log("custom user registration succes");
this.$.document.reset();
});
}.bind(this)).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
console.log('error: ', errorCode);
);
}
}
And that's it, you may want to take a look at this excellent google codelab which is a good introduction into using firebase with polymer.
Here is the code of registration where add the extra fields in the Users table
import { AngularFireAuth } from "#angular/fire/auth";
constructor(private firebaseAuth: AngularFireAuth){}
registration(data: any, password: any) {
return this.firebaseAuth.auth.createUserWithEmailAndPassword(data.Email, password)
.then(res => {
res.user.updateProfile({
displayName: `${data.DisplayName}`
})
data.UserId = res.user.uid;
data.PhoneNumbers = [{
NumberType: '',
NumberValue: ''
}];
data.PhotoUrl = '';
data.Addresses = [{
AddressLine1: '',
AddressLine2: '',
City: '',
State: '',
Country: '',
PostalCode: '',
AddressType: ''
}];
data.IsDeleted = false;
this.fireStore.doc(`users/${res.user.uid}`).set(data);
this.toastr.success('User has been register successfully!', 'Successfull!');
return true;
}).catch(err => {
switch (err.code) {
case 'auth/email-already-in-use':
this.toastr.error(`Email address ${data.Email} already in use.`, 'Error!');
break;
case 'auth/invalid-email':
this.toastr.error(`Email address ${data.Email} is invalid.`, 'Error!');
break;
case 'auth/operation-not-allowed':
this.toastr.error('Error during sign up.', 'Error!');
break;
case 'auth/weak-password':
this.toastr.error('Password is not strong enough. Add additional characters including special characters and numbers.', 'Error!');
break;
default:
this.toastr.error(err.message, 'Error!');
break;
}
});
}
Here's a swift version. Your user structure ("table") is like
--users:
-------abc,d#email,com:
---------------email:abc.d#email.com
---------------name: userName
etc.
After you pass the auth FIRAuth.auth()?.createUser you can set the users in database as below:
let ref = FIRDatabase.database().reference()
let rootChild = ref.child("users")
let changedEmailChild = u.email?.lowercased().replacingOccurrences(of: ".", with: ",", options: .literal, range: nil) // Email doesn't support "," firebase doesn't support "."
let userChild = rootChild.child(changedEmailChild!)
userChild.child("email").setValue(u.email)
userChild.child("name").setValue(signup.name)
Please note that method is changed in v4.0.0. Therefore, you need to use the below code to retrieve the user profile:
afAuth.authState.subscribe((user: firebase.User) => {
this.displayName = user.displayName;
this.email = user.email;
this.photoURL = user.photoURL;
});
The answer from Frank is good, but things are a little different in Angular6/Firebase5/Angularfire5:
Here is my click handler for signing in a user:
this.afAuth.auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).then((e) => {
console.log("Log-In Success" + e.additionalUserInfo.profile.name);
if (e.additionalUserInfo.isNewUser)
this.addUserToDatabase(/*...*/);
}).catch((error) => {
console.log("Log-In Error: Google Sign-In failed");
});

Resources