Firebase Cloud Functions get updated document Details - node.js

I'm trying to write a cloud function where if my app changed some string in user's firestore DB. A cloud function need to send a push notification. Database architecture is Messages => {UID} => UpdatedMessages . The problem is I cannot figure How to retrive which updateMessage under which UID has been updated.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
const toUpperCase = (string)=> string.toUpperCase()
var registrationToken = 'dfJY6hYzJyE:APdfsdfsdddfdfGt9HMfTXmei4QFtO0u1ePVpNYaOqZ1rnDpB8xfSjx7-G6tFY-vWQY3vDPEwn_iZVK2PrsGUVB0q9L_QoRYpLJ3_6l1SVHd_0gQxJb_Kq-IBlavyJCkgkIZ';
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
var message = {
data: {
title: 'Update',
body: 'New Update'
},
token: registrationToken
};
// Send a message to the device corresponding to the provided
// registration token.
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
});
Simply I need to rerive "var registrationToken" from UID .

You have to use the params property of the context object as follows
exports.sendNotification = functions.firestore
.document('messages/{userId}/{updatedMessage}')
.onUpdate((change, context) => {
const userId = context.params.userId;
const updatedMessage = context.params.updatedMessage;
var message = {
data: {
title: 'Update',
body: updatedMessage //For example, use the value of updatedMessage here
},
//...
};
//IMPORTANT: don't forget to return the promise returned by the asynchronous send() method
return admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent messagesssss:', response);
return null;
})
.catch((error) => {
console.log('Error sending message:', error);
return null;
});
});
See https://firebase.google.com/docs/functions/firestore-events#wildcards-parameters and https://firebase.google.com/docs/reference/functions/functions.EventContext#params for more info.
About the remark in the above code noted as "IMPORTANT", you may watch the official Firebase video series here: https://firebase.google.com/docs/functions/video-series/. In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).

Related

firebase cloud functions invoke callback

I'm using firebase admin SDK to create new User to my web app.
// function to create user and store additional info in firestore
exports.createUser = functions
.https.onCall((data, context) => {
admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
}).then((user) => {
// store data in firestore
admin.firestore().collection("user").doc(user.uid).set({...data});
}).catch((err) => {
// handle error
});
});
When I call the function from the client (attach it to onClick event) I want to wait till user is successfully added in firestore and invoke the fetchUser function so I can see the new list of data with the newly added user. Currently fetchUser gets called and page refreshes but I cannot see the newly added user before refreshing it.
const createUser = functions.httpsCallable('createUser');
const createNewUser = () => {
createUser({
phoneNumber: "+1111111111111",
displayName: "test",
introduction: "testcreate",
})
.then((res) => {
fetchUser(); // fetchUser just fetches user data from firestore and rerenders page
})
.catch((err) => {
console.log(err);
});
};
To summarize, can I know when cloud function will complete its job and run a particular function or task ?
As you will see in the three videos about "JavaScript Promises" from the official Firebase video series you MUST return a Promise or a value in a background triggered, Pub/Sub or Callable Cloud Function when all the asynchronous operations are complete.
This has two linked effects:
When the asynchronous operations are complete, it indicates to the Cloud Function platform that it can terminate and clean up your function.
On the opposite, until the asynchronous operations are complete, it indicates to the Cloud Function platform that it should wait before terminating the Cloud Function.
To return a Promise, since you chain several asynchronous Firebase Admin SDK methods (which return Promises), you need to return the Promise chain as follows:
exports.createUser = functions.https.onCall((data, context) => {
return admin // <== See return here
.auth()
.createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
})
.then((user) => {
// store data in firestore
return admin // <== See return here
.firestore()
.collection('user')
.doc(user.uid)
.set({ ...data });
})
.then(() => {
// The set() method returns Promise<void>
// To send data back to the client, return data that can be JSON encoded
return { result: 'user created' };
})
.catch(error => {
// See https://firebase.google.com/docs/functions/callable#handle_errors
// on how to handle errors
});
});
This way, when your front-end gets back the Callable Cloud Function response, you are sure that the user and the Firestore document were created.
If you want to use the async/await keywords, do as follows. Note that it is not recommended to mixup then() with async/await.
exports.createUser = functions
.https.onCall(async (data, context) => { // <== See async here
try {
const user = await admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
});
await admin.firestore().collection("user").doc(user.uid).set({ ...data });
return { result: 'user created' }
} catch (error) {
console.log(error); // If desired
// See https://firebase.google.com/docs/functions/callable#handle_errors
// on how to handle errors
}
});
See how the code is much more readable, as if it was synchronous code.
It looks like you are not returning the promises correctly and hence the function terminates before the docs are updated. Please try adding the return statements as shown below.
exports.createUser = functions
.https.onCall((data, context) => {
return admin.auth().createUser({
phoneNumber: data.phoneNumber,
displayName: data.displayName,
}).then(async (user) => {
// store data in firestore
await admin.firestore().collection("user").doc(user.uid).set({...data});
return {data: user.uid}
}).catch((err) => {
// handle error
return {error: error}
});
});
Then you should ideally received the response after all promises are resolved (i.e. the documents are updated). Also make sure you don't have Firestore local caching enabled.

