* Atom / RSS meta link * Spike * Feeling good about this spike now * Remove document.collection * Remove koa.ctx from all presenters to make them portable outside requests * Remove full serialized model from events Move events.add to controllers for now, will eventually be in commands * collections.create event parentDocument -> parentDocumentId * Fix up deprecated tests * Fixed: Doc creation * documents.move * Handle collection deleted * 💚 * Authorize room join requests * Move starred data structure Account for documents with no context on sockets * Add socket.io-redis * Add WEBSOCKETS_ENABLED env variable to disable websockets entirely for self hosted New installations will default to true, existing installations to false * 💚 No need for promise response here * Reload notice
166 lines
4.0 KiB
JavaScript
166 lines
4.0 KiB
JavaScript
// @flow
|
|
import invariant from 'invariant';
|
|
import { observable, set, action, computed, runInAction } from 'mobx';
|
|
import { orderBy } from 'lodash';
|
|
import { client } from 'utils/ApiClient';
|
|
import RootStore from 'stores/RootStore';
|
|
import BaseModel from '../models/BaseModel';
|
|
import type { PaginationParams } from 'types';
|
|
|
|
type Action = 'list' | 'info' | 'create' | 'update' | 'delete';
|
|
|
|
function modelNameFromClassName(string) {
|
|
return string.charAt(0).toLowerCase() + string.slice(1);
|
|
}
|
|
|
|
export const DEFAULT_PAGINATION_LIMIT = 25;
|
|
|
|
export default class BaseStore<T: BaseModel> {
|
|
@observable data: Map<string, T> = new Map();
|
|
@observable isFetching: boolean = false;
|
|
@observable isSaving: boolean = false;
|
|
@observable isLoaded: boolean = false;
|
|
|
|
model: Class<T>;
|
|
modelName: string;
|
|
rootStore: RootStore;
|
|
actions: Action[] = ['list', 'info', 'create', 'update', 'delete'];
|
|
|
|
constructor(rootStore: RootStore, model: Class<T>) {
|
|
this.rootStore = rootStore;
|
|
this.model = model;
|
|
this.modelName = modelNameFromClassName(model.name);
|
|
}
|
|
|
|
@action
|
|
clear() {
|
|
this.data.clear();
|
|
}
|
|
|
|
@action
|
|
add = (item: Object): T => {
|
|
const Model = this.model;
|
|
|
|
if (!(item instanceof Model)) {
|
|
const existing: ?T = this.data.get(item.id);
|
|
if (existing) {
|
|
set(existing, item);
|
|
return existing;
|
|
} else {
|
|
item = new Model(item, this);
|
|
}
|
|
}
|
|
|
|
this.data.set(item.id, item);
|
|
return item;
|
|
};
|
|
|
|
@action
|
|
remove(id: string): void {
|
|
this.data.delete(id);
|
|
}
|
|
|
|
save(params: Object) {
|
|
if (params.id) return this.update(params);
|
|
return this.create(params);
|
|
}
|
|
|
|
get(id: string): ?T {
|
|
return this.data.get(id);
|
|
}
|
|
|
|
@action
|
|
async create(params: Object) {
|
|
if (!this.actions.includes('create')) {
|
|
throw new Error(`Cannot create ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.create`, params);
|
|
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async update(params: Object): * {
|
|
if (!this.actions.includes('update')) {
|
|
throw new Error(`Cannot update ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.update`, params);
|
|
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async delete(item: T) {
|
|
if (!this.actions.includes('delete')) {
|
|
throw new Error(`Cannot delete ${this.modelName}`);
|
|
}
|
|
this.isSaving = true;
|
|
|
|
try {
|
|
await client.post(`/${this.modelName}s.delete`, { id: item.id });
|
|
return this.remove(item.id);
|
|
} finally {
|
|
this.isSaving = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async fetch(id: string, options?: Object = {}): Promise<*> {
|
|
if (!this.actions.includes('info')) {
|
|
throw new Error(`Cannot fetch ${this.modelName}`);
|
|
}
|
|
|
|
let item = this.data.get(id);
|
|
if (item && !options.force) return item;
|
|
|
|
this.isFetching = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.info`, { id });
|
|
invariant(res && res.data, 'Data should be available');
|
|
return this.add(res.data);
|
|
} finally {
|
|
this.isFetching = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
async fetchPage(params: ?PaginationParams): Promise<*> {
|
|
if (!this.actions.includes('list')) {
|
|
throw new Error(`Cannot list ${this.modelName}`);
|
|
}
|
|
this.isFetching = true;
|
|
|
|
try {
|
|
const res = await client.post(`/${this.modelName}s.list`, params);
|
|
|
|
invariant(res && res.data, 'Data not available');
|
|
runInAction(`list#${this.modelName}`, () => {
|
|
res.data.forEach(this.add);
|
|
this.isLoaded = true;
|
|
});
|
|
} finally {
|
|
this.isFetching = false;
|
|
}
|
|
}
|
|
|
|
@computed
|
|
get orderedData(): T[] {
|
|
return orderBy(Array.from(this.data.values()), 'createdAt', 'desc');
|
|
}
|
|
}
|