How to put a trigger to another Firebase Database from Firebase Functions? - javascript

I'm trying to trigger my database function when something is wrote to a database from an associate.
I know that I need a Service Account created in the other database and the .json file that firebase gives to do the connection properly. Right now I'm giving all the permissions for getting sure than my errors don't come from this.
With what I found on the documentation and with other information on internet, this is how I login to the other database:
var adminAbi = require("firebase-admin");
var functionsAbi = require('firebase-functions');
const serviceAccount = require(`./serviceacountfile.json`);
adminAbi.initializeApp({
credential: adminAbi.credential.cert(serviceAccount),
databaseURL: 'https://DATABASEURL.firebaseio.com/',
},'test' );
And this is my trigger:
exports.copyDatabasess = functionsAbi.database.instance('test').ref('/messages/{user_id}/{now}').onWrite(event =>{
if (!event.data.exists()) {
return;
}
console.log('copydatabase', event.params.body);
// Grab the current value of what was written to the Realtime Database.
const original = event.data.val();
});
With this code I'm getting right now this error when I deploy:
! functions[copyDatabasess]: Deployment error. Failed to configure
Firebase Realtime Database trigger: unknown error, HTTP code 401
I really can't find useful informations about this error and how to solve it. If someone knows something about this would be much appreciated.
Thanks in advice.

You can't put a trigger on a database that's not in the same project as your functions. instance() only works with database shards in the same project.

Related

Create multiple Firebase Instances for the same project in Node.js

I have a Node.js server, inside which I want to have two firebase instances.
One instance should use the JavaScript SDK and will be used to provide authentication - login/register. The other instance should use the Admin SDK and will be used to read/write from the Realtime Database. I want to use this approach, so that I don't have to authenticate the user before each request to the Realtime DB.
I've read how we're supposed to initialize Firebase instances for multiple projects, but I'm not sure if my issue isn't coming from the fact that both instances are for the same project.
My issue is that I can use the JS SDK without any issue and I can login/register the user, but for some reason I can't get the Admin SDK to work.
Here's how I'm instantiating the apps:
const admin = require("firebase-admin");
const { applicationDefault } = require('firebase-admin/app');
admin.initializeApp({
credential: applicationDefault(),
databaseURL: 'my-database-url'
}, 'adminApp');
const firebase = require("firebase/app");
firebase.initializeApp(my-config);
Now I can use the JS SDK without an issue, but not the Admin SDK. I've created a test endpoint to just get data from my Realtime DB:
app.get("/api/test", (req, res) => {
const uid = 'my-user-UID';
admin.database().ref(`users/${uid}`)
.once('value', (snapshot) => {
if(snapshot) {
console.log('data');
} else {
console.log('no data');
}
});
});
Now here as an approach to getting the data from the Realtime DB, I tried all possible described approaches. Using get with child and all sorts of possible combinations. Here's an example of another approach I used:
get(child(ref(admin.database()), `users/${uid}`)).then((snapshot) => {
if (snapshot.exists()) {
// retrieved data
} else {
// No data
}
}).catch((error) => {
console.error(error);
});
For the first approach I wasn't getting any response at all, like the once wasn't executing. For the second one I think I was getting - typeerror: pathstring.replace is not a function firebase. At some point I was getting a no firebase app '[default]' has been created . These errors don't worry me as much, but since I saw the last error I moved my focus to the initialization of the apps, but still to no avail.
I just need a direction of where my issue might be coming from.
Update:
The solution is to not pass a second argument (app name) to any of the Firebase initializations. Looks like it's not needed in case you're referencing the same project.

How do I use Firebase Admin SDK to change data in Firebase Realtime Database while using Cloud Functions?

