firestore cloud functions not triggering - node.js

I am new to firebase cloud functions. So, I followed the tutorial in firebase youtube channel.
So, I created a function that should trigger when firestore document added,but it didn't trigger.
this is the function
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.whenAddData = functions.firestore
.document("chats/{userId}")
.onCreate((change, context) => {
const msg = change.data().msg;
const modifiedMsg = addImoji(msg);
console.log("data changing");
return change.ref({ msg: modifiedMsg });
});
const addImoji = (msg) => {
return msg.replace(/\bPizza\b/g, " 🍕 ");
};
firebase-functions version: "^3.6.1"
firestore db screenshot

First of all, your regex is wrong. It's case sensitive, which means the message from your screenshot "i love pizza" doesn't match "Pizza". To make it case insensitive (and thus match "Pizza", "pizza", "piZZA" ...) add the i flag at the end:
const addImoji = (msg) => {
return msg.replace(/\bpizza\b/gi, "🍕");
};
Secondly, your function returns a database reference. It doesn't actually store the new data by calling set() or update(). Your code should be:
return change.ref.update({ msg: modifiedMsg });
Any async Firebase function should return a promise (and update() returns a promise). If it doesn't, the function won't terminate correctly.
By returning the result of update, your function will keep running until the asynchronous work of writing the new msg to the database is completed.

Related

How to use firebase cloud functions' firestore.onWrite() in netlify lamda

I want to aggregate firestore data but I want to go with netlify lambda for the serverless functions. I want to do something like
onst functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.aggregateComments = functions.firestore
.document('posts/{postId}/comments/{commentId}')
.onWrite(event => {
const commentId = event.params.commentId;
const postId = event.params.postId;
// ref to the parent document
const docRef = admin.firestore().collection('posts').doc(postId)
// get all comments and aggregate
return docRef.collection('comments').orderBy('createdAt', 'desc')
.get()
.then(querySnapshot => {
// get the total comment count
const commentCount = querySnapshot.size
const recentComments = []
// add data from the 5 most recent comments to the array
querySnapshot.forEach(doc => {
recentComments.push( doc.data() )
});
recentComments.splice(5)
// record last comment timestamp
const lastActivity = recentComments[0].createdAt
// data to update on the document
const data = { commentCount, recentComments, lastActivity }
// run update
return docRef.update(data)
})
.catch(err => console.log(err) )
});
but I can't get it to work on netlify lambda. Is there anyway I can use this function in netlify lambda?
You cannot deploy Firebase Cloud Functions to any other provider. Other environments might be totally different and may not have all the credentials/env variables required.
If you want to listen for realtime updates outside of GCP, you can try using Firestore's onSnapshot() but you'll need a server that runs always. The listener will stop once you serverless functions terminates.

Use query data from Firebase Realtime DB as input for another function

