Firestore - Get document collections - firebase

I would automate the backup process of a firestore database.
The idea is to loop over the root document to build a JSON tree, but I didn't find a way to get all collections available for a document. I guess it's possible as in firestore console we can see the tree.
Any ideas?
ref doc: https://firebase.google.com/docs/reference/js/firebase.firestore

Its possible on web (client side js)
db.collection('FirstCollection/' + id + '/DocSubCollectionName').get().then((subCollectionSnapshot) => {
subCollectionSnapshot.forEach((subDoc) => {
console.log(subDoc.data());
});
});
Thanks to #marcogramy comment

firebase.initializeApp(config);
const db = firebase.firestore();
db.settings({timestampsInSnapshots: true});
const collection = db.collection('user_dat');
collection.get().then(snapshot => {
snapshot.forEach(doc => {
console.log( doc.data().name );
console.log( doc.data().mail );
});
});

Update
API has been updated, now function is .listCollections()
https://googleapis.dev/nodejs/firestore/latest/DocumentReference.html#listCollections
getCollections() method is available for NodeJS.
Sample code:
db.collection("Collection").doc("Document").getCollections().then((querySnapshot) => {
querySnapshot.forEach((collection) => {
console.log("collection: " + collection.id);
});
});

If you are using the Node.js server SDK you can use the getCollections() method on DocumentReference:
https://cloud.google.com/nodejs/docs/reference/firestore/0.8.x/DocumentReference#getCollections
This method will return a promise for an array of CollectionReference objects which you can use to access the documents within the collections.

As mentioned by others, on the server side you can use getCollections(). To get all the root-level collections, use it on the db like so:
const serviceAccount = require('service-accout.json');
const databaseURL = 'https://your-firebase-url-here';
const admin = require("firebase-admin");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: databaseURL
});
const db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
db.getCollections().then((snap) => {
snap.forEach((collection) => {
console.log(`paths for colletions: ${collection._referencePath.segments}`);
});
});

Related

Firebase Function Unable to Find userId and tweetId

I am using Firebase functions for Firestore database. I am trying to update a field based on the new tweet being added.
Here is my Firebase Function on production:
const admin = require('firebase-admin')
admin.initializeApp()
const db = admin.firestore()
const functions = require("firebase-functions");
functions.logger.log("START OF FUNCTION");
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate((change, context) => {
const userId = context.params.userId
const tweetId = context.params.tweetId
functions.logger.log(context.params.userId);
functions.logger.log(context.params.tweetId);
db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({likeCount: 200})
})
I am triggering it through an iPhone app. I am logged in to my account and I add a new Tweet. The Firebase function does get called but userId and tweetId are undefined. I am not sure why they are undefined. Any ideas?
Without knowing your client-side logic it's difficult to know if there are other issues. I would suggest adding some error handling to narrow down the cause. You could also try pulling it from the data response instead of context (assuming the schema matches).
Also note using 'snap' instead of 'change' as change is generally reserved for 'onWrite' and 'onUpdate' hooks.
exports.myFunction = functions.firestore
.document('timelines/{userId}/tweets/{tweetId}')
.onCreate(async (snap, context) => {
try {
const { userId, tweetId } = snap.data();
functions.logger.log(userId);
functions.logger.log(tweetId);
return await db.doc(`/timelines/${userId}/tweets/${tweetId}`).update({ likeCount: 200 });
}
catch (error) {
functions.logger.log(error);
}
});

Firestore pre-deployment script