I want to reset a specific value in my Firebase Realtime Database every day at 12:00 AM. To do this, I'm using the Firebase Admin SDK to change the data in Firebase Realtime Database and Cloud Functions to trigger the change at 12:00 AM every day.
This is an example structure of my Firebase Realtime Database:
{
"users": {
"fa54487d9cbb4214b00db80e2118e4e6": {
"daily": 10
}
}
}
This is the code in my index.js:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
var functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
var admin = require('firebase-admin');
// Fetch the service account key JSON file contents
var serviceAccount = require("serviceAccountKey.json");
// Initialize the app with a service account, granting admin privileges
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com"
});
// As an admin, the app has access to read and write all data, regardless of Security Rules
var db = admin.database();
var ref = db.ref("users");
// Reset today GHG emissions at 12:00 AM everyday
exports.dailyReset = functions.pubsub.schedule('0 0 * * *').onRun((context) => {
usersRef.child("{userId}").set({
daily: 0
});
});
Deploy Error:
! functions[dailyReset(us-central1)]: Deployment error.
Function failed on loading user code. This is likely due to a bug in the user code. Error message: Error: please examine your function logs to see the error cause: https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs. Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging. Please visit https://cloud.google.com/functions/docs/troubleshooting for in-depth troubleshooting documentation.
Firebase Console Functions Logs:
Error: function terminated. Recommended action: inspect logs for termination reason.
Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging Function cannot be initialized.
{"#type":"type.googleapis.com/google.cloud.audit.AuditLog","status":{"code":3,"message":"Function failed on loading user code. This is likely due to a bug in the user code.
Error message: Error: please examine your function logs to see the error cause: https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs.
Additional troubleshooting documentation can be found at https://cloud.google.com/functions/docs/troubleshooting#logging.
The script won't deploy when I use firebase deploy as my function is giving me an error. Can someone tell me how to fix my code?
This won't work:
exports.dailyReset = functions.pubsub.schedule('0 0 * * *').onRun((context) => {
usersRef.child("{userId}").set({
daily: 0
});
});
There is nothing here that interprets the {userId} in that path, so the database updates the literal path "/users/{userId}", which is not what you want.
If you know what user ID you want to update, you should use that value in the path:
exports.dailyReset = functions.pubsub.schedule('0 0 * * *').onRun((context) => {
let usersRef = admin.database().ref("users");
usersRef.child("theActualUserIdYouWantToUpdate").set({
daily: 0
});
});
If you don't know what user ID to update, you'll need to query the database to determine that.
If you want to loop over all users, you can do:
exports.dailyReset = functions.pubsub.schedule('0 0 * * *').onRun((context) => {
return usersRef.once("value").then((snapshot) => {
let updates = {};
snapshot.forEach((userSnapshot) => {
updates[userSnapshot.key+"/daily"] = 0
});
return usersRef.update(updates);
});
});
If you are new to JavaScript or interacting with the Realtime Database in it, Cloud Functions for Firebase is not the best way to learn it. I recommend first reading the Firebase documentation for Web developers and/or taking the Firebase codelab for Web developer. They cover many basic JavaScript, Web and Firebase interactions. You could also use the Admin SDK in a local Node.js process, which can be debugged with a local debugger. After those you'll be much better equipped to write code for Cloud Functions too.

Firebase functions with AdminSdk and RealtimeDatabase not working

I'd like to create, edit, read and delete on the RealTime Database using the firebase functions. Looking at other similar questions I saw that the AdminSdk has to be used, and so I did.
I basically copy/pasted the code provided by the same firebase guides.
const admin = require("firebase-admin");
const functions = require("firebase-functions");
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: "https://<DATABASE_NAME>.firebaseio.com"
});
const db = admin.database();
db.ref("devices")
.once("value")
.then(snapshot => console.log("Snapshot: ",snapshot.val())
.catch(error => console.log(error))
});
In the initialization I set the credential with applicationDefault() as I previously set the GOOGLE_APPLICATION_CREDENTIALS env variable with my service_account_key.json path.
I tried anyway to set it with the cert method and the result didn't change. As 3 accounts are showed in the Service account section I tried with all of them as well.
This said,when starting the functions from console with 'firebase serve' the log is not showed and no error either.
Is there anything I'm missing? Some further configuration or whatever error you might be aware of?
Thank you in advance!
Update following your comments:
You want to "create, edit, read and delete on the Realtime Database using Cloud Functions", as indicated in your question, mimicking the behaviour of a Client SDK but from a server that you control. You should use one or more Cloud Functions that you call directly from this server. The most appropriate (based on your comments) would be to use an HTTPS Cloud Function.
For example you could have an HTTPS Cloud Function like the simple one below, to write to a specific node of the Realtime Database, as follows:
exports.writeToNode = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const dbNode = req.body.nodeRef;
const objToWrite = req.body.nodeValue;
return admin.database().ref(dbNode).push(objToWrite)
.then(() => {
return res.send("Node " + dbNode + " updated!");
})
.catch(err => {
//please watch the official video https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3
});
});
});
You would call it by issuing a POST to the following URL https://us-central1-YOURPROJECTID.cloudfunctions.net/writeToNode, with a body like:
{
nodeRef: 'theNode',
nodeValue: {
firstName: 'John',
lastName: 'Doe'
}
}
Initializing the Admin SDK:
If you want to interact, from a Cloud Function, with the Realtime Database that is in the same Firebase project, you just need to initialize the Admin SDK without any parameter (i.e. admin.initializeApp();)
This way, the Admin SDK will use the Project's default service account, and will have full access to the Realtime Database (i.e. bypassing all the security rules).
So, initialize as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
///// Additional thought /////
Note that you could maybe use the REST API exposed by the Realtime Database, instead of developing an entire set of CRUD endpoints through Cloud Functions. See https://firebase.google.com/docs/database/rest/start
REMAINING PART OF THE CONTENT OF THE INITIAL ANSWER, about background triggered Cloud Functions
You then need to declare a Cloud Function, as shown in the example below, by:
Selecting an "event handler";
Specifying the database path where it will listen for events and;
Executing the desired logic (normally using the data that was written at the path, or indicating that the node was deleted, etc...)
exports.makeUppercase = functions.database.ref('/devices/{pushId}/original')
.onCreate((snapshot, context) => {
// Grab the current value of what was written to the Realtime Database.
const original = snapshot.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return snapshot.ref.parent.child('uppercase').set(uppercase);
});
This code snippet, copied from the documentation, will listen to any new node created under the devices node and will create an uppercase node the value of the original node in uppercase.
Note that this is a background triggered Cloud Function which is triggered when something "happens" at the specific path.
If you want to "create, edit, read and delete on the RealTime Database", as indicated in your question, mimicking the behaviour of a Client SDK, you may define one or more Cloud Functions that you call directly from your App. See the Callable Cloud Functions documentation.
You may alse read the following documentation items https://firebase.google.com/docs/functions/get-started and https://firebase.google.com/docs/functions/database-events and also watch the video series: https://firebase.google.com/docs/functions/video-series

