How to fix Cloud Function error admin.database.ref is not a function at exports - javascript

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

Related

Can't synchronise mongoose operation to return an array

As a part of an employee's app management, I want to separate the business logic database operations from my main application file.
The simplest operation is to read all the employees from the database using async/await to synchronize it:
module.exports.getEmployees = async () => {
const employees = await Employee.find();
return employees;
}
in my app.js I typed the following code:
const employee = require(__dirname + "/models/employee.js");
app.get("/employees", (req, res) => {
const employeeList = employee.getEmployees();
employeeList.then(res.send(employeeList));
})
but still, the array shows up empty?
then clause in promise accepts a functions as an argument & this function has a parameter which holds the actual response.
Something like this -
new Promise().then((response) => {
console.log(response);
});
You are doing employeeList.then(res.send(employeeList)); which means the argument to then clause is res.send() which won't work.
Try this -
employeeList.then((list) => {
// please note the response type here depends on the Content-Type set in the response Header
res.send(list);
// In case of normal http server, try this -
res.send(JSON.stringify(list));
});
I hope this helps.

JavaScript Google Cloud Function: write Stripe values to Firebase

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

How do I call the function with a parameter I set up in firebase functions

I've deployed this code to my firebase functions project:
import * as functions from 'firebase-functions'
import * as admin from 'firebase-admin'
admin.initializeApp()
export const getEmail = functions.https.onRequest((request, response) => {
var from = request.body.sender;
admin.auth().getUserByEmail(from)
.then(snapshot => {
const data = snapshot.toJSON()
response.send(data)
})
.catch(error => {
//Handle error
console.log(error)
response.status(500).send(error)
})
})
Which takes in a email parameter that it gets from the user's input on my app. My app's code looks like this:
Functions.functions().httpsCallable("https://us-central1-projectname.cloudfunctions.net/getEmail").call(email) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
//email isnt taken
let code = FunctionsErrorCode(rawValue: error.code)
let message = error.localizedDescription
let details = error.userInfo[FunctionsErrorDetailsKey]
print(code, message, details)
}
// ...
}
if let text = (result?.data as? [String: Any])?["text"] as? String {
// email taken
}
}
When I run the app and when that function is called, it seems to do nothing, no error message is shown and no data has been sent back. What am I missing?
Update: I went to the logs and nothing has happened in there as if the function was never called.
You are actually mixing up HTTP Cloud Functions and Callable Cloud Functions:
You Cloud Function code corresponds to an HTTP one but the code in your front-end seems to call a Callable one.
You should adapt one or the other, most probably adapt your Cloud Function to a Callable one, along the following lines:
exports.getEmail = functions.https.onCall((data, context) => {
const from = data.sender;
return admin.auth().getUserByEmail(from)
.then(userRecord => {
const userData = userRecord.toJSON();
return { userData: userData };
})
});
Have a look at the doc for more details, in particular how to handle errors. The doc is quite detailed and very clear.

How do I use Javascript Promise Chaining with Firebase Authentication and Firebase?

I need to utilize a key stored in my firebase using the user's firebase user id.
The steps to my function are the following:
1) Get the Firebase User ID after authentication
2) Using the Firebase User ID, pull the stored API Key value (which I saved in a node: app/{Firebase User Id})
3) Using the stored API Key value, run my last function
After doing some research, I've come to the conclusion that I should use Javascript Promise Chaining to the below code, which I'm having a difficult time doing
firebase.initializeApp({databaseURL: "{}"});
var dbRef = firebase.database();
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
func(api_key)
})
}
});
}
function final_function(api_key){
console.log('processing final function')
}
pull_api_key_from_firebase_after_auth(final_function)
Alternatively, I'd like to make api_key a global variable as such:
function pull_api_key_from_firebase_after_auth(func){
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var app_ref = dbRef.ref('app').child(user.uid);
app_ref.on("child_added", function(snap) {
dictionary_object = snap.val()
api_key = dictionary_object.api_key
localStorage.setItem('api_key',api_key)
})
}
});
}
api_key = localStorage.getItem('api_key')
final_function(api_key)
However I cant figure out how to make final_function as well as my other functions wait until api_key is defined
You must use firebase.auth().onAuthStateChanged to get the uid bcs .auth.currentUser.uid will NOT be available on page load. It shows up a microsecond after (it's asynchronous).
To do what you want simply build out a promise function and call it within .onAuthStateChanged and .then do your other function. You custom promise might look like:
function fetchUserData(){
return new Promise(function(resolve, reject){
var db = firebase.firestore(); // OR realtime db
db.collection("users").get().then(function(snap) {
resolve (snap);
}).catch(function(error) {
reject (error);
});
});
}
Within .onAuthStateChange just do something like:
fetchUserData().then((snap) => {
// do your thing
})
.catch((error) => {
console.log(error);
});

Firestore query not working

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

Categories

Resources