I'm searching for a way to add pre-deployment scripts to my Firebase project.
I'm using Firestore and my security rules are set up in a way that only cloud functions can write to Firestore.
I've added a user role field to my user table which automatically gets populated on userCreate. This works fine but my prod env still has users without this field.
A logical solution would be to run a pre-deploy command which add this field to all existing users but I have no clue how to do this.
My current best solution is to create a cloud function specifically for this one-time use and trigger it.
This doesn't feel like the right way to handle such things.
How do I run a one time update statement on Firestore?
You can write a temporary script using Firebase Admin SDK and execute it once. The flow would look something like:
Fetching all documents without the userRole field.
Add update statements in an array and execute all the promises at once.
Here's a demo:
const admin = require("firebase-admin");
const serviceAccount = require("/path/to/serviceAccountKet.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://fate-bot-discord.firebaseio.com"
});
async function addRoles() {
try {
const userColRef = admin.firestore().collection("users")
const users = await userColRef.where("userRole", "==", "").get()
const updates = []
users.docs.forEach((user) => {
updates.push(userColRef.doc(user.id).update({ userRole: "theNewRole" }))
})
await Promise.all(updates)
console.log("Roles added successfully")
return "Roles Added"
} catch (error) {
console.log(error);
return error
}
}
//Call the function
addRoles().then((response) => {
console.log(response)
}).catch((e) => {
console.log(e)
})
Please let me know if you need further assistance!
I've updated #Dharmaraj answer with some extra features in case someone ever needs this.
const admin = require('firebase-admin');
// DEV
const serviceAccount = require('./x-dev-firebase-adminsdk-1234.json');
// PROD
// const serviceAccount = require('./x-firebase-adminsdk-1234.json');
const newRoles = [0];
const emails = ['admin1#gmail.com', 'admin2#gmail.com'];
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
});
const addRoles = async () => {
try {
let userColRef = admin.firestore().collection('users');
if (emails.length) {
userColRef = userColRef.where('email', 'in', emails);
}
const users = await userColRef.get();
const updates = [];
users.docs.forEach((doc) => {
const user = doc.data();
let existingRoles = [];
if (user.roles) {
existingRoles = user.roles;
if (newRoles.every((role) => existingRoles.includes(role))) {
return;
}
}
const roles = Array.from(new Set(existingRoles.concat(newRoles)));
updates.push(doc.ref.set({ roles }, { merge: true }));
});
await Promise.all(updates);
console.log(
`Role${newRoles.length > 1 ? 's' : ''} added to ${updates.length} user${
updates.length !== 1 ? 's' : ''
}.`
);
return true;
} catch (error) {
console.log(error);
return error;
}
};
addRoles().catch((e) => {
console.log(e);
});
Here's where you create the service account btw.

Connecting Dialogflow to Firebase question

I have been reading around but cannot find the answer
I tried my firebase and it's not storing any data.
Here's the related inline editor
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
function angerEmotionCapture(agent) {
let angryTo = agent.parameters.angryDirectedTo;
agent.add(`love your ${angryTo},dude`);
return db.collection('directedTo').add({directedTo: angryTo});
}
Here's my firebase database
Any help will be greatly appreciated, thanks!
Please have a look into the following sample code showing how to connect Firebase's Firestore database to Dialogflow fulfillment hosting on Firebase functions:
'use strict';
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {WebhookClient} = require('dialogflow-fulfillment');
process.env.DEBUG = 'dialogflow:*'; // enables lib debugging statements
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
function writeToDb (agent) {
// Get parameter from Dialogflow with the string to add to the database
const databaseEntry = agent.parameters.databaseEntry;
// Get the database collection 'dialogflow' and document 'agent' and store
// the document {entry: "<value of database entry>"} in the 'agent' document
const dialogflowAgentRef = db.collection('dialogflow').doc('agent');
return db.runTransaction(t => {
t.set(dialogflowAgentRef, {entry: databaseEntry});
return Promise.resolve('Write complete');
}).then(doc => {
agent.add(`Wrote "${databaseEntry}" to the Firestore database.`);
}).catch(err => {
console.log(`Error writing to Firestore: ${err}`);
agent.add(`Failed to write "${databaseEntry}" to the Firestore database.`);
});
}
// Map from Dialogflow intent names to functions to be run when the intent is matched
let intentMap = new Map();
intentMap.set('WriteToFirestore', writeToDb);
agent.handleRequest(intentMap);
});
Have a look into the Dialogflow's Firestore GitHub example

Cloud Function Update Problem in Firestore Database

