NoSQL & Firebase: Storing & Iterating over many-to-many relationship - firebase

I have 2 main nodes in my database: Users and Projects.
Each user can be assigned to multiple projects, in different roles.
After reading about how to structure many to many relationship, I ended up that it should look like this:
users : {
user1 : {
name : blah,
email : a#a.com,
projects : {
projects1key : true,
projects2key : true
}
}
}
projects : {
project1key : {
name : blahserve,
category : blahbers,
providers : {
user1 : true,
user7 : true
}
}
}
What I couldn't figure out is how I can assign every user a role in each project.
What's the new database structure should look like if I need to add role (string) to each user within a project?
Correct me if I'm wrong: When I assign user to a project, I need to create 2 new nodes: a projectkey node in my user node, and a userkey node in my projectkeynode. Is that right?
Update
Since the answer given here is correct but not sure it will fit my use case, this is my use case:
(1) Iterating over a list of all project users
(2) Iterating over a list of all member project
(3) Check for project role when the user access a project and give give him permission depends on his role
(4) The "Project -> users" page allow you to add a new user to existing project. the user role is picked in the same form together with the user. the user must have a role in a project.

Or maybe just simply you could add a different node all together:-
What's the new database structure should look like if I need to add role (string) to each user within a project?
users : {
user1 : {
name : blah,
email : a#a.com,
}
},
projects : {
projectKey1 : {
name : blahserve,
category : blahbers,
}
},
projectsLists:{
user1 :{
projectKey1 : true,
projectKey2 : true
}
},
projectsRoles :{
projectKey1 : {
user1 : provider,
user2 : editor,
....
}
}
Correct me if I'm wrong: When I assign user to a project, I need to create 2 new nodes: a projectkey node in my user node, and a userkey node in my projectkeynode. Is that right?
Always prefer the flatter DB structure. So using this structure you only gotta append or update your user's role once.

Related

firestore security rules : deny profile update but allow rating update

