Organizing user/post relation in a database - firebase

Using the Firebase database I need to decide how to organize the user / post(e.g a tweet) relation. Two common reading tasks include:-
Showing an overview of a user's posts
Filtering and selecting specific posts based on their content
I currently use this:
posts
randproject1234jdfs
user_uid: randomUserName1234,
content: "example"
users
randomUserName1234
posts
project_id: randproject1234jdfs
nickname: "Mr Example"
This stores the same information twice:
In posts/randstring1234jdfs/user_uid , the value points to the user.
In users/1234/posts the project_id point to the post.
Would removing one of these lead to significantly slower data-reading (having to loop through the entire folder to see if the user_uid/project_id matches the given post/user)?
Or would it be better to organize the data in a different way altogether (e.g removing the user/posts split)?

You want to do the following:
Showing an overview of a user's posts
Filtering and selecting specific posts based on their content
You can do this then:
posts
randomid
user_uid: randomUserName1234,
content: "example"
posttitle: "English"
randomid2
user_uid: randomUserName1235,
content: "examples"
posttitle: "Math"
users
randomUserName1234 <-------------------- userid
email: email#gmail.com
nickname: "Mr Example"
randomUserName1235<--------------------anotheruserid
email: email#gmail.com
nickname: "Mr Awesome"
Since you want to show the user's post, then you can just query the node posts something like this: orderByChild(user_uid).equalTo(randomUserName1234) using that you can retrieve the content which are the posts I guess.
To retrieve posts of any user depending on content using the above u can do this:
orderByChild("posttitle").equalTo("English") and then retrieve the node content which I'am assuming they are the posts.
Remember denormalization is normal and it is important or you will have a hard time doing the queries. https://firebase.googleblog.com/2013/04/denormalizing-your-data-is-normal.html
The user node let it have the list of users in this app for example.
The posts node will be like above and it will be connected to the user by having this user_uid: randomUserName1235 as seen above.

Related

Cloud Firestore rule for Read - on the basis of where clause [duplicate]