Call external API with Cloud Function and create document

I'm trying to create some cloud functions to:
call Google Directions API using Axios
create a document at Firestore based on the API result
Return the document reference to my iOS App.
(I'm on Blaze plan, pay as you go)
I'm having trouble to create the following functions as my Node / JS knowledge is very basic.
Could someone please have a quick a look and let me know what I'm missing?
Obs.:
The code is deploying to firebase with no warnings and erros. I'm pretty sure that the problem is the way that I'm trying to return my callbacks.
Thanks in advance
EDIT
I've made a few changes on the code, but I'm still receiving nil on my iOS App.
The code is still not creating a document on firestore.
const functions = require('firebase-functions');
const axios = require('axios');
var admin = require("firebase-admin");
admin.initializeApp();
// Func called by iOS App, If user is auth, call google maps api and use response to create a document at firestore
exports.getDistanceAndSavePackage = functions.https.onCall((data, context) => {
if (!context.auth){ return {status: 'error', code: 401, message: 'Not signed in'} }
const userId = context.auth.uid;
const startCoordinates = data.startCoords;
const endCoordinates = data.endCoords;
const pkgDocReference = getGoogleRoute(startCoordinates, endCoordinates, res => {
console.log('google cloud function has returned');
let venueId = userId;
let distance = res.distance.value;
let resultStartAdd = res.start_address;
let resultEndAdd = res.end_address;
const pkgDocRef = createTempPackage(venueId, distance, resultStartAdd, resultEndAdd, resultPkg => {
return resultPkg
})
return pkgDocRef;
})
return pkgDocReference;
});
//Create Package Document
function createTempPackage(venueId, distance, startingAddress, endingAddress, callback){
console.log('Creating temp package');
const docRef = admin.firestore().doc(`/temp_packages/`)
docRef.set({
id: docRef.id,
venue_id: venueId,
distance: distance,
starting_address: startingAddress,
ending_address: endingAddress,
timestamp: admin.database.ServerValue.TIMESTAMP,
status: 0
})
.then(docRef => {
console.log('Doc created')
return callback(docRef);
}).catch(error => {
console.log('Error trying to create document')
return callback(error);
})
}
//Call Google directions API
function getGoogleRoute(startCoords, endCoords, callback){
axios({
method: 'GET',
url: 'https://maps.googleapis.com/maps/api/directions/json',
params: {
origin: startCoords,
destination: endCoords,
key: 'mykey'
},
})
.then(response => {
let legs = response.data.routes[0].legs[0];
return callback(legs);
})
.catch(error => {
console.log('Failed calling directions API');
return callback(new Error("Error getting google directions"))
})
}
I don't know if this is final solution, however there is an error in the code:
const docRef = admin.firestore().doc('/temp_packages/')
This statement should trow error:
Value for argument "documentPath" must point to a document, but was "${documentPath}". Your path does not contain an even number of components.
The error is thrown before docRef.set so it will not be taken into consideration in catch statement. I was trying to test it, but all my tries finished with this error. Maybe this error is somewhere in your logs.
I hope it will help!
For a HTTPS trigger you'd need to return {status: 'OK', code: 200, data: json}.
So that it would actually respond through the web-server.

How perform a query in Cloud Function for Firebase and retrieve a specific attribute

I'm new to Cloud Functions and I'm trying to retrieve the attribute 'name' performing a query using its id, but I don't know how to handle the object returned by the query.
Here is the code I'm using:
// Create and Deploy Your First Cloud Functions
// https://firebase.google.com/docs/functions/write-firebase-functions
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp()
exports.testNotification = functions.firestore
.document('messages/{email}/{groupChat}/{groupId1}/{groupId2}/{message}')
.onCreate((snap, context) => {
console.log('----------------start function--------------------')
const doc = snap.data()
console.log(doc)
const idFrom = doc.idFrom
const idTo = doc.idTo
console.log(idTo)
const contentMessage = doc.content
/*[...] Some awesome code here not correlated to the question */
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot =>{
console.log('querySnapshot: ' + querySnapshot)
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
});
I went for the trial & error solution, but the only syntax I tried that returns something rather than 'undefined' or exception, is 'querySnapshot', that logs 'querySnapshot: [object Object]'.
Other useful info: I know that the const 'idTo' is correct and the element searched into the db has both attributes 'id' and 'name'.
What am I doing wrong? Do you have an useful and complete documentation to link to?
Thanks
I just needed to use
querySnapshot.forEach(doc => {
console.log(doc.data().name)
})
So the final code is:
admin
.firestore()
.collection('settings')
.doc('table')
.collection('room')
.where('id', '==', idTo)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data().name)
});
return null
})
.catch(error => {
console.log('Error sending message:', error)
})
return null
For more info, check out this documentation.

