Suppose I have chatrooms as part of a Meteor app, and inside each chatroom the users should see who else is in the chatroom, as well as the number of users in all other chatrooms.
Where should I store the chatroom that each user is in? Should it be
in the provided Meteor.users collection, as a property under each user?
in the Chatrooms collection, as a list of users inside each chatroom?
What's the right way to denormalize the data so that a user can see the number of people in other chatrooms without needing to get pushed all of those user records as well?
A related question: when should I store things in Meteor.users() as opposed to a separate collection containing documents of user IDs and rooms, such as ChatroomParticipants? i.e.:
{ user: "foo", room: "room 1" }
{ user: "bar", room: "room 2" }
Additionally, is there a way to set this up so that a user can be logged in to the same app from multiple windows and be in different rooms? That is, the rendering for a particular user does not depend on some global state that makes everything change across sessions.
You should make it write on Meteor.users document in that case. The less you centralize read/write in only one document, the better, so it won't be a bottleneck. See how meteor-presence make it on the Advanced Usage.
Related
I'm building an an app in Firebase with a user feature and I need to implement a system to allow:
A user to follow another user
A user to see a list of the users they're following
A user to set their profile as private so that some of their data is only visible to the people following them
A user to be able to send a follow request to a user with a private profile
A user with a private profile to be able to accept/reject follow requests
So far I've made a Firestore collection at the root called users. When a user signs up with Firebase Auth, a document is made in users with the following structure:
user (document)
username: stringaccountIsPrivate: boolean
userData (collection)
userData (document)
where all the data that would be hidden if the account were private is in the userData document.
I'm not sure how I could implement the system to fulfill my requirements from here so that I could use Firestore rules to only allow followers of a private account to view that account's userData. I would appreciate it if anyone could suggest an appropriate data structure and an outline of how to write rules for this.
For this kind of situation, you must maintain two sources of truth, one for the creator and one for the user. this is done with an array of strings in both that have the user_uid and any additional information concatenated.
The goal is to have an array of CSV-like values of which you can split and render within your app.
create a concat string: entry = [user.uid, user.name, user.url].join(';');
return string to object: entry.split(';');
Doing the following ensures that only a unique entry exists
db.doc("user/user_id/userData/followers").set({ followers: Firestore.FieldValue.ArrayUnion(entry)}, {merge: true});
This is only a rough example and some backend logic will be needed to scale large - but with this, you have a theoretical limit of 200k entries depending on how much data you want to store in the string.
Additional logic would involve cloud functions reading and writing when a request to follow has been created which handles a counter that creates new documents as needed and ensure's that the counter is updated to prevent overflow since Security Rules can't do any recursive logic or manipulate the request directly.
I have a question regarding authentication using Google Firebase.
For an app, I want to build an authentication similar to the one Slack uses: first, the user provides the input as to which group they want to log in to. If there exists a group with the same name as provided in the input, the user is then taken to a login/signup screen.
I've thought about storing users in the realtime database as follows, but I think there must be a better way to do this (since I don't think I can use the firebase authentication in this case):
groups: {
"some_group_name": {
"users": [
"user1": {
.. user 1 information
},
"user2": {
.. user 2 information
}
],
"group_details": {
"name": ..,
"someGroupDetail": ..
}
},
"some_other_group_name": {
...
}
}
I haven't realized if there is an obvious answer yet, so I'm open to suggestions. How would you suggest I tackle this?
Thanks
PS: I'm building the application using Nativescript and Angular, and (so far) there is no server or database involved other than Firebase.
Another suggestion that might work, is by using Firebase Auth Custom Claims. That way, you only need to store the group ID and group name in your realtime database, without worrying to keep changing the database each time user is added or removed.
This is one way you can do it:
Store database exactly like you have it, with it's group ID and name.
In your backend script (I recommend Cloud Function), each time a User is registering themselves, add custom claims in your user: Specifying what group is the User belong to.
Every time user authenticate, retrieve the group ID from custom claims. And there you get it!
Note: be careful not to put too much information in your custom claims as it cannot exceed 1000 bytes.
Read more about it here: https://firebase.google.com/docs/auth/admin/custom-claims
I would suggest you to implement Root-level collections.
Which is to create collections at the root level of your database to organize disparate data sets(as shown in the image below).
Advantages: As your lists grow, the size of the parent document doesn't change. You also get full query capabilities on
subcollections.
Possible use case: In the same chat app, for example, you
might create collections of users or messages within chat room
documents
Based on the reference from the firebase cloud firestore
Choose a data structure tutorial (I know you are using Realtime database but structuring the database is the same since both are using the NoSQL Schema)
For your case:
Make 2 Collections: Users, Groups
Users: User info is stored in the form of document
Groups: In the Groups Collection, here comes the tricky part, you can either store all groups subcollection under 1 document or split into multiple documents (based on your preference)
In the group-subcollection, you can now store your group info as well as the user assigned where you can store user assigned in the form of array, therefore whenever a user access the group, query the user assigned first, if yes, then allow (assuming users can view all group)
You do the thinking now
I'm trying to create a simple system that allows users to create an account with info that they provided. Right now, I store all the data in a Collection users which has some Documents that represents users.
I'd like to keep some sensitive data of the user, such as the email address and phone number, private. In Firebase Database I would've created something like this:
users: {
uid: {
public_info:...
private_info:...
}
}
Protecting the data in here is pretty straight forward. I would simply write different rule sets for the private_info and public_info.
How would something like this be achieved in Firestore?
Use different top-level collections for user public and private data, and set their rules appropriately.
Depending on the use case you have two reasonably useful options:
Have a top level collection (public) with a sub collection called something like "private_data" (private) and grant access accordingly.
--OR--
Have a top level collection (private) with a sub collection called something like "public_data" (public) and grand access accordingly.
EDIT: Seems to be an open issue in Firestore. Also see this post.
In Google Cloud Firestore, I want to model a collection of groups. Each group contains a name, the list of it's users and some secretGroupData. For me, the natural way to do this would be:
/groups
/group1 {
name: "Group 1"
users: { //object can be queried, simple array not
"user1": true,
"user5": true
}
secretGroupData: ...
}
/group2 { ... }
Given a user like user1, I want to query all groups he is member of. This query works fine:
groupsRef.where("users.user1", "==", true)
However, I want to secure the group data. This query only works, when all groups are readable for all users. When I protect the group to be readable only by the group members, by the rule
match /groups/{groupId} {
allow read: if resource.data.users[request.auth.uid] == true;
}
the above query does not work any more, because as soon as it sees a group where the current user is not a member of, read access is denied and the whole query fails.
What is the best solution for this problem in Firestore? Should I
tell Firestore to return only the allowed groups and ignore the other ones, instead of throwing an error? If so, how can I achieve this?
make the groups readable for all users and move the secretGroupData into subcollections, where I can then restrict the access to just the group members
add redundancy by adding the IDs of all groups of a user into the user's profile document (/users/user1/groupIds: ["group1"]), so I know the groups beforehand and can query them by ID
use a totally different solution?
Thank you very much for your ideas.
I'm designing a chat app much like Facebook Messenger. My two current root nodes are chats and users. A user has an associated list of chats users/user/chats, and the chats are added by autoID in the chats node chats/a151jl1j6. That node stores information such as a list of the messages, time of the last message, if someone is typing, etc.
What I'm struggling with is where to make the definition of which two users are in the chat. Originally, I put a reference to the other user as the value of the chatId key in the users/user/chats node, but I thought that was a bad idea incase I ever wanted group chats.
What seems more logical is to have a chats/chat/members node in which I define userId: true, user2id: true. My issue with this is how to efficiently query it. For example, if the user is going to create a new chat with a user, we want to check if a chat already exists between them. I'm not sure how to do the query of "Find chat where members contains currentUserId and friendUserId" or if this is an efficient denormalized way of doing things.
Any hints?
Although the idea of having ids in the format id1---||---id2 definitely gets the job done, it may not scale if you expect to have large groups and you have to account for id2---||---id1 comparisons which also gets more complicated when you have more people in a conversation. You should go with that if you don't need to worry about large groups.
I'd actually go with using the autoId chats/a151jl1j6 since you get it for free. The recommended way to structure the data is to make the autoId the key in the other nodes with related child objects. So chats/a151jl1j6 would contain the conversation metadata, members/a151jl1j6 would contain the members in that conversation, messages/a151jl1j6 would contain the messages and so on.
"chats":{
"a151jl1j6":{}}
"members":{
"a151jl1j6":{
"user1": true,
"user2": true
}
}
"messages":{
"a151jl1j6":{}}
The part where this gets is little "inefficient" is the querying for conversations that include both user1 and user2. The recommended way is to create an index of conversations for each user and then query the members data.
"user1":{
"chats":{
"a151jl1j6":true
}
}
This is a trade-off when it comes to querying relationships with a flattened data structure. The queries are fast since you are only dealing with a subset of the data, but you end up with a lot of duplicate data that need to be accounted for when you are modifying/deleting i.e. when the user leaves the chat conversation, you have to update multiple structures.
Reference: https://firebase.google.com/docs/database/ios/structure-data#flatten_data_structures
I remember I had similar issue some time ago. The way how I solved it:
user 1 has an unique ID id1
user 2 has an unique ID id2
Instead of adding a new chat by autoId chats/a151jl1j6 the ID of the chat was id1---||---id2 (superoriginal human-readable delimeter)
(which is exactly what you've originally suggested)
Originally, I put a reference to the other user as the value of the chatId key in the users/user/chats node, but I thought that was a bad idea in case I ever wanted group chats.
There is a saying: https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
There might a limitation of how many userIDs can live in the path - you can always hash the value...