How to call an async firebase function from other async function - node.js

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

Related

Push Data to MongoDB on Google SignIn Firebase

I wanted to write a method where onClick the google sign in starts and after successful sign in it makes a post request to my API.But the weird problem is 30% of the times the sign in data doesnt come to mongo db.I even called signout function in the catch block.Please help if someone notice any error!!
const Hero = () => {
const [user, setUser] = useState(null);
const [fetchUser, setFetchUser] = useState(null);
const handleGoogleSignIn = () => {
const googleProvider = new GoogleAuthProvider();
signInWithPopup(auth, googleProvider)
.then(async (result) => {
console.log(result);
try {
const { data } = await axios.post(
"https://myAPIherokuapp.com/api/v1/9c142e80023e07c3/registerUser",
{ name: result.user.displayName, email: result.user.email }
);
console.log(data);
} catch (err) {
console.log(err);
signOut(auth)
}
})
.catch((error) => {
console.log(error);
});
};
Maybe try async/await at the handleGoogleSignIn level? e.g.
const handleGoogleSignIn = async () => {
const googleProvider = await new GoogleAuthProvider();
const userResult = await signInWithPopup(auth, googleProvier);
await axios.post('url', userResult);
...
}
I think that should help?

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

Fastifyis not defined and fastify-postgres Fastify.pg.connect not working

I have defined my fastify like so:
const fastify = require('fastify')({logger: true})
fastify
.register(require('./setup/envs'))
.after(async (err) => {
if (err) console.log(err);
fastify.register(require('./setup/db'))
await fastify.after()
fastify.register(require('./setup/jwt'))
await fastify.after()
fastify.register(require('./setup/auth'))
await fastify.after()
// load routes
// fastify.register(require('./routes/test'))
fastify.register(require('./routes/posts/index'), { prefix: '/posts' })
})
const start = async () => {
try {
await fastify.ready()
fastify.listen(3000)
} catch (e) {
fastify.log.error(e)
process.exit(1)
}
}
start();
Now in my post routes, I have the following:
const postRoutes = require('../../controllers/posts');
async function PostRoutes(fastify, options) {
fastify.addHook('preValidation', fastify.verifyJWT)
fastify.addHook('preHandler', function (req, reply, done) {
if (req.body) {
req.log.info({ body: req.body }, 'parsed body')
}
if (req.params) {
req.log.info({ params: req.body }, 'parsed params')
}
done()
})
// get all posts
fastify.get('/:limit/:page', postRoutes.getPosts)
fastify.post('/signup', async (req, reply) => {
// some code
const token = fastify.jwt.sign({ test: 'hello' })
reply.send({ token })
})
fastify.get(
"/test-auth",
async function(request, reply) {
return { test: 'auth' }
}
)
}
module.exports = PostRoutes;
and my controller file is the following:
const fastify = require('fastify');
const getPosts = async (req, reply) => {
try {
const client = await fastify.pg.connect()
const { rows } = await client.query(
'SELECT * FROM POSTS LIMIT $1 OFFSET $2;', [req.params.limit, req.params.offset],
)
client.release()
return rows
} catch (e) {
req.log.error('Getting posts failed with params')
throw new Error(e)
}
}
module.exports = {
getPosts,
};
The const client = await fastify.pg.connect() is giving me fastify is not defined and if I require it by adding const fastify = require('fastify') at the top, I get TypeError: Cannot read properties of undefined (reading 'connect').
the following is my db.js:
const fastifyPlugin = require('fastify-plugin');
const fastifyPostgres = require('fastify-postgres');
async function dbConnector (fastify, options) {
const dbUser = encodeURIComponent(fastify.config.DATABASE_USERNAME)
const dbPassword = encodeURIComponent(fastify.config.DATABASE_PASSWORD);
const dbHost = encodeURIComponent(process.env.DATABASE_HOST);
const dbName = encodeURIComponent(fastify.config.DATABASE_NAME);
fastify.register(fastifyPostgres, {
connectionString: `postgres://${dbUser}:${dbPassword}#${dbHost}/${dbName}`
})
}
// Wrapping a plugin function with fastify-plugin exposes the decorators
// and hooks, declared inside the plugin to the parent scope.
module.exports = fastifyPlugin(dbConnector);
I had to change my route to
fastify.get('/:limit/:page', (req, reply) => postRoutes.getPosts(fastify, req, reply))
and then I was able to access the fastify object in my controller.

Make multiple firestore queries in a cloud function

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

How to do Integration tests NodeJS + Firebase Admin?

I'm trying to write some integration tests on NodeJS with Firebase (firebase-admin, with the test library jest and supertest), and some tests are failing randomly when I run all my tests. Separately, my tests are passing, but it seems like when too many test cases are running, some api calls are failing. Does someone here already had such problem? What are the solutions for this problem? What could cause this problem? (NB: I run my tests sequentially for not mixing up the initialization of my database. I use the option --runInBand with jest)
There are some mocking libraries available, but it seems like they work with the old api of firebase.
Another solution would be to mock all my functions that manipulate firebase, but I won't have a "real" integration test anymore, and it means doing a lot of extra coding for writing those mock. Is it a best practice to do so?
Thank you in advance!
EDIT: code snippet:
initTest.js:
const request = require('supertest');
const net = require('net');
const app = require('../src/server').default;
export const initServer = () => {
const server = net.createServer(function(sock) {
sock.end('Hello world\n');
});
return server
}
export const createAdminAndReturnToken = async (password) => {
await request(app.callback())
.post('/admin/users/sa')
.set('auth','')
.send({password});
// user logs in
const res = await request(app.callback())
.post('/web/users/login')
.set('auth','')
.send({email:"sa#optimetriks.com",password})
return res.body.data.token;
}
utils.ts:
import firestore from "../../src/tools/firestore/index";
export async function execOperations(operations,action,obj) {
if (process.env.NODE_ENV === "test") {
await Promise.all(operations)
.then(() => {
console.log(action+" "+obj+" in database");
})
.catch(() => {
console.log("Error", "error while "+action+"ing "+obj+" to database");
});
} else {
console.log(
"Error",
"cannot execute this action outside from the test environment"
);
}
}
//////////////////////// Delete collections ////////////////////////
export async function deleteAllCollections() {
const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
collections.forEach(collection => {
deleteCollection(collection);
});
}
export async function deleteCollection(collectionPath) {
const batchSize = 10;
var collectionRef = firestore.collection(collectionPath);
var query = collectionRef.orderBy("__name__").limit(batchSize);
return await new Promise((resolve, reject) => {
deleteQueryBatch(firestore, query, batchSize, resolve, reject);
});
}
async function deleteQueryBatch(firestore, query, batchSize, resolve, reject) {
query
.get()
.then(snapshot => {
// When there are no documents left, we are done
if (snapshot.size == 0) {
return 0;
}
// Delete documents in a batch
var batch = firestore.batch();
snapshot.docs.forEach(doc => {
batch.delete(doc.ref);
});
return batch.commit().then(() => {
return snapshot.size;
});
})
.then(numDeleted => {
if (numDeleted === 0) {
resolve();
return;
}
// Recurse on the next process tick, to avoid
// exploding the stack.
process.nextTick(() => {
deleteQueryBatch(firestore, query, batchSize, resolve, reject);
});
})
.catch(reject);
}
populateClient.ts:
import firestore from "../../src/tools/firestore/index";
import {execOperations} from "./utils";
import { generateClientData } from "../factory/clientFactory";
jest.setTimeout(10000); // some actions here needs more than the standard 5s timeout of jest
// CLIENT
export async function addClient(client) {
const clientData = await generateClientData(client);
await firestore
.collection("clients")
.doc(clientData.id)
.set(clientData)
}
export async function addClients(clientNb) {
let operations = [];
for (let i = 0; i < clientNb; i++) {
const clientData = await generateClientData({});
operations.push(
await firestore
.collection("clients")
.doc(clientData.id)
.set(clientData)
);
}
await execOperations(operations,"add","client");
}
retrieveClient.ts:
import firestore from "../../src/tools/firestore/index";
import { resolveSnapshotData } from "../../src/tools/tools";
export async function getAllClients() {
return new Promise((resolve, reject) => {
firestore
.collection("clients")
.get()
.then(data => {
resolveSnapshotData(data, resolve);
})
.catch(err => reject(err));
});
}
clients.test.js:
const request = require('supertest');
const app = require('../../../src/server').default;
const {deleteAllCollections, deleteCollection} = require('../../../__utils__/populate/utils')
const {addClient} = require('../../../__utils__/populate/populateClient')
const {getAllClients} = require('../../../__utils__/retrieve/retrieveClient')
const {initServer,createAdminAndReturnToken} = require('../../../__utils__/initTest');
const faker = require('faker');
let token_admin;
let _server;
// for simplicity, we use the same password for every users
const password = "secretpassword";
beforeAll(async () => {
_server = initServer(); // start
await deleteAllCollections()
// create a super admin, login and store the token
token_admin = await createAdminAndReturnToken(password);
_server.close(); // stop
})
afterAll(async () => {
// remove the users created during the campaign
_server = initServer(); // start
await deleteAllCollections()
_server.close(); // stop
})
describe('Manage client', () => {
beforeEach(() => {
_server = initServer(); // start
})
afterEach(async () => {
await deleteCollection("clients")
_server.close(); // stop
})
describe('Get All clients', () => {
const exec = (token) => {
return request(app.callback())
.get('/clients')
.set('auth',token)
}
it('should return a 200 when super admin provide the action', async () => {
const res = await exec(token_admin);
expect(res.status).toBe(200);
});
it('should contain an empty array while no client registered', async () => {
const res = await exec(token_admin);
expect(res.body.data.clients).toEqual([]);
});
it('should contain an array with one item while a client is registered', async () => {
// add a client
const clientId = faker.random.uuid();
await addClient({name:"client name",description:"client description",id:clientId})
// call get clients and check the result
const res = await exec(token_admin);
expect(res.body.data.clients.length).toBe(1);
expect(res.body.data.clients[0]).toHaveProperty('name','client name');
expect(res.body.data.clients[0]).toHaveProperty('description','client description');
expect(res.body.data.clients[0]).toHaveProperty('id',clientId);
});
})
describe('Get client by ID', () => {
const exec = (token,clientId) => {
return request(app.callback())
.get('/clients/' + clientId)
.set('auth',token)
}
it('should return a 200 when super admin provide the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId);
expect(res.status).toBe(200);
});
it('should return a 404 when the client does not exist', async () => {
const nonExistingClientId = faker.random.uuid();
const res = await exec(token_admin,nonExistingClientId);
expect(res.status).toBe(404);
});
})
describe('Update client', () => {
const exec = (token,clientId,client) => {
return request(app.callback())
.patch('/clients/' + clientId)
.set('auth',token)
.send(client);
}
const clientModified = {
name:"name modified",
description:"description modified",
app_user_licenses: 15
}
it('should return a 200 when super admin provide the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId,clientModified);
expect(res.status).toBe(200);
// check if the client id modified
let clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('name',clientModified.name);
expect(clients[0]).toHaveProperty('description',clientModified.description);
expect(clients[0]).toHaveProperty('app_user_licenses',clientModified.app_user_licenses);
});
it('should return a 404 when the client does not exist', async () => {
const nonExistingClientId = faker.random.uuid();
const res = await exec(token_admin,nonExistingClientId,clientModified);
expect(res.status).toBe(404);
});
})
describe('Create client', () => {
const exec = (token,client) => {
return request(app.callback())
.post('/clients')
.set('auth',token)
.send(client);
}
it('should return a 200 when super admin does the action', async () => {
const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
expect(res.status).toBe(200);
});
it('list of clients should be appended when a new client is created', async () => {
let clients = await getAllClients();
expect(clients.length).toBe(0);
const res = await exec(token_admin,{name:"clientA",description:"description for clientA"});
expect(res.status).toBe(200);
clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('name','clientA');
expect(clients[0]).toHaveProperty('description','description for clientA');
});
});
describe('Delete client', () => {
const exec = (token,clientId) => {
return request(app.callback())
.delete('/clients/'+ clientId)
.set('auth',token);
}
it('should return a 200 when super admin does the action', async () => {
const clientId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,clientId);
expect(res.status).toBe(200);
});
it('should return a 404 when trying to delete a non-existing id', async () => {
const clientId = faker.random.uuid();
const nonExistingId = faker.random.uuid();
await addClient({id:clientId})
const res = await exec(token_admin,nonExistingId);
expect(res.status).toBe(404);
});
it('the client deleted should be removed from the list of clients', async () => {
const clientIdToDelete = faker.random.uuid();
const clientIdToRemain = faker.random.uuid();
await addClient({id:clientIdToRemain})
await addClient({id:clientIdToDelete})
let clients = await getAllClients();
expect(clients.length).toBe(2);
await exec(token_admin,clientIdToDelete);
clients = await getAllClients();
expect(clients.length).toBe(1);
expect(clients[0]).toHaveProperty('id',clientIdToRemain);
});
});
})
jest command: jest --coverage --forceExit --runInBand --collectCoverageFrom=src/**/*ts
I found the problem: I had a problem on the "deleteAllCollection" function, I forgot to put an "await".
Here is the correction for this function:
export async function deleteAllCollections() {
const collections = ["clients", "web_users","clients_web_users","clients_app_users","app_users"];
for (const collection of collections) {
await deleteCollection(collection);
};
}

Resources