How to create an Auto-ID inside a firestore document? - javascript

I am trying to achieve this for the friend request functionality in my data structure for sent requests, but I am unable to:
Essentially I am trying to pull this off from the Realtime Database in the Cloud firestore:
Above reqId1 and reqId2 are automatically generated Id's.
Its quite easy to pull this off in the Realtime db as I can just use the push() method.
The issue is not creating a random number, it is that I am unable to create a document with an autoID inside the userId document in which I shall store the data.
I have tried this:
sendRequest() {
const uid = auth().currentUser.uid;
firestore()
.collection('Sent_Reqs')
.doc(`${uid}`)
.collection(`${this.autoId()}`)
.add({
targetId: this.userId,
sentAt: new Date(),
});
}
But the above doesn't do any good because it nests the data two levels inside the document(uid).
Please help me
Thank you

The data structure on Firestore is always in pairs of collection and then document in there. The collection names are usually fixed/hard-coded names, while the document names are usually generated (or based on the data).
You can't immediately nest documents under another document, they must always be in a named collection. If there's no reason for you to have multiple subcollections, you can just pick any subcollection name that makes sense for you.
For example:
Sent_Requests
(uid)
Requests
(request_id)
Here Sent_Requests and Requests are collection names, (uid) and (request_id) are document names. The Requests collection is not really helpful, but needed to satisfy the Firebase requirements.

It's part of your document data. I don't this firebase can help you on that.
If you are structuring the database this way just to fetch the request sent by particular user easiy. I would like to suggest another structure as follows:
Sent_Request (collection)
|-AutoID1 (document, this ID you can generate with help of firebase)
|- targetUser : value
|- sentAt : value
|- requestSentBy : userId
|-AutoID2 (document, this ID you can generate with help of firebase)
|- targetUser : value
|- sentAt : value
|- requestSentBy : userId

Related

How to create a chain path in Firestore [duplicate]

My db structure is like this:
domain -> user uid -> user data
if I try to add data like this:
await FirebaseFirestore.instance
.collection(path)
.doc(firebaseUser.uid)
.collection(collectionName)
.add({...});
where path is the user domain, the database shows like this:
telling me that The document does not exists, it will not appear in queries or shapshots. But if I add the same data by auto id like this:
await FirebaseFirestore.instance
.collection(path)
.add({...});
it works like the second document in picture. Why is this happening?
Look at how the documents in this collection are displayed in an italic font in the Firestore console: This means that these documents are only present as "container" of one or more sub-collection but that they are not "genuine" documents.
As a matter of fact by doing
await FirebaseFirestore.instance
.collection(path)
.doc(firebaseUser.uid)
.collection(collectionName)
.add({...});
You create a doc in the collectionName (sub)collection but not in the path collection.
On the other hand, with
await FirebaseFirestore.instance
.collection(path)
.add({...});
you do create a doc in the path collection.
So if you need to have a document in the path collection AND in the collectionName (sub)collection you need to create these two documents and not only the "child" one.
DETAILED EXPLANATIONS:
Let's take the example of a doc1 document under the col1 collection
col1/doc1/
and another one subDoc1 under the subCol1 (sub-)collection
col1/doc1/subCol1/subDoc1
Actually, from a technical perspective, they are not at all relating to each other. They just share a part of their paths but nothing else.
You can very well create subDoc1 without creating doc1.
A side effect of this is that if you delete a document, its sub-collection(s) still exist. Again, the subcollection docs are not really linked to the parent document.

How to get a subset of object's properties when querying an entire collection in Firebase Firestore? Using JS client sdk v9

