Skip to content

Instantly share code, notes, and snippets.

@davideast
Last active April 20, 2021 17:19
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save davideast/e68aa87ea6f0e7a4dc08 to your computer and use it in GitHub Desktop.
Save davideast/e68aa87ea6f0e7a4dc08 to your computer and use it in GitHub Desktop.
Firebase Social Network Client Fanout

Data Structure:

users: {
  $uid
},
timeline: {
  $postid
},
followers: {
  $uid
},
userPosts: {
  $uid/$postid
}
export class PostFanout {
constructor(rootRef) {
this._rootRef = rootRef;
this.user = rootRef.getAuth();
this.followers = new SyncPath(this._rootRef, 'followers');
this.userPosts = new SyncPath(this._rootRef, 'userPosts');
}
createAll(path, data) {
var fannedOutData = {};
// update own record
fannedOutData['/users/' + this.user.uid + '/' + path] = data;
// update own posts
this.userPosts.keys().forEach((postKey) => {
fannedOutData['/userPosts/' + this.user.uid + '/' + postKey + '/' + path] = data;
});
// update follower's timeline posts
this.followers.keys().forEach((followerKey) => {
this.userPosts.keys().forEach((postKey) => {
fannedOutData['/timeline/' + followerKey + '/' + postKey + '/' + path] = data;
});
});
return fannedOutData;
}
createOne(path, data) {
var fannedOutData = {};
// write to user's own post collection
fannedOutData['/userPosts/' + this.user.uid + '/' + path] = data;
// write to users own timeline
fannedOutData['/timeline/' + this.user.uid + '/' + path] = data;
// write to each follower's timeline
this.followers.keys().forEach((followerKey) => {
fannedOutData['/timeline/' + followerKey + '/' + path] = data;
});
return fannedOutData;
}
}
/**
* posts.js
* This is a demonstration of applying a "fan-out" update from the client using Firebase.
*
* In social networks it is common to update a post to every user's "timeline". With Firebase
* client side fan-out you can apply the entire update in one write.
*
*/
import {SyncPath} from 'SyncPath';
import {PostFanout} from 'PostFanout';
/**
* Data Layer class for "fanning out" posts to a user's followers.
*
* @property {Firebase Reference} _rootRef
* @property {FirebaseAuthUser} user
* @property {PostFanout} fanout
*/
class PostService {
constructor(rootRef) {
this._rootRef = rootRef;
this.user = rootRef.getAuth();
this.fanout = new PostFanout(this._rootRef);
}
/**
* This function writes a post to the timeline of every follower of the posting user.*
* @param {string} text
* @returns void
*/
postToFollowers(text) {
var post = {
text: text,
uid: this.user.uid,
key: this._rootRef.push().key(), // create a unique key
username: this.user.password.email
};
var fannedOutData = this.fanout.createOne(post.key, post);
// Send the fan-out data-structure to the Firebase database
this._rootRef.update(fannedOutData);
}
}
// Example
var ref = new Firebase('https://fur.firebaseio.com/');
var btnPost = document.getElementById('btnPost');
var btnLogin = document.getElementById('btnLogin');
var txtMessage = document.getElementById('txtMessage');
// Post message on click
btnLogin.addEventListener('click', () => {
ref.authWithPassword({
email: 'david-sf@firebase.com',
password: 'password'
}, (authData) => {
var postService = new PostService(ref);
btnPost.addEventListener('click', () => {
postService.postToFollowers(txtMessage.value)
txtMessage.value = '';
});
});
});
{
"rules": {
"users": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
}
},
"timeline": {
"$uid": {
"$postid": {
".read": "true",
".write": "auth.uid == $uid"
}
}
},
"userPosts": {
"$uid": {
"$postid": {
".read": "true",
".write": "auth.uid == $uid"
}
}
},
"followers": {
"$uid": {
".read": "true",
".write": "auth.uid == $uid"
}
}
}
}
export class SyncPath {
constructor(rootRef, path) {
this._rootRef = rootRef;
this.user = this._rootRef.getAuth();
this._userDataRef = this._rootRef.child(path).child(this.user.uid);
this.data = {};
this._userDataRef.on('value', (snap) => this.data = snap.val() || {});
}
keys() {
return Object.keys(this.data);
}
}
/**
* users.js
* This is a demonstration of applying a "fanout" update from the client using Firebase.
*
* In social media netowrks it's common for a user to change their username. When this happens we'll need
* to update every post the user has posted to the proper username.
*
*/
import {SyncPath} from 'SyncPath';
import {PostFanout} from 'PostFanout';
class UserService {
constructor(rootRef) {
this._rootRef = rootRef;
this.fanout = new PostsFanout(this._rootRef);
}
changeUsername(username) {
var fannedOutData = this.fanout.createAll('username', username);
this._rootRef.update(fannedOutData);
}
deletePosts() {
var fannedOutData = this.fanout.createAll('', null);
this._rootRef.update(fannedOutData);
}
}
// Example
var ref = new Firebase('https://fur.firebaseio.com/');
var btnChange = document.getElementById('btnChange');
var btnLogin = document.getElementById('btnLogin');
var btnDelete = document.getElementById('btnDelete');
var txtUsername = document.getElementById('txtUsername');
// Post message on click
btnLogin.addEventListener('click', () => {
ref.authWithPassword({
email: 'david-sf@firebase.com',
password: 'password'
}, () => {
var userService = new UserService(ref);
btnChange.addEventListener('click', () => userService.changeUsername(txtUsername.value));
btnDelete.addEventListener('click', () => userService.deletePosts());
});
});
@lakshaykhatter
Copy link

How would you manage replies to user posts?

@Pulichev
Copy link

Interesting for me too

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment