Firebase Functions Error, "db.batch() is not a function" - node.js

I've been having an issue with Firebase functions where when I deploy this function and try to change a user image, I get an error in the firebase function logs that says "Error, db.batch is not a function".
exports.onUserImageChange = functions
.firestore.document('/users/{userId}')
.onUpdate((change) => {
console.log(change.before.data());
console.log(change.after.data());
if (change.before.data().imageUrl !== change.after.data().imageUrl) {
console.log('image has changed');
var batch = db.batch();
return db
.collection('screams')
.where('userHandle', '==', change.before.data().handle)
.get()
.then((data) => {
data.forEach((doc) => {
const scream = db.doc(`/screams/${doc.id}`);
batch.update(scream, { userImage: change.after.data().imageUrl });
});
return batch.commit();
});
} else return true;
});
This is my db export:
const admin = require('firebase-admin')
admin.initializeApp();
const db = admin.firestore();
module.exports = { admin, db};

I guess the issue is that you have either not imported db correctly or you might have forgotten to assign the db variable before calling it.

Related

Callable cloud functions - handle error in android

im trying to delete an user from firestore and from auth.
I have this callable cloud function:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
await db.collection(collection)
.doc(uid)
.delete();
await auth.deleteUser(uid);
return uid;
}
in android i have this:
fun deleteFromFirebase(){
val data = hashMapOf(
"userEmail" to user.email,
"collection" to "User"
)
functions // Optional region: .getInstance("europe-west1")
.getHttpsCallable("deleteUser")
.call(data)
.addOnCompleteListener() { task ->
if(!task.isSuccessful)
{
Log.d("User", "ERROR")
val e = task.exception
if (e != null) {
Log.d("Admin", e.message.toString())
}
}else{
Log.d("User", "Deleted")
//make something
}
}
}
If the user in auth and the document nin firestore exist, works great.
But i tryed to generate some error.
So I deleted the user from auth and ran the function. The Android log says D/User: User deleted
but in the console from google cloud:
Function execution took 1878 ms, finished with status code: 200
Exception from a finished function: Error: There is no user record corresponding to the provided identifier.
How can I handle the error and get correctly in android? Thanks!
The deleteUserByEmail function is async and returns a Promise. Your return statement runs before the promises is resolved. Try refactoring the code as shown below:
export const deleteUser = functions.https.onCall(async (data, context) => {
const userEmail = data.userEmail;
const collection = data.collection;
try {
// add await, continues after Promise is resolved
await deleteUserByEmail(userEmail, collection)
return "deleted!"
} catch (error) {
console.log(error) // <-- check for any errors
throw new functions.https.HttpsError('invalid-argument', 'there is no user with that email', error);
}
})
async function deleteUserByEmail(userEmail: string, collection: string) {
const auth = admin.auth();
const db = admin.firestore();
const { uid } = await auth.getUserByEmail(userEmail);
return await Promise.all([
db.collection(collection).doc(uid).delete(),
auth.deleteUser(uid)
])
}

Promise.all() throwing error while connecting to database Error: timeout exceeded when used with Node-Postgres

I have Node.js express app with Postgres as a database. I'm using pg for database communication from the app.
This is how my db.service looks like
import { Pool } from 'pg';
const dbConfig = {/*my valid db configuration*/};
const pool = new Pool(dbConfig);
export const connectDB = async () => {
let client;
try {
client = await pool.connect();
} catch (error) {
console.error('error while connecting to database', error);
process.exit(1);
}
return client;
};
I have two queries as below
#1.
export const fetchUser = async (email) => {
const client = await connectDB();
const query = `
SELECT full_name FROM app.users
WHERE email = '${email}'
`;
let result;
try {
result = await client.query(query);
if (result.rowCount) {
return result.rows[0].full_name;
}
} catch (error) {
} finally {
await client.release();
}
return result;
};
#2
export const fetchWallet = async (email) => {
const client = await connectDB();
const query = `
SELECT wallet_money FROM app.user_wallet
WHERE email = '${email}'
`;
let result;
try {
result = await client.query(query);
if (result.rowCount) {
return result.rows[0].wallet_money;
}
} catch (error) {
} finally {
await client.release();
}
return result;
};
Now from one of my controller.js if I call these function separate await, no issues
ctrl.js
const fullName = await fetchUser('some#gmail.com');
const walletMoney = await fetchWallet('some#gmail.com');
No issues this way, however if I merge them into a single promise
const $0= fetchUser('some#gmail.com');
const $1= fetchWallet('some#gmail.com');
const result = await Promise.all([$0, $1]);
this throws the below error
error while connecting to database Error: timeout exceeded when trying
to connect at Error
Please suggest why this error is popping up & how can I get rid of it?
Thanks!
That's happening because you are trying to connect to DB separately for every query.Try to create one connection and use it for all queries!

