Firebase Cloud Function. Create documents in sub-collection of specific collections - javascript

here's the basic premise of what im trying to accomplish here. if a user ask a question about a product i want to send a notification to other users who currently own that product. basically saying "hey, so and so has a question about this product. maybe you can help since you own it already"
each userProfile collection has a subcollection called 'notify' where notifications are stored for various things. what i need to do is sort through the userProducts and find every user who owns the product and then create a notification post in only the notify sub-collections for those specific users who own that product.
here is the basic code. the first bit works in that it does return an array of userIDs who own that product. where im struggling now is getting it to create a new doc in the Notify sub-collection for just those specific users. is this possible to do?
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (data.question == true) {
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userData = userProducts.docs.map((doc) => doc.data().userId);
await db
.collection("userProfile")
.where("userId", "in", userData)
.get()
.then((querySnapshot) => {
return querySnapshot.docs.ref.collection("notify").add({
message: "a user has asked about a product you own",
});
});
});

Your current solution is on the right track, but there are improvements that can be made.
Use a guard pattern for the data.question == true check.
You don't need to get userProfile/<uid> as you aren't using its contents.
When changing multiple documents at once, you should consider batching them together for simpler error handling.
ref.add(data) is shorthand for ref.doc().set(data) which you can use in the batched write to create new documents.
exports.Questions = functions.firestore
.document("/userPost/{id}")
.onCreate(async (snap, context) => {
const data = snap.data();
if (!data.question) {
console.log("New post not a question. Ignored.")
return;
}
const userProducts = await db
.collection("userProducts")
.where("product", "==", data.tag)
.get();
const userIds = userProducts.docs.map(doc => doc.get("userId")); // more efficient than doc.data().userId
// WARNING: Limited to 500 writes at once.
// If handling more than 500 entries, split into groups.
const batch = db.batch();
const notificationContent = {
message: "a user has asked about a product you own",
};
userIds.forEach(uid => {
// creates a ref to a new document under "userProfile/<uid>/notify"
const notifyDocRef = db.collection(`userProfile/${uid}/notify`).doc();
batch.set(notifyDocRef, notificationContent);
});
await batch.commit(); // write changes to Firestore
});
Note: There is no special handling here for when no one has bought a product before. Consider pinging the product's owner too.

Related

How can I get documents from Firestore for current user?

I'm trying do display data from Firestore database in my component.
This is my function:
const getData = async () => {
const data = [];
const querySnapshot = await getDocs(
collection(databaseRef, "mK7DFNJgRAPmtvgrZh7X6AOj8cR2")
);
console.log(querySnapshot);
querySnapshot.forEach((doc) => {
console.log(doc.id, " => ", doc.data().Title);
data.push({
About: doc.data().About,
Title: doc.data().Title,
Who: doc.data().Who,
});
});
setData(data);
};
Collection ID = Current logged in User.
I want to display every document.
Everything works fine but insteed of passing hard-coded string here:
collection(databaseRef, "mK7DFNJgRAPmtvgrZh7X6AOj8cR2")
I would like to pass variable where I store my UID.
Is there any way to do that?
Assuming your user is logged in, you should be able to access their UID via firebase.auth().currentUser.uid. This question's answers may be useful for more information on getting the current user's ID.
With that, you should be able to do:
const querySnapshot = await getDocs(
collection(databaseRef, firebase.auth().currentUser.uid)
);
to get the current user's documents.
https://firebase.google.com/docs/reference/js/v8/firebase.auth.Auth#currentuser

how to trigger a firestore collection via cloud function

I want to trigger a new collection (timeline collection) from the existing collection of followers collection and videos collection whenever I clicked the following button in my app.
Now the problem is that, the Cloud Function is created from the view log but the new collection (timeline collection) won't be created.
Below is the code for the Cloud Function where I target the followers collection and the videos collection to create a new timeline collection. I anticipate for your help.
Videos collection
Followers collection
Cloud function view logs
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
// // 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!");
// });
exports.onCreateFollower = functions.firestore
.document("/followers/{userId}/userFollowers/{userfollowerId}")
.onCreate(async (snapshot, context) => {
console.log("The Event has Created The Follower", snapshot.id);
const userId = context.params.userId;
const userfollowerId = context.params.userfollowerId;
// 1) Create followed users posts ref
const followedUserVideosCollection = admin
.firestore()
.collection("videos")
.doc(userId)
.collection("userVideos");
// 2) Create following user's timeline ref
const timelineVideosCollection = admin
.firestore()
.collection("timeline")
.doc(userfollowerId)
.collection("timelinePosts");
// 3) Get followed users posts
const querySnapshot = await followedUserVideosCollection.get();
// 4) Add each user post to following user's timeline
querySnapshot.forEach(doc => {
if (doc.exists) {
const videoId = doc.id;
const videoData = doc.data();
timelineVideosCollection.doc(videoId).set(videoData);
}
});
});
I figured out what causes the error "querySnapshot.forEach isn't a function". According to this answer, you need to query the collection first because get() returns a document instead of a snapshot. Here's a sample code (see step 3):
// 1) Create followed users posts ref
const followedUserVideosCollection = admin
.firestore()
.collection("videos")
.doc("Videos 1") // I changed the value with your sample for test purposes and also because I'm not sure how you fill up this doc.
.collection("userVideos");
// 2) Create following user's timeline ref
const timelineVideosCollection = admin
.firestore()
.collection("timeline")
.doc(userfollowerId)
.collection("timelinePosts");
// 3) Get followed users posts & Add each user post to following user's timeline
await followedUserVideosCollection.where('id', '==', 0).get().then((querySnapshot) => {
if (querySnapshot) {
querySnapshot.forEach(doc => {
if (doc) {
const videoId = doc.id;
const videoData = doc.data();
timelineVideosCollection.doc(videoId).set(videoData);
}
});
}else {
console.log("Document not found");
}
}).catch((error) => {
console.log(error);
});
A solution is to create a filter, and make sure that the document you're looking for matches the filter. For example, a document inside the subcollection userVideos should have a field called id with value of 0.
You may have to remodel your DB to fix the line where I put a comment but this code should write the timeline collection.

