Make multiple firestore queries in a cloud function - node.js

I want to create a scheduled cloud function that is generating employees bonus at the end of each month.
To do that, i need a list of all employees, of all invoices of that user and of all existing bonus, all contained in firestore collections.
So i need 3 firestore collections but can't find any solution on how to do query that in a cloud function.
i tried this for now :
exports.generateBonus = functions.https.onRequest(async (req, res) => {
var listEmployee = [];
var listInvoice = [];
const employeeRef = admin.firestore().collection('employee');
const invoiceRef = admin.firestore().collection('invoice');
const promiseFacture = new Promise((resolve,reject)=>{
return factureRef.get();
})
.then(list_invoice => {
listInvoice = list_invoice.docs.map(doc => {
return doc.data();
});
})
.catch(error => {
console.log("got an error",error);
});
const promiseEmployee = new Promise((resolve,reject)=>{
return employeeRef.get();
})
.then(list_employee => {
listEmployee = list_user.docs.map(doc => {
return doc.data();
});
})
.catch(error => {
console.log("got an error",error);
});
Promise.all([promiseInvoice, promiseEmployee])
.then((values) => {
console.log(values);
return res.send('ok');
})
.catch(error => {
console.log(error);
})
});
But it return me two empty arrays in 1 sec
Does anyone know how to do this ? Thank you

The following, using destructuring assignment syntax, should do the trick:
exports.generateBonus = functions.https.onRequest(async (req, res) => {
const employeesRef = admin.firestore().collection('employee');
const invoicesRef = admin.firestore().collection('invoice');
const [employeesSnapshot, invoicesSnapshot] = await Promise.all([employeesRef.get(), invoicesRef.get()]);
const listEmployees = employeesSnapshot.docs;
const listInvoices = invoicesSnapshot.docs;
//Logging
listEmployees.forEach(snap => {
console.log(snap.data());
});
listInvoices.forEach(snap => {
console.log(snap.data());
});
//...
res.status(200).send(...); //Adapt the ... to a meaningful value
});
Note that the get() method returns a Promise, so you don't need to wrap it in another Promise.
(note that I have added an s to all the collections/snapshots variables names).

Related

How to call an async firebase function from other async function

Using three functions (getViews, getViewCount and updateCount) I want to retrieve a youtube view count and then store it in a firestore database. Both functions work asynchronously, but when I call getViews() inside updateCount() I get the following error within updateCount:
TypeError: Cannot read properties of undefined (reading 'on') which refers to a promise.
Please let me know am I doing wrong here! Code below:
getViews:
exports.getViews = functions
.runWith({
secrets: ["YOUTUBE_API"]
})
.https.onCall(async (data, context) => {
const count = await getViewCount({});
return count;
});
updateCount:
exports.updateCount = functions.https.onRequest(async (req, res) => {
const viewData = await this.getViews({ "h": "j" }); //Error occurs here
const addData = await admin
.firestore()
.collection("viewCount")
.doc("Count")
.set(viewData)
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
});
getViewCount:
const getViewCount = async (arg) => {
const youtube = google.youtube({
version: "v3",
auth: process.env.YOUTUBE_API,
});
const count = await youtube.channels.list({
id: process.env.YOUTUBE_CHANNEL_ID,
part: "statistics",
});
const countData = count.data.items[0].statistics.viewCount;
return countData;
}
If you want to use the code in getViews() Cloud Function as well, then it might be better to move that to a different function. Try refactoring the code as shown below:
exports.getViews = functions
.runWith({
secrets: ["YOUTUBE_API"]
})
.https.onCall(async (data, context) => {
const count = await getViewCount({}); // <-- pass required arguments
return count;
})
exports.updateCount = functions.https.onRequest(async (req, res) => {
const viewData = await getViewCount({ "h": "j" });
const addData = await admin
.firestore()
.collection("viewCount")
.doc("Count")
.set(viewData)
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
});
// not a Cloud Function
const getViewCount = async (arg) => {
const youtube = google.youtube({
version: "v3",
auth: process.env.YOUTUBE_API,
});
const count = await youtube.channels.list({
id: process.env.YOUTUBE_CHANNEL_ID,
part: "statistics",
});
const countData = count.data.items[0].statistics.viewCount;
return countData;
}

