how to write auth based Firestore Rules for specific collections - firebase

I have a collection called Orders and I have two other collections for Users & Drivers. I want the Orders collection to be accessible only by the UID's that inside the Drivers collection.
All the people signed into the app are users, but Drivers are the only users that can access the Orders documents.
The logic is to check if the user is signed and the Drivers collection contains a field that has the UID of that user. If the collection field contains the UID of that user, then he can access that collection of document.
allow write, read: if isSignIn() & `Driver colletion contains that user UID`
How can I do that and if I can I need some help write that code?
Here are my rules and I did not change that much in it. All that I know is this code is for every document in my database, so I need to write some specifics
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write, read: if isSignIn();
}
function isSignIn(){
return request.auth != null;
}
}
}
here is how I'm getting the data from Firestore
func newOrders(){
let fireStore = Firestore.firestore()
let doc = fireStore.collection("Orders")
self.driverListener = doc.addSnapshotListener { (query, err) in
if err != nil {
print(err?.localizedDescription ?? "")
}
guard let querysnap = query else {return}
querysnap.documentChanges.forEach({ change in
if change.type == .added {
self.DriverOffers = []
for document in querysnap.documents{
let snap = document.data()
guard let orderLoc = snap["orderLocation"] as? GeoPoint else {return}
guard let docId = snap["docId"] as? String else {return}
guard let name = snap["name"] as? String else {return}
guard let phone = snap["phone"] as? String else {return}
guard let time = snap["time"] as? String else {return}
guard let marketName = snap["marketName"] as? String else {return}
guard let price = snap["amount"] as? String else {return}
guard let userImg = snap["userImg"] as? String else {return}
guard let storeImg = snap["storeImg"] as? String else {return}
guard let order = snap["order"] as? String else {return}
guard let userid = snap["userUid"] as? String else {return}
guard let timestamp = snap["date"] as? Timestamp else {return}
let date = Date(timeIntervalSince1970: TimeInterval(timestamp.seconds))
guard let userlocation = snap["userLocation"] as? GeoPoint else {return}
let offer = driverOrdersData(docId: docId, userUid: userid, name: name, phone: phone, amount: price, time: time, marketName: marketName, storeimgUrl: storeImg, userImgUrl: userImg, date: date, orderDetails: order, orderLocation: orderLoc, userLocation: userlocation, distance1: distance1, distance2: distance2 )
self.DriverOffers.append(offer)
DispatchQueue.main.async {
self.DriverOrdersTV.reloadData()
}
}
}
}
})
}
}

You cannot check in security rules whether a document exists with a specific value in it, as that would require your rules to query the collection and would be costly and not scale.
The trick to implement this use-case (and many others) is to use the UID of a user for the document ID in the Drivers (and probably also Users) collection. You can do this by adding those documents with something like
let uid = ....; // wherever you get the UID from
firebase.firestore().collection("Drivers").doc(uid).set({ uid: uid });
Now with that structure in place, you can check whether a document with the specific UID exists in your security rules, which is possible:
function isDriver() {
return exists(/databases/$(database)/documents/Drivers/$(request.auth.uid))
}
Also see the Firebase documentation on accessing other documents in security rules.

Related

How can I use AQL with multiple queries that using the result of one another?

I have 2 vertices and an edge named user, device, ownership respectively.
My business logic is when I receive device information, I upsert it with dateCreated and dateUpdated fields added. If I inserted that device then I insert new user with default values and create edge connection to it. If I update I simple return already connected user as a result.
Without losing atomicity how can I achieve this?
I tried single AQL query but without condition it is not possible it seems and traversal also is not supported with insert/update operation.
I can do separate queries but that loses atomicity.
var finalQuery = aql`
UPSERT ${deviceQuery}
INSERT MERGE(${deviceQuery},{dateCreated:DATE_NOW()})
UPDATE MERGE(${deviceQuery},{dateUpdated:DATE_NOW()})
IN ${this.DeviceModel}
RETURN { doc: NEW, type: OLD ? 'update' : 'insert' }`;
var cursor = await db.query(finalQuery);
var result = await cursor.next();
if (result.type == 'insert') {
console.log('Inserted documents')
finalQuery = aql`
LET user=(INSERT {
"_key":UUID(),
"name": "User"
} INTO user
RETURN NEW)
INSERT {
_from:${result.doc._id},
_to:user[0]._id,
"type": "belongs"
}INTO ownership
return user[0]`;
cursor = await db.query(finalQuery);
result = await cursor.next();
console.log('New user:',result);
}
You can try something like this
Upsert ....
FILTER !OLD
Let model = NEW
LET user= First(INSERT {
"_key":UUID(),
"name": "User"
} INTO user
RETURN NEW)
INSERT {
_from:model._id,
_to:user._id,
"type": "belongs"
}INTO ownership
return user
I end up separating the modification and selection queries.
var finalQuery = aql`
LET device=(
UPSERT ${deviceQuery}
INSERT MERGE(${deviceQuery},{dateCreated:DATE_NOW()})
UPDATE MERGE(${deviceQuery},{dateUpdated:DATE_NOW()})
IN ${this.DeviceModel}
RETURN { doc: NEW, type: OLD ? 'update' : 'insert' })
FILTER device[0].type=='insert'
LET user=(INSERT {
"_key":UUID(),
"name": "User"
} INTO user
RETURN NEW)
INSERT {
_from:device[0].doc._id,
_to:user[0]._id,
"type": "belongs"
}INTO ownership
return user[0]`;
var cursor = await db.query(finalQuery);
var result = await cursor.next();
if (result == null) {
const deviceId=this.DeviceModel.name+"/"+queryParams._key;
finalQuery = aql`
FOR v,e,p IN 1..1
OUTBOUND ${deviceId} ownership
FILTER e.type=="belongs"
RETURN v `;
cursor = await db.query(finalQuery);
result = await cursor.next();
isUpdate=true;
}
This way I ensure the atomicity. There are improvements for controling if cursor.extra.stats.writesExecuted true etc.

