Copy object from one node to another in Cloud Functions for Firebase - node.js

I'm using Cloud Functions for Firebase, and I'm stuck with what seems to be a very basic operation.
If someone adds a post, he writes to /posts/. I want a portion of that post to be saved under a different node, called public-posts or private-posts, using the same key as was used in the initial post.
My code looks like this
const functions = require('firebase-functions');
exports.copyPost = functions.database
.ref('/posts/{pushId}')
.onWrite(event => {
const post = event.data.val();
const smallPost = (({ name, descr }) => ({ name, descr }))(post);
if (post.isPublic) {
return functions.database.ref('/public-posts/' + event.params.pushId)
.set(smallPost);
} else {
return functions.database.ref('/private-posts/' + event.params.pushId)
.set(smallPost);
}
})
The error message I get is: functions.database.ref(...).set is not a function.
What am I doing wrong?

If you want to make changes to the database in a database trigger, you either have to use the Admin SDK, or find a reference to the relevant node using the reference you've been given in the event. (You can't use functions.database to find a reference - that's used for registering triggers).
The easiest thing is probably to use event.data.ref (doc) to find a reference to the location you want to write:
const root = event.data.ref.root
const pubPost = root.child('public-posts')

Related

Firebase Admin SDK with Cloude Functions Confusion

So after I have written a good part of my app, I am now becoming maximally confused regarding the use of the admin sdk in cloud functions regarding firestore.
I only want to query, read and write data from the cloud function environment correctly. Which documentation do I have to use, how do I correctly initialize the "Admin SDK" and implement the corresponding methods and functions?
It seems like I have mixed up v9 and v10 and even by reading the docs I still can't find a red thread, on how to use it correctly.
I am currently importing and initializing like that.
const functions = require("firebase-functions");
const { initializeApp } = require("firebase-admin/app");
const admin = require("firebase-admin");
const app = initializeApp();
when I initialize like that, and would like to work with firestore. There are different options which I do not understand - for example.
const userRef = admin.firestore().collection("users").doc(data.userID);
enables me to access "collection"
However when using
const userRef = admin.firestore
I am only able to choose admin.firestore.CollectionGroup and admin.firestore.CollectionReference, which are classes? What is up with that?
Furthermore this approach seems to be outdated as this site of the docs (which I only recently came to know), says that I should use a modular approach, as I would on the client side, with.
import { getFirestore } from 'firebase-admin/firestore'
getFirestore();
So far so good. Now when I take a look at the docs, I am led to this page. The question I ask myself there are, what are External Api Re-Exports? By clicking on any of the referenced functions I get redirected to this, which contains the reference for the nodejs client and also subgroups called firestore admin client, as well as FirestoreAdmin. Neither of those subgroups contain anything which has something to do with querying a collection. There is a section about collections and querying which displays examples of asynchronous programming with .then, but from what I heard it is generally more beneficial to use async/await?
In addition to that the quickstart guide, recommends an initialization like this.
const {Firestore} = require('#google-cloud/firestore');
// Create a new client
const firestore = new Firestore();
Furthermore the firebase docs, seem to have a dedicated documentation on how to use the Admin SDK, with the realtime database here but not how to use it with firestore?
I am just so confused, as to which documentation to use, and I couldn't find any examples how to do standard operations. I guess I also lack fundamental understanding about the Admin SDK itself and its integration in firebase.
The code I have written now is working, however I think it is not "right" from a documentation point of view.
exports.createUserDoc = functions.auth.user().onCreate((user) => {
const userRef = admin.firestore().collection("users").doc(user.uid);
const userData = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
tokens: 0,
};
return userRef.set(userData);
});
exports.setWriteTimestamp = functions.https.onCall((data, context) => {
//in the if check below, retrieve the corresponding pool document and check whether the "open" field is true
const poolRef = admin.firestore().collection("pools").doc(data.slug);
const poolDoc = poolRef.get().then((doc) => {
if (doc.exists && doc.data().open) {
return poolRef.update({
writeTimestamp: admin.firestore.FieldValue.serverTimestamp(),
});
} else {
return null;
}
});
});
Thank you for your help.

Using Firebase Cloud Functions to fan out data

I'm extremely new to using Firebase cloud functions, and I am struggling to find the error in my code. It is supposed to trigger on a firestore write and then copy that document into all of the user's feeds who follow that user who posted.
My current code is below:
exports.fanOutPosts = functions.firestore
.document('posts/{postId}')
.onCreate((snap, context) => {
var db = admin.firestore();
const post = snap.data();
const userID = post['author'];
const postCollectionRef = db.collection('friends').document(userID).collection('followers');
return postCollectionRef.get()
.then(querySnapshot => {
if (querySnapshot.empty) {
return null;
} else {
const promises = []
querySnapshot.forEach(doc => {
promises.push(db.collection('feeds').document(doc.key).collection('posts').document(post.key).update(data));
});
return Promise.all(promises);
}
});
});
So this successfully deploys to Firebase, but it receives this error when a document is created:
TypeError: db.collection(...).document is not a function
at exports.fanOutPosts.functions.firestore.document.onCreate (/workspace/index.js:22:60)
Line 22 is const postCollectionRef = db.collection('friends').document(userID).collection('followers');
I am unsure why this line is causing errors with the .get, but if anyone could point me in the right direction it would be much appreciated!
Given that this is the nodejs API, you'll want to use doc() instead of document(). Other languages might use document().
I found this info via the Admin SDK on CollectionReference https://googleapis.dev/nodejs/firestore/latest/CollectionReference.html
According to the reference, the collection should be defined as the following:
const postCollectionRef = db.collection(`friends/${userId}/followers`);
Using template literals will allow you to dynamically add variables into the collection ref.
I would also take a look into the else logic to use template literals within your return statement.