Nodejs Function is returning undefined

I have 2 files index.js and models.js
my index.js file requires the model.js file
const models = require("./modules/models.js");
app.post('/message', (req, res) => {
const _lead = models.createLead(req.body)
_lead.then(l => {
console.log("resulted in lead", JSON.stringify(l))
// GETTING UNDEFINED HERE
})
// const _message = await models.createMessage(req.body.message)
res.send();
});
My models.js file has a functions that contain Promises
const createLead = async function (payload) {
// Find lead
let result
new Promise((resolve, reject) => {
findLeadById(payload.lead.rid).then(async _lead => {
// If a lead has not been found
if (!_lead) {
if(payload.companyRid != payload.lead.rid){
await db.collection('leads').add(payload.lead).then((l) => {
result = payload.lead
result['id'] = l.id
})
}
} else {
result = _lead
}
}).then(() => {
console.log(" result of create lead ",JSON.stringify(result))
resolve(result)
}).catch(error => {
error.log("reject", error)
reject(error)
})
})
}
const findLeadById = async function (lead) {
new Promise(async (resolve, reject) => {
const ref = db.collection('leads');
console.log("finding lead", JSON.stringify(lead))
await ref.where("rid", "==", lead).get().then((s) => {
let obj = null
if(s){
s.forEach(doc => {
console.log("found the lead in loop", JSON.stringify(doc.data()))
obj = doc.data()
obj['id'] = doc.id
})
}
resolve(obj)
}).catch(error => {
error.log("reject", error)
reject(error)
})
})
}
exports.createLead = createLead;
exports.findLeadById = findLeadById;
The model.js returns an object as expected
console.log(" result of create lead ",JSON.stringify(result))
However, I'm expecting to see that same object in the index.js in the then block but I'm getting undefined.
console.log("resulted in lead", JSON.stringify(l))
// GETTING UNDEFINED HERE
The following is not waiting for the associated functions to finish
const _lead = models.createLead(req.body)
I have also tried adding async and await which also didn't work
const _lead = await models.createLead(req.body).then(res => {console.log("result", res) // Still undefined
})
I think you can use resolve for any success response, and reject for error response. Ex:
await db.collection('leads').add(payload.lead).then((l) => {
let result = payload.lead
result['id'] = l.id
resolve(result)
})

Problem to use a Map in Firebase Functions

I am trying to get the length of a Map and I keep getting "undefined". Could please someone tell me what am I doing wrong?
This is the part of the code that gives me problems.
const GYMdetail: { [key: string]: number} = {};
GYMdetail[`${doc.data().name} (${doc.data().personalID})`] = 650;
const subtotal = 650 * GYMdetail.size;
This is the complete function code
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
query.get()
.then(async (allUsers) => {
allUsers.docs.forEach(async (doc) => {
if (doc != undefined) {
const houseForGym = doc.data().subscriptions.gym.house;
await admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
if (snapshot.data() == undefined) {
console.log(`${houseForGym}-${doc.data().name}: CREAR!!`);
} else if (snapshot.data()!.issued == false) {
let detail: { [key: string]: any} = {};
const GYMdetail: { [key: string]: number} = {};
detail = snapshot.data()!.detail;
GYMdetail[
`${doc.data().name} (${doc.data().personalID})`
] = 650;
const subtotal = 650 * GYMdetail.size;
detail["GYM"] = {"total": subtotal, "detail": GYMdetail};
snapshot.ref.set({"detail": detail}, {merge: true});
}
return null;
})
.catch((error) => {
console.log(
`${houseForGym} - ${doc.data().name}: ${error}`);
response.status(500).send(error);
return null;
});
}
});
response.send("i");
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
Since you are executing an asynchronous call to the database in your code, you need to return a promise from the top-level code; otherwise Cloud Functions may kill the container when the final } executes and by that time the database load won't be done yet.
So:
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
return query.get()
...
Next you'll need to ensure that all the nested get() calls also get a chance to finish before the Functions container gets terminated. For that I recommend not using await for each nested get call, but a single Promise.all for all of them:
query.get()
.then(async (allUsers) => {
const promises = [];
allUsers.docs.forEach((doc) => {
const houseForGym = doc.data().subscriptions.gym.house;
promises.push(admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
...
});
});
response.send("i");
return Promise.all(promises);
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});

