Cloud Functions Firestore import doesn't work - node.js

I'm trying to use Firestore from Cloud functions package like this:
export async function deleteDocRecursively(docRef) {
await Firestore.recursiveDelete(docRef);
}
This works:
const {Firestore} = require('#google-cloud/firestore');
This doesn't work:
import {Firestore} from '#google-cloud/firestore';
I get this error:
TS2339: Property 'recursiveDelete' does not exist on type 'typeof Firestore'.
https://www.npmjs.com/package/#google-cloud/firestore

As mentioned in the documentation, recursiveDelete is a method on an instance of Firestore class and not a static method on the class itself. Try:
import { Firestore } from '#google-cloud/firestore';
// const { Firestore } = require('#google-cloud/firestore');
const db = new Firestore();
export async function deleteDocRecursively(docRef) {
await db.recursiveDelete(docRef); // <-- not 'Firestore'
}

You are using client firestore inside firebase functions instead you should use the one which comes with admin one.
As you are using client firestore
const {Firestore} = require('#google-cloud/firestore');
Above one is working and
import {Firestore} from '#google-cloud/firestore';
Is being failed and getting below error
Property 'recursiveDelete' does not exist on type 'typeof Firestore'.
To get it work you have to use the admin one as this will run on the server.
As Firebase client SDK is meant to run in a client-side environment hence we use Client SDK using firebaseConfiguration listed in the firebase console.
While in firebase functions by initializing the Firebase Admin SDK with admin.initializeApp(), we can perform actions on behalf of your users and take advantage of the security and management features provided by Firebase.
When using firebase function it is advisable to use admin services as stated in the docs
If you have configured your firebase functions with typescript then follow this:
import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
admin.initializeApp();
const firestore = admin.firestore();
exports.deleteDocRecursively = functions.https.onRequest(async (req, res) => {
// Delete Recursively
const documentRef = firestore.collection("users").doc("<doc_id of doc to be deleted>");
await firestore.recursiveDelete(documentRef);
});
If you have configured your firebase functions with javascript follow this:
const = functions require("firebase-functions");
const = admin require("firebase-admin");
admin.initializeApp();
const firestore = admin.firestore();
exports.deleteDocRecursively = functions.https.onRequest(async (req, res) => {
// Delete Recursively
const documentRef = firestore.collection("users").doc("<doc_id of doc to be deleted>");
await firestore.recursiveDelete(documentRef);
});
For more information go through these links:
Thread using recursiveDelete

Related

Error: Can't determine Firebase Database URL

I need to execute cloud functions on a database that is not the default one, but when I try to load it I get this error:
your textError: Can't determine Firebase Database URL.
This is my code:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
const app2 = admin.initializeApp(
{
databaseUrl: "{my_db_url}",
}, "app2");
exports.MyFunction = functions.pubsub.schedule("* * * * *") .timeZone("Europe/Rome") .onRun((context) => {
...
const database = admin.database(app2);
...
});
I cannot find any solution in the documentation.
I tried to look in the documentation or for people with the same problem, but I didn't find any solution.
In the databaseUrl, I'm pasting the database url I copy from the Firebase console.

How to read RTDB randomly generated node and it's value?