How to update a quantity in another document when creating a new document in the firebase firestore collection?

When I create a new document in the note collection, I want to update the quantity in the info document. What am I doing wrong?
exports.addNote = functions.region('europe-west1').firestore
.collection('users/{userId}/notes').onCreate((snap,context) => {
const uid = admin.user.uid.toString();
var t;
db.collection('users').doc('{userId}').collection('info').doc('info').get((querySnapshot) => {
querySnapshot.forEach((doc) => {
t = doc.get("countMutable").toString();
});
});
let data = {
countMutable: t+1;
};
db.collection("users").doc(uid).collection("info").doc("info").update({countMutable: data.get("countMutable")});
});
You have... a lot going on here. A few problems:
You can't trigger firestore functions on collections, you have to supply a document.
It isn't clear you're being consistent about how to treat the user id.
You aren't using promises properly (you need to chain them, and return them out of the function if you want them to execute properly).
I'm not clear about the relationship between the userId context parameter and the uid you are getting from the auth object. As far as I can tell, admin.user isn't actually part of the Admin SDK.
You risk multiple function calls doing an increment at the same time giving inconsistent results, since you aren't using a transaction or the increment operation. (Learn More Here)
The document won't be created if it doesn't already exist. Maybe this is ok?
In short, this all means you can do this a lot more simply.
This should do you though. I'm assuming that the uid you actually want is actually the one on the document that is triggering the update. If not, adjust as necessary.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.addNote = functions.firestore.document('users/{userId}/notes/{noteId}').onCreate((snap,context) => {
const uid = context.params.userId;
return db.collection("users").doc(uid).collection("info").doc("info").set({
countMutable: admin.firestore.FieldValue.increment(1)
}, { merge: true });
});
If you don't want to create the info document if it doesn't exist, and instead you want to get an error, you can use update instead of set:
return db.collection("users").doc(uid).collection("info").doc("info").update({
countMutable: admin.firestore.FieldValue.increment(1)
});

How to query firebase realtime database in cloud code

I am using Firebase cloud code and firebase realtime database.
My database structure is:
-users
-userid32
-userid4734
-flag=true
-userid722
-flag=false
-userid324
I want to query only the users who's field 'flag' is 'true' .
What I am doing currently is going over all the users and checking one by one. But this is not efficient, because we have a lot of users in the database and it takes more than 10 seconds for the function to run:
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
exports.test1 = functions.https.onRequest((request, response) => {
// Read Users from database
//
admin.database().ref('/users').once('value').then((snapshot) => {
var values = snapshot.val(),
current,
numOfRelevantUsers,
res = {}; // Result string
numOfRelevantUsers = 0;
// Traverse through all users to check whether the user is eligible to get discount.
for (val in values)
{
current = values[val]; // Assign current user to avoid values[val] calls.
// Do something with the user
}
...
});
Is there a more efficient way to make this query and get only the relevant records? (and not getting all of them and checking one by one?)
You'd use a Firebase Database query for that:
admin.database().ref('/users')
.orderByChild('flag').equalTo(true)
.once('value').then((snapshot) => {
const numOfRelevantUsers = snapshot.numChildren();
When you need to loop over child nodes, don't treat the resulting snapshot as an ordinary JSON object please. While that may work here, it will give unexpected results when you order on a value with an actual range. Instead use the built-in Snapshot.forEach() method:
snapshot.forEach(function(userSnapshot) {
console.log(userSnapshot.key, userSnapshot.val());
}
Note that all of this is fairly standard Firebase Database usage, so I recommend spending some extra time in the documentation for both the Web SDK and the Admin SDK for that.

How to get inner child in cloud function for Firebase?

Here is my database and I want to trigger onWrite event on children of PUBLISHED_CONTENT_LIKES. When I add another userId under publishedContentId1, I can identify contentId as publishedContentId1 in my cloud function using event.params.pushId.
exports.handleLikeEvent = functions.database.ref('/USER_MANAGEMENT/PUBLISHED_CONTENT_LIKES/{pushId}')
.onWrite(event => {
// Grab the current value of what was written to the Realtime Database.
//const userId = event.data.child(publishedContentId);
//const test = event.params.val();
const publishedContentId = event.params.pushId;
var result = {"publishedContentId" : "saw"}
// 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 event.data.ref.parent.parent.child('PUBLISHED_CONTENTS/'+publishedContentId).set(result);
});
However I want to get newly added userId as well. How to get that userId using above event?
You can get the data that is being written under event.data. To determine the new user ID:
event.data.val().userID
I recommend watching the latest Firecast on writing Database functions as it covers precisely this topic.

Resources