how to refer to return value of a function from outside in Node.js

I've been making dynamic dropdown box that each option has the table's name of BigQuery and I want to use return value (list) that is made inside .then method in function listTables(). However it seems not to work well . I'm new to Js so could you give any tips ?? Thank you so much.
function listTables() {
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery({
projectId: 'test_project',
});
const list = [];
bigquery
.dataset("test_table")
.getTables()
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);←I want to use this list outside a function
})
.catch(err => {
console.error('ERROR:', err);
});
}
listTables();
// select tag
let slt = document.getElementById("slt");
addTables(slt);
// return data
function getList() {
return new Promise(function (onFulliflled, onRejected) {
onFulliflled(list);
});
}
function addTables(slt) {
getList()
.then((list) => {
for (item of list) {
// optionを作成
let option = document.createElement("option");
option.text = item;
option.value = item;
// optionの追加
slt.appendChild(option);
}
})
.catch((err) => {
console.error("error", err);
});
}
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);
})
RESULT
[ 'test', 'test1', 'test2', 'test3' ]
You can do this in multiple ways.
Using calllback
function listTables(callback) {
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
callback(list);
})
//...
}
listTables(function(list){
});
Using promise or async/await
function listTables() {
return new Promise(function(resolve, reject){
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
resolve(list);
})
//...
});
}
// Promise
listTables().then(function(list){
});
//Async/await
var list = await listTables();
For the await to work you also need to run in within an async function. For example wrap it in async iife.
(async function(){
var list = await listTables();
})();
I don't use await myself so this is just from top of my head and might need some changes.

How to respond with array of query results in Node/ Express/ Postgresql?

newbie here. I am trying to fetch an array of objects from my local database through two select queries, using knex.js and postgresql like so:
app.get('/trips/:name', (req, res) => {
const {name} = req.params;
db.select('tripid').from('member')
.where('name', '=', name)
.then(data => {
const array = data.map(elem => {
db.select('tripname')
.from('trip')
.where('id', '=', elem.tripid)
.then(obj => {
array.push(obj[0])
})
})
return array
})
.then(arr => res.json(arr))
.catch(err => res.status(400).json(err))
})
I want to push all the objects that the second query returns into an array and respond to the front end. However, it always returns an empty array, which I suspect because
.map
is asynchronous, so the array is returned before the loop finishes. How can I fix this? Any help is appreciated. Thanks. Sorry if my code is hard to read.
try this way using async await :
app.get('/trips/:name', async (req, res) => {
try{
const {name} = req.params;
let arr = await db.select('tripid').from('member').where('name', '=', name);
if(arr && arr.length>0){
for(let item of arr){
let tripResult = await db.select('tripname').from('trip').where('id', '=', item.tripid).catch(e=>e);
item.trip = tripResult && tripResult[0]? tripResult[0]:null;
}
}
return res.json(arr);
}catch(error){
return res.status(400).json(error);
}
}
2nd way :
app.get('/trips/:name',(req, res) => {
const {name} = req.params;
db.select('tripid').from('member').where('name', '=', name)
.then(arr =>{
Promise.all(arr.map(item =>{
db.select('tripname')
.from('trip')
.where('id', '=', item.tripid)
.then((tripResult=>{
item.trip = tripResult && tripResult[0]? tripResult[0]:null;
return item;
}).catch(e=>e);
})
).then(arr => {
return res.status(200).json(arr);
})
}).catch(err => res.status(400).json(err));

Resources