Data Structure:
users: {
$uid
},
timeline: {
$postid
},
followers: {
$uid
},
userPosts: {
$uid/$postid
}
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()); | |
}); | |
}); |
How would you manage replies to user posts?