Firestore manual entry for indices? - javascript

I have the following query:
get invitations() {
return this.firestore.colWithIds$(`users/${this.authProvider.currentUserId}/meetings`, (ref) => {
return ref
.where(`participants.${this.authProvider.currentUserId}.invitation`, '==', 'pending')
.orderBy('createdAt', 'desc')
});
}
Everytime Firebase will generate an error that says: This query required an index and generates a link with the currentUserId in it. I add it to the console and everything works as expected..
However, this seems a bit too manual to maintain when users are registering. How can I generate a index that is dynamic and does not require manual entry every time a new user downloads my app?

The query you want to do, in the general case, is not possible in Cloud Firestore with your data structure.
You will quickly run into the limit of 200 composite indexes per database, probably around your 200th user:
https://firebase.google.com/docs/firestore/quotas#indexes

Related

How to get a subset of object's properties when querying an entire collection in Firebase Firestore? Using JS client sdk v9

I have this Firestore onSnapshot listener on a collection (getting the entire collection). I would like to get only a subset of the the properties of each object.
Something like we do with the firebase-admin using select() on a query:
Ex: admin.firestore().collection('NAME').where(conditions).select('field1', 'field2').get()
This is the onSnapshot code: it works just fine but it's getting the full objects (containing all the properties).
const db = getFirestore();
const col = 'COL_NAME';
const q = query(collection(db, col));
onSnapshot(q, (querySnapshot) => { // How to get only a subset of fields here ?
const results = {};
querySnapshot.forEach((doc) => {
// Do something with each object
});
});
Of course I can map it on the client, but my goal is to keep data network traffic to a minimum.
How can I do it?
With the Client SDKs this is not possible.
As you have mentioned this is possible with the Admin SDK but it is also possible with the Firestore REST API: You can use a DocumentMask when fetching documents, which will "restrict a get operation on a document to a subset of its fields. ".
Note however that fetching via the REST API from a web app is much less simple than using the JS SDK. In particular the format of the response is a complex object that is not funny to parse...
Another approach would be to dernomalize your data: You create another collection which contains documents that only have the fields you want to display in the front end.
The complexity is that you need to keep the two collections in sync: when you create/modify/delete a doc in the master collection, you need to update the other collection. This can be done from your front-end (e.g. you write to the two collections in a batched write) or with a Cloud Function, triggered in the back-end with an onWrite trigger.

cannot get fields from SnapShot in firebase

I'm trying to get the real-time document fields (text and timeStamp) to be displayed from the "first" collection in firestore collection, with the use of onSnapshot. I can verify that the snapshot realtime updation is working, on addition of a new document, it does shows an update. But I cannot access the text and timeStamp in the document.
onSnapshot(collection(db, 'first'), (snapshot) => {
console.log(snapshot.text, snapshot.timeStamp);
});
It just shows undefined to me. Also, I just want to access this database, db only when the user is authenticated. So is there a way to check if the user is authenticated?
To get the data from a document snapshot, you need to call data() on it. In addition, since you're reading an entire collection, you get back a query snapshot that can contain multiple documents, which you also need to handle.
So:
onSnapshot(collection(db, 'first'), (querySnapshot) => {
querySnapshot.docs.forEach((docSnapshot) => {
console.log(docSnapshot.data().text, docSnapshot.data().timeStamp);
})
});
See the documentation on getting data from Firestore and on reading all documents from a collection for more examples like this.

Firebase firestore: how to query relevant documents and then update each of them

I am developing a web app on Firebase/firestore, in which users can sign in and write their own posts. The data are stored in the following way:
-User information are stored as under collection('user').doc('uid').
-Information about posts the user has written are stored in collection('post').doc('postid'), and the doc has 'userinfo' and 'uid' fields. The 'userinfo' field contains exact copy of what is stored in 'uid' doc, just in object format.
Here are the operations that I want to do:
When the user changes the data, the changes are reflected in the document.
Look for the all the posts that the user has written based on 'uid' data, and then update userinfo in those data.
The last part is tricky for me. The Firebase documentations cover situations where the references are pretty much static, i.e. you know the exact path to write/update. What I am trying to do is look for a set of documents that is not necessarily static, and then update each of them.
Here is the code I wrote for this effort. The first part works without any problem. Of course, the second part doesn't work. :) What would be the code to do the do the second part?
const update = () => {
//This part is for updating user information. This works without any problem.
firebase.firestore().collection('user').doc(user.uid).update({
username: username1,
nickname: nickname1,
intro: intro1
})
.then(()=>{
//This part is for updating all of the document that the user has written based on 'uid' value. This doesn't work.
//Below code is probably way off, but it shows where I am going and what I am trying to do.
firebase.firestore().collection('post').where('uid','==',user.uid).get()
.then((querysnapshot)=>{
querysnapshot.forEach((doc)=>{
let ref=firebase.firestore().collection('post').doc(doc.id);
ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
})
})
})
}).then(()=>{
alert("Successfully updated!");
window.location.href='/'+username1;
}).catch((error)=>{
alert("Error!");
})
}
Thanks a lot in advance!
What's the error that you get running this code? It seems on the right track for me.
But despite that, here are some suggestions to deal with this kind of update:
Don't do the second part on the client side, do it on the server side with a Firestore Trigger (create a onUpdate trigger in the user collection in your case): https://firebase.google.com/docs/functions/firestore-events.
The problem of doing in the client side, is because if the user closes the page/browser or the site goes offline in the middle of the update, you will have inconsistent data.
You don't need to recreate the DocumentReference after getting the query result, the docs returned already have a .ref that you can call .ref.update() directly.
EDIT: If you want to keep your original code (updating on client side), the problem of the navigation occurring before all the updates to conclude is because ref.update() returns a promise.
So the update queue is asynchronous being performed on database when the client navigates away.
To solve this, I would use a Promise.all() to wait all updates being completed.
firebase.firestore().collection('post').where('uid','==',user.uid).get()
.then((querysnapshot)=>{
const promises = [];
querysnapshot.forEach((doc)=>{
promises.push(doc.ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
});
});
Promise.all(promises).then(()=>{window.location.href='/'+username1;});
});
Or using the await syntax (I think it's easier to maintain and understand):
const querysnapshot = await firebase.firestore().collection('post').where('uid','==',user.uid).get();
const promises = [];
querysnapshot.forEach((doc)=>{
promises.push(doc.ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
});
});
await Promise.all(promises);
window.location.href='/'+username1;

