Firebase rules to avoid Duplicate Entries - firebase

What are the firebase rules required to avoid duplicate entries in below users array at sList collection level
"sList" : {
"-KZawgegLrIyq9h6GSf8" : {
"name" : "Test",
"users" : [ "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawhnFZLcqFKNwZnSi", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBSAwL-lbi7dF-h", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV", "-KZawxBgz8k7v8-fKpDV" ]
}
}

What you're trying to model is a set: a collection of unique entries.
What you've modeled is an array: a sequence of non-unique entries.
The simplest and best solution is to change your data model to actually reflect a set. The closest you can get to that in Firebase is:
"sList" : {
"-KZawgegLrIyq9h6GSf8" : {
"name" : "Test",
"users" : {
"-KZawhnFZLcqFKNwZnSi": true,
"-KZawxBSAwL-lbi7dF-h": true,
"-KZawxBgz8k7v8-fKpDV": true
}
}
}
With such a set-like structure, duplicates are automatically prevented by the data structure itself. You won't need to write security rules for that.

Related

How to apply permissions per all tables?

My scenario is very simple. Two user roles one admin and one viewer. Viewer can see read-only data. Is it possible to somehow set this without manually specifying select permissions for every single table?
Seems like such a common problem but I can't find anything in documentation.
Unfortunately there's no API method for changing permissions (or other metadata) across every table at once. It requires table name and schema.
I'm not sure what your "read-only" data permission translates into, but you can do this in two ways:
Call the metadata API with create_select_permission for every table name, programmatically
POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type" : "create_select_permission",
"args" : {
"table" : "article",
"role" : "user",
"permission" : {
"columns" : "*",
"filter" : {
"author_id":{ "_eq": "X-Hasura-User-Id" }
},
"limit": 100,
"allow_aggregations": false
}
}
}
Use the Metadata SDK to programmatically generate a tables.yaml or metadata.json which has the updated select permissions on every table, and then apply the metadata
See https://github.com/hasura/graphql-engine/issues/5459
import { Convert } from './customMetadataConverter'
import { TableEntry } from '../generated/HasuraMetadataV2'
// Read "tables.yaml" file as text from filesystem
const tablesMetadataFile = fs.readFileSync('./metadata/tables.yaml', 'utf8')
// Convert it to JSON object with type annotation using loadYAML utility
const tablesMetadata: TableEntry[] = Convert.loadYAML(tablesMetadataFile)
for (let table of tablesMetadata) {
if (table.insert_permissions.length == 0) table.insert_permissions = []
// Put the permission you want applied on every table here
table.insert_permissions.push({
columns : "*",
filter : { user_id: { _eq: "X-Hasura-User-Id" } },
limit: 100,
allow_aggregations: false
})
}
// Ouput the updated "tables.yaml" to filesystem
fs.writeFileSync('./tables-updated.yaml', Convert.metadataToYAML(tablesMetadata))

How to structure data in Firebase created by one user but accessible to users in a group?

