I have 2 Firebase functions that I want to execute when there is an Http request, one function (createEmailList) to save data in the Firebase database, the other (zohoCrmHook) to to save in a 3rd party CRM called Zoho.
When the functions are deployed to Firebase, the functions log shows that both are properly deployed. However, when the Http request is made from the frontend, the log shows that only one of the functions (createEmailList) is being executed.
As the log shows, the first function createEmailList is being executed and the data shows up in the Firebase database with no problem. However, The second function zohoCrmHook is not even being executed.
index.js
const functions = require('firebase-functions');
const admin = require("firebase-admin")
const serviceAccount = require("./service_account.json");
const createEmailList = require('./createEmailList')
// zoho
const zohoCrmHook = require('./zohoCrmHook')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://landing-page.firebaseio.com"
})
exports.zohoCrmHook = functions.https.onRequest(zohoCrmHook)
exports.createEmailList = functions.https.onRequest(createEmailList)
createEmailList.js
const admin = require('firebase-admin')
const cors = require('cors')({ origin: true })
module.exports = (req, res) => {
cors(req, res, () => {
if (!req.body.email) {
return res.status(422).send({ error: 'Bad Input'})
}
const email = String(req.body.email)
const firstName = String(req.body.firstName)
const lastName = String(req.body.lastName)
const data = {
email,
firstName,
lastName
}
const db = admin.firestore()
const docRef = db.collection('users')
.doc(email)
.set(data, { merge: false })
.catch(err => res.status(422).send({ error: err }))
res.status(204).end();
})
}
zohoCrmHook.js
const axios = require('axios');
const functions = require('firebase-functions');
// zoho
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = async (req, res) => {
const newLead = {
'data': [
{
'Email': req.body.email,
'Last_Name': req.body.lastName,
'First_Name': req.body.firstName,
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead));
}
catch (e) {
console.log(e);
}
}
else res.json({ message: 'Lead already in CRM' })
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log(e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log(response.data)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Since you're exporting two functions.https.onRequest declarations, you'll end up with two Cloud Functions, each with their own URL/endpoint. So if that's what you need, you'll need to configure two web hooks that call these functions.
From reading your question however, it sounds more like you want a single Cloud Function that does two things, in which case you should only have one functions.https.onRequest declaration that then calls two regular JavaScript functions (for example).
So something more like:
exports.myWebHook = functions.https.onRequest(function(req, res) {
zohoCrmHook(...);
createEmailList(...);
})
You'll need to figure out what to pass into the two function calls here, as you can't pass the request and response along.
Alternatively you can call the two Cloud Functions from here, but that typically just drives up your cost with little benefit.
Related
Below is my firebase function:
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
When calling this function, how can I pass in the context.auth.id?
Below is what i've tried:
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}
But im getting the following error:
Unhandled error TypeError: Cannot read property 'uid' of undefined
I know the the context.auth.id is available server side but In this instance I need a way i can pass it in.
You don't have to pass user's UID in callable cloud function. The user must be logged in with Firebase authentication and Firebase SDKs will take care of the rest.
Can you try logging current user in deleteAccount function before calling cloud function just to ensure user is logged in? Also context.auth.uid is UID of user that is calling the function. If you want to access the userId that you are passing in the function, refactor the code as shown below.
The deleteUser() function would take only 1 parameter that's the data you want to pass in Cloud functions.
// not deleteUser({}, { userId })
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
When you are explicitly passing any data in Cloud function, that can be access from data and not context:
const { userId } = data;
I'm using nuxtjs/axios and Mongoose to write to MongoDB. The POST always works but it takes a few seconds for the insert to get into MongoDB. Problem is that I'm trying to call a GET immediately after a new POST so i can get all the latest records. That doesn't always happen because it takes a few seconds for the data to get into the DB. Here's my index.js file for the server:
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}
const Post = require('./models/post');
const express = require('express');
const { Nuxt, Builder } = require('nuxt');
const app = express();
const mongoose = require('mongoose');
const xss = require('xss-clean');
app.use(
express.urlencoded({
extended: true
})
)
app.use(express.json())
app.use(xss());
const config = require('../nuxt.config.js');
config.dev = process.env.NODE_ENV !== 'production';
const nuxt = new Nuxt(config);
const { host, port } = nuxt.options.server;
const username = process.env.username;
const pwd = process.env.pwd;
const server = process.env.server;
const db = process.env.db;
const dbURI = `mongodb+srv://${username}:${pwd}#${server}/${db}?
retryWrites=true&w=majority`;
async function start() {
if (config.dev) {
const builder = new Builder(nuxt);
await builder.build();
} else {
await nuxt.ready();
}
app.use(nuxt.render);
}
start();
mongoose
.connect(dbURI, {useNewUrlParser: true, useUnifiedTopology: true})
.then((result) => {
app.listen(port, host); // listen
}
)
.catch(err => console.log(err));
app.get('/posts', (req, res) => {
Post
.find()
.sort({createdAt: -1})
.then((result) => {
res.send(result);
})
.catch((err) => console.log(err));
})
app.post(
'/posts',
(req, res) => {
const post = new Post({
body: req.body.post.trim()
});
post
.save()
.then((result) => {
res.send(result);
})
.catch((err) => console.log(err));
}
);
I feel like in app.post the .save() isn't waiting for the insert to complete. Is my implementation wrong? Here's my Store:
export const actions = {
async getPosts() {
let res = await this.$axios.get(`/posts`);
return res;
}
}
export const mutations = {
async savePost(state, data) {
let res = await this.$axios.post('/posts', {post: data});
return res;
}
}
And here's my index.vue file:
export default {
components: {},
data: () => ({
posts:[],
confession: ""
}),
mounted(){
this.getPosts();
},
methods: {
async getPosts() {
let res = await this.$store.dispatch('getPosts');
this.posts = res;
},
async savePost(payload) {
let res = await this.$store.commit('savePost', payload);
return res;
},
clear(){
this.confession = "";
},
focusInput() {
this.$refs.confession.focus();
},
onSubmit() {
this
.savePost(this.confession.trim())
.then((result) => {
this.playSound();
this.getPosts();
this.clear();
this.focusInput();
});
},
playSound: function(){
// sound code
}
}
}
}
Maybe you can try to add w: "majority" option in save method.
Mongoose Documentation of save options
MongoDB Documentation for further explanation of 'writeConcern'
app.post(
'/posts',
(req, res) => {
const post = new Post({
body: req.body.post.trim()
});
post
.save({w: "majority"})
.then((result) => {
res.send(result);
})
.catch((err) => console.log(err));
}
);
Here I am making a fetch request to an api -
export async function getServerSideProps(ctx) {
let id = ctx.params.id;
let userObject;
let userId;
const cookie = parseCookies(ctx);
if (cookie.auth) {
userObject = JSON.parse(cookie.auth);
userId = userObject.id;
}
if (!userId) {
return {
redirect: {
permanent: false,
destination: '/',
},
};
}
const res = await fetch(`http://localhost:3000/api/tests/${id}`);
console.log(await res.json());
const data = await res.json();
console.log(data);
// return {
// props: { product: data },
// };
return {
props: {},
};
}
Here I am reading data from firebase realtime database -
export default async (req, res) => {
const { id } = req.query;
console.log(id);
let obj;
firebase
.database()
.ref('/test/' + id)
.once('value')
.then(snapshot => {
console.log('here');
const data = snapshot.val();
obj = data;
})
.then(() => res.status(200).json(obj))
.catch(err => console.log(err));
};
Which gives me this error -
Server Error FetchError: invalid json response body at https://localhost:3000/api/tests/-MUT5-DbK6Ff6CstPSGc reason: Unexpected end of JSON input
Everything seems to work except the json response I am getting after making fetch request. I can't even console.log to see what response I am actually getting. What am I missing?
Edit - Here's my firebase database structure, where test node is root node
There is no return in your promise. That's why obj is null. Instead of then just send the response in first capture.
export default async (req, res) => {
const { id } = req.query;
console.log(id);
let obj;
firebase
.database()
.ref('/test/' + id)
.once('value')
.then(snapshot => {
console.log('here');
const data = snapshot.val();
obj = data;
res.status(200).json(obj)
})
.catch(err => console.log(err));
};
const axios = require("axios");
const Parser = require("./utils/parser");
const WebStore = require("./models/webstore");
const mongoose = require("mongoose");
require("./db/connection");
require("dotenv").config();
const url = "";
const app_secret = process.env.APP_SECRET;
const buff = new Buffer(app_secret);
const base64 = buff.toString("base64");
axios
.get(url, {
headers: {
Accept: "application/json",
Authorization: `Basic ${base64}`,
},
params: {
from_date: "2020-02-19",
to_date: "2021-02-21",
},
})
.then(async (response) => {
const data = response.data.split("\n");
const events = [];
data.forEach((point) => {
try {
const dat = Parser.toDB(JSON.parse(Parser.parser(point)));
events.push(dat);
} catch (err) {}
});
events.forEach(async (event) => {
try {
const e = new WebStore(event);
await e.save();
console.log("saved");
} catch (err) {
console.log("fail");
}
});
})
.catch((err) => {
console.error(err);
});
So After the execution the script outputs saved multiple times but the script is not closed itself.
The data is stored in the database after the execution I have tried mongoose.disconnect() and I've also tried mongoose.connection.close(). How do I resolve this issue ?
I'm getting the following error:
TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at stringify (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:1123:12)
at ServerResponse.json (/usr/local/lib/node_modules/firebase-tools/node_modules/express/lib/response.js:260:14)
at cors (/Users/landing-page-backend/functions/zohoCrmHook.js:45:43)
at process._tickCallback (internal/process/next_tick.js:68:7)
for the HTTP response in my createLead function, despite the fact that the function is executed properly and does what it's supposed to do (which is to create an entry in my CRM).
I've pointed out in the following code where the error occurs:
const axios = require('axios');
const functions = require('firebase-functions');
const cors = require('cors')({ origin: true })
const clientId = functions.config().zoho.client_id;
const clientSecret = functions.config().zoho.client_secret;
const refreshToken = functions.config().zoho.refresh_token;
const baseURL = 'https://accounts.zoho.com';
module.exports = (req, res) => {
cors(req, res, async () => {
const newLead = {
'data': [
{
'Email': String(req.body.email),
'Last_Name': String(req.body.lastName),
'First_Name': String(req.body.firstName),
}
],
'trigger': [
'approval',
'workflow',
'blueprint'
]
};
const { data } = await getAccessToken();
const accessToken = data.access_token;
const leads = await getLeads(accessToken);
const result = checkLeads(leads.data.data, newLead.data[0].Email);
if (result.length < 1) {
try {
return res.json(await createLead(accessToken, newLead)); // this is where the error occurs
} catch (e) {
console.log(e);
}
} else {
return res.json({ message: 'Lead already in CRM' })
}
})
}
function getAccessToken () {
const url = `https://accounts.zoho.com/oauth/v2/token?refresh_token=${refreshToken}&client_id=${clientId}&client_secret=${clientSecret}&grant_type=refresh_token`;
return new Promise((resolve, reject) => {
axios.post(url)
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getAccessToken error", e))
});
}
function getLeads(token) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
axios.get(url, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
return resolve(response);
})
.catch(e => console.log("getLeads error", e))
})
}
function createLead(token, lead) {
const url = 'https://www.zohoapis.com/crm/v2/Leads';
return new Promise((resolve, reject) => {
const data = JSON.stringify(lead);
axios.post(url, data, {
headers: {
'Authorization': `Zoho-oauthtoken ${token}`
}
})
.then((response) => {
console.log("response in createLead", response)
return resolve(response);
})
.catch(e => reject(e))
})
}
function checkLeads(leads, currentLead) {
return leads.filter(lead => lead.Email === currentLead)
}
Console.logging all the parameters indicate that the they are not the issue. The configuration of the Zoho API is, also, not the issue considering the fact that the entries are being properly made into the CRM. My assumption is that it would have to do with the HTTP response in the JSON format.
You're trying to convert a promise to JSON, not going to work. Your createLead function returns a promise, not a JSON. The promise is the 'circular object'.