Shown above is my firestore collection.
I am attempting to get data from this collection using a Google Cloud Function that I have deployed:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
let results = []
admin.firestore().collection('news_stories')
.get()
.then(docs => docs.map(doc => results.push(doc.data())))
.catch(e => resoponse.status(400).send(e))
response.status(200).send(results)
}
When I run the above function I get an:
Error: could not handle the request
I also tried running the function this way to see if it would work.
module.exports= function(request, response){
let ref = admin.firestore().collection('news_stories')
.get()
.then(docs => response.status(200).send(docs))
.catch(e => resoponse.status(400).send(e))
}
This function returned a this JSON object:
There is no information regarding data or any of the docs.
I uploaded the collection to the firestore DB using this function:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
module.exports = function(request,response){
if(!request.body.data){
response.status(422).send({error: 'missing data'})
}
let data = request.body.data
data.map(item => {
admin.firestore().collection('news_stories').add({item})
})
response.status(200).send('success!')
}
Not sure what I am doing wrong. Why is the function not returning any of the documents?
Data is retrieved from Firestore asynchronously. By the time your send you response back to the caller, the results haven't been retrieved from Firestore yet.
It's easiest to see this by replacing the bulk of the code with three log statements:
console.log("Before starting get");
admin.firestore().collection('news_stories')
.get()
.then(() => {
console.log("In then()");
});
console.log("After starting get");
It's best if you run the above in a regular node.js command, instead of in the Cloud Functions environment, since the latter may actually kill the command before the data is loaded.
The output of the above is:
Before starting get
After starting get
In then()
That is probably not the order that you expected. But because the data is loaded from Firestore asynchronously, the code after the callback function is allowed to continue straight away. Then when the data comes back from Firestore, your callback is invoked and can use the data as it needs to.
The solution is to move all the code that requires the data into the then() handler:
const admin = require('firebase-admin')
const functions = require('firebase-functions')
module.exports= function(request, response){
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let results = []
docs.map(doc => results.push(doc.data()))
response.status(200).send(results)
})
.catch(e => resoponse.status(400).send(e))
}
So after some trouble shooting I found the source of the problem . For some reason if you use .map on the return object the server will respond with a 500 status...
change the .map to forEach and the function works
this will work ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.forEach(doc => data.push(doc.data()))
response.status(200).send(data)
})
yet this wont ...
admin.firestore().collection('news_stories')
.get()
.then(docs => {
let data = []
docs.map(doc => data.push(doc.data()))
response.status(200).send(data)
})
Related
I can only give an example of my code, since my real code is too big. I'm working with Firebase Cloud Functions and Firebase Firestore.
First I define a returnData dictionary (within my Firebase Cloud Function):
let returnData = {};
Then I get() "snapshots" of my db:
const db = admin.firestore();
const snapshotUsers = await db
.collection("users")
.where("followers", "array-contains", exampleId)
.get();
Then, I'm trying to populate the returnData dictionary:
snapshotUsers.forEach(async (docUsers) => {
await db
.collection("users")
.doc(docUsers.id)
.collection("profileProjects")
.listDocuments()
.then(async (documentRefsProjects) => {
if (!(Object.keys(documentRefsProjects).length === 0)) {
return await db.getAll(...documentRefsProjects);
}
})
.then(async (documentSnapshotsProjects) => {
if (!!documentSnapshotsProject) {
for (let documentSnapshotProject of documentSnapshotsProjects) {
if (documentSnapshotProject.exists) {
// ----------- Populate here -----------
returnData = {
...returnData,
[documentSnapshotProject.id]: {
followers: docUsers.data().followers,
}
};
// --------------------------------------
}
}
}
})
});
// Send response when forEach is done with returnData
res.status(201).send({ returnData: returnData });
Problem is: once it successfully populates returnData, it then throws that data away and
res.status(201).send({ returnData: returnData }); returns the initial empty dictionary
I understand that: "forEach() executes the callback function once for each array element; unlike map() or reduce() it always returns the value undefined and is not chainable", but I seems like I have no other option than to use forEach for firestore, so I'm stuck.
I tried using the map() function instead, but Firestore returns TypeError: snapshotUsers.map is not a function.
I even tried doing a manual for...in and for...of loop, but those also gave me errors when I would try to call id on docUsers:
for (let docUsers of snapshotUsers) {
await db
.collection("users")
.doc(snapshotUsers[docUsers].id) // <---- error'd here
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
I wondering if anyone can help me out here. Any advice or solution helps.
I'm having a real tough time here since the only way I can test this is deploying the Firebase Cloud Function every time.
check it: https://firebase.google.com/docs/hosting/test-preview-deploy
I used to work with:
firebase serve --only functions
And it just work fine.
For your problem...
Your foreach is ending before your returnData is populated.
Foreach has one argument that show the current data index in your array.
So, in your success callback, you can check if it is the last array's data then just send your response.
Something like this:
let returnData;
xpto.forEach((data,index)=>{
db.then(()=>{
//populates your returnData
if(index === xpto.lenght-1) res.status(201).send({ returnData })
})
})
Edit with askers experience:
for some reason didn't work with the snapshots, I had to initialize an
index outside the forEach and increment it within the .then()
function.
I am querying firebase firestore by...
let database = firebase.firestore();
let places = database.collection("place");
console.log("places", places);
now the logged data is bizarre and not the actual documents..
here is a picture of the log...can you please advice regarding tackling this ?
If you want to retrieve all items in your collections called "place" you can do something like this:
let database = firebase.firestore();
let places = database.collection("place");
const querySnapshot = places.get()
// You can make an empty array to eventually push the items into
const collectionArray = []
querySnapshot.forEach((doc) => {
const data = doc.data()
collectionArray.push(data)
}).catch(function(error) {
console.log("Error getting documents: ", error);
})
console.log('collectionArray:',collectionArray)
}
Your code hasn't actually executed any query yet. All it's done is build a Query object.
If you want to execute the query, call get() on it, and handle the results as shown in the documentation.
let database = firebase.firestore();
let query = database.collection("place");
query.get()
.then(querySnapshot => {
querySnapshot.forEach(documentSnapshot => {
console.log("document", documentSnapshot.data());
})
})
I'm new to JavaScript and I have written the following JS Google Cloud Function with the help of various resources.
This function handles a Stripe invoice.payment_succeeded event and instead of writing the entire data I am trying to save just both the sent period_start and period_end values back to the correct location in my Firebase DB (see structure below).
How can I write these two values in the same function call?
exports.reocurringPaymentWebhook = functions.https.onRequest((req, res) => {
const hook = req.body.type;
const data = req.body.data.object;
const status = req.body.data.object.status;
const customer = req.body.data.object.customer;
const period_start = req.body.data.object.period_start;
const period_end = req.body.data.object.period_end;
console.log('customer', customer);
console.log('hook:', hook);
console.log('status', status);
console.log('data:', data);
console.log('period_start:', period_start);
console.log('period_end:', period_end);
return admin.database().ref(`/stripe_ids/${customer}`).once('value').then(snapshot => snapshot.val()).then((userId) => {
const ref = admin.database().ref(`/stripe_customers/${userId}/subscription/response`)
return ref.set(data);
})
.then(() => res.status(200).send(`(200 OK) - successfully handled ${hook}`))
.catch((error) => {
// We want to capture errors and render them in a user-friendly way, while
// still logging an exception with StackDriver
return snap.ref.child('error').set(userFacingMessage(error));
})
.then((error) => {
return reportError(error, {user: context.params.userId});
});
});//End
HTTP type functions are terminated immediately after the response is sent. In your code, you're sending the response, then attempting to do more work after that. You will have to do all the work before the response is sent, otherwise it may get cut off.
If you just want to save the period_start and period_end values, instead of the entire data object, you can use the update() method (see https://firebase.google.com/docs/database/web/read-and-write#update_specific_fields).
You should then modify your code as follows. (Just note that it is not clear from where you receive the userId value, since you don't show the stripe_ids database node in your question. I make the assumption that it is the value at /stripe_ids/${customer}. You may adapt that.)
exports.reocurringPaymentWebhook = functions.https.onRequest((req, res) => {
const hook = req.body.type;
const data = req.body.data.object;
const status = req.body.data.object.status;
const customer = req.body.data.object.customer;
const period_start = req.body.data.object.period_start;
const period_end = req.body.data.object.period_end;
admin.database().ref(`/stripe_ids/${customer}`).once('value')
.then(snapshot => {
const userId = snapshot.val();
let updates = {};
updates[`/stripe_customers/${userId}/subscription/response/period_start`] = period_start;
updates[`/stripe_customers/${userId}/subscription/response/period_end`] = period_end;
return admin.database().ref().update(updates);
})
.then(() => res.status(200).send(`(200 OK) - successfully handled ${hook}`))
.catch((error) => {...});
});
I'm currently trying to modify my Cloud Functions and move in under https.onRequest so that i can call use it to schedule a cron job. How it i'm getting the following error in the logs.
TypeError: admin.database.ref is not a function
at exports.scheduleSendNotificationMessageJob.functions.https.onRequest (/user_code/index.js:30:20)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:57:9)
exports.scheduleSendNotificationMessageJob = functions.https.onRequest((req, res) => {
admin.database.ref('/notifications/{studentId}/notifications/{notificationCode}')
.onCreate((dataSnapshot, context) => {
const dbPath = '/notifications/' + context.params.pHumanId + '/fcmCode';
const promise = admin.database().ref(dbPath).once('value').then(function(tokenSnapshot) {
const theToken = tokenSnapshot.val();
res.status(200).send(theToken);
const notificationCode = context.params.pNotificationCode;
const messageData = {notificationCode: notificationCode};
const theMessage = { data: messageData,
notification: { title: 'You have a new job reminder' }
};
const options = { contentAvailable: true,
collapseKey: notificationCode };
const notificationPath = '/notifications/' + context.params.pHumanId + '/notifications/' + notificationCode;
admin.database().ref(notificationPath).remove();
return admin.messaging().sendToDevice(theToken, theMessage, options);
});
return null;
});
});
You cannot use the definition of an onCreate() Realtime Database trigger within the definition of an HTTP Cloud Function.
If you switch to an HTTP Cloud Function "so that (you) can call use it to schedule a cron job" it means the trigger will be the call to the HTTP Cloud Function. In other words you will not be anymore able to trigger an action (or the Cloud Function) when new data is created in the Realtime Database.
What you can very well do is to read the data of the Realtime Database, as follows, for example (simplified scenario of sending a notification):
exports.scheduleSendNotificationMessageJob = functions.https.onRequest((req, res) => {
//get the desired values from the request
const studentId = req.body.studentId;
const notificationCode = req.body.notificationCode;
//Read data with the once() method
admin.database.ref('/notifications/' + studentId + '/notifications/' + notificationCode)
.once('value')
.then(snapshot => {
//Here just an example on how you would get the desired values
//for your notification
const theToken = snapshot.val();
const theMessage = ....
//......
// return the promise returned by the sendToDevice() asynchronous task
return admin.messaging().sendToDevice(theToken, theMessage, options)
})
.then(() => {
//And then send back the result (see video referred to below)
res.send("{ result : 'message sent'}") ;
})
.catch(err => {
//........
});
});
You may watch the following official Firebase video about HTTP Cloud Functions: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3. It shows how to read data from Firestore but the concept of reading and sending back the response (or an error) is the same for the Realtime Database. Together with the 2 other videos of the series (https://firebase.google.com/docs/functions/video-series/?authuser=0), it also explains how it is important to correctly chain promises and to indicate to the platform that the work of the Cloud Function is finished.
For me, this error happened when writing admin.database instead of admin.database().
I'm trying to remove a node from Firebase using cronjob and i have this function but when it gets executed I get an error saying "Error: could not handle the request" and the log says: "database is not defined"
This is my function:
exports.cleanStatsOnRequest = functions.https.onRequest((req, res) => {
const ref1 = firebase.database.ref;
const dbref = ref1.child(`/dailystats`);
console.log('removing dailystats');
return dbref.remove
.then(() => {
res.send('dailystats removed');
})
.catch(error => {
res.send(error);
});
});
What am I doing wrong? What is the right way to define the database?
You need to use the Firebase Admin SDK to access the Realtime Database from an HTTP trigger Cloud Function. This documentation shows you how to read from the database. This example shows writing to the database, which would be similar to deleting.
Try this. database,ref and remove are functions. Read this guide.
Also you should not return dbref.remove() as remove() will return a promise.
exports.cleanStatsOnRequest = functions.https.onRequest((req, res) => {
const ref1 = firebase.database().ref(); // changes here
const dbref = ref1.child('/dailystats');
console.log('removing dailystats');
return dbref.remove() // changes here
.then(() => {
res.send('dailystats removed');
})
.catch(error => {
res.send(error);
});
});