I know that this question is already asked but it didn't help.
I have a "chats" collection with one document "members" and a sub-collection "messages" and I would like to trigger a cloud function when a new message is added in the sub-collection.
This is what I tried but it is only triggered when "members" is updated, and does not have any information on the sub-collection:
exports.chatsCollectionTriggers = functions.firestore.document('/chats/{chatId}/messages/{messageId}').onUpdate(async (change, context) => {
let chatBefore = change.before.data();
let chatAfter = change.after.data();
console.log(JSON.stringify(chatBefore, null, 2));
console.log(JSON.stringify(chatAfter, null, 2));
console.log(context.params.chatId);
console.log(context.params.messageId);});
My firestore collection :
My question is how can I trigger a cloud function on a sub-collection update ?
Your Cloud Function will not be triggered when you modify a document of the chats collection (e.g. if you modify the members field of the G162R... document).
Your Cloud Function will be triggered when you modify (not when you create) a document in the messages subcollection of a document in the chats collection. For example, if you change the value of the text field of the message document vVwQXt.....
So, to answer to your question
My question is how can I trigger a cloud function on a sub-collection
update
If by "sub-collection update" you mean the update of an existing document in a subcollection, your Cloud Function is correct.
If by "sub-collection update" you mean the creation of a new document in a subcollection (which can be one interpretation of "a sub-collection update"), you should change your trigger type from onUpdate() to onCreate().
From the following sentence in your question, i.e. "I would like to trigger a cloud function when a new message is added in the sub-collection", it seems you want to go for the second case, so you should adapt you Cloud Function as follows:
exports.chatsCollectionTriggers = functions.firestore.document('/chats/{chatId}/messages/{messageId}').onCreate(async (snap, context) => {
const newValue = snap.data();
console.log(newValue);
console.log(context.params.chatId);
console.log(context.params.messageId);
return null; // Important, see https://firebase.google.com/docs/functions/terminate-functions
})
More details in the doc.
Related
So I am using this currently to check and see if a doc exists for a user
let user = firebase.auth().currentUser;
let userInfo = db.collection("stripe_customers").doc(user.uid);
and then if it does, it runs a script I want
userInfo.get().then(function (doc) {
if (doc.exists) {
However, instead of checking for a specific doc, I need to try and check for a field value inside some documents.
For example I have a collection called "stripe_customers" > that has a document per user via their UID > then inside the document is the collection "charges" > then inside "charges" is a document under a random string of numbers and letters "89nzVNrfQCOVqogDaGvo" for example that is generated by stripe for their charge after they purchase (there may be multiple of these if they have an older charge which is why I need to find the most recent one) > then inside the most recent charge document, I need to check for the field "status" and that is has the value "succeeded". That way I can check to see who has a succeeded payment and if they do it will run the script I want. I am just so confused on how to achieve this. I know how to do basic queries but this is somewhat complex. I need to be able to make sure the current UID has that field with that value so I can see if the current UID paid or not and if they did the script runs, which sets a custom claim.
Here is a visual of my db storage flow for what im trying to do so its easier to understand https://imgur.com/a/NE1x6sU
So, you want to get the most recent document in a charges (sub)collection based on a created field which contains a timestamp value, and check the value of the status field of this doc.
The following should do the trick:
const user = firebase.auth().currentUser;
const subCollRef = db.collection("stripe_customers").doc(user.uid).collection("charges");
const query = subCollRef.orderBy('created', 'desc').limit(1);
query.get()
.then(snapshot => {
if (snapshot.size > 0 && snapshot.docs[0].data().status === "succeeded") {
//payment was succeeded, you can "run your script"
});
})
.catch(err => {
console.log('Error getting documents', err);
});
I wanted to make the id of each document as a field of that document so that I can store it inside the doc. here is the cloud function I created:
exports.assignPID = functions.database
.ref('/players/{playerId}')
.onCreate((snapshot,context)=>{
const playerId = context.params.playerId;
console.log("new player "+playerId);
// const data = snapshot.val();
return snapshot.ref.update({'pid': playerId})
})
this deploys without any errors but whenever I add a new document to the 'players' collection there is no change in the document whatsoever
In your question, you use the word "document" to describe your data, which is a Firestore concept, and you've tagged this question google-cloud-firestore. However, the code you've written is for Realtime Database, which is a completely different product. So it will never run in response to changes in Firestore.
When you declare a function with functions.database, that's means Realtime Database. Instead, you should declare a Firestore trigger with functions.firestore. Please read the linked documentation for information about how to do that.
I am trying to query my firestore database using cloud functions.
I want to trigger an email notification every time a new reading in my database is under the value of 10.
Here is the relevant database structure for reference: database structure.
The "readings" field is an array and each "reading" is a map which holds the fields "date" and "value".
Currently I am at the point where I can send an email notification every time a new user is created however I want this to work for the database. I am unsure how to query for the "readings" array and then for each individual reading.
Here is my code so far which sends an email when a new user is created
exports.sendNotification = functions.auth.user().onCreate((user) => {
const mailOptions = {
from: '"Spammy Corp." <noreply#firebase.com>',
to:"fakeEmail#btopenworld.com",
text: "TEST"
};
return mailTransport.sendMail(mailOptions)
.then(() => console.log("It worked"))
.catch((error) =>
console.error('There was an error while sending the email:', error));
});
See: https://firebase.google.com/docs/firestore/extend-with-functions
For example, to fire on all new readings added to that first child:
exports.sendEmail = functions.firestore
.document('sensor/UGt.../readings')
.onCreate((snap, context) => {
const newValue = snap.data();
const value = newValue.value;
if (value < 10) {
// send email
}
});
In further comments you mentioned listening for new readings in all sensor elements, not just your first one. This is unfortunately not possible in an efficient / simple way (source). Instead you will have to listen to all onUpdate events on /sensor/, check if the update is adding a reading, then check the value & send your email.
It may be easier to call the cloud function directly from wherever adds the reading, depending on how many times the /sensor/ path is going to be updated for other reasons (since every time this happens, it's a waste of resources).
What I'm trying to do:
I want to add data (id, data) to db. While doing so, I want to check if the id already exists and if so, append to existing data. else add a new (id, data) entry.
Ex: This could be a db of student id and grades in multiple tests. If an id exists in the db already, I want to just append to the existing test scores already in db, else create a new entry: (id, grades)
I have setup an update handler function to do this. I understand, couchdb insert by default does not do the above. Now - how do I check the db for that id and if so, decide to whether to add a new entry or append. I know there is db.get(). However, I presume since the update handler function is already part of the db itelf, there may be a more efficient way of doing it.
I see this sample code in the couchdb wiki:
function(doc, req){
if (!doc){
if ('id' in req && req['id']){
// create new document
return [req, 'New Document'];
}
// change nothing in database
return [null, 'Incorrect data format'];
}
doc[id] = req;
return [doc, 'Edited World!'];
}
a few clarifications in this example that's not clear: where do we get the id from? Often the id is not explicitly passed in while adding to db.
Does that mean, we need to explicitly pass a field called "_id"?
how do I check the db for that id and if so, decide to whether to add a new entry or append.
CouchDB does this for you, assuming HTTP client triggers your update function using the ID. As the documentation describes:
When the request to an update handler includes a document ID in the URL, the server will provide the function with the most recent version of that document
In the sample code you found, the update function looks like function(doc, req) and so in the code inside of that function the variable doc will have the existing document already "inside" your CouchDB database. All the incoming data from the client/user will be found somewhere within req.
So for your case it might look something like:
function(doc, req){
if (doc) {
doc.grades.push(req.form.the_grade);
return [doc, "Added the grade to existing document"];
} else {
var newDoc = {_id:req.uuid, grades:[req.form.the_grade]};
return [newDoc, "Created new document ("+newDoc._id+") for the grade"];
}
}
If the client does a POST to /your_db/_design/your_ddoc/_update/your_update_function/existing_doc_id then the first part of the if (doc) will be triggered, since you will have gotten the existing document (pre-fetched for you) in the doc variable. If the client does a POST to just /your_db/_design/your_ddoc/_update/your_update_function, then there is no doc provided and you must create a new one (else clause).
Note that the client will need to know the ID to update an existing document. Either track it, look it up, or — if you must and understand the drawbacks — make them determinate based on something that is known.
Aside: the db.get() you mentioned is probably from a CouchDB HTTP client library and is not availble (and would not work) in the context of any update/show/list/etc. functions that are running sandboxed in the database "itself".
Cited from the documentation:
If you are updating an existing document, it should already have an _id set, and if you are creating a new document, make sure to set its _id to something, either generated based on the input or the req.uuid provided. The second element is the response that will be sent back to the caller.
So tl;dr, if the _id is not specified, use req.uuid.
Documentation link: http://docs.couchdb.org/en/stable/ddocs/ddocs.html#update-functions
How can I listen to a specific field change with firestore js sdk ?
In the documentation, they only seem to show how to listen for the whole document, if any of the "SF" field changes, it will trigger the callback.
db.collection("cities").doc("SF")
.onSnapshot(function(doc) {
console.log("Current data: ", doc && doc.data());
});
You can't. All operations in Firestore are on an entire document.
This is also true for Cloud Functions Firestore triggers (you can only receive an entire document that's changed in some way).
If you need to narrow the scope of some data to retrieve from a document, place that in a document within a subcollection, and query for that document individually.
As Doug mentioned above, the entire document will be received in your function. However, I have created a filter function, which I named field, just to ignore document changes when those happened in fields that I am not interested in.
You can copy and use the function field linked above in your code. Example:
export const yourCloudFunction = functions.firestore
.document('/your-path')
.onUpdate(
field('foo', 'REMOVED', (change, context) => {
console.log('Will get here only if foo was removed');
}),
);
Important: The field function is not avoiding your function to be executed if changes happened in other fields, it will just ignore when the change is not what you want. If your document is too big, you should probably consider Doug's suggestion.
Listen for the document, then set a conditional on the field you're interesting in:
firebase.firestore().collection('Dictionaries').doc('Spanish').collection('Words').doc(word).collection('Pronunciations').doc('Castilian-female-IBM').onSnapshot(function(snapshot) {
if (snapshot.data().audioFiles) { // eliminates an error message
if (snapshot.data().audioFiles.length === 2) {
audioFilesReady++;
if (audioFilesReady === 3) {
$scope.showNextWord();
}
}
}
}, function(error) {
console.error(error);
});
I'm listening for a document for a voice (Castilian-female-IBM), which contains an array of audio files, in webm and mp3 formats. When both of those audio files have come back asynchronously then snapshot.data().audioFiles.length === 2. This increments a conditional. When two more voices come back (Castilian-male-IBM and Latin_American-female-IBM) then audioFilesReady === 3 and the next function $scope.showNextWord() fires.
Just out of the box what I do is watching before and after with the before and after method
const clientDataBefore = change.before.data();
console.log("Info database before ", clientDataBefore);
const clientDataAfter = change.after.data();
console.log("Info database after ", clientDataAfter );
For example now you should compare the changes for a specific field and do some actions or just return it.
Some more about before.data() and after.data() here