Setting Google FireStore Permissions to avoid overwriting data - swift

I have a project I am working on that takes data written by the following statement in swift 4:
#IBAction func saveButton(_ sender: Any) {
db.collection("violets").document(plantName.text!).setData([
"Plant Name": "\(plantName.text ?? "")",
"Hybridizer": "\(hybridizer.text ?? "")",
"Registration Number": Int("\(registrationNumber.text ?? "")"),
"Type": "\(type.text ?? "")",
"Description": "\(generalDescription.text ?? "")",
"Notes": "\(notes.text ?? "")"
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
uploadImage(violetImage.image!)
}
This is written to my FireStore successfully:
My problem is when adding more data, its easy to overwrite an existing document in FireStore. I think the best way to go about fixing this is to implement specific permissions to restrict overwriting data.
My hope was to try to only allow overwrite if a user is Authenticated and if the document name wasn't already present in the database.
This is my FireStore permission config:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read : if request.auth.uid != null;
//allow write: if request.auth.uid != null && !data.exists
}
match /violets/{violetID} {
allow write: if !exists(violetID)
}
}
}
At this point is just denying all writing period. Am i misunderstanding the file structure of FireStore or is something else here wrong?
EDIT: Think I got closer but still no cigar...

So I was never actually able to get the permission validation itself to work; but I found a way to do it in code by validating that the document I was trying to write existed or not. Here was my final solution:
#IBAction func saveButton(_ sender: Any) {
let docRef = db.collection("violets").document(plantName.text!)
docRef.getDocument { (document, err) in
guard
let document = document,
let user = document.exists ? document : nil
else {
print("Plant does not exist in DB. Writting new Entry")
self.db.collection("violets").document(self.plantName.text!).setData([
"PlantName": "\(self.plantName.text ?? "")",
"Hybridizer": "\(self.hybridizer.text ?? "")",
"Registration Number": Int("\(self.registrationNumber.text ?? "")"),
"Type": "\(self.type.text ?? "")",
"Description": "\(self.generalDescription.text ?? "")",
"Notes": "\(self.notes.text ?? "")"
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
self.violetImage.image = self.violetImage.image?.resizeWithWidth(width: 120)
self.uploadImage(self.violetImage.image!)
self.itemFinishedAdding()
return
}
print("\(self.plantName.text!) is already in the database")
}
}
While this logic works... I am not quite satisfied with it. Still Chuggin' at it.

Related

SwiftUI and Firebase - Stream error: 'Not found: No document to update:

So, I have a program that, when it opens, looks for a specific document name in a specific collection (both specified) and, when it is found, copies the document name and starts a listener. If it doesn't find the document name after 5 x 5 second intervals, the app stops. For some reason, when I run the code, after it does the first check I get about a thousand writes of this error:
[Firebase/Firestore][I-FST000001] WriteStream (7ffcbec0eac8) Stream error: 'Not found: No document to update:
Here's the code I'm using to call firestore:
let capturedCode: String? = "party"
.onAppear(perform: {
Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { timer in
print("running code check sequence")
if let code = capturedCode {
calcCloud.checkSessionCode(code)
if env.doesCodeExist {
print("code found! applying to environment!")
env.currentSessionCode = code
calcCloud.watchCloudDataAndUpdate()
allClear(env: env)
timer.invalidate()
}
else if timerCycles < 5 {
timerCycles += 1
print("code not found, this is cycle \(timerCycles) of 5")
} else {
print("could not find document on firebase, now committing suicide")
let x = ""
let _ = Int(x)!
}
}
}
})
here is the code I'm using to check firebase:
func checkSessionCode(_ code: String) {
print("checkSessionCode running")
let docRef = self.env.db.collection(K.sessions).document(code)
docRef.getDocument { (document, error) in
if document!.exists {
print("Document data: \(document!.data())")
self.env.doesCodeExist = true
} else {
print("Document does not exist")
self.env.doesCodeExist = false
}
}
}
and here is the code that should be executed if the code is found and applied:
func watchCloudDataAndUpdate() {
env.db.collection(K.sessions).document(env.currentSessionCode!).addSnapshotListener { (documentSnapshot, error) in
guard let document = documentSnapshot else {
print("Error fetching snapshot: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
Where did I go wrong, and what is this error all about...thanks in advance :)
EDIT: For clarity, it seems that the errors begin once the onAppear finishes executing...
This is why I need to stop coding after 1am...on my simulator, I deleted my app and relaunched and everything started working again...sometimes the simplest answers are the right ones...

How to check if email already exists in Firestore db

I have implemented Auth method for the Firestore database, but when the user tries to register with the same email, the app crash. I'd like to implement a function to check if the email already exists (if it does, fire UIAlert, otherwise if it doesn't, create a new user).
I have so far:
Auth.auth().createUser(withEmail: email, password: password) { (Result, err) in
let db = Firestore.firestore()
let docRef = db.collection("email users").document("email")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let emailAlreadyInUseAlert = UIAlertController(title: "Error", message: "Email already registered", preferredStyle: .alert)
emailAlreadyInUseAlert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(emailAlreadyInUseAlert, animated: true, completion: nil)
return
} else {
let db = Firestore.firestore()
db.collection("email users").addDocument(data: [
"firstName": firstName,
"lastName": lastName,
"email": email,
"created": Timestamp(date: Date()),
"uid": Result!.user.uid
])
}
self.transitionToHome()
}
}
}
}
func transitionToHome() {
let homeViewController = storyboard?.instantiateViewController(identifier: "HomeViewController") as? HomeViewController
view.window?.rootViewController = homeViewController
view.window?.makeKeyAndVisible()
}
}
At this code the UIAlert doesn't fire, and have an error at : "uid": Result!.user.uid - Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value. When creating new user with unique email, it works as it should, the user is created.
If I change if let document = document, document.exists to if error !=nil, I get the UIAlert when the email already exist and also if it doesnt exist, the code of creating user doesnt execute.
Even tried to implement addsnapshotlistener, no luck.
Any suggestions? Thank you
Regarding error: it's a common practice to return success code as error code instead of setting error to nil, and google docs seems mention it as well.
The other issue is because you are forcibly unwrapping items that can legitimately be nil.
Instead, use guard to isolate any invalid cases and exit:
guard error == nil || case FirestoreErrorCode.OK = error else {
// got error; process it and
return
}
guard let result = result else {
// got no error, but no result either
// fail and
return
}
//if you are here, it means you've got no error and `result` is not nil.
Also notice that result should not be capitalized in callback:
Auth.auth().createUser(withEmail: email, password: password) { (result, err) in ...
You may not need a custom function to check if an email already exists as that's a default error Firebase Auth will catch and allow you to handle when a user is created.
For example, this code will catch situations where the user is attempting to use an email that already exists.
func createUser() {
let email = "test#thing.com"
Auth.auth().createUser(withEmail: email, password: "password", completion: { authResult, error in
if let x = error {
let err = x as NSError
switch err.code {
case AuthErrorCode.wrongPassword.rawValue:
print("wrong password")
case AuthErrorCode.invalidEmail.rawValue:
print("invalid email")
case AuthErrorCode.accountExistsWithDifferentCredential.rawValue:
print("accountExistsWithDifferentCredential")
case AuthErrorCode.emailAlreadyInUse.rawValue:
print("email already in use")
default:
print("unknown error: \(err.localizedDescription)")
}
return
}
let x = authResult?.user.uid
print("successfully created user: \(x)")
})
}
There's a number of Authentication Error Codes so you can handle a wide variety of errors without any special error handling.
And the AuthErrorCode API has some more useful information which is demonstrated in the answer code.

Function not stopping after handleComplete

I created a basic Google Places app that lets users check-in to a location. When a user tries to check in, I loop through the list of likelihood places to verify that the user is actually at the location in the app. However, when I try to escape the loop after confirming the location is correct, my function still ends up going to my "else" situation (an error message that asks the user to please check in to the correct location).
The following function gets called in viewWillAppear:
func checkIn(handleComplete:#escaping (()->())){
guard let currentUserID = User.current?.key else {return}
// Specify the place data types to return.
let fields: GMSPlaceField = GMSPlaceField(rawValue: UInt(GMSPlaceField.name.rawValue) |
UInt(GMSPlaceField.placeID.rawValue))!
placesClient.findPlaceLikelihoodsFromCurrentLocation(withPlaceFields: fields, callback: {
(placeLikelihoodList: Array<GMSPlaceLikelihood>?, error: Error?) in
if let error = error {
print("An error occurred: \(error.localizedDescription)")
return
}
if let placeLikelihoodList = placeLikelihoodList {
for likelihood in placeLikelihoodList {
let place = likelihood.place
if likelihood.likelihood >= 0.75 && place.placeID! == self.hangoutID {
let place = likelihood.place
print("Current Place name \(String(describing: place.name!)) at likelihood \(likelihood.likelihood)")
print("Current PlaceID \(String(describing: place.placeID!))")
self.delta = 0.0
// update checkin
DispatchQueue.main.async {
let hangoutRef = self.db.collection("users").document(currentUserID).collection("hangout").document(self.hangoutID).updateData([
"lastCheckin": Date()
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
}
handleComplete()
}
}
self.presentDismissableAlert(title: "", message: "Please check in to the hangout to join this chat", button: "OK", dismissed: { (UIAlertAction) in
self.performSegue(withIdentifier: "unwindSegueToChats", sender: self)
})
}
})
}
If the correct conditions are met, the code will land on the handleComplete() line but then it will still execute the dismissableAlert underneath and segue the user out of the room. How can I fix the flow so that the app will cycle through the list of likely Places and stop the function on handleComplete if the correct condition is met, or else then proceed to the error message if the correct conditions are not met (user is not at the correct Place)?
Thanks

How to add Document with Custom ID to Firebase (Firestore) on Swift

Is there any way to add a document to firestore collection with custom id on Swift Language, not the id generated by firestore engine.
Thanks in advance for your responses.
Yes. Just try this:
Firestore.firestore().collection("items").document("yourId").setData(["item": "test"])
2020
Usually you'd want the error handling:
Firestore.firestore()
.collection("companyAddress")
.document(companyID)
.setData([
"address1": a1,
"address2": a2,
"city": c
]) { [weak self] err in
guard let self = self else { return }
if let err = err {
print("err ... \(err)")
self.yourErrorAlert()
}
else {
print("saved ok")
self.proceedWithUX()
}
}

Firestore how to store reference to document / how to retrieve it?

I am new to Firestore/Firebase and I am trying to create a new document with one of the fields being a document reference to an other document. I have read all the guides and examples from Firebase and did not find anything...
Also, when I retrieve this document I created, I would be able to access what is inside the reference I added inside. I have no idea how to do that.
Here is some code I tried for the creating part
let db = Firestore.firestore()
// Is this how you create a reference ??
let userRef = db.collection("users").document(documentId)
db.collection("publications").document().setData([
"author": userRef,
"content": self.uploadedImagesURLs
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
You're just missing one step. This took me a while to figure out as well.
First, store a variable DocumentReference!
var userRef: DocumentReference!
Then, you want to create a pointer to the document ID — create a DocumentReference from that <- this part you are missing
if let documentRefString = db.collection("users").document(documentId) {
self.userRef = db.document("users/\(documentRefString)")
}
Finally, resume saving your data to the database
db.collection("publications").document().setData([
"author": userRef,
"content": self.uploadedImagesURLs
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
Hope that helps!
This way works perfectly for me:
let db = Firestore.firestore()
let documentRefString = db.collection("users").document(documentID)
let userRef = db.document(documentRefString.path)
Then, when you will set the data:
db.collection("publications").document().setData([
"author": userRef,
"content": self.uploadedImagesURLs
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}