nodejs Firestore suddenly breaks: Date objects - node.js

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

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.

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

Error Deploying Firebase function

I am trying to deploy a firebase cloud function that will push something to my algolia search engine everytime a new event is added under the events node. I followed the tutorial here which seems to detail how to do things for the most part.
https://www.youtube.com/watch?v=Njtbo3YUdH4
However, when I run my project i get the strangest error
Error occurred while parsing your function triggers.
TypeError: Cannot read property 'config' of undefined
I have never seen this error when trying to deploy functions in the past. Im not sure if something changed or not but I can't really find where to fix this error on firebase docs or stackoverflow so far.
I am running node version v8.11.3 in addition to that I am pretty sure that I have my algolia configured properly due to the fact that when I run
$ firebase functions:config:get
I get this
{
"algolia": {
"appid": "xxxxxxx",
"adminkey": "xxxxxxxxxx"
}
}
The only thing it could possibly be is my code. I will include it below if anyone could help me fix it I would greatly appreciate it.
//similar to import functions in swift
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
//exports acts as a global variable that you can init other properties on
//on
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
const algoliasearch = require('algoliasearch');
const algolia = algoliasearch(functions.config().algolia.appid,functions.algolia.config().adminKey)
exports.updateIndex = functions.database.ref('/events/{eventKey}').onWrite(event =>{
const index = algolia.initIndex('events');
const eventKey = event.params.eventKey
const data = event.data.val();
if(!data){
return index.deleteObject(eventKey,(err) =>{
if (err) throw err
console.log('Event deleted from algolia index',eventKey)
})
}
data['objectID'] = eventKey
return index.saveObject(data,(err,content) =>{
if (err) throw err
console.log('Event updated in algolia index',data.objectID)
})
});
Look at this line (I added a carriage return):
const algolia = algoliasearch(functions.config().algolia.appid,
functions.algolia.config().adminKey)
This looks correct:
functions.config().algolia.appid
This doesn't:
functions.algolia.config().adminKey
It looks like you meant to say this instead:
functions.config().algolia.adminKey

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