Check for existing value inside of Firebase Realtime Database - swift

Hello, I have a problem I created a Registration form and im trying to check if there is any user which have a certain username inside the Firebase Db. I tried to get the reference of all the users.
var users = Database.database().reference("users")
But I don't know how I could check if there is any user with a specified username.

You'll want to use a query for that. Something like:
let query = users.queryOrdered(byChild: "username").equalTo("two")
Then execute the query and check whether the result snapshot exists.
Note though that you won't be able to guarantee uniqueness in this way. If multiple users perform the check at the same time, they may both end up claiming the same user name.
To guarantee a unique user name, you will need to store the user names as the key - as keys are by definition unique within their parent node. For more on this, see some of these top search results and possibly also from here.

Related

Convenient data structure and querying for message application using Firebase Firestore

I've been trying to implement live messaging in my application and I cannot seem to think of a convenient data structure inside Firestore. My current structure looks like this:
collection("conversations").document(id).collection("messages")
Each document holds two attributes user1 and user2 with nicknames of contributors to the conversation. Each document also owns a collection called messages which holds documents where each represents a single message sent with some info.
What I'm trying to do next is to check if the conversation already exists, if not then create it. The problem for me is write a correct query to find out if it exists.
My first idea was: create users array instead which holds nicknames of users and then simply query:
db.collection("conversations").whereField("users", in: ["username1", "username2"])
Problem with this is that it means "where users contains username1 OR username2", but I need it to contain "username1 AND username2".
I tried to be smart and chain the whereField function as following:
db.collection("conversations").whereField("users", arrayContains: "username1").whereField("users", arrayContains: "username2")
Turns out that you cannot use arrayContains more than once in a single query.
After that I came back to the structure as displayed on the screenshot with user1 and user2 and ran a new query:
db.collection("conversations").whereField("user1", isEqualTo: user).whereField("user2", isEqualTo: friend)
This query is ran in a function where user and friend are string parameters holding nicknames of both sender and receiver of the message we're currently sending. Imagine you are sending a message, user is always going to be your nickname and friend the receiver's one. The problem with the query is that you're nickname might be saved under user1 or user2 and receiver's nickname aswell. In either of those situations the conversation exists. How would I have to change the query since I don't know in an advance who will have which position in the query aswell as in Firestore. Running the last query that I included twice while switching user and friend parameter seems very unconvenient.
Any tips or solutions to progress in this problem will be much appreciated!

Use Firestore rules to limit list fails on array key