Below is my typescript code and its already connected to telegram and managed to sent a message.
Currently I'm trying to tinker with it even more by reading the randomly generated node Arduino and MQ7 created and sending it as a message on telegram.
import * as functions from "firebase-functions";
import * as express from "express";
import * as cors from "cors";
import * as admin from "firebase-admin";
admin.initializeApp();
const bot = express();
bot.use(cors( {origin: true}));
bot.post("/", async function(req, res) {
const telegramText = req.body;
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name;
if (telegramText) {
const chat_id = req.body.message.chat.id;
const first_name = req.body.message.from.first_name;
const receivedMessage = req.body.message.text;
// Define your RTDB Reference
const rtdbReference = admin.database().ref("Sensor MQ7");
const mq7ref = rtdbReference.child("-NHi7dBPMlVi6hXrnI03");
const valref = mq7ref.child("MQ7");
// Fetch the data
const snap = await valref.get();
const snapValue = snap.val();
// Inject snapvalue in the response
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `${first_name}\n${receivedMessage}\n${snapValue}`,
});
}
return res.status(200).send({status: "An error occured"});
});
export const router = functions.https.onRequest(bot);
The typescript code works I figured out how to read entries from the database and write it into telegram as well as learning a little bit about the reference and functions. Right now I'm trying to figure out how to output the value whenever a new node is created. The new node are randomly generated # Arduino and mq7 gas sensor. Basically whenever the sensor picks up dangerous amount of carbon monoxide, it'll send the input to firebase, and a new node is created with a new value. I've classified the Parent = Sensor MQ7, Middle child = "randomly generated node", Last child = MQ7. Any help, advice, or recommendation is greatly appreciated. Please explain like I'm 5 because I just started doing all of it this week and I'm really really new to cloud functions. Thank you!
RTDB
The typescript code (for sending to Telegram) works and right now I'm
trying to figure out how to read entries from the database and write
it into telegram
In a Cloud Function you need to use the Node.js Admin SDK to interact with the Firebase services.
Here is how to read the data at a specific Reference in the Realtime Database:
import * as functions from "firebase-functions";
import * as express from "express";
import * as cors from "cors";
import * as admin from 'firebase-admin'; <== NEW
admin.initializeApp(); <== NEW
const bot = express();
bot.use(cors( {origin: true}));
bot.post("/", async function(req, res) {
const telegramText = req.body;
req.body.message &&
req.body.message.chat &&
req.body.message.chat.id &&
req.body.message.from &&
req.body.message.from.first_name;
if (telegramText) {
const chat_id = req.body.message.chat.id;
const first_name = req.body.message.from.first_name;
const receivedMessage = req.body.message.text;
//Define your RTDB Reference
const rtdbReference = admin.admin.database().ref('foo/bar');
// Fetch the date
const snap = await rtdbReference.get();
const snapValue = snap.val();
// Do whatever you need with snapValue to inject it in your response...
return res.status(200).send({
method: "sendMessage",
chat_id,
text: `Hello ${first_name}, \n You sent us message: ${receivedMessage}`,
});
}
return res.status(200).send({status: "An error occured"});
});
export const router = functions.https.onRequest(bot);

I need to index firebase data to algolia , rules in firebase for both read and write requires Authentication (auth!=null)

