loop through documents in a collection to get fields - node.js

I have a collection called users in firebase firestore. Each document in the collection users is a user registered in the app. Each document has a field called token_ids. How can I loop through all the documents to get the values in the token_ids field. I am using firebase cloud functions to do so. Here is the code snippet I tried using but it did not work:
const functions = require('firebase-functions');
const admin = require ('firebase-admin');
admin.initializeApp();
//fetch all token ids of users
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
tokenSnapshot.forEach((doc) =>{
console.log("Token ids are:" + doc.data().token_id);
});
});

Took me a while but finally found the solution to it. Here it is. It is the first solution given by Dhruv Shah but slightly modified :
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const results = [];
tokenSnapshot.forEach(doc => {
results.push(doc.data().token_id);
});
const tokenIds = await Promise.all(results);
return console.log("Here =>" +tokenIds);
}

Since Firestore operations are asynchronous, you should ideally wrap your code in an async-await block.
For example:
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const results = [];
// Recommendation: use for-of loops, if you intend to execute asynchronous operations in a loop.
for(const doc of tokenSnapShot) {
results.push(doc.data().token_id);
}
const tokenIds = await Promise.all(results);
}
In this way all the tokenIds variable will be populated with an array of tokenIds.
Alternatively, you can also make all the asynchronous calls in parallel since they are independent of each other using Promise.all (Reference)
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const tokenIds = await Promise.all(tokenSnapShot.map(doc => {
return doc.data()
.then(data => (data.token_id))
}))
In this case, the tokenIds variable will contain the array of all the tokenIds.

How the code snippet will be structured depends whether you're using the Firebase Admin SDK, be it as a script ran on your local machine or a httpsCallable being called by a client app. For the first case, it is written like this:
In your script file.js, after initialising app, write the following code.
exports.test_f = async function() {
try {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
tokenSnapshot.forEach((doc) =>{
console.log("Token ids are:" + doc.data().token_id);
});
} catch (error) {
console.log(error);
}
}
exports.test_f();
Run this script on your command line using the command node file.js, which will print the provided output

Related

add a subcollection under each document that resulted from geofirestore query

I queried 'users/userid/pros' firestore collection using cloud functions geofirestore, and I get a few specific documents('users/userid/pros/proid') from the query. Now, I want to add a new 'notifs' collection subsequently under each of these specific documents I get from the query. Here is my code to implement that functionality.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
var GeoFirestore = require('geofirestore').GeoFirestore;
admin.initializeApp();
const firestore = admin.firestore();
const geofirestore = new GeoFirestore(firestore);
exports.sendNotification = functions.firestore
.document("users/{userId}/clients/{client}")
.onCreate(async snapshot => {
const clientfield = snapshot.data().field;
const clientgeopoint = snapshot.data().g.geopoint;
const geocollection = geofirestore.collectionGroup('pros');
const query = geocollection.near({center: clientgeopoint, radius:
10}).where('field', '==', clientfield);
await query.get().then( querySnapshot => {
querySnapshot.forEach(async doc => {
await doc.ref.collection('notifs').add({
'field': clientfield, ... });
});
}).catch ((error) =>
console.log(error)
);
})
But this code gives me an error 'TypeError: Cannot read property 'collection' of undefined' on cloud functions console. How can I fix my code in order to add 'notifs' collection right under each of these specific documents like in the picture? Thanks.
I think you're trying to fetch the querysnapshot data wrong. From Muthu's reply it should look like this
let querySnapshot = await admin.firestore().collection('users').doc(userid).collection('pros').get();
querySnapshot.then(querySnapshot => {
querySnapshot.forEach(doc => {
// frame your data here
await doc.ref.collection('notifs').add({ ... });
});
});
If you are going to get the data from your snapshot you need to add the .then() statement after .get to properly reference the query.
DocumentSnapshot carries their reference in a field; For Node.JS - ref and for Dart - reference. You shall use this to perform any action within that document. Assuming the code was written in NodeJS, for creating sub-collection,
query.get().then(querySnapshot => {
querySnapshot.forEach(doc => {
// frame your data here
await doc.ref.collection('notifs').add({ ... });
})
});

Synchronously iterate through firestore collection