I'm trying to secure requests to a collection to allow any single get, but to allow list only if a specific key is matched.
Database structure is like this:
posts
post1
content: "Post 1 content"
uid: "uid1"
post2
content: "Post 2 content"
uid: "uid1"
post3
content: "Post 3 content"
uid: "uid2"
The Firestore query I'm making from Vue:
// Only return posts matching the requested uid
db
.collection("posts")
.where("uid", "==", this.uid)
The security rules I'd like to have would be something like this:
match /posts/{post} {
allow get: if true // this works
allow list: if [** the uid in the query **] != null
I want to do this so you can list the posts of a specific user if you know their uid but can't list all posts of the system.
Is there a way to access the requested .where() in the security rules or how can I write such rule or structure my data in this case?
Relevant & credits:
Seemingly, I can make a request on a query's limit, offset, and orderBy. But there's nothing on where. See: #1 & #2.
I copy-pasted much from this question. I don't see how the accepted answer answers the question. It seems like it answers another case where a user is allowed to list some other users' posts. That is not my case; in my case, what's public is public. So, it doesn't answer the main question in my case, it seems.
There's currently no way, using security rules, to check if a field is being used in query. The only thing you can do is verify that a document field is being used as a filter using only values you allow.
Instead, consider duplicating enough data into another collection organized like this:
user-posts (collection)
{uid} (document using UID as document ID)
posts (subcollection)
{postId} (documents using post ID as document ID)
This will require the client to call out a UID to query in order to get all the posts associated with that user. You can store as much information about the post documents as you like, for the purpose of satisfying the query.
Duplicating data like this is common in NoSQL databases. You might even want to make this your new default structure if you don't want your users to query across all posts at any given moment. Note that a collection group query naming the "posts" subcollection would still query across all posts for all users, so you'd have to make sure your security rules are set up so that this is enabled only when you allow it to happen.
Also note that UIDs are typically not hidden from users, especially if your web site is collaborative in nature, and you combine multiple users' data on a single page.

How to structure collections in Firestore and access them in flutter?

I am trying to create an social app using flutter, in that app I have users and their posts, so in firestore I am creating 2 collections,
Users
Posts
So, in users it will have user data like email, display picture, bio etc. For identification I'm creating a key in posts which will have a reference to the user to whom the post belongs, like so,
Now, I while I ask a particular post I also want some of the user details like UserName and display picture so that I can show it in the Post in the UI like,
So, I want to use StreamBuilder as it will reflect any changes made, but I can't get user details if I'm using StreamBuilder.
How can I achieve this?
According to your data model, you need to make two queries, the first one to get the user ID and others to get the posts corresponding to the user.
final StreamController<List<Post>> _postsController =
StreamController<List<Post>>.broadcast();
Stream listenToPostsRealTime() {
// Register the handler for when the posts data changes
_postsCollectionReference.snapshots().listen((postsSnapshot) {
if (postsSnapshot.documents.isNotEmpty) {
var posts = postsSnapshot.documents
.map((snapshot) => Post.fromMap(snapshot.data, snapshot.documentID))
.where((mappedItem) => mappedItem.userId == FirebaseAuth.instance.currentUser().uid)
.toList();
// Add the posts onto the controller
_postsController.add(posts);
}
});
return _postsController.stream;
}
I would like to recommend you to visit this video: "How to structure your data" and this post, remember that you will be charged for every read to the database, so if you're going to gather information from the Users document every time you retrieve a Post, it would be a better idea to have duplicated information from the Users document inside your Posts document, this way you only have to query your Posts document. Remember that denormalizing data (like replicating it) on the NoSQL world is a common behaviour.
Your structure could be something similar to:
Posts:
createdAt: "June 25, 2020"
description: "New food"
email: "test#email.com"
image: "https://example.com/assets/image.jpg"
likes:
[updatedAt: "June 25,2020", user: likerUser]
user:
[ name: posterUser,
profile_picture: "https://example.com/assets/posterPicture.jpg",
profile_url: "https://example.com/profiles/userPoster"]
In order to keep your data synced between both documents I recommend you to check this answer

When to duplicate data in noSQL?

I'm working on a project where Firebase Firestore will be used. I've not had experience with noSQL so I work on understanding this technology.
The application lets an user to select a movie category and download an associated movie to let him movie edition.
The category and movie won't be change by the users and will be fixed or change by the owner. Think this like a Netflix catalog where the user can only watches the movies
There are several categories but for the moment only one movie is inside a category (may have more later).
Each movie has related metadata.
In the future:
- an user object will be used to rank each user regarding their score (information related to the application).
- some movie will be available with localisation restriction, i.e a movie would be available for US user only.
In my first thought, the data structure will look like this:
// Collection
Category: {
name: "drama" // Could be action, or other
}
// Collection
Movie: {
name: "Matrix"
description: "Best movie ever"
duration: 1321312
url: "https://www....."
allowedCountry: "us" // is it the right place for this field?
category: "drama" // is it ok to duplicate data here?
}
//Collection
user: {
ranking: 3
withMovie: "Matrix" // is it ok to duplicate data here?
}
I'm not sure if it's the right data structure for this problem.
The flow of the app would present in the first time all the possible categories (so I create a separated collection in order to avoid to iterate on all the song to get the categories)
Then when the user select a category the possible movies are display and the app download the selected one.
Is is okay to iterate on all possible movies to show the movies related to the category?
Or should I put the movies as a sub collection of the category collection?
I'd typically indeed keep the category directly in the movie document in this scenario, since it makes the queries much easier to read.
firebase.firestore().collection("movies").where("category", "==", "drama")
In fact, consider if your movies can really only have a single category (as you've modeled it now), or whether they may have multiple categories in the future (as Netflix does as far as I know). You'd model the latter as an array of categories:
categories: ["drama", "sci-fi"]
And then query it with:
firebase.firestore().collection("movies").where("categories", "array-contains", "drama")

Firestore social media posts table

so I want to create a sort of social media application and use firestore as main database.
the goal is to create "facebook" news feed.
each user will have a list of friends and each user will be able to create posts.
each post can be modified to be visible to all the users of the application or just the user friends. so each user will be able to post posts to all his friends and to post posts to everyone in the application.
also, users can "save" posts they liked in the newsfeed.(LikedPosts subcollection)
USERS (collection)
DocumentID - userId gathered from Authentication uid
firstName: String
lastName: String
username: String
birthdate: Timestamp
accountCreationDate: Timestamp
FRIENDS (subcollection)
DocumentID - userId of the friend
username: String
LikedPosts (subcollection)
authorUserId: String
authorUsername: String
text: String
imagePath: String
POSTS (collection)
DocumentID - postId randomly generated
authorUserId: String
authorUsername: String
text: String
imagePath: String
likesCount: Number
forFriendsOnly:yes
LIKES (subcollection)
DocumentID - userID of the liker
username: String
now in the newsfeed for a user - How can I query for all the visible post (forFriendsOnly:no) and also to all the posts for friend only, that the current user is in the author friends subcollection.
also, if the user change his name, how can I change his name accordingly for all his previous posts, and all the save posts related to the user?(located in user likedpost subcollection)
I guess you were asking 2 questions.
First, Firestore recommends data duplication instead of joining query across collections. The way you designed the post and user has to rely on query concept in SQL.
It is still possible to achieve that, if you don't mind to have all the author's friend id as an array inside of that post document. Meanwhile, you have to sync author's friend array through trigger function when author add/delete friends.
I wouldn't really recommend this solution, because as a social platform, user's friends might be changing constantly, then you have to keep on updating all his post's friend array.
There is another solution, which is add one more subcollection under user as his visible "feeds". Then whenever an author creates a post, trigger function will write this post's summary to all his friends' visible "feeds" collection.
However, both above solutions are not perfect if you are concerned about accuracy, realtime, cost, etc. I guess that is the drawback we have to bear with. If you have to achieve the same thing as SQL, I guess the only option is using other solutions for query part, such as elastic search, mysql, neo4j, etc. PS: You can still wrap it with cloud functions.
Regards to your 2nd question, one way is not duplicate username if you think your user would change their name frequently. And always query username by user id from user collection. The other way is using trigger function to update the duplicated username when user change their names. I would recommend the second way, since user wouldn't change their names frequently.
Not necessarily related to your original question, but your LikedPosts subcollection likely needs a restructuring. If you can ensure uniqueness on your postId, then it should probably be something like:
LikedPosts (subcollection)
postId: Unique identifier for liked post
authorUserId: String
authorUsername: String
text: String
imagePath: String
The current structure only allows for one liked post, so you'll need to change it to be one document per liked post, or a document containing a list of all of the liked post ids.

Firebase database structure - denormalized data?

I read a lot about nosql databases lately. I get that rule of thumb is to structure the data based on our view (of course, depends on the use case).
Now, let's say that we have a social app and the user has a profile but he also creates posts and we have to store them in the database.
So, I see some developers choose to do that like so:
Posts
-----UserID
-----------PostID
-----------------username: John
-----------------profileImage: https://...
-----------------posted_photo: https://...
This totally fits the structure base on the view. We'd go into posts and our userID and we could get all the data that our view needs. Now my question is, what happens when the user has made 100K posts and he decides to change his profile photo for example. All of his posts so far contain his old photo, so now, we have to write a method that would cycle through 100K of posts (or all of his posts in general) and update his photo. In 2 hours, he decides that "Nah, I don't like this photo, I'd change it back" and we have to make another 100K queries.
How is that (denormalized data) ok? Sure, its easier, its flat but then we have to make ridiculous amounts of queries to change a single profile photo. What's the way to handle this really?
I've done this storing user's data in a place and setting just the userID as post attribute.
posts:
userID:
postID:
userID: 'user1',
attachedImageURL: 'http:..',
message: 'hey',
reblogID: 'post4',
type: 'audio|poll|quote'
users:
user1:
name: 'john',
profileImage: 'http..'
It requires one more query to Firebase to retrieve user's profile data but it's a good way to solve this. It really depends on how you want to use those data.

Resources