I have collection called 'services' inside every document I have 4 fields :
name
description
userID
Rating
and I want to give only the user to change his profile except rating I want to make it accessible (can update) to everyone , I check some problem like that and I get one solution that is create sub collection in profile doc I call it "rating" that contain a doc with the name "rate" and I make only this sub collection accessible to update from everyone ,
but I don't know how to to get subcollection data from a doc any solution plz with this rating problem :
My security rules after creation a sub collection for rating :
match /{category}/{serviceid} {
allow read,create;
allow update : if resource.data.userID == request.auth.uid;
match /rating/rate {
allow read,write;
}
}
I get all services with const docs =db.collection("categoryname").get() :
and I fetch every item data like that :
docs.foreach(doc => doc.data().name)
How can I get subcollection("rating") data from the doc?
You realize that with these rules, you allow everyone access? Even people unauthenticated would be able to make unlimited writes. Is that what you really want?
To answer your question, to access to the /rating/rate document you could just do:
let category = "categoryname";
db.collection(category).get()
.then(function(querySnapshot) {
querySnapshot.foreach(
function(doc) {
let serviceid = doc.id;
db.collection(category).doc(serviceid).collection("rating").doc("rate").get()
.then(function(doc) {
... // do whatever
}
}
)
}
You can also check the Official Documentation to get a document.

Firebase simple many to many relationship

I am bit familiar with NoSQL and Firebase Realtime Database and also I know that it is not best solution to solve tasks where relational database should be more appropriate. I want to verify about structure of simple many to many relationship that I have.
I have events and users. I want to use Firebase for storing information about users participating in events, later I will need to
Get list of users for event knowing it's id and city
Get list of events for users knowing it's id and city
add or delete information about user attending to event
I would like to have first tree of events ids divided by cities.
events {
'city1' : {
event_id_1 : {'user_1', 'user_2'},
event_id_2 : {'user_3', 'user_4'}.
}
'city2' : {
event_id_3 : {'user_5', 'user_6'},
event_id_4 : {'user_7', 'user_7'}.
}
}
And second tree for users
users {
'user1' : {
'city1' : {event_id_1, event_id_2},
'city2' : {event_id_3, event_id_4},
'city3' : {event_id_3, event_id_4}
},
'user2' : {
'city1' : {event_id_1, event_id_2},
'city2' : {event_id_3, event_id_4},
'city3' : {event_id_3, event_id_4}
},
'user3' : {
'city1' : {event_id_1, event_id_2},
'city2' : {event_id_3, event_id_4},
'city3' : {event_id_3, event_id_4}
},
}
Would it be easy and fast to use and maintain?
Your structure looks pretty OK to me given the requirements listed. Most importantly: you store the data in both directions already, which is the biggest hurdle for many developers new to NoSQL data modeling.
A few notes about your data model, though most are on the level of typos:
Be sure to store the data as maps, not arrays. So event_id_1 : {'user_1': true, 'user_2': true }
If there is a many-to-many relationship between users and events, I'd usually have four top-level lists: users and events (for the primary information about each), and then userEvents and eventUsers (for connections between the two).
Adding a user to an event can be done with a single multi-location update, e.g.:
ref.update({
'/userEvents/userId1/eventId1': true,
'/eventUsers/eventId1/userId1': true
});
Unregistering them is a matter of doing the same with null as the value (which deletes the existing key):
ref.update({
'/userEvents/userId1/eventId1': null,
'/eventUsers/eventId1/userId1': null
});
Also see my answer here: Many to Many relationship in Firebase
You can do this:
List of user
Users
useruid
name:userx
email:userx#gmail.com
useruid
name:usery
email:usery#gmail.com
Events
eventid
useruid
name:userx
location: city1
eventname: party
eventid2
useruid1
name:usery
location: city2
eventname: Boring Party
Get list of users for event knowing it's id and city:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("Events").child(eventid);
ref.orderByChild("location").equalTo(city1);
//retrieve users using a listener
Get list of events for users knowing it's id and city:
DatabaseReference ref=FirebaseDatabase.getInstance().getReference().child("Events");
Query q=ref.orderByChild("location").equalTo(city1);
using a listener this can give you the events that are in location:city1

Firebase security rules for friends list

Is there an efficient way to implement security rules on a list of nodes in Firebase, to prevent many network connections and queries?
I'm doing an application with a user list, and each user has a list of posts that they want to share with people in their friends list (basically a bit like a social network).
One way is to have a "/users" table where each user has a list of friends "/users/xxx/friends" under their user node, and a list of their own posts "/users/xxx/posts". To query the recent posts of all their friends requires many Firebase queries, one to each of the user's friends' "posts" node.
{
"users" : {
"FKOZlhz05xrVvKGl2" : {
"name" : "John Smith",
"friends" : {
"Tz7Ztv546lOaowrq2" : true
}
"posts" : {
"-Kc-n235d5" : {
"text" : "This is a status update"
}
},
"Tz7Zt546jlOaowrq2" : {
"name" : "Helen test",
"friends" : {
"FKOZlhz05xrVvKGl2" : true
},
"posts" : {
"-Kc-neow05" : {
"text" : "Another status update"
},
"-Kc-ab3243" : {
"text" : "Feeling confused"
}
}
}
}
This requires many simultaneous queries if someone wants to query "posts" for everyone in their friends list, because Firebase wouldn't allow someone to query the whole "users" table and filter out any that are not in the friends list.
Another option is to have a separate "/posts" list for all users which can be queried in one action. However in this case one also can't implement security rules to filter out only posts for your friends, because Firebase doesn't allow filtering the "posts" list based on security rules.
The only way I can see of looking up the "posts" list for only people in your friends list, is to fire off multiple simultaneous queries for each friend.

Access-control on the item-level with Firebase [duplicate]

This question already has an answer here:
Granting access to Firebase locations to a group of users
(1 answer)
Closed 7 years ago.
Let's say you have a firebase database with news articles to show in a webapp. On the top level we use Firebase auth to make sure that the user has access to the news-articles in the first place.
But then the different articles (items in the news-database) should be visible for different people. For example should users I put in a group called "management" have access to view some articles that the rest of the users cannot.
How can I achieve this using Firebase? (Web-version)
Sample database:
{
"newsfeed" : {
"1001" : {
"body" : "Lots of text here for the body of the article.",
"header" : "This is an open article",
"leadParagraph" : "With a lead paragraph and lots of content",
"permissions" : {
"All" : false,
"Group11" : true,
"Management" : true
}
},
"1002" : {
"Permissions" : {
"All" : true
},
"body" : "Content here",
"header" : "Testarticle for everyone",
"leadParagraph" : "Everyone can read this one"
}
}
}
So the question is not about how to limit a users access to the whole of "newsfeed", but rather how to edit access individually on the item level. In the example above all users can read item 1002, whilst only users that are tagged as members of the groups "Management" and/or "Group11" should be able to read item 1001.
Is this possible with rules dynamically? If I have to create a new rule for every item, is there a limit to the number of rules?
If you haven't already, I would read the Security Quickstart guide. It does a great job of explaining how Firebase security rules work.
In the case of your problem, I would suggest having a list of groups, where each group has the uids of the users in that group. For example:
{
"groups" : {
"Group11" : {
"<uid1>": true,
"<uid2>": true
},
"Management" : {
"<uid1>": true,
"<uid2>": true
}
}
}
If a user is a member of a group, add their uid to the group's list of members. Then in your rules, you can make the .read check whether or not the user who is trying to read the data has their uid in any of the allowed groups.

how to retrieve data ordered by key inside unspecified key with firebase

I have a snapshot for my reference in firebase like this:
"friendlist" : {
"user1" : {
"user3" : 1
},
"user2" : {
"user1" : 0
}
"user3" : {
"user1" : 1
}
}
The explanation for the reference:
Every user has an unique id, i'm using user's id for their friendlist unique id. In example above i have 3 users and every user have his own friendlist. Inside their friendlist, there's other user's id that already be friend with him. If the value is 1, the user already be friend. But when the value is 0, the user is requesting to be friend.
My problem is:
How to get all user's friendlist's id which have "user1" with value 0 inside their friendlist? Can i do that in just one query?
I think i need to iterate through all friendlist and orderbykey for every friendlist and looking for "user1". Or there's any good approach to do that?
Any answer would be appreciated, thanks!
It would help if you next time tell a bit more about what you've already tried. Or at the very least specify what language/environment you're targeting.
But in JavaScript, you can get those users with:
var ref = new Firebase('https://yours.firebaseio.com/friendlist');
var query = ref.orderByChild('user1').equalTo(0);
query.once('value', function(usersSnapshot) {
usersSnapshot.forEach(function(userSnapshot) {
console.log(userSnapshot.key());
});
});
With the sample data you specified, this will print:
user2
You should add (and will get a warning about) an index for efficiently performing this query:
{
"rules": {
"friendlist": {
".indexOn": ['user1']
}
}
}
Without this index, the Firebase client will just download all data to the client and do the filtering client-side. With the index, the query will be performed server-side.
A better data model
You'll likely want to search for any friend, which turns the index into:
".indexOn": ['user1', 'user2', 'user3']
But with this structure, you'll need to add an index whenever you add a user. Firebase SDKs don't have an API to add indexes, which is typically a good indication that your data structure is not fitting your needs.
When using a NoSQL database, your data structure should meet the needs of the application you're building. Since you are looking to query the friends of user1, you should store the data in that format too:
"friendlist" : {
"user1" : {
"user3" : 1
},
"user2" : {
"user1" : 0
}
"user3" : {
"user1" : 1
}
},
"friendsOf": {
"user1": {
"user2": 0,
"user3": 1
},
"user3": {
"user1": 1
}
}
As you can see, we now store two lists:
* friendList is your original list
* friendsOf is the inverse of your original list
When you need to know who friended user 1, you can now read that data with:
ref.child('friendsOf').child('user1').on('value'...
Note that we no longer need a query for this, which makes the operation a lot more scalable on the database side.
Atomic updates
With this new data model, you need to write data in two places when adding a friend relation. You can do this with two set()/update() operations. But in recent Firebase SDKs, you can also perform both writes in a single update like this:
function setRelationship(user1, user2, value) {
var updates = {};
updates['friendList/'+user1+'/'+user2] = value;
updates['friendsOf/'+user2+'/'+user1] = value;
ref.update(updates);
}
setRelationship('user3', 'user4', 1);
The above will send a single command to the Firebase server to write the relationship to both friendList and friendsOf nodes.

Resources