I'm trying to build an Android application. In my Firestore database I have Users collection and Counters collection. In Counters collection I have userCounter. What I want to do is whenerever a new user logs in and I push it to firestore, userCounter to increase.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addNewUser =
functions.firestore.document('Users/{userID}').onCreate((event) => {
var db = admin.firestore();
var counterRef = db.collection("Counters");
var temp = counterRef.doc("0").data().userCounter++;
counterRef.doc("0").update(
{
userCounter: temp
});
});
In this state, this function doesn't work, and I'm a newbie so I'd appreciate any help.
Thx beforehand
EDIT
After implementing Firebaser and Pablo Almécija Rodríguez's answers, my code looks like this.
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore({
projectId: process.env.GCP_PROJECT,
});
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.addNewUser =
functions.firestore.document('Users/{userId}').onCreate((snapShot) => {
const userCounterRef = db.collection('Counters').doc('Users');
return db.runTransaction(transaction => {
const doc = transaction.get(userCounterRef);
console.log("1");
const count = doc.data();
console.log(`5`);
const updatedCount = count + 1;
console.log(`6`);
return transaction.update(userCounterRef, {counter: updatedCount })
})
});
And this is the firebase console log. The problem is
const count = doc.data();
TypeError: doc.data is not a function
Firebase Console Log
I suggest you to create a collection named counters and inside it a document named users to handle counter for users. Here is the structure:
- counters (collection)
- users (document)
count: 0 (field)
Then, you should use a transaction to perform an update on this counter document to make sure you are working with up-to-date data to deal with concurrency (multiple accounts created at the same time)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.addNewUser =
functions.firestore.document('users/{userId}').onCreate((snapShot) => {
const userCounterRef = db.doc('counters/users');
return db.runTransaction(async transaction => {
const doc = await transaction.get(userCounterRef)
const { count } = doc.data()
const updatedCount = count + 1
return transaction.update(userCounterRef, {count: updatedCount })
})
});
https://firebase.google.com/docs/firestore/manage-data/transactions
EDIT: If you don't want to deal with async/await syntax, update your transaction like that :
return db.runTransaction(transaction => {
return transaction.get(userCounterRef)
.then(doc => {
const count = doc.data().count
const updatedCount = count + 1
transaction.update(userCounterRef, {count: updatedCount })
})
})
I have reproduced it in Cloud Functions, and this simple solution worked. Edited answer to fit Firebase, and it also uses the Firestore dependency for nodejs.
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore({
projectId: process.env.GCP_PROJECT,
});
const functions = require('firebase-functions');
//I am not using next two lines right now though
const admin = require('firebase-admin');
admin.initializeApp();
exports.helloFirestore =
functions.firestore.document('users/{userID}').onCreate((event) => {
const doc = firestore.doc('Counters/UserCounter/');
doc.get().then(docSnap => {
//Get the specific field you want to modify
return docSnap.get('userCount');
}).then(field => {
field++;
//Log entry to see the change happened
console.log(`Retrieved field value after +1: ${field}`);
//Update field of doc with the new value
doc.update({
userCount: field,
});
});
});
The wildcard you used should be fine, just be careful with the full path for the collection, mind the upper/lower case. For this case, this is how my package.json looks like:
{
"name": "sample-firestore",
"version": "0.0.1",
"dependencies": {
"#google-cloud/firestore": "^1.1.0",
"firebase-functions": "2.2.0",
"firebase-admin": "7.0.0"
}
}

TypeError: functions.database is not a function

I want to update or creat an object, but i have this error :"TypeError: functions.database is not a function" on the registry of firebase function
this is my code:
const functions = require('firebase-functions');
exports.actualizar = functions.https.onRequest((request, response) => {
const obj = request.body;
const MAC = obj.MAC;
functions.database().ref ('/sensores/{MAC}').update(obj).promise.then(() =>
{
console.log("UpDate Success");
return req.status(200).send("ok");
})
.catch(() => {
functions.database.ref('/sensores'). set(obj).promise.then(() =>{
console.log ("Created Succces");
return req.status(200).send("");
})
.catch(() =>{
console.log("Error");
return req.status(500).send("error");
})
})
});
You can't use the Cloud Functions for Firebase SDK to query the database. It's just used for building the function definition. To query your database or other Firebase products, you need to use the Firebase Admin SDK, or whatever SDK is normally used to do so.
For example, you will see lots of official sample code that starts like this:
const admin = require('firebase-admin'); // this is the Admin SDK, not firebase-functions
admin.initializeApp();
// Then use "admin" to reach into Realtime Database, Firestore, Cloud Storage, etc.

Resources