I have this Firestore onSnapshot listener on a collection (getting the entire collection). I would like to get only a subset of the the properties of each object.
Something like we do with the firebase-admin using select() on a query:
Ex: admin.firestore().collection('NAME').where(conditions).select('field1', 'field2').get()
This is the onSnapshot code: it works just fine but it's getting the full objects (containing all the properties).
const db = getFirestore();
const col = 'COL_NAME';
const q = query(collection(db, col));
onSnapshot(q, (querySnapshot) => { // How to get only a subset of fields here ?
const results = {};
querySnapshot.forEach((doc) => {
// Do something with each object
});
});
Of course I can map it on the client, but my goal is to keep data network traffic to a minimum.
How can I do it?
With the Client SDKs this is not possible.
As you have mentioned this is possible with the Admin SDK but it is also possible with the Firestore REST API: You can use a DocumentMask when fetching documents, which will "restrict a get operation on a document to a subset of its fields. ".
Note however that fetching via the REST API from a web app is much less simple than using the JS SDK. In particular the format of the response is a complex object that is not funny to parse...
Another approach would be to dernomalize your data: You create another collection which contains documents that only have the fields you want to display in the front end.
The complexity is that you need to keep the two collections in sync: when you create/modify/delete a doc in the master collection, you need to update the other collection. This can be done from your front-end (e.g. you write to the two collections in a batched write) or with a Cloud Function, triggered in the back-end with an onWrite trigger.

Firestore query with only a field inside an array