Firebase query won't return all documents

I'm trying to return all document IDs in a specific collection. I'm writing this in Javascript for a web application.
I made this function, and call it where it is needed.
function getUserList() {
var rootRef = firebase.database().ref();
var db = firebase.firestore();
//var docRef = db.collection("Users");//.doc(getUserID()).collection("userControl").doc("UserStatus") //EXAMPLE: /Users/UUID/userControl/UserStatus
db.collection('Users')
.get().then(function(querySnapshot) {
size = querySnapshot.size // will return the collection size
console.log(size);
querySnapshot.forEach(function(doc) {
console.log(doc.id);
});
});
}
However when I check the console log for the data, I only ever see 6 out of 8 documents. There are 8 documents total, but the count and log of doc.id only shows 6. can be seen in screenshot (blanked one ID out for reasons).
If I specify one of the missing documents .get.collection('Users').doc('UUID HERE')... it reads that document without issue.
Most of these documents are uploaded from an IOS app, with the exception of mine (blurred out), the UID template. If I manually edit the document, ie add an extra field, etc the document appears in the search, but for documents straight from the IOS app, they aren't appearing unless speficifed.
I'm not sure how to go about fixing this, or why this issue is occuring.
The documents you see listed in italics ("AB47..." and "DCBC...") are not actually documents present in the collection. The italics means that there are subcollections with other documents organized under that document ID. No query will ever match those documents, since they don't exist. You might have deleted them without deleting all the documents in all of their subcollections. But they remain visible in the console because you might want to navigate into their subcollections.

How can we search through 5,000 users in Firestore db?

We have a staffing application, built using vuejs and a firestore database with over 5,000 users. Our challenge is that we need a layout for admins to search for users in the db.
Previously, we were loading all users on our Users.vue layout and then searching/viewing them in a Vuetify data table. The problem now is that we just have too many users. That layout loads way too slowly and will even cause the app to crash on mobile browsers.
The solution we are trying to make work is to search for users in the db, and only load those results into our data table. The code below (using vuex) works, as long as the "name" is EXACT.
getUsersState({commit}, payload){
fb.usersCollection.where("name", "==", payload.search).limit(10).onSnapshot(querySnapshot => {
let usersArray = []
console.log(payload.search)
querySnapshot.forEach(doc => {
let user = doc.data()
user.id = doc.id
usersArray.push(user)
})
commit('setUsers', usersArray)
})
},
The problem is that we need it to work even if we only type in the first few letters of a name or even an email address. Firestore only offers ==, >=, <=, >, and < parameters. And "array-contains" only works with an array, not our "user" object.
On Users.vue:
created () {
console.log('getting users')
this.$store.dispatch("getUsersState", {search: this.search})
},
computed: {
...mapState(['currentUser', 'users', 'userProfile']),
isAdmin: function() {
return this.userProfile.accessLevel >= 5
},
isUsers: function() {
return this.users
}
},
watch: {
search: 'updateSearch'
},
methods: {
clearSearch () {
return this.isSearch = ''
},
updateSearch() {
this.$store.dispatch("getUsersState", {search: this.search})
},
},
Does anyone have any ideas for how we can search the users in our firestore DB by only typing in the first few letters of their name?
Integrate a full text search engine, and keep it in sync with Firestore. Nontrivial to implement. Official docs recommend Algolia: https://firebase.google.com/docs/firestore/solutions/search
The right answer is full text search, but is a big hammer for this use case. Here are some other options that can keep you going for a while:
1) First, note that Firestore has an index sitting there that looks like
Collection\User\a -> somedoc
Collection\User\aaa -> somedoc
Collection\User\aba -> somedoc
Collection\User\abc -> somedoc
Collection\User\bbc -> somedoc
If you have a username prefix like a there is nothing to say you can't run a query for user >='a' and user <= 'b' and have if fetch (in this example) {a,aaa,aba}
Similarly >= 'ab' && <= 'b' gets you {ab, abc}
So you go from fetching all 5000 users to just the users with the prefix -- which is alot smaller.
2) Stuff the things you want to autocomplete into a few documents and load them.
Imagine you have 5000 users, and you store their names into 10 documents with 500 usernames each -- you keep those documents up to date as users add or remove. To get the entire autocomplete list you fetch those 10 documents into the browser and feed the 5000 users to some sort of autocomplete widget. You could do the same thing for emails.
The browser can now do fancy instant autocomplete. This is faster/cheaper than fetching the entire collection of 5000 users -- you only ask for the data you need.

Categories