So I'm using Firebase Realtime Database to store some data and would like to use the results of a query, as an input for another function (generate signed URLs). My code is as follows:
// initialize the empty list
let lista = []
// define the async function that does everything
async function queryandgeturls() {
// make the query to the firebase realtimeDB
await ref.orderByChild('timestamp_start').startAt(timestamp1).endAt(timestamp2).on('child_added', (snapshot) => {
lista.push(snapshot.val().name);
});
// use the list as input for another function to get Signed URLs
for (const fileName of lista) {
const [signedUrl] = await storage.bucket(bucketName).file(fileName).getSignedUrl({
version: 'v4',
expires: Date.now() + 15 * 60 * 1000,
action: 'read'
});
console.log(`The signed URL for ${fileName} is ${signedUrl}`);
}
};
// call the function
queryandgeturls().catch(console.error);
No luck so far. Any leads?
The on method keeps an open listener to the event that can be called repeatedly, so it doesn't return a promise (since a promise can only resolve once). So the await ref.orderByChild....on('child_added'... in your code doesn't do anything, which probably explains the problem.
To properly solve this problem, use once('value', ... and don't combine await and callbacks.
async function queryandgeturls() {
// make the query to the firebase realtimeDB
const results = await ref.orderByChild('timestamp_start').startAt(timestamp1).endAt(timestamp2).once('value');
results.forEach((snapshot) => {
lista.push(snapshot.val().name);
});
...

How to increment a field value in Firestore using Cloud Functions using Javascript?

I have a Cloud function which I am trying to get to increment a field in Firestore:
exports.incrementUpvotes = functions.https.onCall((data, context) => {
async function incrementValue() {
const Id = data.thisId;
const increment = firebase.firestore.FieldValue.increment(1);
const docRef = admin.firestore().collection('posts').doc(Id)
docRef.update({ upvoteCount: increment })
};
return incrementValue()
})
I have tried lots of variations of the above. Each time it fails with this error: Uncaught (in promise) Error: INTERNAL
I'm calling it just using this:
const incrementUpvotes = getFunctions.httpsCallable('incrementUpvotes')
EDIT: getFunctions is an export from my Firebase.js ie. firebase.functions()
and then this in a function:
incrementUpvotes({thisId})
Can anyone see what I'm doing wrong?
You're neither returning anything nor using await in your incrementValue function, which means that it ends up returning undefined.
Since docRef.update is an asynchronous call, you'll want to either await that or return the promise it returns.
So:
function incrementValue() { // 👈 Removed async
const Id = data.thisId;
const increment = admin.firestore.FieldValue.increment(1); // 👈 Use admin
const docRef = admin.firestore().collection('posts').doc(Id)
return docRef.update({ upvoteCount: increment }) // 👈 Added return
};

Retrieve JSON from URL and convert it to Cloud Firestore Collection with Cloud Functions

Here is what I want to achieve : I want to get a JSON on a daily basis from a URL and convert it to a cloud firestore collection in order to be able to use it in my Flutter app. Ideally, the script would only add new data to the collection.
I saw that I can use scheduler from Firebase cloud functions to run tasks daily. That's not the problem for now.
However, I don't know how to use Firebase cloud functions properly to get data from URL and convert it to collection. Maybe that's not the point of cloud functions and I misunderstood something. So first question : Can I run classic nodeJS stuff inside cloud functions? I suppose I can
Next, I initialized a cloud function project locally, connected it to my Google account and started to write code into index.js.
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const fetch = require('node-fetch');
const db = admin.firestore();
const collectionToiletRef = db.collection('mycollection');
let settings = { method: "Get" };
let url = "my-url.com"
fetch(url, settings)
.then(res => res.json())
.then((json) => {
print(json);
// TODO for each json object, add new document
});
Second question : How can I run this code to see if it works ? I saw that emulator can be used but how can I check visually my cloud firestore collection ? On this simple example, I only want to print my json to see if I can get the data correctly. Where would the printing be done ?
Maybe cloud functions is not what I need for this task. Maybe my code is bad. I don't know. Thanks for your help.
EDIT
I tried this but the call never ends. I think it's waiting for a promise that never returns or something like that.
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const fetch = require('node-fetch');
admin.initializeApp();
const db = admin.firestore();
exports.tempoCF = functions
.firestore.document('/tempo/{docId}')
.onCreate(async (snap, context) => {
console.log("onCreate");
let settings = { method: "Get" };
let url = "https://opendata.paris.fr/api/records/1.0/search/?dataset=sanisettesparis&q=&rows=-1"
try {
let response = await fetch(url, settings);
let json = await response.json();
// TODO for each json object, add new document
await Promise.all(json["records"].map(toiletJsonObject => {
return db.collection('toilets').doc(toiletJsonObject["recordid"]).set({}); // Only to create documents, I will deal with the content later
}));
}
catch(error) {
console.log(error);
return null;
}
}
);
This code works and create all the documents I want but never return. However, the async (snap, context) => {} passed to onCreate is a Promise. And this promise ends when Promise.all ends. I'm missing something but I don't know why. I'm struggling a lot with async programming with Dart or JS. Not very clear in my mind.
Can I run classic nodeJS stuff inside cloud functions?
Sure! Since the fetch method returns a Promise you can very well use it in a background triggered or a scheduled Cloud Function.
How can I run this code to see if it works?
Your code will work perfectly in the emulator suite, but you will need to trigger the Cloud Function with one of the Firebase services that can run in the emulator. For example you can trigger the Cloud Function by creating a document in the Firestore emulator console.
The following Cloud Function will do the trick: just create a doc in a dummy tempo collection and the CF will add a new doc in a newDocscollection. It's up to you to adapt the fields values for this doc, I've just used the entire JSON object.
exports.tempoCF = functions
.firestore.document('/tempo/{docId}')
.onCreate((snap, context) => {
let settings = { method: "Get" };
let url = "https://..."
return fetch(url, settings)
.then(res => res.json())
.then((json) => {
console.log(json);
// TODO for each json object, add new document
return admin.firestore().collection('newDocs').add(json);
})
.catch(error => {
console.log(error);
return null;
});
});
You could also deploy your Cloud Function to the Firebase backend, and if you want to schedule it, just change the code as follows (change the trigger):
exports.scheduledFunction = functions.pubsub.schedule('every 5 minutes').onRun((context) => {
let settings = { method: "Get" };
let url = "https://..."
return fetch(url, settings)
.then(res => res.json())
.then((json) => {
console.log(json);
// TODO for each json object, add new document
return admin.firestore().collection('newDocs').add(json);
})
.catch(error => {
console.log(error);
return null;
});
});
Edit following your edit:
The following code does work correctly in the emulator, creating docs in the toilets collection.
exports.tempoCF = functions.firestore
.document('/tempo/{docId}')
.onCreate(async (snap, context) => {
console.log('onCreate');
let settings = { method: 'Get' };
let url =
'https://opendata.paris.fr/api/records/1.0/search/?dataset=sanisettesparis&q=&rows=-1';
try {
let response = await fetch(url, settings);
let json = await response.json();
return Promise.all( // Here we return the promise returned by Promise.all(), so the life cycle of the CF is correctly managed
json['records'].map((toiletJsonObject) => {
admin
.firestore()
.collection('toilets')
.doc(toiletJsonObject['recordid'])
.set({ adresse: toiletJsonObject.fields.adresse });
})
);
} catch (error) {
console.log(error);
return null;
}
});

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