How to create a new field in firestore using cloud functions? - node.js

I am a newbie in cloud functions. I want to create a new field counter whenever a document is created.
I tried the following codes:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap,
context) => {
console.log('onCreate created');
return snap.data.ref.set({counter: 0}, {merge: true})
.then(() => {
console.log("Count is created! "+userId);
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
});
});
and using firebase-admin:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap, context) => {
const id = context.params.userId;
console.log('onCreate created');
return admin.firestore().collection('users')
.doc(id).set({counter: 0}, {merge: true})
.then(() => {
console.log("Document successfully written! "+id);
})
.catch((error) => {
console.error("Error writing document: ", error);
});
});
But both are not triggered when a new document is created.
UPDATE
At first, I create users/userId/collection/doc. When it is created, I want to add a counter field into users/userId

Update following your comment
You can add a field to the users/userId document when you create a doc under the users/userId/theCollection collection by modifying the code of the second solution presented below.
Just trigger at the level of the subcollection, get the parent document id through the context object and build its DocumentReferencebased on this id, as follows:
exports.createCounter = functions.firestore.document('users/{userId}/theCollection/{docId}').onCreate((snap,
context) => {
const id = context.params.userId;
return admin.firestore().collection('users')
.doc(id).set({ counter: 0 }, { merge: true })
.then(() => {
console.log("Count is created! " + userId);
return null;
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
return null;
});
});
Initial answer
The following should do the trick:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap,
context) => {
console.log('onCreate created');
const docRef = snap.ref;
return docRef.set({counter: 0}, {merge: true})
.then(() => {
console.log("Count is created! "+userId);
return null;
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
return null;
});
});
Note that instead of doing snap.data.ref to get the DocumentReference you have to do snap.ref. As a matter of fact, snap is a DocumentSnapshot and you need to use its ref property.
Your second code snippet should normally work, but you need to return a value in the then() and in the catch() to indicate to the Cloud Function platform that the work of the function is complete. Note that you need to do the same thing for the above code. I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/ which explain this key point.
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap, context) => {
const id = context.params.userId;
console.log('onCreate created');
return admin.firestore().collection('users')
.doc(id).set({counter: 0}, {merge: true})
.then(() => {
console.log("Document successfully written! "+id);
return null;
})
.catch((error) => {
console.error("Error writing document: ", error);
return null;
});
});

Related

what is the best way to get data inside a cloud-function in case you don't want to send a response?

I Have the following function and I want to do a conditional inside of the snapshot and then do some actions,
the current issue is I a can see the first console.log in the logs but the the function is not proceeding in to the snapshot for some reason what is the best way to get data inside a cloud-function ? in case you don't want to send them as response ?
export const deleteClient = functions.https.onCall((data, context) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
db.collection('ClientsData')
.doc(data.clientAccountId)
.get()
.then((snapshot) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
admin
.auth()
.deleteUser(data.clientAccountId)
.then(() => {
console.log('Successfully deleted user');
})
.catch((error: any) => {
console.log('Error deleting user:', error);
});
db.collection('ClientsData').doc(data.clientAccountId).delete();
}
})
.catch((err) => {});
});
If you do not want to return anything from the function, you can simply return null. Also you should log any errors in the catch block so you'll know if something is wrong. Try refactoring the code using async-await syntax as shown below:
export const deleteClient = functions.https.onCall(async (data, context) => {
try {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
const snapshot = await db.collection('ClientsData').doc(data.clientAccountId).get()
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
await Promise.all([
admin.auth().deleteUser(data.clientAccountId),
db.collection('ClientsData').doc(data.clientAccountId).delete()
]);
}
} catch (e) {
console.log(e)
}
return null;
});

Cloud Function finished with status: 'timeout' while adding data to Firestore

Here's the code,
exports.onEmailRecieved = functions.database
.ref("/emails/recieved/{id}/")
.onCreate(async (snapshot, context) => {
const email = snapshot.val();
const id = context.params.id;
const trimmedEmailBody = String(email.body).replace("\n", "").trim();
if (trimmedEmailBody === cmd) {
const queueRef = fs.collection("requests").doc("all").collection("queue");
await fs
.runTransaction(async (t) => {
const doc = await t.get(queueRef);
const size = doc.size;
console.log(`Size: ${size}`);
console.log("Adding to queue.");
await queueRef
.add({
email: email.email,
subject: email.subject,
body: email.body,
})
.then(() => {
console.log("Successfully added to queue.");
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log("It's finally over.");
});
return console.log("Worked?");
})
.then(() => {
return console.log("Complete");
})
.catch((err) => {
return console.log(err);
});
return console.log("Worked I guess.");
} else {
return console.log("Not equal.");
}
});
Don't mind the bunch of useless console.logs. Added em to debug the error.
That first console.log gets called and then nothing, no then, catch or finally functions get triggered and I get a function finished with status: 'timeout' message in the logs.
What I'm doing wrong?
The add() method returns a promise which then when you await a promise, the function is paused in a non-blocking way until the promise settles. It will wait for the transaction to be finished before resolving the creation of the document which results in timeout of the cloud function which by default is 1min. By removing the await on the add method you're instead executing the function. See code below:
messageRef
.add({
email: "email",
subject: "subj",
body: "body",
})
.then(() => {
console.log("Successfully added to queue.");
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log("It's finally over.");
});
This will now return something like this:
Size: 1
Adding to queue.
test
Successfully added to queue.
It's finally over.
For more relevant information, you may check this documentations:
Sync, async, and promises
CollectionReference
How to use promises