ReferenceError: conv is not defined in actions-on-google

I want to implement Suggestions chips in my Dialog flow (to be use in Google Assistant)..But I am getting this error
"ReferenceError: conv is not defined"
which i didn't understand.I have go through the official docs but what am i missing? I have also added actions_intent_OPTION in his Event
following is my code
const functions = require('firebase-functions');
const {actionssdk} = require('actions-on-google');
const app = actionssdk({debug: true});
var admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
var firestore = admin.firestore();
exports.webhook = functions.https.onRequest((request, response) => {
switch (request.body.result.action) {
case 'countitem':
firestore.collection('orders').get()
.then((querySnapshot) => {
var orders = [];
querySnapshot.forEach((doc) => { orders.push(doc.data()) });
// now orders have something like this [ {...}, {...}, {...} ]
response.send({
speech: `you have ${orders.length} orders11, would you like to see them? (yes/no)`
});
})
.catch((err) => {
console.log('Error getting documents', err);
response.send({
speech: "something went wrong when reading from database"
})
})
conv.ask(new Suggestions('Suggestion Chips'));
conv.ask(new Suggestions(['suggestion 1', 'suggestion 2']));
break;
default:
response.send({
speech: "no action matched in webhook"
})
}
});
The issue is that conv isn't defined. Typically, if you're using the actions-on-google library, conv is passed to your fulfillment function and contains methods you can use to set replies and so forth.
It looks like you're handling everything yourself and generating the JSON response manually. If so, you should consult the guide for using JSON as part of your webhook and the repository of JSON examples.

How to: GET Data from 2 APIs, compare, POST bool

I'm working on a project that requires me to:
GET IDs from API1, push the IDs into an array, then map over those IDs, using them for a second GET request, where IDs are used as params for API2 GET request, populates an array with IDs or N for "Not existing" -- this array is then called in:
A POST request. This post maps over the returned array from the GET request. IF the item is not "N", it POSTS to API1 with checked: true. IF the item is "N", it emails us telling us API2 is missing this project.
I want this system to automatically do a GET and POST every 2 hours, so I'm using setInterval (not sure this is the best idea). EDIT: Cron job would be a better solution.
I'm working with NodeJS, Express, Request-Promise, Async / Await.
Here is some of my pseudo code so far:
// Dependencies
const express = require('express');
const axios = require('axios');
const mailgun = require('mailgun-js')({ apiKey, domain });
// Static
const app = express();
app.get('/', (req, res, next) => {
// Replace setInterval with Cron job in deployment
// Get All Ids
const orders = await getGCloud();
// Check if IDs exist in other API
const validations = await getProjectManagementSystem(orders);
// If they exist, POST update to check, else, mailer
validations.map(id => {
if (id !== 'n') {
postGCloud(id);
} else {
mailer(id);
}
});
}
// Method gets all IDs
const getGCloud = async () => {
try {
let orders = [];
const response = await axios.get('gCloudURL');
for (let key in response) {
orders.push(response.key);
}
return orders;
} catch (error) {
console.log('Error: ', error);
}
}
// Method does a GET requst for each ID
const getProjectManagementSystem = async orders => {
try {
let idArr = [];
orders.map(id => {
let response = await axios.get(`projectManagementSystemURL/${id}`);
response === '404' ? idArr.push('n') : idArr.push(response)
})
return idArr;
} catch (error) {
console.log('Error: ', error);
}
}
const postGCloud = id => {
axios.post('/gcloudURL', {
id,
checked: true
})
.then(res => console.log(res))
.catch(err => console.log(err))
}
const mailer = id => {
const data = {
from: 'TESTER <test#test.com>',
to: 'customerSuppoer#test.com',
subject: `Missing Order: ${id}`,
text: `Our Project Management System is missing ${id}. Please contact client.`
}
mailgun.messages().send(data, (err, body) => {
if (err) {
console.log('Error: ', err)
} else {
console.log('Body: ', body);
}
});
}
app.listen(6000, () => console.log('LISTENING ON 6000'));
The TL;DR: Need to do a GET request to API 1, then another GET request to API 2 following it (using IDs from API 1 as params), then send data from second GET to a POST request that then either updates API 1's data or emails Customer support. This is an automatic system that runs every two hours.
Main Questions:
1. Is it okay to have a setInterval in a get req?
2. Can I have a GET request automatically call a POST request?
3. If so, how can I pass GET request data onto a POST request?
To make it work for both of your calls one post and one get you have to do an Ajax call to get post processed information in another method.
I hope this works.

Resources