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.
Related
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))
Practical example: Consider an expand/collapse list. Each item expands another list.
export interface MainDomainList {
id: number
name: string;
}
export interface SubDomainList {
id: number
name: string;
}
export interface AppState {
mainDomainList: MainDomainList[];
subDomainList: SubDomainList[];
}
On the UI the list should be represented like this:
MainDomainList[1]
SubDomainList[] (entire list)
MainDomainList[2]
Another SubDomainList[] (entire list)
etc..
When the user clicks on the MainDomain[n] there is a call to the backend which returns a list of SubDomain[]. There are no connections between the two of them.
It seems that the most complicated part is that the SubDomains are being loaded one by one on click not all at once, and multiple MainDomains can be open at the same time like in the example above. Also, it should be possible to easily perform CRUD operations on the subDomainList entities.
I tried using a selector which selects an item from the state by id but every time the state is overridden.
My initial idea was to create a separate state in which after the SubDomainList[] is loaded successfully, then I could add the loaded SubDomainList[] by dispatching an 'ADD' action thus adding the entities and the id of the clicked MainDomainList in the newList state as the user clicks on through the list obtaining something like this:
exportt interface AppState {
mainDomainList: MainDomainList[];
subDomainList: SubDomainList[];
newList: NewList[];
}
{
mainDomainList : {
entities: {
md1: {
id: 'md1',
name: '1'
},
md2: {
id: 'md2',
name: '2'
}
}
},
subDomainList : {
entities : {
sd1 : {
id : 'sd1',
name: 'name1'
},
sd2 : {
id : 'sd2',
name: 'name2'
}
},
newList : {
entities : {
md1 : {
id : 'md1',
subDomainList: [{}, {}]
},
md2 : {
id : 'md2',
subDomainList: [{}, {}]
}
}
}
}
Then somehow i would get all the newList entities and match them in the UI with the id of the MainDomainList[n].id
Is my approach correct or is there any other better or less complicated solution for this issue?
I'm fairly new to the subject but I had a lot of headaches trying to figure out how to implement this with ngrx/Entity and failed so far, although it should be a pretty common case. Any help would be much appreciated.
You can write selectors with argument by passing of main domain list
ref: ngrx parameter to select function
and https://blog.angularindepth.com/ngrx-parameterized-selector-e3f610529f8
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
So I'm planning data structure like this for an food/potluck event where people can bring or reserve food.
{
"potLuckEvents": {
"eventID1": {
"cake": {
"userID1" : 2
"userID2" : 3
}
"burger": {
"userID3" : 1
"userID4" : 2
}
}
}
}
So in this example, userID1 will be bringing 2 servings of cake to the event. All examples I have seen in the documentation were using something like:
"userID1" : true
I'm wondering if there is a specific reason for the list of ids to only have true as the value? Can I use non-true value for this case?
==========
Extra:
I'm also thinking to do use the int value for status of event invitation
{
"potLuckEvents": {
"eventID1": {
"attendees": {
"userID1" : -1
"userID2" : 0
"userID3" : 1
}
}
}
}
In this example:
userID1 declined invitation
userID2 did not accept or decline
invitation yet userID3 accepted invitation
Is this another good use case to use int values instead of 'true'?
The value of a node can be any valid JSON value type.
The reason you see true (more often than other values) is because when you're creating an index node (essentially a list of "foreign keys") there isn't any real value to store. But since a node without a value will be deleted immediately, it's a convention to use true as the value.
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.