Transnational email with SendGrid and Firebase No Errors but no emails (Used NodeMailer as answer)

ok so I set up an Ionic webapp with a contact form and I have the form interacting with firebase meaning all my form info is being stored on the real time database. Now I have setup SendGrid according to this tutorial:
Firestore - Database Triggered Events;
https://fireship.io/lessons/sendgrid-transactional-email-guide/
However the cloud functions are not being triggered when new data is being entered. I am not getting any errors on the console and from sendgrid dashboard there are no requests. My understanding is that when there is change in the database it will automatically trigger the function and then sendgrid will send emails with the relevant data.
Here is my code;
// Firebase Config
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
// Sendgrid Config
import * as sgMail from '#sendgrid/mail';
const API_KEY = functions.config().sendgrid.key;
const TEMPLATE_ID = functions.config().sendgrid.template;
sgMail.setApiKey(API_KEY);
// Emails the author when a new messages is added
export const newMessage = functions.firestore.document('messages/{messageId}').onCreate( async (change, context) => {
// Raw Data
// const post = postSnap.data();
const msgData = change.data();
// Email
const msg = {
to: msgData.email,
from: 'Your_email#gmail.com',
templateId: TEMPLATE_ID,
dynamic_template_data: {
subject: 'New Message',
name: msgData.name,
text: `Here is the message: ${msgData.message}`,
phone: msgData.phone
},
};
// Send it
return sgMail.send(msg);
});
Deployment of the functions was successful to firebase.
Please any help is appreciated.
edit //////////////////////////////////////////////// edit
Ended up using Nodemailer instead.
It's Probobly Free Firebase Spark Plan https://firebase.google.com/pricing. Cloud Functions: Outbound Networking = Google Services Only. If You change to Blaze Plan You still will not pay any thing if You no use much Outbound Networking. I have 2x Blaze Plans 3 months and pay nothing.
ok so this is what worked for me after searching and searching. Thanks to #Mises for giving me a direction to follow. For others that are trying to send transactional emails with firebase using nodemailer here is how I did it.
I followed the above link given to me by #Mises;
https://github.com/firebase/functions-samples/tree/Node-8/email-confirmation
I was able to upload the function to firebase, but I was still getting an error in firebase function logs;
-There was an error while sending the email: { Error: Missing
credentials for "PLAIN"
So then from there I followed this link;
Missing credentials for "PLAIN" nodemailer
unfortunately activating less secure apps on google did not work for me.
aslo offical docs from nodemailer here;
https://nodemailer.com/about/
Hope this helps someone else.

Having trouble connecting to my Firebase database - Node & Express

I am still learning programming in general so sorry if I don't make sense.
I am trying to connect to my firebase database but I get a PERMISSION_DENIED error. The database in my firebase is set to Test mode so anyone should be able to access it.
I have added all the npm packages needed based on the firebase docs as well.
Let me know if I need to provide more information.
I am not sure what I am doing wrong here. Would anyone know? Any help is appreciated.
Here is my module file
var express = require('express');
var firebase = require('firebase');
// Initialize Firebase
var config = {
apiKey: "apikey",
authDomain: "authdomain",
databaseURL: "databaseurl",
storageBucket: "storagebucket"
};
firebase.initializeApp(config);
var db = firebase.database();
var ref = db.ref("/users");
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
var index = require('./routes/index');
app.use('/', index);
module.exports = app;
Here is my routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'The Title' });
});
module.exports = router;
Asynchronous listeners: Data stored in a Firebase Realtime Database is retrieved by attaching an asynchronous listener to a
database reference. The listener is triggered once for the initial
state of the data and again anytime the data changes. An event
listener may receive several different types of events.
Helpful link https://firebase.google.com/docs/database/admin/retrieve-data
You need to create a reference variable that corresponds to your database path
var ref = db.ref("server/saving-data/fireblog/posts");
and then you'll attach an asynchronous callback to read the data at the reference
ref.on("value", function(snapshot) {
console.log(snapshot.val());
}, function (errorObject) {
console.log("The read failed: " + errorObject.code);
});
I believe your issue with PERMISSION_DENIED is that you're using
var db = firebase.database();
instead of
var db = admin.database();
So I figured out a way to properly connect to my firebase database. I am not sure if this is the best way but the first thing I did was delete the current database and recreated it (not sure if this helped or was needed but I just wanted a fresh install just in-case something was wrong before.)
Then inside my database dashboard in firebase I went to the "Gear Icon => Project Settings" next to "Project Overview" header on the upper left of the dashboard screen. From here under Firebase Admin SDK I clicked "Generate New Private Key" button on the bottom.
This gave me a .json file which was downloaded onto my computer. I changed the downloaded files name to something more simple like myfirstapp-firebase-db.json. I added this file into the folder where my node js is being stored.
Before I started writing the code to connect to my firebase database, I had to make sure that my "Database => Rules" were set to true for read & write privileges in my firebase project.
Database Rules setup:
{
"rules": {
".read": true,
".write": true
}
}
After everything needed to configure firebase was taken care of, I simply configured my module.js file to properly connect. Below is the code necessary to connect to firebase. The two important things were "serviceAccount" and "databaseURL".....
var firebase = require('firebase');
// Initialize firebase
firebase.initializeApp({
serviceAccount: "./<your-service-account-url>-firebase-db.json",
databaseURL: "https://<your-database-url>.firebaseio.com/"
});
var db = firebase.database();
var ref = db.ref("/users");
..... The "serviceAccount" is a route to the .json file downloaded from the step "Generate New Private Key" above. You can find the databaseURL inside the "Database" dashboard on the top of the white block inside firebase. I simply copied and pasted that url into the databaseURL.
Then I put firebase.database() into a var = db and then specified the ref.
At this point my connection was successful and when I did a node modules.js it showed me in the console everything that is in the ref database. Make sure to have some pre populated fields in the database for the console to show you all the items inside. I hope this may be helpful to someone and if anyone knows of a better way of doing this I would love to know your suggestions!

Categories

Resources