How to Properly Structure Firebase Database to Allow Easy Reading and Removal

How to properly structure Firebase database to allow easy reading and removal

You might wanna consider making favourites as a NSDictionary:-

 app:
users:
2ReAGRZlYiV5F2piwMakz59XDzl1: //(uid)
favorites:
{172171 : true,
4123123 : true,..}
  • For Appending in the favourites:-

      USERS_REF.child(uid).child("favorites").updateChildValues([recipeID: "true"]) 

    Mind that , if your recipeID is unique, i.e doesnt already exist at favourites node
    Only then it will append the value, if the recipieID already exists it will just update its value (Dont prefer this for appending, look up the next option)

    Or

    let prntRef = USERS_REF.child(uid).child("favorites")
    prntRef.observeSingleEventOfType(.Value, withBlock: { (snap) in

    if let favDict = snap.value as? NSMutableDictionary{
    favDict.setObject("true",forKey : recipeID)
    prntRef.setValue(favDict)
    } else {
    prntRef.setValue(["true":recipeID])
    }
    })
  • For Updating in the favourites:-

     USERS_REF.child(uid).child("favorites").updateChildValues([recipeID: "false"])  //User doesn't like's the recipe anymore
  • For Deleting from the favourites:-

     USERS_REF.child(uid).child("favorites").child(recipeID).removeValue()  //User want to remove this item from its history
  • Whilst Retrieving

     let prntRef = USERS_REF.child(uid).child("favorites")
    prntRef.observeSingleEventOfType(.Value, withBlock: {(snap) in

    if let favDict = snap.value as? [String:AnyObject]{

    for each in favDict{

    let eachRecipeId = each.0 //recipeID
    let isMyFav = each.1 // Bool
    }

    } else {
    print("No favourites")
    }
    })
  • Whilst Retrieving For a known key-value pair

     let prntRef = USERS_REFFIR.child("users").queryOrderedByChild("favorites/\(recipeID)").queryEqualToValue(true)
    prntRef.observeSingleEventOfType(.Value, withBlock: {(snap) in

    //snap containing all the Users that carry that recipeID in their favourite section
    })

Best way to structure my firebase database

Working with NoSql Data , your need to take care of few things:-

  • Avoid Nesting The Data Too Deep
  • Flatten your dataStructure as possible
  • Prefer Dictionaries
  • Security Rules [Firebase]

Try this structure:-