Mocha/Chai expect an error when calling an async function

Im using mocha / chai to to do some testing.
The function is supposed to throw new Error if called with the same arguments.
The function:
Create a new user and save it do my database (mongoDB), if a user with the same discord id doesn't exist already. fetchUser(disc_id)searches for an existing user, by using findOne() from mongoose.
async function createUser(disc_id, moodleToken) {
const profileData = await fetchUser(disc_id);
if (profileData) {
throw new Error("The account already exist in the database!");
}
const newUser = profileModel.create({
discord_id: disc_id,
moodle_token: moodleToken,
}, (err) => {
if (err) throw err;
console.log("Document created!")
})
return newUser;
};
fetchUser
function fetchUser (disc_id) {
return profileModel.findOne({ discord_id: disc_id });
}
Testing
Im using sinon to create the "test-database". The following test passes just fine
describe("Create user", function () {
it("Should create a new user in database if the discord id is not in the database", async function () {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
sinon.stub(profileSchema, "findOne").returns(null);
sinon.stub(profileSchema, "create").returns({
discord_id, moodle_token
});
const returnedUser = await createUser(discord_id, moodle_token);
expect(returnedUser.discord_id).to.equal(discord_id);
})
This tests that it is possible to create and save a new user to my database, if no existing user has the same discord id.
If I want to test the opposite, to create a new user, but a current user already exists with the same id, it should throw the error: "The account already exist in the database!"
it("Should throw an error if there exists a user with that discord id", async () => {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
const fakeObject = {
discord_id: discord_id,
moodle_token: moodle_token
};
sinon.stub(profileSchema, "findOne").returns(fakeObject);
sinon.stub(profileSchema, "create").returns({ discord_id, moodle_token })
I have tried to following without success:
try {
await createUser(discord_id, moodle_token);
} catch (err) {
console.log(err);
expect(err).to.equal("The account already exist in the database!");
}
expect(async function () { await createUser(discord_id, moodle_token); }).to.throw("The account already exist in the database!");
expect(() => { createUser(discord_id, moodle_token)}).to.throw("The account already exist in the database!");
rejects(async () => { await createUser(discord_id, moodle_token); })
How should i test such a function?
I found a way to solve the problem, i restructured my createUser() to this:
async function createUser(disc_id, moodleToken) {
const profileData = await fetchUser(disc_id);
if (profileData) {
throw TypeError("The account already exist in the database!");
}
const newUser = new profileModel({
discord_id: disc_id,
moodle_token: moodleToken,
})
await newUser.save();
return newUser;
}
The test passes if they are like this:
it("Should throw an error if there exists a user with that discord id", async () => {
const discord_id = "2341243451";
const moodle_token = "fwefw89fwefHFEF0F90ls";
const fakeObject = {
discord_id: "2341243451",
moodle_token: "fwefw89fwefHFEF0F90ls"
};
sinon.stub(profileSchema, "findOne").returns(fakeObject);
await createUser(discord_id, moodle_token).catch((error) => {
expect(error.message).to.equal("The account already exist in the database!")
})
Don't know if it is the right way to do it, but if i change sinon.stub to return null the test wont pass.
So i guess this is a way to handle it.

Firebase function works when deployed but not locally

I have a function that just fetches the data from my firebase and displays it. This works perfectly when deployed, but not locally.
I've attached my code just in case, but seeing as it works when deployed, I dont think that will be the problem, also its copy pasted from freecodecamp tutorial.
Directory is as follows:
firebase folder
|functions
||APIs
|||todos.js
||util
|||admin.js
||index.js
Also, the local version does have an output, its just the empty array initialised in todos.js line 9.
//todos.js
const { db } = require('complete file path');
exports.getAllTodos = (request, response) => {
db
.collection('todos')
.orderBy('createdAt', 'desc')
.get()
.then((data) => {
let todos = [];
data.forEach((doc) => {
todos.push({
todoId: doc.id,
title: doc.data().title,
body: doc.data().body,
createdAt: doc.data().createdAt,
});
});
return response.json(todos);
})
.catch((err) => {
console.error(err);
return response.status(500).json({ error: err.code});
});
};
//admin.js
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
module.exports = { admin, db };
//index.js
const functions = require('firebase-functions');
const app = require('express')();
const {
getAllTodos
} = require('./APIs/todos')
app.get('/todos', getAllTodos);
exports.api = functions.https.onRequest(app);
I also performed export GOOGLE_APPLICATION_CREDENTIALS="path/to/key.json" to no avail.
You initialized the the app without any credentials:
const refreshToken; // Get refresh token from OAuth2 flow
admin.initializeApp({
credential: admin.credential.refreshToken(refreshToken),
databaseURL: 'https://<DATABASE_NAME>.firebaseio.com'
});
[Reference this site for more information:] https://firebase.google.com/docs/admin/setup/#initialize-without-parameters

How to send data via HTTP request to Firestore database

So, I am trying to make an app with Node.js, Vue, and Firebase. I currently have a very basic function using firebase functions to send data to the realtime database via http request. I cant figure out how to do the same for Firestore database.
My goal is to send data or a file of some JSON data to my Firestore database from outside the app with an HTTP request. I know how to deploy functions I just am not sure how to send files or JSON to the database for further use.
Any help is greatly appreciated!
I have been playing around with different functions but can't seem to figure it out. I have looked at many of the docs/videos for firebase but I am pretty knew to this and can't quite grasp it.
const functions = require('firebase-functions');
const admin = require('firebase-admin')
admin.initializeApp() //need admin sdk
const toUpperCase = (string) => string.toUpperCase();
exports.addMessage = functions.https.onRequest((request, response) => {
const text = request.query.text;
const secretText = toUpperCase(text);
admin
.database()
.ref('/sentMSG')
.push({ text: secretText })
.then(() =>
response.json({
message: 'great!!!!',
text
})
)
.catch(() => {
response.json({
message: 'not great :^('
});
});
});
exports.writeToStore = functions.firestore.https.onRequest((request,
response) => {
let data = request.query.text
let setDoc = db.collection('cities').doc('LA').set(data)
.then(() =>
response.json({
message: 'great!!!!',
text
})
)
.catch(() => {
response.json({
message: 'not great :^('
});
});
});
The addMessage function adds data to realtime database but I need to do it for Firestore
index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const _ = require('lodash');
admin.initializeApp();
const db = admin.firestore();
const express = require('express');
const cors = require('cors');
const app = express();
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.get('/', async (req, res) => {
let data = req.query.data;
try {
await db.collection('users').doc().set({ userId: data });
} catch(err) { res.send(JSON.stringify(err)) }
res.send('Success');
})
app.post('/', async (req, res) => {
let payload = req.body;
let keys = Object.keys(payload);
let obj = {};
let i = 0;
try {
_.forEach(payload, async data => {
obj[keys[i]] = data;
i++;
})
await db.collection('users').doc().set(obj);
} catch(err) { res.send(JSON.stringify(err))}
res.send('Success');
})
exports.writeToFirestore = functions.https.onRequest(app);
You can then call this Cloud Function like such: linkToURL/writeToFirestore?data=5 or make a Post request to linkURL/writeToFirestore
From what I understand, whenever the Cloud Function is triggered, you want to write data in both the Firebase Realtime Database and in Firestore. Cloud Function will only execute one function in the exports object. In this case, since the function is being triggered by HTTPS, you can create a single exports.writeToBoth that encapsulates the code to write in Realtime DB and Firestore. Furthermore, I noticed that the writeToFirestore written is invalid, you need to change functions.firestore.https.onRequest to functions.https.onRequest.
In any case, you can refer to this code as a base, should you want:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
let writeToFirestore = data => {
let setDoc = db.collection('users').doc('0').set(data);
}
let writeToRealtimeDB = data => {
// ...
// Your code
// ...
}
exports.writeToBoth = functions.https.onRequest((req, res) => {
let data = req.query.text;
// Write to both DB
try {
writeToFirestore(data);
writeToRealtimeDB(data);
} catch(err) { res.send('Error') }
res.send('Success');
})

Resources