How to access an object into another object in a query database from firebase functions? - firebase

I am trying to access to a object into another object in my firebase database, i have a structure like this:
I want to get all the objects that have the email that i send by parameters, i am using .child to access to the childs into my object but i am not success with the query, this is my code
$ ref_db.child("/groups").child("members").orderByChild("email").equalTo(email).once("value", (snapshot)=>{
console.log(snapshot.val());
});
The snapshot.val() always is undefined.
could you help me with the query?

One efficient way to get "all the groups that have that email inside the members object" would be to denormalize you data and have another "main node" in your database where you store all "members" (i.e. their email) and the "groups" they belong to.
This means that each time you add a "member" node under a "group" (including its email) you will also add the group as a child of the member email, in this other "main node".
More concretely, here is how would be the database structure:
Your current structure:
- groups
- -LB9o....
...
- members
- -LB9qbd....
-email: xxxx#zzz.com
- -LBA7R....
-email: yyyyy#aaaa.com
And the extra structure:
- groupsByMembers
- xxxxxx#zzzcom
- Grupo1: true
- yyyyy#aaaacom
- Grupo1: true
- Grupo2: true
- bbbcccc#dddcom
- Grupo6: true
- Grupo8: true
Note that in the "extra structure" the dots within an email address are removed, since you cannot include a point in a node id. You will have to remove them accordingly when writing and querying.
This way you can easily query for the list of groups a member is belonging to, as shown below. Without the need to loop several times over several items. This dernomalization technique is quite classic in NoSQL databases.
const mailToSearchFor = xxxx.xx#zzz.com;
const ref = database.ref('/groupsByMembers/' + mailToSearchFor.replace(/\./g, ''));
ref.once('value', snapshot => {
const val = snapshot.val();
for (let key in val) {
if (val.hasOwnProperty(key)) {
console.log(key);
}
}
});
In order to write to the two database nodes simultaneously, use the update method as explained here https://firebase.google.com/docs/database/web/read-and-write#update_specific_fields

This is because you have a random key before members, you need to go through the path and not skip a node, to be able to access the values:
ref_db.child("groups").child("-LB9oWcnE0wXx8PbH4D").child("members").orderByChild("email").equalTo(email).once("value", (snapshot)=>{
console.log(snapshot.val());
});

Related

Flutter Firebase firestore append data with unique ID

I'm working on the Flutter app where users can save multiple addresses. Previously I used a real-time database and it was easier for me to push data in any child with a unique Id but for some reason, I changed to Firestore and the same thing want to achieve with firestore. So, I generated UUID to create unique ID to append to user_address
This is how I want
and user_address looks like this
And this is how it's getting saved in firestore
So my question Is how I append data with unique id do I have to create a collection inside users field or the above is possible?
Below is my code I tried to set and update even user FieldValue.arrayUnion(userServiceAddress) but not getting the desired result
var uuid = Uuid();
var fireStoreUserRef =
await FirebaseFirestore.instance.collection('users').doc(id);
Map locationMap = {
'latitude': myPosition.latitude,
'longitude': myPosition.longitude,
};
var userServiceAddress = <String, dynamic>{
uuid.v4(): {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
}
};
await fireStoreUserRef.update({'user_address': userServiceAddress});
If I use set and update then whole data is replaced with new value it's not appending, so creating a collection is the only solution here and If I create a collection then is there any issue I'll face?
You won't have any issues per se by storing addresses in a separate collection with a one-to-many relationship, but depending on your usage, you may see much higher read/write requests with this approach. This can make exceeding your budget far more likely.
Fortunately, Firestore allows updating fields in nested objects via dot notation. Try this:
var userServiceAddress = {
'complete_address': completedAddressController.text,
'floor_option': floorController.text,
'how_to_reach': howtoreachController.text,
'location_type': locationTag,
'saved_date': DateTime.now().toString(),
'user_geo_location': locationMap,
'placeId': addressId
};
await fireStoreUserRef.update({'user_address.${uuid.v4()}': userServiceAddress});

Firestore: How to query a map of roles inside a subcollection of documents?

