Firebase rules: How to access document id when querying collections - javascript

I am stuck with this one and need help please 🙏
I have a firebase document like so:
"789" : {
slug: "document-slug-name",
owner_uid: "123",
}
which has a subcollection called "roles" that looks like this:
"345" : {
permission_type: 1,
}
Now I am querying the documents like this:
const snapshot = await firebaseApp.firestore()
.collection("documents")
.where("owner_uid", "==","123")
.where("slug", "==", "document-slug-name")
.limit(1)
.get()
const document = snapshot.docs[0]
my rules look like this:
match /documents/{documentId} {
allow list: if
get(/databases/$(database)/documents/documents/$(documentId)/roles/$(currentUser().uid)).permission_type == 1;
}
But for some reason, both the rules simulator and the actual firebase db tell me that documentId is not set?
I have also tried resource.data.uid and resource.id to get the documents id but both are not set :(
is there any other way to query the roles subcollection?

Related

firstore query skips all documents with same startAfter value

TLDR; firestore skips all the documents that has same startAfter value. How to overcome this limitation.
My firestore postLikers collections keeps a document for each person who liked my post.
It stores the UID of the liker, and the epoch timestamp of when the post was liked.
postLikersCol : [
doc_1 : { uid: uid1, timeOfLike: 1661924000 },
doc_2 : { uid: uid2, timeOfLike: 1661924001 },
doc_3 : { uid: uid3, timeOfLike: 1661924002 }, // same time
doc_4 : { uid: uid4, timeOfLike: 1661924002 }, // same time
doc_5 : { uid: uid5, timeOfLike: 1661924002 }, // same time
doc_6 : { uid: uid6, timeOfLike: 1661924003 },
doc_7 : { uid: uid7, timeOfLike: 1661924004 },
]
and I am readin the data like this :
firestore()
.collection('postLikersCol')
.orderBy('timeOfLike', 'desc')
.startAfter(lastReadDoc.timeOfLike)
.limit(3)
.get()
In the first round of query the .startAfter() is not added so I get last 3 docs (doc_7, doc_6, doc_5, in this order)\
In the second call, .startAfter() is added in query and it takes timeOfLike of the last read document (doc_5) i.e. 1661924002
With this call, the firestore returns doc_2 and doc_1, both of which has timeOfLike < 1661924002
With this scenario, doc_4 and doc_3 are never read !!
Can someone suggest a solution for this, that I can read all documents with .orderBy('timeOfLike', 'desc')
Only solution I thought is using unique orderBy fields.
So appending uid with timestamp may work (1661924002_uid3).
Is there a better solution ?
You can use startAfter() with DocumentSnapshot of last document instead of the timestamp value. Try:
const snap = await firestore()
.collection('postLikersCol')
.orderBy('timeOfLike', 'desc')
.startAfter(lastReadDocSnap)
.limit(3)
.get()
Here, lastReadDocSnap would be snap.docs[3] i.e. the last document of previous query.
OK, so I found that that i can give two orderBy and two startAfter, to overcome this issue.
https://firebase.google.com/docs/firestore/query-data/query-cursors#set_cursor_based_on_multiple_fields
I suppose it will require a compound index (additional cost overhead)
But the solution of appending uid to the timestamp also costs more storage, and lengthier data is stored and it's not a number anymore.
Hmm, If you still have a better answer please let me know.

Exclude certain attributes from object using the populate from mongodb using aggregate

I want to exclude for example email and address using populate() function from mongodb, just get the name from it:
Example:
const results = await Seller.aggregate(aggregatePipeline).exec();
const sellers = await Seller.populate(results, { path: "user" });
When populating the user instead of having:
...
user: {
email: "hgjh#gmail.com",
address:{},
name: "name"
}
I want to only have (exclude certain data from the path):
...
user: {
name: "name"
}
You can do either,
const sellers = await Seller.populate(results, { path: "user", select: '-
email -address' });
or
const sellers = await Seller.populate(results, { path: "user", select:
'name' });
As i understand mongoose documentation https://mongoosejs.com/docs/populate.html, populate as $lookup is use to resolve a relation with other collection.
MongoDB has the join-like $lookup aggregation operator in versions >= 3.2. Mongoose has a more powerful alternative called populate(), which lets you reference documents in other collections.
In your case, you don't need to resolve a field with an other collection. You already have the final data you target . You could use $project at the end of your pipeline aggregation to keep only name field, like :
{ $project: { name:1 } }
Let me know if i helped you.
Edit :
I read too fast, if you have this data res after the populate and not after the aggreg, you may select your final field, like is said
here https://stackoverflow.com/a/72481338/16205278
user: {
email: "hgjh#gmail.com",
address:{},
name: "name"
}

How do I get a virtual in mongoose, If a property is deselected

lets say I have post Model and schema contains UserId , Title, Desc and likes array which takes userId's as ref
when I make a query I get a virtual property like this to find out num of like of a post would have
schema.virtual("numLikes").get(function () {
return this.likes.length;
});
But the problem is when I run the findById() Method I dont want to get likes from database because likes array would contain large list of users
const post = await Post.findById(postId).select("-likes -shares");
so how do I get Likes count without fetching likes array ?
I believe this can be done using aggregation, by using the $size operators in a projection:
const aggregate = Post.aggregate([
{ $match: {_id: postId}},
{ $project: {
numberOfLikes: { $size: "$likes" }
}
}
]);
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
https://mongoosejs.com/docs/api/aggregate.html#aggregate_Aggregate-project

mongodb changestream "pipeline" not working

I have some code that watches a mongodb collection for updates... I have it setup so that when anyone sends a message, the changestream will detect that... The issue is that when I try to add a pipeline, the updates do not get detected...
Here are some things that I've tried:
const pipeline = [
{ $match: { 'fullDocument.username_pair': 'HardCodedUsernameInDatabase' }}
];
changeStream = message_collection.watch(pipeline);
and
const pipeline = [
{ $match: { username_pair: 'HardCodedUsernameInDatabase' }}
];
changeStream = message_collection.watch(pipeline);
again, the code will detect all messages in the absence of any pipeline, ie:
changeStream = message_collection.watch();
...
UPDATE:
The reason why this is tricky is because the document I'm listening to looks like this:
_id:some_id
username_pair:"Test1234test1111"
messages:Array
The username_pair never updates, the messages array is what updates... I need to be looking at the document matching the username_pair, and I need to detect changes in the messages array that corresponds to the username_pair.
I guess the trick is to add option called fullDocument while opening the change stream on collection. see here
someCollection.watch([{
"$match": {
"operationType": {
"$in": ["update"]
},
"fullDocument.username_pair": "HardCodedUsernameInDatabase"
}
}], {"fullDocument": "updateLookup"})
Hope this helps

How to query for dynamic amount of aliases?

Assume I have a GraphQL Schema like this:
Schema {
user($id: ID) {
id: ID
name: String
}
}
I know how to query for one user by his ID, and I could use aliases to query for several users like so:
GetSomeMore {
john: user(id: "123") {
name
}
mary: user(id: "321") {
name
}
}
However, assume I just have a list of user IDs, and would like to query for the names of each one of them. How would I write a query like that?
The approaches I can come up with is ether dynamically generating a query with aliases like user1, user2, etc., or implementing a users(ids: [ID]) edge on the server. But I feel like there should be a better solution. Is there?
(PS: my code examples might have syntax errors, I'm writing it more as pseudocode, to get my point across.)
You could implement that functionality as part of your schema, for example with a id_in: [String] argument to the users query.
At Graphcool, we use the filter argument to expose functionality like this. For example, your query could be done like this:
query {
allUsers(filter: {id_in: ["123", "321"]}) {
id
name
}
}
results in
{
"data": {
"allUsers": [
{
"id": "123",
"name": "John"
},
{
"id": "321",
"name": "Mary"
}
]
}
}
To read more about the filter argument check the documentation.
I would go with your last proposition:
Schema {
users($ids: [ID]) {
id: ID
name: String
}
}
With a resolver like:
const users = async (root, { ids }) => await knex('users').whereIn('id', ids)
Note: I use graphql-js + graphql-tools + knex.js to create my schema, my types and my resolvers.

Categories