Firebase: how to receive/add info to separate collection via JS

yeap, if read a title you can think that it is simple action via collection - but no -> in you we have a button "Add collection": not clear how to add this collection via code or how to receive these created collections.
Please take a look at the structure of BD in the attachment:
.
I can receive ID of a document, but not clear how to receive collection(s)/data of these collections. here i just receive the main ID, not "1" collection and his data:
const listCollections = [];
await db
.collection(table)
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
debugger
const dataCollectionObject = doc.data();
dataCollectionObject.id = doc.id;
listCollections.push(dataCollectionObject);
});
});
When you receive a document, you need another query to inspect its sub-collections.
Also, I suggest not mixing async/await with then.
A basic approach
const rootSnapshot = await db.collection(table).get();
const promises = rootSnapshot.docs.map(rootDoc => rootDoc.ref.collection("1").get());
const childrenDoc = await Promise.all(promises);
Using collection group queries
see the doc
const childrenDoc = await db.collectionGroup("1").get();
If you do not know the sub-collection name
Bad luck! With Firestore you are required to know the name of your collections. You could for exemple store it in another data member:
document {
someField: ...
collectionIds: ["1", "2"] // store the sub-collection ids
"1" // a sub-collection
"2" // another sub-collection
}
Then when you retrieve such a document, inspect its data and loop on collectionIds to query deeper.

How to read data from firestore database without trigger functions in index.js file?

I have stored some data in firestore. There is collection(books) which link to document(book id)and book id has field like name, image,location,title.
I have another collection(Users) which has document(user id)user id has field as token id .Whenever there will be any write operation in book collection then I have to send notification to all the user using token id .
I am able to send notification to all user if I have hard-coded token id in my index.js file of firestore.
But I have to send notification dynamically. I am not able to read user id from collection users.
'use-strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.firestore.document("Books/{book_id}").onWrite((change, context) => {
const book_id = context.params.book_id;
console.log("Book ID: " + book_id);
return admin.firestore().collection("Users").doc(user_id).collection("Notifications").doc(notification_id).get().then(queryResult => {
const tokenid= queryResult.data();
const token_id='fOGd94em4ik:APA91bHyZBGBYvO_ZFlLO1lWL1LU-r-1JkuF3fuKieWvV4HuPDKAiG5hdn-BQrMPFeICBdKZ3UR2nkM2PMxClEzVI3V2C38OxoP-1w71Dz-GbO0sbDlg-nswCMZ';
const payload = {
notification : {
title : 'Hi',
body : 'New Books list is available in the database! Please check the book store',
icon : "default"
}
};
return admin.messaging().sendToDevice(token_id, payload).then(result => {
//console.log("Notification sent");
return 0;
});
});
});
In above code I want to read user_id from collection users. How can I read it since it is not linked with collection books ,I am not able to read.
If I understand well and if token_id is a key in users record, you may try something like this:
admin.firestore().collection('Users')
.where('token_id', '==', 'YOUR_TOKEN').get()
.then(snap => snap.docs.map(user => {
// iterate on your notification process
})

Firestore Cloud Function empty collection

I have a problem that's bugging me for days. I am trying to create a Firebase Cloud function that reads from the Firestore database.
My Firestore DB looks like this:
Problem is that I cannot list users like this:
db.collection('users').get().then((snapshot) => snapshot.forEach(...));
If I try to do this I get empty response, like there are no users in my users collection.
But I try to access user directly it works:
await db.collection('users/5CZxgu8nmNXu2TgplwOUdOIt8e33/receipts').get()
My complete code:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.cat = functions.https.onRequest(async (req, res) => {
const receiptList: any = [];
const db: Firestore = admin.firestore();
const usersRef = await db.collection('users').get();
console.log(usersRef.empty); // Returns true
const receiptsRef = await db
.collection('users/5CZxgu8nmNXu2TgplwOUdOIt8e33/receipts')
.get();
receiptsRef.forEach((receipt: any) => {
console.log(receipt);
receiptList.push(receipt);
// Here I can access data
});
res.send(receiptList);
return '';
});
Does anyone have any idea what I'm doing wrong? Thank you!
Your users collection is actually empty. See how the document IDs are shown in italics? That means there is not actually a document in its place, however, there are subcollections with documents organized underneath them.
When you query a collection, you only get the documents that are immediately within that collection. A query will not pick up documents organized in subcollections. In this respect, queries are said to be "shallow". As you've seen, you need to reach deeper into the subcollection to get its documents.
Bottom line is that the queries you're showing are doing exactly what they're supposed to do.
Thanks again Doug for your help.
I manage to solve my problem. Here is my complete solution.
import * as functions from 'firebase-functions';
import {
Firestore
} from '#google-cloud/firestore';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.cat = functions.https.onRequest(async (req, res) => {
const receiptList: any = [];
const db: Firestore = admin.firestore();
const receipts = await db.collectionGroup('receipts').get();
receipts.forEach((doc: any) => {
console.log(doc.id, ' => ', doc.data());
receiptList.push(doc.data());
});
res.send(receiptList);
return '';
});
.get() gets all documents. In your case those documents are empty therefore .get() doesn't consider them.
The simplest solution that I found for this is to replace .get() with .listDocuments(). Now you could read each doc entry like you would a doc.

Categories