I am using the code described in the algolia docs. It is working when firebase rules allow both read and write without authentication. This does not work with data which requires authentication. What I can do to add auth in below code ?
I tried using firebase-admin, I think this method will work only when rule is changed to allow read for a single uid.
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const firebase = require('firebase');
const admin = require("firebase-admin");
var serviceAccount = require("./config/serviceAccountKey.json");
// load values from the .env file in this directory into process.env
dotenv.config();
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DATABASE_URL
});
firebase.initializeApp({
databaseURL: process.env.FIREBASE_DATABASE_URL
});
admin
.auth()
.createCustomToken('siddharth')
.then((customToken) => {
console.log(customToken);
firebase.auth().authenticateWithCustomToken(customToken);
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
// admin.auth.createCustomToken('siddharth').then(token => {
// });
const database = firebase.database();
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
database.ref('/questions').once('value', questions => {
const records = [];
questions.forEach(question => {
// get the key and data from the snapshot
const childKey = question.key;
const childData = question.val();
// We set the Algolia objectID as the Firebase .key
childData.objectID = childKey;
// Add object for indexing
records.push(childData);
});
console.log(records);
// Add or update new objects
index
.saveObjects(records)
.then(() => {
console.log('questions imported into Algolia');
})
.catch(error => {
console.error('Error when importing question into Algolia', error);
process.exit(1);
});
});
Since this seems to be a Node.js script that accesses Firebase Authentication by using service credentials with the Admin SDK, you can also use that Admin SDK to access the database. Accessing a Firebase service through the Admin SDK with service credentials gives full administrative access, and bypasses any security rules you may have configured for your database.
In code, change:
const database = firebase.database();
To:
const database = admin.database();

Firebase Admin SDK global app initialization in Node.js

I am building an Express.js app, using the Firebase Admin SDK for several features such as ID Token validation and Cloud Firestore access. In my main app.js file, I am initializing the app as:
const admin = require('firebase-admin')
const serviceAccount = require('../config/account-credentials.json')
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://databaseurl.firebaseio.com'
})
In another file, all I'm doing is importing:
const admin = require('firebase-admin')
I am able to call admin.auth().verifyIdToken and verify ID tokens just fine. However when I call app.database(), it complains that the app is never initialized. Inititalizing the app again creates a new error saying:
The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once. But if you do want to initialize multiple apps, pass a second argument to initializeApp() to give each app a unique name.
Do I need to create multiple apps with different names for this to work? Or how can I use one app throughout the project.
You should initialize your application exactly once and then re-use that reference. There are various ways to do this, but the way I prefer is to import firebase.ts (which initializes the Firebase application and services) into my index.ts (or whatever your entry point is). Then I pass a reference to any other files that need a particular service. I'm using TypeScript, so adjust this as needed if you're using vanilla JS.
firebase.ts
import * as admin from 'firebase-admin';
// Initialize our project application
admin.initializeApp();
// Set up database connection
const firestoreDb: FirebaseFirestore.Firestore = admin.firestore();
firestoreDb.settings({ timestampsInSnapshots: true });
export const db = firestoreDb;
My index.ts file will import it:
import { db } from './firebase';
Then when I set up my routes with Express, I'll have each route in another file with its own function. Then pass in the db reference to any that need it.
app
.route('events')
.get((req: Request, res: Response) => {
get_events(db, res);
return;
});
Here is a blog post where I explain it a bit more:
https://medium.com/#jasonbyrne/how-to-structure-a-serverless-rest-api-with-firebase-functions-express-1d7b93aaa6af
If you don't like the dependency injection method or prefer to lazy-load only the services you nee, you could go another it a different way. In that method you'd have your firebase.js file (or whatever you call it) that you import to any pages that need it and call a function to load that service. Here I'm just doing Firestore, but you could create similar functions for references to other services.
Just typed this up as a sample...
import * as admin from 'firebase-admin';
// Initialize our project application
admin.initializeApp();
// Database reference, not yet loaded
let db: FirebaseFirestore.Firestore | null = null;
// Get cached db reference or create it
export function getDatabaseReference() {
if (db === null) {
db = admin.firestore();
}
return db;
}
I hope this helps. Let me know if you have any questions.
I got this working very nicely in a microservice API in cloudRun using global.
const admin = require('firebase-admin');
global.GeoFirestore = require('geofirestore').GeoFirestore;
admin.initializeApp({
credential: admin.credential.applicationDefault()
});
global.db = admin.firestore();
global.admin = admin;
In another module I can access collections:
var docRef = db.collection("collection").doc(doc.id);
docRef.update({state}).then((doc) => {
console.log(doc)
}).catch((error) => {
console.log("Error getting document:", error);
});
For working on a GeoFirestore GeoPoint I needed to have admin globally:
const geofirestore = new GeoFirestore(db);
const geocollection = geofirestore.collection('bin');
var objectToBeStored = {
...data,
coordinates: new admin.firestore.GeoPoint(coordinates.lat, coordinates.lng)
}
geocollection.add(objectToBeStored ).then((docRef) => {
console.log(`added data: ${docRef.id}`);
}).catch((error) => {
console.log(`error: ${error}`);
})
Here's how I did it: I created a wrapper around the admin.firestore() etc functions I needed, and I import that and use it in all my other functions:
FunctionsShared.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp(functions.config().firebase);
export const FunctionsShared = {
firestore: () => admin.firestore()
};
MyFirebaseFunction.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import {FunctionsShared} from './FunctionsShared';
export getStaleDocumentsQuery = functions.https.onCall(() => {
// Use FunctionsShared.firestore() instead of admin.firestore()
return FunctionsShared.firestore()
.collection('mirror')
.where('updated', '<', oneMonthAgo)
.orderBy('updated')
.limit(limit);
}

Cloud Functions not Working

A cloud firestore trigger has to happen whenever a new collection is to added to the cloud firestore. The cloud functions are successfully executed, but they are not triggering when a new doc was added to the firestore it means the logs are empty and no.of execute times is 0.The following the cloud function code.
const functions = require('firebase-functions');
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore();
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.wallet=functions.firestore
.document('Samyata/{authid}')
.onCreate(event => {
const db = admin.firestore();
const ID = event.params.authId
var pymntDetails = db.collection('Samyata').doc(ID).collection('Accounts').doc(ID);
var details = pymntDetails.set({
'stripecustomerId':'0',
//var bankAccount = db.collection('Samyata').doc(ID).collection('Accounts').doc(ID).doc('BankDetails');
//var bnkdetails = bankAccount.set({
//'AccountNumber':'',
//'RoutingNumber':''
//});//SET CLOSE for bank details
'deyaCoins':'0',
'BitCoins':'0',
'Ether':'0'
});//matched set paranthesis
return event.data.ref.update({
ID
});
});//end of onCreate event
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });

Resources