Norbert de Langen 7ab74c6a34 LINTING
2017-09-06 20:57:31 +02:00

237 lines
5.8 KiB
JavaScript

import deepEquals from 'deep-equal';
import { EventEmitter } from 'events';
export default class DataStore {
constructor(db) {
this.db = db;
this.currentStory = null;
this.callbacks = [];
this.cache = {};
this.users = {};
this.user = null;
this.eventStore = new EventEmitter();
}
addToCache(currentStory, comments) {
const key = this.getStoryKey(currentStory);
this.cache[key] = {
comments,
addedAt: Date.now(),
};
}
getFromCache(currentStory) {
const key = this.getStoryKey(currentStory);
const item = this.cache[key];
if (!item) {
return {
comments: [],
invalidated: false,
};
}
const comments = item.comments;
let invalidated = false;
// invalid caches created 60 minutes ago.
if (Date.now() - item.addedAt > 1000 * 60) {
delete this.cache[key];
invalidated = true;
}
return { comments, invalidated };
}
reloadCurrentComments() {
if (this.stopReloading) {
clearInterval(this.stopReloading);
}
this.stopReloading = setInterval(
() => {
this.loadUsers().then(() => this.loadComments());
},
1000 * 60 // Reload for every minute
);
}
setCurrentStory(sbKind, sbStory) {
this.currentStory = { sbKind, sbStory };
// We don't need to do anything if the there's no loggedIn user.
// if (!this.user) return;
this.reloadCurrentComments();
const item = this.getFromCache(this.currentStory);
if (item) {
this.fireComments(item.comments);
// if the cache invalidated we need to load comments again.
if (item.invalidated) {
return this.loadUsers().then(() => this.loadComments());
}
return Promise.resolve(null);
}
// load comments for the first time.
// TODO: send a null and handle the loading part in the UI side.
this.eventStore.emit('loading', true);
this.fireComments([]);
this.loadUsers()
.then(() => this.loadComments())
.then(() => {
this.eventStore.emit('loading', false);
return Promise.resolve(null);
});
return this.currentStory;
}
setCurrentUser(user) {
this.user = user;
}
loadUsers() {
const query = {};
const options = { limit: 1e6 };
return this.db.persister.getAppInfo().then(info => {
if (!info) {
return null;
}
return this.db
.getCollection('users')
.get(query, options)
.then(users => {
this.users = users.reduce((newUsers, user) => {
const usersObj = {
...newUsers,
};
usersObj[user.id] = user;
return usersObj;
}, {});
});
});
}
loadComments() {
const currentStory = { ...this.currentStory };
const query = currentStory;
const options = { limit: 1e6 };
return this.db.persister.getAppInfo().then(info => {
if (!info) {
return null;
}
return this.db
.getCollection('comments')
.get(query, options)
.then(comments => {
// add to cache
this.addToCache(currentStory, comments);
// set comments only if we are on the relavant story
if (deepEquals(currentStory, this.currentStory)) {
this.fireComments(comments);
}
});
});
}
getStoryKey(currentStory) {
return `${currentStory.sbKind}:::${currentStory.sbStory}`;
}
fireComments(comments) {
this.callbacks.forEach(callback => {
// link user to the comment directly
const commentsWithUser = comments.map(comment =>
Object.assign({}, comment, { user: this.users[comment.userId] })
);
callback(commentsWithUser);
});
}
onComments(cb) {
this.callbacks.push(cb);
const stop = () => {
const index = this.callbacks.indexOf(cb);
this.callbacks.splice(index, 1);
};
return stop;
}
addPendingComment(comment) {
// Add the pending comment.
const pendingComment = { ...comment, loading: true };
const { comments: existingComments } = this.getFromCache(this.currentStory);
const updatedComments = existingComments.concat(pendingComment);
this.fireComments(updatedComments);
return Promise.resolve(null);
}
setDeletedComment(commentId) {
const { comments } = this.getFromCache(this.currentStory);
const deleted = comments.find(c => c.id === commentId);
if (deleted) {
deleted.loading = true;
}
this.fireComments(comments);
return Promise.resolve(null);
}
addAuthorToTheDatabase() {
if (this.users[this.user.id]) {
// user exists in the DB.
return Promise.resolve(null);
}
// add user to the local cache
this.users[this.user.id] = this.user;
// add user to the actual collection
return this.db.getCollection('users').set(this.user);
}
// NOTE the "sbProtected" makes sure only the author can modify
// or delete a comment after its saved on the cloud database.
addCommentToDatabase(comment) {
const doc = {
...comment,
...this.currentStory,
...this.currentStory,
sbProtected: true,
};
return this.db.getCollection('comments').set(doc);
}
deleteCommentOnDatabase(commentId) {
const query = { id: commentId };
return this.db.getCollection('comments').del(query);
}
addComment(comment) {
return this.addAuthorToTheDatabase()
.then(() => this.addPendingComment(comment))
.then(() => this.addCommentToDatabase(comment))
.then(() => this.loadUsers())
.then(() => this.loadComments());
}
deleteComment(commentId) {
return this.setDeletedComment(commentId)
.then(() => this.deleteCommentOnDatabase(commentId))
.then(() => this.loadComments());
}
onLoading(cb) {
this.eventStore.on('loading', cb);
return () => {
this.eventStore.removeListener('loading', cb);
};
}
}