In my cloud function i have an array that contains all userId's that need to get a cloud-message(notification)
const aNotify = [{id: 'id001', text: 'specialTextFor001'}, {id: 'id002', text: 'specialTextFor002'};
This is how the devices collection looks like. the Document ID is the token ID but to find them i need to query on the userId
Is it possible to do it through the DB like with a where clause or do I need to do this by getting all devices and in cloud method do a foreach... ?
In order to find a device document corresponding to a userId, you have to use a simple query like:
const db = admin.firestore();
db.collection('devices').where("userId", "==", element.id).get();
see the corresponding doc here.
Since you need to make a query for each element of the aNotify array, you need to use Promise.all(), since get() returns a Promise.
Something like the following will work. You have to adapt it in order to correctly return the promises in your Cloud Function (since you didn't share your Cloud Function code it is difficult to give more guidance on this point).
const db = admin.firestore();
var aNotify = [{ id: 'id001', text: 'specialTextFor001' }, { id: 'id002', text: 'specialTextFor002' }];
var promises = []
aNotify.forEach(function (element) {
promises.push(db.collection('devices').where("userId", "==", element.id).get());
});
return Promise.all(promises)
.then(results => {
results.forEach(querySnapshot => {
querySnapshot.forEach(function (doc) {
console.log(doc.id, " => ", doc.data());
//here, either send a notification for each user of populate an array, or....
//e.g. return admin.messaging().sendToDevice(doc.data().token, ....);
});
});
});
Note that the results array has exactly the same order than the promises array. So it is not complicated to get the text property of the corresponding object of the aNotify array when you send the notifications.
Related
i have a collection that is called employees, which includes documents and each document contains some data, and an array of objects that is called orgsanizations, for instance:
orgsanizations: [
{
orgId: 'org1',
registrationDate: '08/05/2021',
status: 'pending'
},
{
orgId: 'org2,
registrationDate: '12/01/2021',
status: 'approved'
}
];
I am trying to retrieve all the documents in employees that contains orgId === org1 in the orgsanizations, here is what i tried to do but keeps returning empty array.
const allEmployees = async () => {
const employeesList = db.collection('employees');
const snapshot = await employeesList
.where('orgsanizations', 'array-contains', { orgId: 'org1' })
.get();
if (snapshot.empty) {
console.log(snapshot.empty);
} else {
snapshot.forEach((doc) => {
console.log(doc.data());
});
}
};
};
Is there a solution for this or should start considering changing the structure to something else?
Thanks in advance
You can't check for the contents of a map, using array-contains. There are a couple of solutions for this...
Create a second array called orgIds, which contains only the orgId strings. You can then find any documents which contain these orgIds. To achieve this, you will need to write the orgId into the map AND the orgIds array.
Create an organizations sub-collection of your employee document and use a collectionGroup query.
{
organizations: [
{orgId: 'org1', registrationDate: '08/05/2021', status: 'pending'},
{orgId: 'org2', registrationDate: '12/01/2021', status: 'approved'}
],
orgIds: ['org1', 'org2']
}
const employeesList = db.collection('employees');
const snapshot = await employeesList
.where('orgIds', 'array-contains', 'org1')
.get();
You may also want to change your registrationDate to either a Timestamp or an ISO8601 string, so that you can sort them (if needed).
My interface looks like this:
Interface react:
export interface UserTask {
assignee: String;
date: String;
}
export interface TaskData {
id: String;
taskName: string;
taskIcon: string;
taskLog: UserTask[]
}
Firebase Data model (Structure):
- taskData (Main Collection)
- taskIcon (fields)
- taskName (fields)
- taskLog (sub-collection)
- assignee (fields)
- date (fields)
React code to get firestore data:
import { firestore } from '../firebase';
...
function toTaskData(doc): TaskData {
if (!doc.exists) {
throw new Error("TaskData not found!");
} else {
return { id: doc.id, ...doc.data() } as TaskData;
}
}
const getTaskData = async() => {
const taskDataRef = firestore.collection('taskData');
await taskDataRef.get().then(({docs}) => {
setTaskData(docs.map(toTaskData));
});
}
Response JSON from firestore:
I am only getting an response which contains an array of the taskName and taskIcon and not the subcollection taskLog, my question is how to I retrieve the subcollection or map it to UserTask array?
Firestore queries are shallow: when you fetch a document you don't fetch the the data contained in any of its linked subcollections.
If you want to get a Firestore Document together with all the documents from one of its subcollection, you need to do two fetches: one fetch for the "parent" doc and one for all the docs of the subcollection.
For example, you can do something along the following lines, using Promise.all():
const taskDataRef = firestore.collection('taskData');
const querySnapshot = await taskDataRef.get();
const promises = querySnapshot.docs.map(doc => doc.ref.collection('taskLog').get());
const querySnapshotsArray = Promise.all(promises);
querySnapshotsArray.forEach(querySnapshot => {
// Do something with the querySnapshot
// e.g. querySnapshot.docs....
// or querySnapshot.forEach(...)
});
If you want to combine the data of one parent (a taskData doc) together with all the children taskLog docs, note that Promise.all() returns a single Promise that resolves to an array of the results of the input promises which is in the same order than the input Array. In other words, querySnapshotsArray has the same order than promises.
Note that doing so will cost the read of ALL the taskData docs and ALL the taskLog documents from ALL the taskLog subcollections. It may be less exepensive to organize your front end in such a way the subcollections are only fetched on demand (e.g. if the user clicks on a button or expands a section, etc...)
So, I have a Firestore database group like so.
companies > acme-industries > items > []
OR
collection > document > collection > document
Would it be better to just store all items inside a base collection and then add a string value to each item that defines what company it goes too? Then just query the items collection for all items linked to that company?
I am trying to retrieve the items and run them through a forEach in my firebase function. I have tried two different approaches and watched multiple videos and still am not getting results.
First Attempt Code Block
This resulted in a 500 Server Error with no explanation returned.
const itemQuerySnapshot = db.collection('companies').doc(data.userData.company).collection('items').get();
const items: any = [];
itemQuerySnapshot.forEach((doc:any) => {
console.log('doc', doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
response.json(items);
Second Attempt Code Block
This resulted in the No Such Documents! being returned
const itemRef = db.collection('companies').doc(data.userData.company).collection('items');
itemRef.get().then((doc:any) => {
if(!doc.exists) {
response.send('No such documents!');
} else {
response.send('Document Data: '+ doc.data());
}
}).catch((err:any) => {
response.status(500).send(err);
});
I am expecting something like an array of all the items to be returned from this call. I'm completely new to Firebase Firestore, what am I missing here?
UPDATE
I replaced my code with a third attempt code block and I got success with the console.log(doc.data()). However, the items object still returns empty. Is this because it's returning before the for each is done? If so, how would you prevent that to ensure every item that should be returned is?
const items: any = [];
const userRef = db.collection("companies").doc(data.userData.company);
const itemsRef = userRef.collection("items");
itemsRef
.get()
.then((snapshot: any) => {
snapshot.forEach((doc: any) => {
console.log(doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
})
.catch((err: any) => {
response.status(500).send(err);
});
response.json(items);
How would you add one more document into the mix? Say you want to get a single item. How would you do that? The following always results in Item does not exist being returned from my function.
const companyRef = db.collection('companies').doc(data.userData.company);
const itemRef = companyRef.collection('items');
const item = itemRef.where('number', '==', itemSku).get();
I must be doing something incredibly wrong here because all the videos are telling me it's incredibly easy to fetch data from Firestore. But I have yet to see that.
get returns a Promise , the callback of then function will be called once the data ready from firestore .
the line response.json(items); will be called before the items array collected correctly.
you need to move this line inside the then callback
checkout this :
.then((snapshot: any) => {
snapshot.forEach((doc: any) => {
console.log(doc.data());
items.push({
id: doc.id,
data: doc.data()
});
});
response.json(items); //items ARRAY IS READY , YOU CAN SEND YOUR RESPONSE HERE
})
firebase.firestore().collection('items')
Returns all strings as expected, but the created at timestamp is returned as an empty object {}
{"created_at":{},"name":"item one","description":"item one desc"}
How can I get it to provide the actual timestamp that appears in the db in the firebase console?
The result returned from the above collection is stored as items and is displayed in a table in vue js.
Firebase Firestore Timestamp is an object, if you want to display it, you have to convert it into a js date object. You can read more here in the public constructors section.
Example js:
firebase
.firestore()
.collection('items')
.get()
.then((querySnapshot) => {
const data = [];
querySnapshot.forEach((item) => {
const { created_at, ...rest } = item.data();
data.push({
created_at: new Date(created_at.seconds * 1000), // or you can use moment - moment.unix(created_at.seconds)
...rest,
});
});
console.log(data); // [{"created_at": "2019-04-10T15:25:04.000Z", "name": "item one", "description": "item one desc"}]
});
Im trying to retrieve some data from firestore within a cloud function, but get nothing back. The same query on the client-side gives me the correct results. It's probably something small but I don't see the issue. What am I doing wrong?
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
db.settings({ timestampsInSnapshots: true });
exports.myFunction = functions.https.onCall((data, context) => {
const info = getInfo();
//do some stuff with the info
return info;
}
function getInfo() {
const query = db
.collection('info')
.where('table_nr', '==', 1)
.where('number', '<=', 25)
.orderBy('number', 'desc')
.limit(1);
const info = query.get().then(snapshot => {
snapshot.forEach(doc => {
return doc.data();
})
})
return info;
}
When I make a call to this function I get: "data: null"
let info = functions.httpsCallable('myFunction')
info().then(res => { console.log(res) })
I tried a lot of different options, like when I change the last part to:
const info = query.get().then(snapshot => {
snapshot.docs;
})
I get an array with 1 object. So I'm sure there is a document in the query with data. The console.log gives me:
{data: Array(1)}
data: Array(1)
0: {_ref: {…}, _fieldsProto: {…}, _serializer: {…}, _validator: {…},
_readTime: {…}, …}
length: 1
__proto__: Array(0)
__proto__: Object
And:
return query.get().then(querySnapshot => {
if (querySnapshot.empty) {
return { exists: false }
} else {
return { exists: true }
}
})
The console.log:
{data: {…}}
data:
exists: true
__proto__: Object
__proto__: Object
Mabye good to add that I created an (working) index for the query.
In both cases, you're returning a promise for an object that isn't what you really want to send to the client. When you write a callable, you need to return a promise that resolves to the exact JavaScript object that you want to send. You can't just return anything. What you'll have to do is convert that querySnapshot into plain old JavaScript objects that describe what you want the client to know. A querySnapshot object itself is not serializable - it is a complex object that describes many things about the query results.
First define this: What exactly do you want the client to receive? Define what the actual JavaScript object should look like. Now, convert the query results to look like that. At a minimum, you can send the entire set of documents as plain JS objects like this:
return query.get().then(querySnapshot => {
return querySnapshot.docs.map(doc => doc.data());
})
This will return an array to the client with raw document objects. But it's not clear to me that's what you want to send (since you didn't define your expectations). But it's a start.
So to give a clear example of using the .where in the Firebase Functions library for Firebase Cloud Functions
admin.firestore().collection("fruit")
.where("color", "==", "purple")
With ordering
admin.firestore().collection("fruit").orderBy("size")
.where("color", "==", "purple")
See demo doc if you want more details about how admin functions
Furthermore, the list of all query functions are found in the Query class can be used from the Firestore Functions library by using method chaining like my two examples in case you'd like things like "limit", "offset", etc.