users:{
userID1: {..//Users info..
posts:{
postID1: true,
postID2: true,
postID3: true,
}
},
userID2: {..//Users info..},
userID3: {..//Users info..},
userID4: {..//Users info..},
},
posts: {
userID1 :{
postID1: {..//POST CONTENT },
postID2: {..//POST CONTENT },
postID3: {..//POST CONTENT },
}

}

Firebase database structure - need advice

In my opinion, your book and badges data models look totally fine. They will be public to any user in the app and do not store any complex data.

Now to get to the user objects:
1) For this type of app, where user interactions will be occurring, you will most likely want to protect the privacy of your users by not storing their email (since the user object has to be readable by others). The email will already be embedded in the user's authentication token, so saving it into the database as well is redundant and reduces privacy. If you really wanted to save it, you could make a new object named "user_private" and have it only be readable by the user himself.

2) For an app where a friends list is present, you will also need a system of friend requests. To do this, I would suggest creating a key named "outgoing_requests", that is all the friend requests you have sent to other users (w/ userID) that are still incomplete. Also, create a key named "incoming_requests", that contains requests other users have sent you. This is what you would use to create a friend requests page and allow a user to accept or deny. This creates some complexities with the JSON rules. I would lay it out like this:

  • outgoing_requests: writable by user himself (cancel request) or by the user who has the id of request( accept or deny request)
  • incoming_requests: writable by user himself (accept or deny) or by user who has id of request (cancel)
  • friends_list: writable if new entry and id is part of incoming requests, or deleting and this is my user

3) You will also likely be querying your user's books based on timestamps. Therefore, I would lay it out so this is easy to accomplish (an array won't work because the user may start reading a book they havent opened in a while and the ordering will be mixed up). You could store user's books like this:

books:
book_id:
lastopened_date:
status:
current_page:
start_date:
finish_date:

Then, in status, either store "complete" or "incomplete". "lastopened_date" & "current_page" will be used if incomplete. "start_date" & "finish_date" will be used if complete.Then it is easy to query for finished books by what date they were finished, and query for unfinished books by what date they were last opened.
Badges can be stored like this:

badges:
badge_id:
get_date:

Storing the books & badges like this makes it much easier to query based on timestamps. This is everything I can think of for now. Let me know if you have any questions.

Firebase Realtime Database Allow read/write if string matches an attribute

To send the special key, add a field specialKey to the model.

Basically, check that the model-key specialKey is equal to the database-field specialKey.

The obvious problem with this approach is, that the specialKey of the model is then persisted with every object, there is a suggestion for that below, too.

Note: newData is the incoming model-data.

The code below might point in the right direction. I did not test it and might have made wrong assumptions.

Rule

{
"rules": {
"shops": {
"$shop_id" {
"payload" {
".read": if true,

// OPTION A

".write": "root.child(shops).child($shop_id)
.child(newData.child('specialKey)).exists()"

// OPTION B
//
// Try deleting the secrectKet in the model.
// No idea if this works in real life.
// === seems to be correct for assigning values?

".write": "root.child(shops).child($shop_id)
.child(newData.child('specialKey)).exists()
&& newData.child('specialKey).val() === '' "

},
"specialKey" {
// is READ:false ok to be processed by rule above?
".read": if false,
".write": if false,
}
}
}
}
}

Data Structure

{
"shops": {
"shop-a": {
"payload": "yourPayload",
"SuperSecretSpecialKey123": true
}
}
}

Source

https://firebase.google.com/docs/rules/rules-language#building_conditions

Bottom Line

It might be worth considering a normal authentication process, then one could assign users write-roles and user proper authentication.

Firebase database rules allowing to read deeper nodes by field

You said you're trying to read from items/{userUid}, but your rules are set to items/{userUid}/{itemUid}. You should remove that deeper child and keep your read rule at {userUid}:

"items": {
"$uid": {
".read": "$uid === auth.uid || data.child('privacy').val() > 0",
".write": "auth != null && $uid === auth.uid"
}
}

Update 1: Have in mind that rules are not filters. This means that a user can either read all data from that node, or not read any data at all. But your problem can be solved by using the recently introduced Query-based rules.

You can keep the query you're using to read the owner data:

getReference("items/{userUid}").addChildEventListener

But when reading data for the other users, you'll need to filter it to show only the ones with privacy>0:

getReference("items/{userUid}").orderByChild('privacy').startAt(1).addChildEventListener

(orderByChild('privacy').startAt(1) is equivalent to privacy>=1)

And then you can add this query to your rules to make sure that this data is only read when using this query:

{
"rules": {
"items": {
"$uid": {
".read": "$uid == auth.uid || (query.orderByChild === 'privacy' && query.startAt === 1)",
".write": "auth != null && $uid === auth.uid",
".indexOn":["privacy"]
}
}
}
}

Note that I've also added .indexOn to improve performance when ordering the data by privacy.

Update 2: In order to access an item directly, you can use the following rules:

".read": "(auth != null && $uid === auth.uid) || (auth != null && query.orderByKey) || (auth != null && query.orderByChild === 'privacy' && query.startAt === '1')"

and access it using:

getReference(ref_to_items_userUid).orderByKey().equalTo(itemKey)

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

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 Realtime Rules: Allowing multiple Users Access to Data

First, in the Realtime Database, avoid using arrays and use a map instead.

Change this:

"Allowed": {
"0": "8ZiQGBPFkiZOLgLJBgDeLw9ie9D3",
"1": "KEuhrxnAWXS0dnotjhjFAYUOcm42",
"2": "48yULftKSxgyS84ZJC4hs4ug4Ei2"
}

to this:

"Allowed": {
"8ZiQGBPFkiZOLgLJBgDeLw9ie9D3": true,
"KEuhrxnAWXS0dnotjhjFAYUOcm42": true,
"48yULftKSxgyS84ZJC4hs4ug4Ei2": true
}

Read that linked blog post for more info, but in short, it makes adding/removing users really simple:

const groupRef = firebase.database.ref(`Groups/${groupId}`);

// add a user
groupRef.child("E04HLbIjGDRUQxsRReHSKifaXIr2").set(true);

// remove a user
groupRef.child("KEuhrxnAWXS0dnotjhjFAYUOcm42").remove();

You can also change true to whatever you want. Here are some examples:

  • false = participant, true = moderator
  • false = read-only, true = can edit
  • Role names: "member", "admin", "moderator", etc.
  • Privilege levels: 0 (member), 500 (moderator), 1000 (owner), etc. (make sure to space these out, you don't want to have to add in a level between 0 and 1 and have to edit your entire database).

The most important point though, is that Realtime Database security rules don't know about arrays. data.val() won't return an array, it will just return a sentinel value that says "non-null object is here!". This means a map is necessary for security rules.

This reference document covers the structure and variables you can use in your Realtime Database Security Rules.

With your proposed rules, you attempt to allow any user in the group to be able to write to the group's data - but you don't manage what they can and can't write to. Any malicious member of a group could add/delete anyone else, make themselves the owner, or even delete the group entirely.

{
"rules": {
"Groups" : {
"$group": {
// If this group doesn't exist, allow the read.
// If the group does exist, only the owner & it's members
// can read this group's entire data tree.
".read": "!data.exists() || (auth != null && (data.child('Owner').val() === auth.uid || data.child('Allowed').child(auth.uid).val() === true))",

"Owner": {
// Only the current owner can write data to this key if it exists.
// If the owner is not yet set, they can only claim it for themselves.
".write": "auth != null && (data.val() === auth.uid || (!data.exists() && newData.val() === auth.uid))",

// Force this value to be a string
".validate": "newData.isString()"
},

"Allowed": {
// Only the owner can edit the entire member list
// For a new group, the owner is also granted write access
// for it's creation
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))",

"$member": {
// Allows the user to remove themselves from the group
".write": "auth != null && auth.uid === $member && !newData.exists()",

// Force this value to be a boolean
".validate": "newData.isBoolean()"
}
},

"Data": {
// The owner and members can edit anything under "Data"
// Currently this includes deleting everything under it!
// For a new group, the owner is also granted write access
// for it's creation
// TODO: tighten structure of "Data" like above
".write": "auth != null && (data.parent().child('Owner').val() === auth.uid || data.parent().child('Allowed').child(auth.uid).val() === true || (!data.exists() && newData.parent().child('Owner').val() === auth.uid))"
}
}
}
}
}


Related Topics



Leave a reply



Submit