fetch and retrieve data from firebase in swift

what I am trying to do is fetching data from firebase, but the data is nil because the user did not send his data to firebase yet, so when he enter the view controller that should show his data, the compiler make error. How can I solve this error? I tride to add alert, but it's still not working.
func getData(){
ref = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
ref.child("users")
.queryOrdered(byChild: "uid")
.queryEqual(toValue:userID)
.observe(.value) { (snapshot, error) in
if error == nil{// alert} elses{
if let data = snapshot.value as? NSDictionary {
if snapshot.exists() {
for a in ((snapshot.value as AnyObject).allKeys)!{
let users = data.value(forKey:a as! String) as! NSDictionary
let address = users.value(forKey:"Address") as! NSDictionary
self.lblAddressNickname.text = address.value(forKey:"addressNickname") as? String
}
}
}
}
}
}
So as of now i understand the question that you have problem in fetching data from firebase when no data is in the firebase and then alert will come and it will redirect to a new controller?
So based on my understanding i will give answer if any problem of understanding things then reply me?
first you have to check that particular user have any data in firebase database so if there is no data then alert function will call
if let data == data
{
fetch_logic is here
}
else
{
let alert = UIAlertController(title:"Add Data",message:"",preferredStyle: .alert)
let action = UIAlertAction(title: "Add Button", style: .default) { (UIAlertAction) in
}
alert.addAction(action)
present(alert,animation:true,completion:true)
}

Checking a child's entries for a match

let ref = firebase.database().ref('players').child(playerId).child('voters');
ref.child(uid).once('value', snap => {
var key = snap.key;
console.log("snapkey: " + key + " uid: " + uid)
if (key === uid) {
console.log("Exists")
} else {
console.log("Doesn't exist")
}
});
I'm trying to see if a variable uid, which holds the users unique ID from firebase-auth is present in my database's voters
So for me, when I'm using the app, my uid is vKl6rIUuI0WsbeWVORz3twPUfnd2. So if I go to vote on this Firstname Lastname person, it should tell me I exist in the above image's scenario.
The problem is, it seems to always say it exists. The console.log for key and uid are both putting out my uid. Is it something with the ref.child(uid)...?
let ref = firebase.database().ref('/players/' + playerID + '/voters');
ref.once('value', snap => {
var value = snap.val()
console.log(value)
if (value !== null) {
console.log("Exists")
} else {
console.log("Doesn't exist")
}
});
https://firebase.google.com/docs/database/web/read-and-write#read_data_once
A snapshot will always have a key. Always. And it will be at the location you requested by reference. Whether or not there is data behind that key is irrelevant to the fact that the snapshot will always have a key.
What you need to do is check the data behind that key. Is it null? Then there's no data there. A number? That's data, and it's present.
Use .exists() method:
let ref = firebase.database().ref('players').child(playerId).child('voters');
ref.child(uid).once('value', (snap) => {
console.log(snap.exists()); // This will print true or false
});

Iteration through dictionaries, Firebase snapshot using

I am having a problem while iterating through a dictionary. First check my database
And here is my code
ref.child("Events").observe(.value, with: { (snapshot) in
for child in snapshot.children{
let snap = child as! DataSnapshot
let valueSnap = snap.value as? [[String:Any]]
let nombreEventDansClub = valueSnap?.count
print(valueSnap)
for index in 0...nombreEventDansClub!-1
{
print(valueSnap![index]["name"])
//print(valueSnap![index]["end_time"])
if(valueSnap![index]["end_time"] as? String == nil)
{
//do Something
}
else{
let end_time_test = valueSnap![index]["end_time"] as? String
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+2:00")
let end_test = dateFormatter.date(from: end_time_test!)!
The recuperation is perfect but for example : if Event-0-(3) didn't exist the app would crash, I know the problem is that I calculate the number of events in a : Event-(something) and iterate according to this. I was wondering if there is a smarter way. Thanks in advance
You can parse the snapshot this way:-
ref.child("Events").observe(.value, with: { (snapshot) in
guard let values = snapshot as? [Any] else { return }
for value in values {
guard let childrens = value as? [Any] else { continue }
for children in childrens {
guard let dict = children as? [String: Any] else { continue }
for key in dict.allkeys {
// TO debug all keys and values
print(dict[key])
}
print(dict["description"] as? String)
print(dict["end_time"] as? String)
print(dict["id"] as? String)
// other keys
}
}
}
Force unwrapping may cause your app crash unexpectedly.

Fetching images from Firebase child

everyone. I have troubles with fetching a chunk of images from a child in my Firebase database. I am doing an iOS project and using swift 3 and XCode. Basically, after all, my "image" variable returns "nil" Please, help me if you can. Here is the code I am using:
let ref = FIRDatabase.database().reference().child("menu")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let image = dictionary["foodImageUrl"] as? String
print(image as Any) // this returns nil
and an example of a database structure
I need to fetch all the "foodImageUrl" from all children from the menu node
your snapshot.value is the list of sub items from menu, not the details info of each node, so you have to use forEach, and then inside each node, use dictionary["foodImageUrl"] as? String to get your image
try this
let ref = FIRDatabase.database().reference().child("menu")
ref.observeSingleEvent(of: .value, with: { (snapshot) in
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
if let dictionary = rest.value as? [String: AnyObject]{
let image = dictionary["foodImageUrl"] as? String
print(image as Any)
}
}

Resources