Firebase database structure with 2 collections - firebase

I am new to Firebase and would like to receive feedback on my proposed database structure. I am building a mobile app using 1) Firebase to store my user data, and 2) an external API to fetch recipe data.
I am wondering if my approach below would be acceptable for building a basic app?
Here is my database structure:
Users Collection
user1: {
--name: "John"
--recentlyViewed: {key1: '0001',key2: '0002',...key25: '0025'} // max 25 elements
--favorited: {key1: '0001', key2: '0002',...key1000: '1000'} // no max number of elements
},
user2: {...},
Recipe Thumbnails Collection
'0001': { // this key (or ID number) can be used in the GET request to the external API for more data on the item
--title: "Curry"
--description: "Stew"
--image: "www.url.com"
},
'0002': {
--title: "Fish"
--description: "Stew"
--image: "www.url.com"
},
Here is my approach:
My app shows each user their "Recently Viewed" and "Favorited" recipes as thumbnails. In order to retrieve the data for each thumbnail, I am making a GET request to the external API.
For each GET request, the API returns a ton of information (approx. 200 data points), however I only need to access 3 data points -- title, description, and image URL. Therefore, I thought it would be more efficient if every time the user views a recipe, to save the necessary thumbnail information (title, description, and URL) as a document in a Firebase collection, with a key of the unique ID that corresponds to the external API.
If a user taps on a thumbnail, I would make a GET request to the external API (using the unique record number, which is saved as the Key for each document) to access additional information about the recipe and display it on a new screen to the user.

Related

Firebase - Showing users that they have unread messages

I'm trying to determine the best way to handle showing the user that they have an unread message, in the navbar for example.
Currently I have separate documents for each conversation with data like so:
users: [ 'userId-1', 'userId-2' ]
messages: [
{
message: 'Test message',
timestamp: 12345678910,
userId: 123456
},
// etc...
]
Currently I'm thinking about adding an unread property to the message objects. Then, on page load, I would have to fetch each document where users contains the currentUser id and if any of the message objects in messages contains the unread: true property.
But then I would have to mark the message as read, but only for one of the users. So my data structure already doesn't work.
Also, this doesn't seem very performant to me, especially if the user has a great amount of conversations. Any idea on how to approach this differently?
I'm trying to determine the best way to handle showing the user that
they have an unread message, in the navbar for example
I understand that you only want to show a number of unread messages (or the information that there is a least one unread message). If this is the case you can get advantage of the new count() aggregation which takes into account any filters on the query.
Your data model is not 100% clear to me but since you have an Array of users, you could have an extra Array field containing the users that haven't read the message. So on page loading, you need to build the query of all messages where this array contains the currentUser uid and then call getCountFromServer() on this query.
Instead of being charged for each message that corresponds to the query you'll be charged one document read for each batch of up to 1000 index entries matched by the query.

Firestore chat app - build messages home page

I have a Flutter chat app with Firestore RTDB backend:
messages (collection)
message_1 (document)
chat (collection)
chat_1 (document)
chat_2 (document)
users (array, document field)
user_id_1 (String)
user_id_2 (String)
user_info (map to store user info, like name, avatar etc)
message_2 (document)
chat (collection)
chat_1 (document)
chat_2 (document)
users (array, document field)
user_id_1 (String)
user_id_2 (String)
user_info (map to store user info, like name, avatar etc)
I want to create a home page where it shows all the chats a user is involved in, sorted by most recent, just like any normal chat app:
I know how to show the chats the user is involved in. Problem is, I don't know how to handle the sorting. There is one simple way to do this: each time a new message is sent, use a cloud function and update a field in the message document called lastSent, then do orderBy('lastSent', descending: true) in your query. The problem is, each time you send a message, you have to do two writes instead of one just to update this field. Is there a better way to handle this?
Note: My app is not solely a chat app, this is only part of the main app. Imagine a chat functionality similar to Airbnb, so the volume or frequency of chat messages may not be as large as Facebook messenger for example
The common solution is to do what you propose: store a latest_updated timestamp in the document of each "chat room".
That indeed means that you'll need to do two writes instead of one. But on the other hand, you can now determine the correct ordering with by just reading the "chat room" documents, instead of having read individual messages under it.
Note that, while it is certainly possible to do this with Cloud Functions, this can also be done directly from the client. You can even catch the requirement in security rules that a client can only write a new message, if they also set the latest_updated timestamp of the corresponding "chat room" document to the same value as the timestamp of the message, although this will incur the cost of one additional document read for each message you add.

Will appsync still request relationship if i don't request it in frontend

I'm struggling to calculate the read capacity unit of a subscription (or query) when there's a relationship in my graphql schema.
Let's say I have 2 types which are connected to dynamodb tables:
type User{
user_id: String!
.... some user info
}
type Tweet{
tweet_id: String!
user_id: String!
user: User
}
and in the resolver, I connected user in type Tweet to type User with GetItem operation.
My question is, when I query a tweet without user. e.g.
query tweet {
getTweet(tweet_id: 'test'){
tweet
}
}
Even though I'm not requesting user field, Will appsync execute the GetItem operation to my user table?
Thank you!
Subscriptions do not read from your back-end data source; Instead the input of the mutation is forwarded to the relevant subscribers.
As for getTweet query, it's all based on how your resolver (mapping template) is set up. Your getTweet will only call the Tweets table unless you also write instructions to read the Users table (or the users record, if you're using a single-table setup).

Custom Authentication in Google Firebase

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

Select from Firebase database using more than one where conditions

In Firebase database I have a list of Bookings.
Each Booking as the following structure
{
userUid: string,
status: string,
moreStuff: {
....
}
}
I need to select all the bookings associated to a certain user (i.e. whose userUid is equal to the uid of the user, which is known by by app) which have a certain status (e.g. status = confirmed).
I can select the Bookings belonging to a specific user using the following query
db.list('bookings', {
query: {
orderByChild: 'userUid',
equalTo: user.uid
}
})
but I have no idea if I can add the additional select condition e.g. status = confirmed
In a firebase query you can't filter on more than a single field. I have encountered this restriction many times.
I think your challenge would become easier if you model your data differently. I believe that you have a one to many relationship between Users and Bookings. That is, a user may have many bookings but a booking may only have one user. If this is the case, I would create a top level node "bookingLists" which contains separate booking list for each user. The structure is illustrated below. The path "bookingList/< userId >" contains the booking list for a given user. You can access the list if you know the userId. It will contain all bookings for that user. You can then query on a single field in each booking to filter by.
bookingLists
<userId>
bookings
<bookingKey>
status
...other fields
If you need to further filter the booking, you could filter on the client side using observable operations map and filter. This will scale, as long as, each user doesn't have too large a booking list.

Resources