I have the following subcollection structure:
/projects/{projectId}/documents/{documentId}
Where each document has an access map used for checking security rules for each document:
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
bob: false,
david: true,
jane: false
// ...
}
}
How the security rules are set:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true;
}
When I want to query all documents alice can access across all projects:
db.collectionGroup("documents").where(`access.${uid}`, "==", true);
I get an error telling me that I need to create a collection group index for access.alice, access.david, etc.
How can I overcome this indexing issue with collection group queries?
As a quick overview of indexes, there are two types - a composite index (used for connecting multiple fields together) or a single-field index. By default, single-field indexes are automatically created for each field of a document for ordinary collection queries and disabled for collection group queries. If a field's value is an array, the values of that array will be added to an Array Contains index. If a field's value is primitive (string, number, Timestamp, etc), the document will be added to the Ascending and Descending indexes for that field.
To enable querying of the access field for a collection group query, you must create the index. While you could click the link in the error message you get when making the query, this would only add an index for the user you were querying (e.g. access.alice, access.bob, and so on). Instead, you should use the Firebase Console to tell Cloud Firestore to create the index for access (which will create indexes for each of its children). Once you've got the console open, use the "Add Exemption" button (consider this use of "exemption" to mean "override default settings") to define a new indexing rule:
In the first dialog, use these settings:
Collection ID: "documents"
Field Path: "access"
Collection: Checked ✔
Collection group: Checked ✔
In the second dialog, use these settings:
Collection Scope
Collection Group Scope
Ascending
Enabled
Enabled
Descending
Enabled
Enabled
Array Contains
Enabled
Enabled
In your security rules, you should also check if the target user is in the access map before doing the equality. While accessing a missing property throws a "Property is undefined on object." error which denies access, this will become a problem if you later combine statements together with ||.
To fix this, you can either use:
match /{path=**}/documents/{documentId} {
allow list, get: if request.auth.uid in resource.data.access
&& resource.data.access[request.auth.uid] == true;
}
or provide a fallback value when the desired key is not found:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true;
}
As an example of the rules breaking, let's say you wanted a "staff" user to be able to read a document, even if they aren't in that document's access map.
These rules would always error-out and fail (if the staff member's user ID wasn't in the access map):
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access[request.auth.uid] == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
whereas, these rules would work:
match /{path=**}/documents/{documentId} {
allow list, get: if resource.data.access.get(request.auth.uid, false) == true
|| get(/databases/$(database)/documents/users/$(request.auth.uid)).data.get("staff", false) == true;
}
As a side note, unless you need to query for documents where a user does not have access to it, it is simpler to simply omit that user from the access map.
{
title: "A Great Story",
content: "Once upon a time ...",
access: {
alice: true,
david: true,
// ...
}
}
As noted on https://cloud.google.com/firestore/docs/query-data/queries#collection-group-query "Before using a collection group query, you must create an index that supports your collection group query. You can create an index through an error message, the console, or the Firebase CLI."
In this case you'll want to enable CollectionGroup queries on the access map (https://cloud.google.com/firestore/docs/query-data/indexing#add_a_single-field_index_exemption)
You would have to create an index for it as mentioned by #Jim.
If you don't want to get much into it, you can just catch the error and it'll have a direct link to to create one as shown below:
db.collectionGroup("documents").where(`access.${uid}`, "==", true).get().then((snap) => {
//
}).catch((e) => console.log(e))
Other ways as mentioned in the documentation include:
Before using a collection group query, you must create an index that
supports your collection group query. You can create an index through
an error message, the console, or the Firebase CLI.

firebase what is the best way/structure to retrieve by unique child key

I have a firebase database like this structure:
-groups
--{group1id}
---groupname: 'group1'
---grouptype: 'sometype'
---groupmembers
----{uid1}:true
----{uid2}:true
--{group2id}
---groupname: 'group2'
---grouptype: 'someothertype'
---groupmembers
----{uid1}:true
----{uid3}:true
----{uid4}:true
Now, I am trying to pull groups of authenticated user. For example for uid1, it should return me group1id and group2id, and for example uid3 it should just return group2id.
I tried to do that with this code:
database().ref('groups/').orderByChild('groupMembers/' + auth().currentUser.uid).equalTo('true').on('value' , function(snapshot) {
console.log('GROUPS SNAPSHOT >> ' + JSON.stringify(snapshot))
})
but this returns null. if I remove "equalTo" and go it returns all childs under 'groups'.
Do you know any solution or better database structure suggestion for this situation ?
Your current structure makes it easy to retrieve the users for a group. It does not however make it easy to retrieve the groups for a user.
To also allow easy reading of the groups for a user, you'll want to add an additional data structure:
userGroups: {
uid1: {
group1id: true,
group2id: true
},
uid2: {
group1id: true,
group2id: true
},
uid3: {
group2id: true
},
uid3: {
group2id: true
}
}
Now of course you'll need to update both /userGroups and /groups when you add a user to (or remove them from) a group. This is quite common when modeling data in NoSQL databases: you may have to modify your data structure for the use-cases that your app supports.
Also see:
Firebase query if child of child contains a value
NoSQL data modeling
Many to Many relationship in Firebase

