I'm creating an app with a "Card stack" similar to Tinder, with a Firebase Realtime DB backend. Each card will be a new unread post, if the user runs out of new posts they will run out of cards. However I don't know the best way to structure the data for this. Could I store the ID of the read posts under the user, then as I watch the Posts feed I could filter out read posts client side?
That seems a bit messy and not a very good option performance wise. Are there better options?
EDIT: Rough code of what I'm thinking:
Data Example
posts:
"-KibasdkjbSAdASd": {
title: 'New Post',
body: {
...
}
},
"-KisadBVsdadSd": {
title: 'New Post 2',
body: {
..
}
}
"-KibaFNQsicaASd": {
title: 'New Post 3',
body: {
...
}
}
users :
"-KisadBVsdadSd": {
name: 'Tom',
readPosts: {
"-KibasdkjbSAdASd": {
title: 'New Post',
body: {
...
}
},
"-KisadBVsdadSd": {
title: 'New Post 2',
body: {
..
}
}
}
}
Code
const rootRef = firebase.database.ref();
const postRef = rootRef.child("posts");
const readPostRef = rootRef.child("users/"+uid+"/readPosts");
let readPosts= [];
//Get initial list of read posts
readPostRef.once("value", function(snapshot) {
readPosts = Object.keys(snapshot);
});
//Update read posts when added
readPostRef.on("child_added", function(snapshot) {
readPosts = Object.keys(snapshot);
});
//Get list of posts, filtered on read post array
urlRef.on("value", function(snapshot) {
snapshot.forEach(function(child) {
if(!readPosts.includes(child.key)){
//Unread post
}
});
});
It depends on the order in which you show the cards to the user.
If you show them in a predictable order (e.g. from newest to oldest) you can just remember the ID of the last card the user saw.
If you show them in a random or personalized order you might indeed have to track precisely what cards each user has already seen.
I'm not sure why that would be messy or perform badly. So if you want a better option, you'll have to show how you'd implement the messy/slow option.
I'm running into this same design problem. I only see two options, perhaps there are others!
1) Download every post and ignore the ones that have been read. Expensive when you have a lot of posts.
2) Save a copy of every post to every user account and allow them to delete them once they have been read. Expensive when you have a lot of users.
Related
I'm struggling with a problem of fetching data from DB and applying "pagination" functionality. My case is that I have webstore with multiple users. Every user can publish their profile and create their products. On the website, I'd like to show the products of users who did publish their profiles.
Currently, this is my code:
Product.find({ active: true, deleted: false })
.sort({ created: -1 })
.populate({
path: 'user',
match: { isPublished: true },
select: 'details slug name',
})
.exec((error, products) => {
if (error) {
return response.status(500).send({ error: 'There was an error.' });
}
if (products === null) {
return response.status(404).send({ error: 'Products not found.' });
}
const productsWithPublishedTrainerProfile = _.filter(products, product => !_.isNull(product.user));
return response.status(200).send(_.map(productsWithPublishedTrainerProfile, product => mapProduct(product)));
});
It works - It fetches all the active products, populate the user who created the product and if their profile is published then the product is not filtered out.
Now I need to implement "pagination" functionality which means that I want to use .skip() and .limit() functions but this skip/limit will be run on all the products (not only the filtered ones.) what means that the endpoint won't be returning the exact number of products (set in the limit() function.
I wonder if there is any solution for that. I read about .aggregate with setting $lookup, $skip and $limit but after hours of fighting with it I couldn't make it work.
I will really appreciate any help ✌️
I have a direct messaging application. All the data is stored in Firebase. Each chat contains an array of user IDs.
I use the following function to get all chats from componentDidMount():
return dispatch => new Promise(resolve => FirebaseRef.child('chats')
.on('value', snapshot => resolve(dispatch({
type: 'CHATS_REPLACE',
data: snapshot.val() || [],
})))).catch(e => console.log(e));
Which goes through:
chatReducer(state = initialState, action) {
case 'CHATS_REPLACE': {
let chats = [];
if (action.data && typeof action.data === 'object') {
chats = Object.values(action.data).map(item => ({
id: item.id,
title: item.title,
authorizedUsers: Object.values(item.authorizedUsers).map(user => ({
id: user.id,
// Somedata: fetchUserData(user.id)
// -> pretty sure it can't be done here <-
})),
}));
}
return {
...state,
error: null,
loading: false,
chats,
};
How would I go about fetching more data of every user inside each chat from Firebase at users/:uid?
I don't know what is the use case of this. It would be great if you can share, like how much information about the user you want to use. If its small data, why don't you add it in same API Only. You can pass the users data in the same object with user id as keys, and use the same keys inside your nested data like (only if user data is small or you know API data is always limited like because of pagination or page size. :
{
posts : [
{
title : 'abc'
authorizedUsers : ['1a', '2b', '3c']
}, ....
],
users : {
'1a' : {
name : 'john doe',
profileImage : 'https://some.sample.link',
},
'2b' : {
name : 'bob marshal',
profileImage : 'https://some.sample.link2',
}
}
}
If data is huge or cannot be added in the API ( because API is owned by 3rd party), then only place you can put you code is, instead of just dispatching the actions after the response is recieved, loop over the response in your service only, make async calls to get all "Unique users" only, append that data to the data you recieved from the previous api call, and then dispatch the action with the complete data to the store. It might not be the best way, as everything will have to stall i.e. even the data recieved in 1st api also will stall(not updated on screen) till all the users data is fetched. But best solution can only be given once we know more details about the use case. Like maybe lazy fetching the users data as end user scrolls the screen and may see a particular post Or fetching the user details once you start rendering your data from 1st API call like making a component for showing user associate with a post and in its componentDidMount, you pass the userIds as props from top component which might be "article/post/blog" component and it fetched the data at the time when it is actually rendering that "article/blog/post".
Hope this helps.
I want to implement a follow system between users.
For that, I want to display all of the 250 users of my app, then add a checkmark button next to the ones I already follow, and an empty button next to the ones I do not follow.
var usersRef = firebase.database().ref(‘/users’);
var followingRef = firebase.database().ref(‘/followingByUser’);
var displayedUsers = [];
// I loop through all users of my app
usersRef.once('value', users => {
users.forEach(user => {
// For each user, I check if I already follow him or not
followingRef.child(myUid).child(user.key).once('value', follow => {
if (follow.val()) {
// I do follow this user, follow button is on
displayedUsers.push({
name: user.val().name,
following: true
});
} else {
// I do not follow this user, follow button is off
displayedUsers.push({
name: user.val().name,
following: false
});
}
})
})
})
When doing that, I often (not always) get the following error: "Error: Firebase Database (4.1.3) INTERNAL ASSERT FAILED: sendRequest call when we're not connected not allowed."
Eventually, all the data is fetched, but after 10 seconds instead of 1 (without the error).
I do not believe it is an internet connection issue, as I have a very fast and stable wifi.
Is it a bad practice to nest queries like that?
If not, why do I get this error?
My data is structured as below:
users: {
userId1: {
name: User 1,
email: email#exemple.com,
avatar: url.com
},
userId2: {
name: User 2,
email: email#exemple.com,
avatar: url.com
},
...
}
followByUser: {
userId1: {
userId2: true,
userId10: true,
userId223: true
},
userId2: {
userId23: true,
userId100: true,
userId203: true
},
...
}
Your current database structure allows you to efficiently look up who each user is following. As you've found out it does not allow you to look who a user is follow by. If you also want to allow an efficient lookup of the latter, you should add additional data to your model:
followedByUser: {
userId2: {
userId1: true,
}
userId10: {
userId1: true,
},
userId223: {
userId1: true,
},
...
}
This is a quite common pattern in Firebase and other NoSQL databases: you often expand your data model to allow the use-cases that your app needs.
Also see my explanation on modeling many-to-many relations and the AskFirebase video on the same topic.
const stream = require('getstream');
//newsfeed stream
const client = stream.connect( null, );
var user1 = client.feed('user', 'user1');
// Add activity; message is a custom field - tip: you can add unlimited custom fields!
user1.addActivity({
actor: 'user1',
verb: 'add',
object: 'picture:10',
foreign_id: 'picture:10',
"time": now.toISOString(),
});
// jack's 'timeline' feed follows chris' 'user' feed:
var jack = client.feed('timeline', 'jack');
jack.follow('user', 'user1');
// Read 'timeline' for jack - the post by chris will show up:
jack.get({ limit: 10 }).then(function(results) {
var activityData = results;
// Read the next page, using id filtering for optimal performance:
jack.get({ limit: 10, id_lte: activityData[activityData.length-1].id }).then(function(results) {
var nextActivityData = results;
});
});
// Remove activity by referencing foreign_id:
user1.removeActivity({ foreign_id: 'picture:10' });
In this example I'm in using the code provide to me to create a newsfeed with getstream.io. I have not done anything like this before, so I don't know where to start.
We have a Node-specific "Pinterest" example app you can use to get started, https://github.com/GetStream/Stream-Example-Nodejs
I also recommend you check out our blog post on tips to make a news feed that's really engaging, https://getstream.io/blog/13-tips-for-a-highly-engaging-news-feed/
Sorry if I'm not getting the terminology right. Here's what I have currently my MongoDB user docs db.users:
"liked" : [
"EBMKgrD4DjZxkxvfY",
"WJzAEF5EKB5aaHWC7",
"beNdpXhYLnKygD3yd",
"RHP3hngma9bhXJQ2g",
"vN7uZ2d6FSfzYJLmm",
"NaqAsFmMmnhqNbqbG",
"EqWEY3qkeJYQscuZJ",
"6wsrFW5pFdnQfoWMs",
"W4NmGXyha8kpnJ2bD",
"8x5NWZiwGq5NWDRZX",
"Qu8CSXveQxdYbyoTa",
"yLLccTvcnZ3D3phAs",
"Kk36iXMHwxXNmgufj",
"dRzdeFAK28aKg3gEX",
"27etCj4zbrKhFWzGS",
"Hk2YpqgwRM4QCgsLv",
"BJwYWumwkc8XhMMYn",
"5CeN95hYZNK5uzR9o"
],
And I am trying to migrate them to a new key that also captures the time that a user liked the post
"liked_time" : [
{
"postId" : "5CeN95hYZNK5uzR9o",
"likedAt" : ISODate("2015-09-23T08:05:51.957Z")
}
],
I am wondering if it might be possible to simply do this within the MongoDB Shell with a command that iterates over each user doc and then iterates over the liked array and then updates and $push the new postId and time.
Or would it be better to do this in JavaScript. I am using Meteor.
I almost got it working for individual users. But want to know if I could do all users at once.
var user = Meteor.users.findOne({username:"atestuser"});
var userLiked = user.liked;
userLiked.forEach(function(entry) {
Meteor.users.update({ username: "atestuser" },
{ $push: { liked_times: { postId: entry, likedAt: new Date() }}});
console.log(entry);
});
Still a bit of a newbie to MongoDB obviously......
Here is something i made real quick you should run this on the server side just put it into a file e.g. "migrate.js" in root meteor and run the meteor app
if (Meteor.isServer) {
Meteor.startup(function () {
var users = Meteor.users.find().fetch();
users.forEach(function (doc) {
liked.forEach(function (postId) {
Meteor.users.update(doc._id, { $push: { liked_times: { postId: postId, likedAt: new Date() } } });
});
});
console.log('finished migrating');
});
}
p.s I didn't test it
If this is a one time migration i would do something like this in a one time js script.
Get all users
Iterate over each user
Get all likes
Iterate over them, get likedAt
var liked_times = _.collect(likes, function (likeId) {
return {
'postId' : likeId,
'likedAt': // get post liked time from like id.
}
});
Insert the above in the collection of choice.
Note:
The above example makes use of lodash
I would rather just save likedAt as a timestamp.