Best way to delete Firestore document that you are unsure if it exists? - kotlin

I'm using Firestore as the NoSQL database to build an app that needs to let users add as friends, block, ... other users
To block someone, I'll set the blocked values, and then delete the current friendship status (if any), but I find this a bit tricky. Should I first check if the document exists, and just then delete it, or does Firestore achieve this automatically? Would I be wasting time & Firestore operations if I add the extra checks?
fbRef.runBatch {
it.delete(userFriendsWith)
it.delete(blockedUserFriendsWith)
...
}
fbRef.runBatch {
it.get() {
...
if (document.exists()) {
it.delete(userFriendsWith)
}
}
}
Thanks!

Just delete the document. There's no need to read it first, if you don't care what's inside. The delete operation won't fail if the document already doesn't exist.

Related

KeystoneJS `filter` vs `Item` list access control

I am trying to understand more in depth the difference between filter and item access control.
Basically I understand that Item access control is, sort of, higher order check and will run before the GraphQL filter.
My question is, if I am doing a filter on a specific field while updating, for instance a groupID or something like this, do I need to do the same check in Item Access Control?
This will cause an extra database query that will be part of the filter.
Any thoughts on that?
The TL;DR answer...
if I am doing a filter on a specific field [..] do I need to do the same check in Item Access Control?
No, you only need to apply the restriction in one place or the other.
Generally speaking, if you can describe the restriction using filter access control (ie. as a graphQL-style filter, with the args provided) then that's the best place to do it. But, if your access control needs to behave differently based on values in the current item or the specific changes being made, item access control may be required.
Background
Access control in Keystone can be a little hard to get your head around but it's actually very powerful and the design has good reasons behind it. Let me attempt to clarify:
Filter access control is applied by adding conditions to the queries run against the database.
Imagine a content system with lists for users and posts. Users can author a post but some posts are also editable by everyone. The Post list config might have something like this:
// ..
access: {
filter: {
update: () => ({ isEditable: { equals: true } }),
}
},
// ..
What that's effectively doing is adding a condition to all update queries run for this list. So if you update a post like this:
mutation {
updatePost(where: { id: "123"}, data: { title: "Best Pizza" }) {
id name
}
}
The SQL that runs might look like this:
update "Post"
set title = 'Best Pizza'
where id = 234 and "isEditable" = true;
Note the isEditable condition that's automatically added by the update filter. This is pretty powerful in some ways but also has its limits – filter access control functions can only return GraphQL-style filters which prevents them from operating on things like virtual fields, which can't be filtered on (as they don't exist in the database). They also can't apply different filters depending on the item's current values or the specific updates being performed.
Filter access control functions can access the current session, so can do things like this:
filter: {
// If the current user is an admin don't apply the usual filter for editability
update: (session) => {
return session.isAdmin ? {} : { isEditable: { equals: true } };
},
}
But you couldn't do something like this, referencing the current item data:
filter: {
// ⚠️ this is broken; filter access control functions don't receive the current item ⚠️
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId ? {} : { isEditable: { equals: true } };
},
}
The benefit of filter access control is it doesn't force Keystone to read an item before an operation occurs; the filter is effectively added to the operation itself. This can makes them more efficient for the DB but does limit them somewhat. Note that things like hooks may also cause an item to be read before an operation is performed so this performance difference isn't always evident.
Item access control is applied in the application layer, by evaluating the JS function supplied against the existing item and/or the new data supplied.
This makes them a lot more powerful in some respects. You can, for example, implement the previous use case, where authors are allowed to update their own posts, like this:
item: {
// The current user can update any post they authored, regardless of the isEditable flag
update: (session, item) => {
return item.author === session.itemId || item.isEditable;
},
}
Or add further restrictions based on the specific updates being made, by referencing the inputData argument.
So item access control is arguably more powerful but they can have significant performance implications – not so much for mutations which are likely to be performed in small quantities, but definitely for read operations. In fact, Keystone won't let you define item access control for read operations. If you stop and think about this, you might see why – doing so would require reading all items in the list out of the DB and running the access control function against each one, every time a list was read. As such, the items accessible can only be restricted using filter access control.
Tip: If you think you need item access control for reads, consider putting the relevant business logic in a resolveInput hook that flattens stores the relevant values as fields, then referencing those fields using filter access control.
Hope that helps

firestore nested object field needs updating, not replacing