I have a firebase callable function that does some batch processing on documents in a collection.
The steps are
Copy document to a separate collection, archive it
Run http request to third party service based on data in document
If 2 was successful, delete document
I'm having trouble with forcing the code to run synchronously. I can't figure out the correct await syntax.
async function archiveOrders (myCollection: string) {
//get documents in array for iterating
const currentOrders = [];
console.log('getting current orders');
await db.collection(myCollection).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
currentOrders.push(doc.data());
});
});
console.log(currentOrders);
//copy Orders
currentOrders.forEach (async (doc) => {
if (something about doc data is true ) {
let id = "";
id = doc.id.toString();
await db.collection(myCollection).doc(id).set(doc);
console.log('this was copied: ' + id, doc);
}
});
}
To solve the problem I made a separate function call which returns a promise that I can await for.
I also leveraged the QuerySnapshot which returns an array of all the documents in this QuerySnapshot. See here for usage.
// from inside cloud function
// using firebase node.js admin sdk
const current_orders = await db.collection("currentOrders").get();
for (let index = 0; index < current_orders.docs.length; index++) {
const order = current_orders.docs[index];
await archive(order);
}
async function archive(doc) {
let docData = await doc.data();
if (conditional logic....) {
try {
// await make third party api request
await db.collection("currentOrders").doc(id).delete();
}
catch (err) {
console.log(err)
}
} //end if
} //end archive
Now i'm not familiar with firebase so you will have to tell me if there is something wrong with how i access the data.
You can use await Promise.all() to wait for all promises to resolve before you continue the execution of the function, Promise.all() will fire all requests simultaneously and will not wait for one to finish before firing the next one.
Also although the syntax of async/await looks synchronous, things still happen asynchronously
async function archiveOrders(myCollection: string) {
console.log('getting current orders')
const querySnapshot = await db.collection(myCollection).get()
const currentOrders = querySnapshot.docs.map(doc => doc.data())
console.log(currentOrders)
await Promise.all(currentOrders.map((doc) => {
if (something something) {
return db.collection(myCollection).doc(doc.id.toString()).set(doc)
}
}))
console.log('copied orders')
}

How to push an object into an array in async function

i have been trying to insert an object into an array in async function ,but it
return an empty array as output in nodejs ,mongoose
var data = [];
app.get("/api/post", async (req, res) => {
const post = await UserPost.find();
post.forEach(async element => {
const email = await element.userid;
const user = await Account.find({ email });
const usern = await user[0].username;
var userobject = {
element,
usern
};
//Promise.all(userobject)
data.push(userobject);
});
console.log(data);
res.send({ data });
});
It seems you are struggling with promises. In order to achieve this specific scenario, you can use Promise.all and Array.map.
Here is a code I edited for you:
(*please note that this is just a dummy code for the sake of explanation)
app.get("/api/post", async (req, res) => {
try {
const posts = await dummyPromiseResolver(); // first promise
const promises = posts.map(async element => {
const user = await dummyEmailReturn(element.userid); // second promise
const usern = user[0].username;
return {
usern,
...element
};
});
const fresult = await Promise.all(promises);
res.send(fresult);
} catch (error) {
console.error("error in posts fetch:" + error);
}
});
If I describe this code, posts.map is creating an Array of promises since we need to iterate through every object in the array and needs to add values from separate promises.
Then Promise.all can execute your promise array and return final results array with your desired results.
Note: You can also use for … of as well but when we need to happen things parallelly we use Promise.all. You can find more information from this thread.
here is a link for code sandbox: https://codesandbox.io/embed/serverless-cookies-nu4h0
Please note that I have added dummyPromiseResolver and dummyEmailReturn which would be equal to UserPost.find() and Account.find() functions respectively. In addition to that, I removed a few unnecessary awaits in your code. I added a try catch block to catch any exceptions. You can change that try catch as you please.
hope this will help you. let me know if you need more clarifications.

Async/Await and Then not working in my case