This is the thing I want to accomplish: I'm building a web shop. The web shop has a React Front-end. The front-end fetches 5 collections from Firestore and displays all the items from the collection array on the shop page. A user selects an item on the shop page. I send the item fields such as (price, name, quantity, id) to my express server and the server makes a checkout session of the item fields. The user goes to a Stripe checkout form and is sent back to my front-end by Stripe when the payment is complete. I listen for that event on my server and when then want to update the quantity field of the item in Firestore.
But how do I query Firestore for this item? Is there a way to query Firestore with only this id field (or name field)? Some something like:
db
.collection('collections')
.where('id', '===', 1)
Or do I need to save the document id (of the collection) as a field inside the item map and also send that to Stripe? Or is there a better way to do this? I can't find anything online about this.
Here is a screenshot of Firestore.
Please forgive my beginner question. I'm still learning React, Firestore and Node.js.
First be sure you are sticking to the Firestore terminology correctly. There are collections and there are documents.
Collections you access via a path such as:
collRef = db.collection("products")
collRef = db.collection("products").where("quanity_on_hand", ">", "0")
collRef = db.collection("products").doc("12345").collection("purchase_history")
The latter instance can also be accessed via collRef = db.collection("products/12345/purchase_history").
In all the above cases you will get back a CollectionReference.
Documents you access such as:
docRef = db.collection("products").doc("12345")
docRef = db.doc("products/12345")
This returns you a DocumentReference for the document whose ID is "12345" in the collection "products".
So for your code example above, you want to use docRef = db.doc("collections/1") to get back the DocumentReference for the item you are after. (Or, alternatively, you could use: docRef = db.collection("collections").doc("1")
If you stick with the code that you have above, you'd get back a CollectionReference then you'd need to fetch the data with .get(), then extract the resulting documents (that will just be a single document), then work with that. Oh...and you will need to put an "id" field into all of your documents because the document's ID value (the "name" of the document) is not part of the document by default so if you want to use .where("id", "==", "1"), then you need to add an "id" field to your document and populate it correctly.
If you go with docRef = db.doc("collections/1"), you are querying for the document directly and will get back a reference to just that one. No need for extra fields, nor extracting a single document from a result set.

Firebase orderByChild with equalTo() doesn't work in javascript

I have tried to use .indexOn of firebase on field 'status' which is inside the child of 'BOUGHT_PRODUCT'.
But, when i run following query.
db.ref().child('BOUGHT_PRODUCT')
.orderByChild('status')
.equalTo('Service')
.on("value", function (snapshot) {
console.log(snapshot.val());
});
I get null snapshots.
Even, Firebase warning of index :
"#firebase/database: FIREBASE WARNING: Using an unspecified index. Your data will be downloaded and filtered on the client. Consider adding ".indexOn": "status" at /BOUGHT_PRODUCT to your security rules for better performance."
Firebase Data:
Index firebase:
Your help would be appreciated.
Firebase Database queries run against each child under the location where you run them. The field you order/filter on must be at a fixed path under each child. Since you run the query on /BOUGHT_PRODUCT, the database searches for /BOUGHT_PRODUCT/$uid/status. And since that property doesn't exist, there are no results matching the query.
In other words: your current data structure allows you to query a specific user for the status of their products, but not across all users. If you want to implement that use-case, you will need to create a data model that allows it, e.g. a single top-level list of product statuses.
Statuses
product1: "Service"
product2: "Service"
product3: "Something else"
Also see:
Firebase Query Double Nested
Firebase query if child of child contains a value

Adding information from another mongodb collection before providing the data via meteor's publish method

What I want to accomplish on http://crowducate.me:
Display the usernames of the course authors (i.e. "owner" of a document).
Current Code:
Meteor.publish 'popularCourses', ->
# find all courses
courses = Course.find({}, {sort: {createdAt: -1}}).fetch()
for course in courses
# find each User by course owner
owner = Meteor.users.findOne({_id: course.owner})
# overwrite the ownerId with the desired username
course.owner = owner.username
return courses
If I turn autopublish on, it works. The image shows the current status (autopublish off). As seen in the image, the author's name is only rendered if the current user is the same as the author.
--
A friend suggested the following:
https://gist.github.com/wiesson/1fd93d77ed9df353b7ab
"The basic idea was to attach the username to the course before providing the data with the publish method. However, as described in Meteor MongoDB find / fetch issues, the publish method should return a curser and not an array of objects.”
Any ideas how to solve that? Putting the owner usernames in an array? If so, how?
P.S.: Sourecode can be found here (currently, has more commits than the deployed version):
https://github.com/Crowducate/crowducate.me
Thanks a lot.
There are a couple of ways you can accomplish this join. A few notes before before we begin:
As I explained in the answer to this question, sorting in the publish function has no affect on the order of documents on the client.
Using the plural form in a collection name is the accepted standard. Course just looks odd when the collection contains courses.
This question is fundamentally about joins, so I'd recommend reading Reactive Joins In Meteor.
Server Transform
The literal answer to your question is to transform the documents on the server like so:
Meteor.publish 'popularCourses', ->
transform = (fields) ->
if fields.owner
username = Meteor.users.findOne(fields.owner)?.username
fields.owner = username
fields
handle = Course.find().observeChanges
added: (id, fields) =>
#added 'course', id, transform fields
changed: (id, fields) =>
#changed 'course', id, transform fields
removed: (id) =>
#removed 'course', id
#ready()
#onStop ->
handle.stop()
Advantages
All of the work is done on the server, so the client can just use owner as if it was a username.
Disadvantages
Using observeChanges is probably more computational work than a simple join deserves.
If you publish courses somewhere else, it's entirely likely that owner will be overwritten when the documents are merged on the client. This can be countered by appending a field like ownerUsername but that would also require a more expensive observe.
This isn't helpful if you actually need the owner id somewhere on the client.
It isn't reactive if the username changes (probably rare but figured I'd point that out).
Non-Reactive Publish + Client Join
You could implement the publish like this:
CoffeeScript
Meteor.publish 'popularCourses', ->
courseCursor = Course.find()
userIds = courseCursor.map (c) -> c.owner
userCursor = Meteor.users.find {_id: $in: userIds}, {fields: username: 1}
[courseCursor, userCursor]
JavaScript
Meteor.publish('popularCourses', function() {
var courseCursor = Course.find();
var userIds = courseCursor.map(function(c) {return c.owner;});
var userCursor = Meteor.users.find(
{_id: {$in: userIds}},
{fields: {username: 1}
});
return [courseCursor, userCursor];
});
Note that I'm being careful to only publish the username and _id from userCursor (you don't want to publish the hashed password and session data by accident). Then you can join the two collections on the client like this:
Template.myTemplate.helpers
courses: ->
Course.find().map (c) ->
c.owner = Meteor.users.findOne(c.owner)?.username
c
Advantages
Computationally light-weight and simple publish function.
Reactive if the username changes.
Disadvantages
Not reactive if the owner changes.
You'll need to do the join on the client. An interesting alternative is to use something like Collection Helpers.
Finally, I'll point out that you can use a package to do a fully reactive join. However, unless the owner (or owner's username) is changing a lot then this is probably overkill.
A simple solution would be to just publish both popularCourses and owners and add the owner to each course on the client (with the exact same code you have written on the publication).

Categories