Firebase - How do I store user data so that I can easily fetch it by their email? [duplicate]

I have the following structure on my Firebase database:
I would like to search for a user by name, last name or email but as I don't have the user key in the level above I don't know how I can achieve this. I'm doing and administrator session so it wouldn't have access to the user key.
I have tried:
let usersRef = firebase.database().ref('users');
usersRef.orderByValue().on("value", function(snapshot) {
console.log(snapshot.val());
snapshot.forEach(function(data) {
console.log(data.key);
});
});
But it brings all the users on the database. Any ideas?
You can use equalTo() to find any child by value. In your case by name:
ref.child('users').orderByChild('name').equalTo('John Doe').on("value", function(snapshot) {
console.log(snapshot.val());
snapshot.forEach(function(data) {
console.log(data.key);
});
});
The purpose of orderByChild() is to define the field you want to filter/search for. equalTo() can get an string, int and boolean value.
Also can be used with auto generated keys (pushKey) too.
You can find all the documentation here
A warning to avoid unpleasant surprises: when you use orderByChild and equalTo do not forget to add an index on your data (here's the doc)
If you don't all the nods will be downloaded and filtered client side which can become very expensive if your database grows.

Grabbing Keys with Firebase

I'm currently creating an app using ionic2 and firebase for a class and I've run into an issue.
I"m not posting code because I don't have anything relevant enough to the question.
In my database I have a 'group' that people can create. I need the 'group' to be able to store a list of users. The way I'm currently doing it is that users will click an addGroup button, and the currentUser will be added to the list of users in that group simply by typing in the name of the group. (yeah i know that's going to need a password or something similar later, people obviously shouldn't just be able to join by group name)
*Names of groups will all be different so I should be able to query the database with that and get a single reference point.
The problem is that I don't know how to properly query the database and get the key value of the group object I want.
I keep looking things up and cant find a simple way to get the key of an object. I feel like this is a dumb question but, I'm unfamiliar with typescript, firebase, and ionic2 so I'm pretty lost in everything.
Answer for the question in comment.
you can query the database like this
this.group_val:any="group1"
let group=db.list('/groups', {
query: {
orderByChild: GroupName, //here specify the name of field in groups
equalTo: this.group_val //here specify the value u want to search
}
});
group.subscribe(grp=> {
//you can use the grup object here
})
First, define your database structure. The below is probably best:
groups
<first group> (Firebase-generated ID)
name: "Cat lovers"
users:
user1ID: true
user2ID: true
<second group>
...
I assume that when the user clicks on the group he wants to join, you know the group ID. For instance, it could be the value of an option in a select.
Get the list of groups:
groups$ = this.angularFireDatabase.list('groups');
To populate a list of groups into a select:
<select>
<option *ngFor="let group of groups$ | async" [value]="group.$key">
{{group.name}}
</option>
</select>
To create a new group:
groups$.push({name: "Dog lovers"})
The select (drop-down) will update itself.
To add a user to a group:
addUserToGroup(groupId, userId) {
this.angularFireDatabase.list(`groups/${groupID}/users`).update(userID, true);
}
To find a group based on its name:
// Define the shape of a group for better type checking.
interface Group { name: string; users: {[userId: string]: boolean;}; }
// Keep local list of groups.
groups$: FirebaseListObservable<Group>;
groups: Group[];
// Keep an updated local copy of groups.
ngOnInit() {
this.angularFireDatabase.list('groups').subscribe(groups => this.groups = groups);
}
function findGroupByName(name) {
return this.groups.find(group => group.name === name);
}

Resources