So, let's say I have data like this:
{
"events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
text: "Bob says 'My name is Robert'" },
"kjl34jl234j" :
{ creator: "fred#fred.com",
text: "Fred says 'My name is Fredrick'" }
}
"users" : {
"bob#bob.com" : { "paid": true },
"fred#fred.com" : { "paid": false }
}
}
I'm assuming this is the correct way to structure the data. When the data is created, I use the push() method to create a new key for the data, and then store the creator of the data inside it.
I'd like to make it so that:
I can allow anyone from a group of users to access certain data (and disallow others obviously).
The query is "optimized," meaning if I have thousands of records I am not iterating over all the data.
More concretely, for example, I want lizzie#lizzie.com to be able to see the s0d980983s.
I'm confused how to structure the data, and what my Firebase rules should look like.
Would it be something like this?
{ "events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
viewers: { "bob#bob.com": true,
"lizzie#lizzie.com" : true },
text: "Bob says 'My name is Robert'" },
...
}
I don't understand how I can search for events that are viewable by a group of users. I don't believe Firebase supports some kind of wildcard that would make this code work, right?
var ref = firebase.database().ref( "events/*/viewers/lizzie#lizzie.com" ).on(...);
Do I also want to reference the events inside my users table? I'm not sure I understand how to flatten data (denormalize it) and keep references in both places to support a query like this. Should I expect to make multiple queries where I first retrieve a list of events stored in a user object and then retrieve them one by one using their key? But, how do I put that logic into my firebase rules?
{ "events" : {
"s0d980983s" :
{ creator: "bob#bob.com",
viewers: { "[insert bobs id]": true,
"[insert liz id]" : true
},
text: "Bob says 'My name is Robert'" },
...
}
Based on the above structure as you suggested, and if you are using firebase authentication to authenticate your user, you can add another 'read' or 'write' rule for checking whether that user is in the list of your 'viewers'. something like:
{
"rules": {
"users": {
"$uid": {
".write": "auth != null &&
root.child('users').child(auth.uid).child('viewers').child(auth.uid).val() ==
true"
}
}
}
}
This should help. setting firebase security rules at a location/node

Firebase nested data using "reference" : true instead of array

On the firebase structure data section, it shows how to structure data with a many-many user-group situation. But, why they have used "referece":true on both the side instead of using a simple array od ids.
Like, it can be used like both the ways:
A user having array of groups
"groups" : [ "groupId1", "groupId2", ... ]
A user having
"groups": {
"groupId1" : true,
"groupId2" : true,
..
}
They have done it a second way. What is the reason for that?
Something was told at the Google I/O 2016 for that in some video. But, I'm unable to recall.
Example from structure your data:
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
},
...
}
}
Firebase recommends against using arrays in its database for most cases. Instead of repeating the reasons here, I'll refer you to this classic blog post on arrays in Firebase.
Let's look at one simple reason you can easily see from your example. Since Firebase arrays in JavaScript are just associative objects with sequential, integer keys, your first sample is stored as:
"groups" : {
0: "groupId1",
1: "groupId2"
]
To detect whether this user is in groupId2, you have to scan all the values in the array. When there's only two values, that may not be too bad. But it quickly gets slower as you have more values. You also won't be able to query or secure this data, since neither Firebase Queries nor its security rules support a contains() operator.
Now look at the alternative data structure:
"groups": {
"groupId1" : true,
"groupId2" : true
}
In this structure you can see whether the user is in groupId2 by checking precisely one location: /groups/groupId2. It that key exists, the user is a member of groupId2. The actual value doesn't really matter in this case, we just use true as a marker value (since Firebase will delete a path if there's no value).
This will also work better with queries and security rules, because you now "just" needs an exists() operator.
For some great insights into this type of modeling, I highly recommend that article on NoSQL data modeling.

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.

Different priority based on user in Firebase

I have data which looks like this:
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill"
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill"
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John"
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack"
}
When a user is logged in with id simplelogin:5 I want to order the output based on their sort preferences. So say for example user simplelogin:5 previously set his order to Jack,Jill,Joh,Bill and simplelogin:1 set their order to Bill,John,Jack,Jill.
I know I can set priority but that's priority for the data as a whole and it isn't tied to a user, this is shared data which needs custom priority per user.
I was thinking of setting up something like this:
users[
{
"uid":"simplelogin:1",
"nameOrder":[-gn643Jp3rgsIeM2O0EN, -yryexaJp3rgsIeM2O0EN, etc.]
}
];
But it seems like there should be a better way, and even if I was able to generate a list like that, i'm not sure how to sort the output to follow the order in the nameOrder entry.
First you need to modify your main list with a persistent sort id. I used a numeric value but you can use whatever value you prefer.
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill",
nameIndex": 0
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill",
"nameIndex": 1
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John",
"nameIndex": 2
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack",
"nameIndex": 3
}
Then store your user defined sort preference
"simplelogin:1" : {
"nameOrder": "0,2,3,1"
},
"simplelogin:5" : {
"nameOrder": "3,1,2,0"
}
Now when you read the name list, use the array index saved in nameOrder to display results.

Resources