I'm trying to use Firestore rules to return only documents where the current user has some sort of rights, following the advice given in https://firebase.google.com/docs/firestore/solutions/role-based-access.
However, when I implement the rule I get the dreaded "[code=permission-denied]: Missing or insufficient permissions" error message which obviously tells me nothing, I was wondering if anyone can spot what is going wrong.
My rules:
//Specific project rules - authorised users who appear in the project list
match /documents/{document} {
function isSignedIn() {
return request.auth.uid != null;
}
function getUser(rsc) {
return rsc.data.users[request.auth.uid];
}
function isOneOfUsers(rsc, array) {
return isSignedIn() && (getUser(rsc) in array);
}
allow list: if isOneOfUsers(resource, ['user','admin']);
The data stores the users information in a field on the document (12345 in the example below. The field is of type Object which allows me to put a key (the userid, 76544 in the example below) and a value against it, such as "admin".
My data:
documents/12345/users{76544:"admin"}
Now when I log on and try to get a list of the documents, I'm expecting to see this document coming back, but I get the error. I can change the function getUser to return "user" and that works, so the problem is somewhere in the evaluation of
rsc.data.users[request.auth.uid]
I would normally accept that I'm trying something that can't be done but it is a near direct copy of the official docs so I must be missing something!
Thanks in advance for your help
Here's what I think is going on: LIST is explicitly not checking every document that it is picking up, rules are not filters etc.
When writing queries to retrieve documents, keep in mind that security
rules are not filters—queries are all or nothing. To save you time and
resources, Cloud Firestore evaluates a query against its potential
result set instead of the actual field values for all of your
documents. If a query could potentially return documents that the
client does not have permission to read, the entire request fails.
Therefore, LIST will always return the full list of /documents/, there is nothing you can write in there beyond authorisation rules that will stop it from returning all of them. If this is true, if any malicious actor gets hold of an authentication, they can download the full list of all of your /documents/.
The only sensible approach to this is to lock down any attempt to use LIST (deny all) and keep your list accessible /documents/ against an individual user instead. This seems onerous, but may be the only way of doing it.

Firebase second node deep query without knowing the value of the higher node key (swift)

I have the following Firebase structure
There is a node that keeps a list of albums by user. In certain circumstances I do not have the user id (E7Bv..), I only have the album id (-L0uG...). If I had both then I could easily access the specific album node.
Since I do not have the userid, i need a way to query the node where the albumin is equal to the value I have in hand. I do not see how to structure such a query.
As one approach I also tried to add the albumid (-L0uG...) to the sub node as the value of _key. I'd prefer not to have to duplicate that value and just query for where the sub-node for albumid equals the value I have in hand for albumid.
Or, if that can not be done then can anyone tell me how to query where the sub-node has a value for _key that matches the value i have in hand - Without knowing the userid node value?
I would like to do something like this ... (where albumRef is the top node for byUser_albums)
albumRef.queryOrdered(byChild: "_key").queryEqual(toValue: albumID).observeSingleEvent(of: .value, with: { snapshot in
of course this fails because _key is not on the userid node, it is on the albumid node. I need to either query by the subnode id, or go deeper to query by _key
UPDATE
From Frank Below. I tried this ...
albumRef.queryOrdered(byChild: albumID).queryStarting(atValue: nil).observeSingleEvent(of: .value, with: { snapshot in
This works and finds the right data. It returns the key of the scoping autoid. The only downside is that Firebase says (in debug log) ....
"**Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "-L0vTQtLwBe_hilTOGid" at /byUser_albums to your security rules for better performance**"
If I were doing a typical read then I could just add the rule to the database, but I can't add this rule because the value that it wants the .indexOn is not a static value - it's an auto. I don't see a way to index on the autoid. I think I will have to restructure the albums node to have user as an attribute instead of defining a scoping node by user. I do not see a way to read without downloading all data and filter on the client.

Using childByAutoId On Single Value?

I am pretty new to both Swift and Firebase, and I am attempting to make a simple app using Firebase as the backend. As far as I know, there is no memory-efficient way to use the numChildren() function without loading every single child into memory for counting, so I am implementing my own simple counter for the number of "Events" that have been created in my app.
The documentation for Firebase states that the childByAutoID() method should be used for updating lists in multi-user applications. I am assuming it adds a timestamp to the requested update and does them in order.
My question is whether it is necessary to use childByAutoID() when only updating a SINGLE field in a multi-user application. That is, will there be conflicts on my numEvents field if I do:
dbRef = FIRDatabase.database().reference()
dbRef.child("numEvents").setValue(num)
Or must I do:
dbRef = FIRDatabase.database().reference()
dbRef.child("numEvents").childByAutoId().setValue(num)
In order to avoid write conflicts? My only real confusion is that the documentation for childByAutoID stresses that it is useful when the children are a list of items, but mine is only a single item.
If you are only updating a single field you should not be using childByAutoId. To update a child value for an object, you need to obtain a reference to that object somehow, perhaps by a query of some sort (in many cases you will naturally already have a reference to the object if it needs to be changed) and you can change the value like this:
dbRef.child("events").child(objectToUpdateId).child(fieldToUpdateKey).setValue(newValue)
childByAutoId in this context would be used to create a new field like:
dbRef.child("events").childByAutoId().setValue(newObject)
I'm not exactly sure how this applies to your situation, but those are some descriptions of how to update a field, and use childByAutoId.
What childByAutoId does is create a unique key for a node, to avoid using the same key multiple times and then creating data conflicts like inconsistency (not write conflicts) to avoid write conflicts you use the transaction blocks.
The best way to learn is to try it out
If num == 1 , in the first example the result will be
dbRef:{
numEvents:1
}
While the second will be
dbRef:{
numEvents:{
//The auto-generated key
KLBHJBjhbjJBJHB:1
}
}
The childByAutoId would be useful if you want to save in a node multiple children of the same type, that way each children will have its own unique identifier
For example
pet:{
KJHBJJHB:{
name:fluffy,
owner:John Smith,
},
KhBHJBJjJ:{
name:fluffy,
owner:Jane Foster,
}
}
This way you have a unique identifier for cases where there is no clear way with the item data to guarantee it will be unique (in this case the pet's name)
Few things here:
childByAutoId is not a timestamp. But is used to create unique nodes in any given node.
Use case of childByAutoId :
You have messages node which stores messages from multiple user who are involved in a group chat. So each user can add messages in the group chat so you would do something like this each time user sends message:
dbRef = FIRDatabase.database().reference()
dbRef.child("messages").childByAutoId().setValue(messageText)
So this will create a unique message id for each message from different users. This will kind of act like primary key of message in normal databases.
The structure of database will be something like this:
messages: {
"randomIdGenerated-12asd12" : "hello",
"randomIdGenerated-12323D123" : "Hi, HOw are you",
}
So in your case your first approach is good enough! Since you dont need unique node for counting number of events added.

What is the correct way to associate with a ABPerson?

In many of my apps, it requires associating some data with a contact in addressbook. What I used to do is save the record id of an ABPerson and use that id to pull information upon each app launch. However, more and more I find that this approach is wrong because many times a user will use a service like mobileme where the addressbook is wiped and resynced. This causes the record id to change and all associations are lost. The user will have to go through each one and re-link them.
What is a better approach to holding a robust pointer to addressbook entries?
You should store three values: the record ID, the first name, and the last name.
1) In the case that the record ID hasn't changed, you're golden - just use that to locate the proper record.
2) If ABAddressBookGetPersonWithRecordID() does not locate a record for your stored record ID (it returns NULL), then you'll need to search the person records for a match based on the first and last name. You can drop down to using ABAddressBookCopyPeopleWithName() potentially or write your own locating code if you already have an array with all the person records in-memory. Locating the new record is up to you. Once to locate the new record, you can update your data storage with the new record ID.
Ultimately, you end up storing the record ID to use directly incase it doesn't change (if you're lucky) plus storing some keys from the address book entry that are unlikely to change. The name of the person or organization associated with an address book entry is most likely to change. You should, of course, account for the case where you may not find a record with the stored record ID or by searching for the name. This could trivially mean that the record was deleted, or it could mean that the record was renamed. You should handle that case whichever way you decide is best for your specific application.
I know this was last year, however, I thought I might suggest a method I use. The first time I ask the user to pick a contact (in order to associate certain of my app's private data with it) I then grab the record, create my own internal record id (the initials of the app name and a sequence number usually) modify the contact by adding a new ABRelatedName (type of "pref" name of "Other") value of my own internal record id. It looks like this in the .vcf
item3.X-ABRELATEDNAMES;type=pref:BZA101
item3.X-ABLabel:_$!<Other>!$_
That way, I can simply reference that record id when i add more data about the user such as the last time the app user contacted them, etc. Seems to work for me.
Hope that helps someone.
If the address book is indeed being completely wiped and re-loaded, and the only part that doesn't change is the display name, then storing the display name as the link seems like the only option.