How would I paginate Firestore data with cloud functions?

I'm trying to paginate data so I can get an infinite scroll for posts on my app. I have a cloud function called getPosts where there are multiple get functions a reads. Everything works fine with that. But when I try thinking of pagination using cloud functions, I run into a problem sending the last snapshot as a query parameter. This snapshot will be super long and an unexpected length, way more than 3000 characters. Here is my getPosts function:
exports.getPosts = (req, res) => {
const postId = req.query.postId;
if(postId != null){
db
.collection('posts')
.doc(postId)
.get()
.then((doc) => {
if(!doc.exists){
throw 'postNotFound';
}
else{
const voteCount = sumValues(doc.data().votingOptions);
let liked;
let votingOptionsDictionary;
return db.collection('votes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((voteDoc) => {
return db.collection('likes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
liked = likeDoc.empty ? false : true;
return res.json([{
postId: doc.id,
userHandle: doc.data().userHandle,
postQuestion: doc.data().postQuestion,
userImageUrl: doc.data().userImageUrl,
imageUrl: doc.data().imageUrl,
postDescription: doc.data().postDescription,
createdAt: doc.data().createdAt
}]);
});
});
}
})
.catch((err) => {
if(err == "postNotFound"){
return res.json({'Error': `Post ID ${postId} does not exists`});
}
else{
console.error(err);
return res.json({error: err});
}
});
}
else{
db
.collection('posts')
.orderBy('createdAt', 'desc')
.limit(10)
.get()
.then(async (data) => {
const promises = await data.docs.map((doc) => {
const voteCount = sumValues(doc.data().votingOptions);
let liked;
let votingOptionsDictionary;
return db.collection('votes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((voteDoc) => {
return db.collection('likes')
.where('postId', '==', doc.id)
.where('userHandle', '==', req.user.userHandle)
.get()
.then((likeDoc) => {
liked = likeDoc.empty ? false : true;
return {
postId: doc.id,
userHandle: doc.data().userHandle,
postQuestion: doc.data().postQuestion,
userImageUrl: doc.data().userImageUrl,
imageUrl: doc.data().imageUrl,
postDescription: doc.data().postDescription,
createdAt: doc.data().createdAt
};
});
});
})
Promise.all(promises)
.then((posts) => {
res.json(posts);
})
})
.catch((err) => {
console.error(err);
res.status(500).json({ error: err.code});
});
}
}
I was thinking of saving the snapshot object on the client side, then sending that as an optional query parameter to the getPosts cloud function to get the data I want, but I'm almost positive I can't send that object as a query paramter...
If you can't use an actual DocumentSnapshot object as the anchor document for your pagination, you can simply use the field values in the document that are relevant for sort ordering. This is described in the documentation. So, if you have a single ordering on createdAt, can can just pass the relevant field value to startAt() or startAfter().
If you don't have a order defined at all, then the sort order is based on the document ID, and you can simply use a where clause to get all documents greater than or less than the ID you specify. For example: where(FieldPath.documentId(), ">", id).

KnexJS Transaction

How can I execute this transaction? My problem is the correct use of for instruction.
app.db.transaction(function (trx) {
app.db('sales_record_products').transacting(trx).insert(products)
.then(_ => {
for (let i = 0; i < stockProducts.ids.length; i++) {
app.db('products').transacting(trx).where('id',stockProducts.ids[i]).update({quantity: stockProducts.results[i]})
.then(_ => {
})
}
})
.then(trx.commit)
.catch(trx.rollback)
})
.then(_ => res.status(200).json({success: true}))
.catch(err => res.status(500).send(err))
Why not using something like
// Start a database transaction
return app.db.transaction(function (trx) {
// Insert products
return trx.insert(products)
.then(function (newIds) {
// Product inserted, you have the new database id in newIds
// Update all product individually but sequentially
return Promise.mapSeries(stockProducts, function (sp) {
// Update a single product
return trx
.table('products')
.where('id', stockProducts.ids[i])
.update({ quantity: stockProducts.results[i] });
});
});
});

Firebase Firestore transaction is returning a querySnapshot instead of a documentSnapshot

i'm replicating this code from the firebase documentation in google cloud functions:
var cityRef = db.collection('cities').doc('SF');
var transaction = db.runTransaction(t => {
return t.get(cityRef)
.then(doc => {
// Add one person to the city population
var newPopulation = doc.data().population + 1;
t.update(cityRef, { population: newPopulation });
});
}).then(result => {
console.log('Transaction success!');
}).catch(err => {
console.log('Transaction failure:', err);
});
But i get: property 'data' does not exist on type 'QuerySnapshot' when it should be a documentSnapshot.
I found the answer after i opened an issue on github and get some insight https://github.com/firebase/firebase-admin-node/issues/344
async function setCounts(storeRef: admin.firestore.DocumentReference) { // Type here
var transaction = admin.firestore().runTransaction(t => {
return t.get(storeRef)
.then((doc: admin.firestore.DocumentSnapshot) => { // Type here
x = doc.data(); // Now i can get data() from the doc
});
}).then(result => {
console.log('Transaction success!');
}).catch(error => {
console.log('Transaction failure:', error);
});
}
I finally made it work by explicitly declaring the types for DocumentReference and DocumentSnapshot, i don't know why, but while deploying the linter inferred doc as QuerySnapshot even though it was not.

Resources