I am creating an app, where announcements are shown, stored in firestore and with that there is a hasRead object for each announcement.
It works, as in when a user is reading the announcement it is shown as read on the users app. But when another user is reading the same announcement, his/her usrid is being stored, overwriting the any other usrid stored.
Here his how I store it.
setAnnounceToRead(userId) {
firebase.firestore().collection('announcements').doc(this.state.id).set({
hasread: {
userId
}
},
{ merge: true });
}
I already found out that it is because of the merge, as it doesn't "adds" the usrid but overrides it instead.
How can I add every userid that reads the announcement, but keeping the already existing userids?
Cheers
Right now you're storing each user's UID as a field named userId. Since you're using the same field name for each user, you end up storing only the last user's UID.
To store the UID for all users, you'd usually have a structure like this:
hasread: {
udartsUid: true,
pufsUid: true
}
In your code that would translate to something like:
let update = {};
update[userId] = true;
firebase.firestore().collection('announcements').doc(this.state.id).set({
hasread: update
},
{ merge: true });
But this type of operation got a lot easier recently, since Firestore now has operations that allow you to use an array for this type of information.
let doc = firebase.firestore().collection('announcements').doc(this.state.id);
doc.update({ "hasRead": FieldValue.arrayUnion(userId) });
This snippets will add the userId value to the array if it isn't already in there. If the value is already in the array, it does nothing.
For more on the latter, see the blog post Better arrays in Cloud Firestore.

Firestore Database Rules for User

I'm following a tutorial about firestore but I don't understand firestore rules very well. I'm trying to allow anyone to be able to create in the standard
users/uid/
path but only allow updates if the requester is trying to update
users/theirUserId/
I saw this in the documentation, but it didn't seem to work for me:
allow write: if request.auth.uid == resource.data.author_id;
Can anyone explain the functionality of the above line and/or offer suggestions as to how I can achieve this?
Additionally, is there any way to specify rules for a specific piece of data within a document?
It looks like that your document doesn't contain a author_id field.
The Firebase documentation Writing Conditions for Security Rules use this example:
service cloud.firestore {
match /databases/{database}/documents {
// Make sure the uid of the requesting user matches the 'author_id' field
// of the document
match /users/{user} {
allow read, write: if request.auth.uid == resource.data.author_id;
}
}
}
It means that a random user will be able to read and write in the users collections only if their authentication ID equals the author_id field of a specific document.
The resource variable refers to the requested document, and resource.data is a map of all of the fields and values stored in the document. For more information on the resource variable, see the reference documentation.
For your second question, I recommend you to have a look on the documentation about resource variable (link in the quote above). It is the same logic as your author_id question.
You can split allow write in to three create, update, delete for specific cases.
In your case
allow create: if request.auth.uid != null;
allow update: if request.auth.uid == resource.data.author_id;
which says any authenticated users can create and only update their on document. and created user must have a field author_id which is their user id.

Deleting multiple CKRecords at the same time

I have been programming in objective-C for about a year now, but i am new to cloud kit. I can do simple things such as fetch, save and delete Records but I have not been able to find a way of deleting multiple Records at a time.
I tried a for loop but, although there were no errors, nothing was deleted.
heres some of the code:
for (CKRecord* r in self.allRecords) {
[[[CKContainer defaultContainer] publicCloudDatabase] deleteRecordWithID:r.recordID completionHandler:^(CKRecordID *recordID, NSError *error) {
if (error) {
NSLog(#"error");
}else
NSLog(#"deleted");
}];
}
allRecords is an array containing the records which i need deleting but it does not delete any of the records.
Thanks
If you need to modify (that is, save or delete) multiple records in one CloudKit round-trip, you need to use a CKModifyRecordsOperation: https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKModifyRecordsOperation_class/index.html
You mention "allRecords is an array containing the records which i need deleting but it does not delete any of the records".
It's not clear whether you mean that the records aren't being deleted from CloudKit, or you mean that the records aren't being deleted from your self.allRecords array.
In case you're expecting the records to be removed from self.allRecords: they won't. That's your job to manage after examining the response from either the CKModifyRecordsOperation or the deleteRecordWithID:completionHandler: call in your snippet above.

Updating deeply nested documents in ravendb

I am having following document structure and I need to insert values in nested documents.
{
"Level-1": {
"Level-2": {
"Level-3": {
"aaa": "bbb"
"Level-4": {
}
}
}
}
}
how can I get keys every time at any level. There is a function for getting keys
var workingDOc = session.Load<RavenJObject>("xyz/b");
workingDoc.Keys will give me all key for this document But how could I get Keys of second level.when I provide key for nested document . For example now I want all keys for "Level-1".Is there any way? How can I check that the key is of nested document. please help .Thanks in advance
Rajdeep, you can't partially load a document. You can certainly have multiple levels of nested objects withing one single document and depending on your data model this is probably a good idea, however, you will always need to load the document as a whole if you want to do modify it.