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

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.

Related

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;
}
});

Firestore functions listCollections() only returning empty array

Here is my database...
I have the following code in firebase functions...
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.getCollections = functions.https.onCall(async (data, context) => {
const path = admin.firestore().collection('BusinessName').doc('employee');
const collections = await path.listCollections();
collections.forEach(collection => {
console.log('Found subcollection with id:', collection.id);
});
return({ collections: collections })
});
And the front end code...
let buttonClick = () => {
let getCollections = firebase.functions().httpsCallable('getCollections');
getCollections().then((res) => {
console.log(res);
})
}
There is two subcollections in this path. This function should return an array with the two test collections seen in the above image. However, it only returns an empty array.
and in the functions log...
I've tired different paths with different database structures, but the return is always an empty array. There must be something wrong with the node.js function, but it's right from firebase's docs. What do you think...?
If this issue is only occurring while using Emulators, my first suggestion would be to check your Emulator Firestore Database. Is it empty? Did you create the necessary dummy data?
I'm saying this because your database screenshot is a LIVE database, and the Emulator doesn't touch that, it queries the Emulator db, typically located in: http://localhost:4000/firestore

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({ ... });
})
});

nodejs Firestore suddenly breaks: Date objects

I have a simple node script I run to update data in a Firestore database. I used it a couple of hours ago, worked fine. Got dinner, came back, and now I get this error when I run it:
node ./json-to-firestore.js
The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK. To hide this warning and ensure your app does
not break, you need to add the following code to your app before
calling any other Cloud Firestore methods:
const firestore = new Firestore(); const settings = {/* your
settings... */ timestampsInSnapshots: true};
firestore.settings(settings);
The example provided in the error does not apply to my case. I've looked for help on this issue, but all the posts seem to be angular-specific. This is all I'm trying to do:
var admin = require("firebase-admin");
var serviceAccount = require("./service-key.json");
const data = require("./occ-firestore.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "xxxxxxxxxxx"
});
data && Object.keys(data).forEach(key => {
const nestedContent = data[key];
if (typeof nestedContent === "object") {
Object.keys(nestedContent).forEach(docTitle => {
admin.firestore()
.collection(key)
.doc(docTitle)
.set(nestedContent[docTitle])
.then((res) => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
});
}
});
I execute this script by running:
node ./json-to-firestore.js
I'm running NodeJS 8.11.3.
I checked Google's docs and there's no reference to this new behavior.
Can someone provide me with a suggestion? Thanks in advance!
This can be fixed as such:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
I solve this by downgrade version firebase to this
{
firebase-admin: "5.12.0",
firebase-functions: "1.0.1"
}
Firebase is changing how Date objects are stored in Firestore. Bellow is an actual warning from them (at the date of posting this answer) whenever you try to save Date objects to Firestore:
The behavior for Date objects stored in Firestore is going to change
AND YOUR APP MAY BREAK.
To hide this warning and ensure your app does not break, you need to add the
following code to your app before calling any other Cloud Firestore methods:
const firestore = new Firestore();
const settings = {/* your settings... */ timestampsInSnapshots: true};
firestore.settings(settings);
And more importantly, how to handle these changes so your code does not break:
With this change, timestamps stored in Cloud Firestore will be read back as
Firebase Timestamp objects instead of as system Date objects. So you will also
need to update code expecting a Date to instead expect a Timestamp. For example:
// Old:
const date = snapshot.get('created_at');
// New:
const timestamp = snapshot.get('created_at');
const date = timestamp.toDate();

How to implement a counter function using firebase functions?

I WANT TO IMPLEMENT A COUNTER FUNCTION USING FIREBASE FUNCTION TO COUNT NO OF TIMES MY APP IS BEING OPENED.
What I want:
Android app opened---> app calling ------>firebase function executed via http trigger------->firebase function increment value stored in firebase database
How to read and update data on firebase realtime database from firebase Functions.
I made an Firebase function which triggers on http triggers.
Code:
const functions = require('firebase-functions');
exports.counterFunction = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase");
******Code to increment value in firebase database*********
});
I have a field in firebase realtime database called count and 0 corresponds to it
count : 0
I want to increment count variable every time the firebase function is executed
IN SHORT I WANT TO IMPLEMENT A COUNTER USING FIREBASE.
I just want to record the number of times my app started . So I am calling the Http Trigger function using http request using 'okhttp' and the function need to increment the count.
The following Firebase function example should help you understand how to write to the realtime database from a function
https://github.com/firebase/functions-samples/tree/3b41b2e04d854a821a0149e1e5b0769bad397d67/quickstarts/uppercase
Have also a look at https://firebase.google.com/docs/functions/get-started
You could try:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const app = admin.initializeApp();
exports.counterFunction = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase");
return app.database().ref(`/count`).once('value', (snap) => {
const value = snap.val() || 0;
return app.database().ref(`/count`).set(value + 1).then(res => {
return true;
}).catch(err => {
return Promise.reject(err);
});
});
});
This will increment your counter each time your cloud function is executed.

Resources