I have a function which contains a thousand of objects in an array:
function Alltransaction(transactionArray) {
transactionArray.map(async (transaction) => {
dataAgainsthash = await web3.eth.getTransaction(transaction)
TransactionObject = {
transactionHash : transaction,
from : dataAgainsthash.from
};
transactionArray.push(TransactionObject)
console.log("transaction array", transactionArray)
});
}
then i have another function which stores these thousands of object array into db
function saveTransactionToDb() {
console.log("after loop",transactionArray)
transactionss = new Transaction({
blockNumber : blockNumbers ,
transactions : transactionArray
})
// Now save the transaction to database
await transactionss.save();
// console.log("save to database")
}
then I call this in my router
await Alltransaction(transactionArray);
await saveTransactionToDb();
and I also try
Alltransaction(transactionArray).then(saveTransactionToDb())
But it always runs saveTransactionToDb() before the array of object populates the Alltransaction() method
have you try the async keyword before saveTransactionToDb and Alltransaction functions??
async function Alltransaction(transactionArray){
// your code
}
async function saveTransactionToDb(){
// your code logic*
await transactionss.save();
}
First, in Alltransaction the promise must be returned as well. In your code the function starts some processes but doesn't not await on it. Also, do not push the promises to the original array. I'm not sure what you were trying to accomplish there. Because mapping over the array gives you an array of promises, you can unify all of them with Promise.all().
function Alltransaction(transactionArray) {
const promises = transactionArray.map(async (transaction) => {
dataAgainsthash = await web3.eth.getTransaction(transaction)
const TransactionObject = {
transactionHash : transaction,
from : dataAgainsthash.from
};
return TransactionObject;
});
return Promise.all(promises);
}
Change saveTransactionToDb to receive an array instead of using the original array.
Then you'll be able to call it as:
const t = await Alltransaction(transactionArray);
await saveTransactionToDb(t);
Your second try it's not correct:
Alltransaction(transactionArray).then(saveTransactionToDb())
It's the same as:
const t = Alltransaction(transactionArray);
const s = saveTransactionToDb();
t.then(s)
That's why saveTransactionToDb doesn't way for transactions to complete. To use then, just pass the function without calling it:
Alltransaction(transactionArray).then(saveTransactionToDb)

How to delete data from Firestore with cloud functions

I'm writing a cloud functions in conjunction with google's Firestore database.
I'm trying to write recursive delete more data. I can't find the syntax for accessing and deleting data in other parts of the database.
The code I have already is below.
exports.deleteProject = functions.firestore.document('{userID}/projects/easy/{projectID}').onDelete(event => {
// Get an object representing the document prior to deletion
// e.g. {'name': 'Marie', 'age': 66}
// console.log(event)
// console.log(event.data)
console.log(event.data.previous.data())
var deletedValue = event.data.previous.data();
});
I found some info here but I don't have time to check through it atm, if I find something useful I'll amend the question.
https://firebase.google.com/docs/firestore/manage-data/delete-data?authuser=0
One can use below code to delete all the documents in a collection recursively.
This code worked perfectly for me.
Make sure you have JSON file of firebase credentials and firebase-admin installed.
const admin = require('firebase-admin');
const db = admin.firestore();
const serviceAccount = require('./PATH_TO_FIREBASE_CREDENTIALS.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
deleteCollection(db, COLLECTION_NAME, NUMBER_OF_RECORDS)
async function deleteCollection(db, collectionPath, batchSize) {
const collectionRef = db.collection(collectionPath);
const query = collectionRef.orderBy('__name__').limit(batchSize);
return new Promise((resolve, reject) => {
deleteQueryBatch(db, query, resolve).catch(reject);
});
}
async function deleteQueryBatch(db, query, resolve) {
const snapshot = await query.get();
const batchSize = snapshot.size;
if (batchSize === 0) {
// When there are no documents left, we are done
resolve();
return;
}
// Delete documents in a batch
const batch = db.batch();
snapshot.docs.forEach((doc) => {
batch.delete(doc.ref);
});
await batch.commit();
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(() => {
deleteQueryBatch(db, query, resolve);
});
}
The answer is that you must write a cloud function that deletes the data on its own and is trigger by the client. There isn't an efficient way to do it with client side. The method I use is I listen in the cloud function for the first delete and then fire the recursive.
Code to delete in node js:
db.collection("cities").document("DC").delete(

Resources