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
pairlet 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
= moderatorfalse
= 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 between0
and1
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
Error Setting Text in Collection View Cell
How to Properly Structure Firebase Database to Allow Easy Reading and Removal
Uiimageview Not Showing Transparency of Png Images from Uiimagepickercontroller
Always Got Nil Value from the Function in iOS
Trigger a Method in Uiviewcontroller from Its View
Application Is "Ready to Sale" But Not Reflected on Itunes Store
Calculating the Broadcast Address in Objective-C
Why Do Some Views Appear as a Red No Entry Sign in Widgets
Multiple Unusernotifications Not Firing
Alamofire 5: Value of Type 'Result<Data, Aferror>' Has No Member 'Value'
Uitextview Text Selection and Highlight Jumping in iOS 8
Submitting iOS App Using Beta Version of Xcode
Image in Tableviewcell Swipe Action
How to Get the Scnview Camera Position When Using Allowscameracontrol
Uisearchbar's Cancel and Clear Buttons Not Working in iOS 7
Type 'String.Index' Does Not Conform Protocol 'Integerliteralconvertible'