diff --git a/server/api/documents.js b/server/api/documents.js
new file mode 100644
index 000000000..ee4fde880
--- /dev/null
+++ b/server/api/documents.js
@@ -0,0 +1,63 @@
+import Router from 'koa-router';
+import httpErrors from 'http-errors';
+
+import auth from './authentication';
+import pagination from './middlewares/pagination';
+import { presentDocument } from '../presenters';
+import { Document, Atlas } from '../models';
+
+const router = new Router();
+
+router.post('documents.info', auth(), async (ctx) => {
+ let { id } = ctx.request.body;
+ ctx.assertPresent(id, 'id is required');
+
+ const team = await ctx.state.user.getTeam();
+ const document = await Document.findOne({
+ where: {
+ id: id,
+ teamId: team.id,
+ },
+ });
+
+ if (!document) throw httpErrors.NotFound();
+
+ ctx.body = {
+ data: await presentDocument(document, true),
+ };
+});
+
+
+router.post('documents.create', auth(), async (ctx) => {
+ let {
+ atlas,
+ title,
+ text,
+ } = ctx.request.body;
+ ctx.assertPresent(atlas, 'atlas is required');
+ ctx.assertPresent(title, 'title is required');
+ ctx.assertPresent(text, 'text is required');
+
+ const team = await ctx.state.user.getTeam();
+ const ownerAtlas = await Atlas.findOne({
+ where: {
+ id: atlas,
+ teamId: team.id,
+ },
+ });
+
+ if (!ownerAtlas) throw httpErrors.BadRequest();
+
+ const document = await Document.create({
+ atlasId: ownerAtlas.id,
+ teamId: team.id,
+ title: title,
+ text: text,
+ });
+
+ ctx.body = {
+ data: await presentDocument(document, true),
+ };
+});
+
+export default router;
\ No newline at end of file
diff --git a/server/api/index.js b/server/api/index.js
index 3e0e9d072..302eb1953 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -7,6 +7,7 @@ import Sequelize from 'sequelize';
import auth from './auth';
import user from './user';
import atlases from './atlases';
+import documents from './documents';
import validation from './validation';
@@ -44,6 +45,7 @@ api.use(validation());
router.use('/', auth.routes());
router.use('/', user.routes());
router.use('/', atlases.routes());
+router.use('/', documents.routes());
// Router is embedded in a Koa application wrapper, because koa-router does not
// allow middleware to catch any routes which were not explicitly defined.
diff --git a/server/models/Document.js b/server/models/Document.js
index f810dec4a..11342f834 100644
--- a/server/models/Document.js
+++ b/server/models/Document.js
@@ -7,11 +7,11 @@ import Team from './Team';
const Document = sequelize.define('document', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
- name: DataTypes.STRING,
- content: DataTypes.STRING,
+ title: DataTypes.STRING,
+ text: DataTypes.TEXT,
});
-Document.belongsTo(Atlas);
+Document.belongsTo(Atlas, { as: 'atlas' });
Document.belongsTo(Team);
-export default Atlas;
+export default Document;
diff --git a/server/models/index.js b/server/models/index.js
index c154204d1..b2dc15375 100644
--- a/server/models/index.js
+++ b/server/models/index.js
@@ -3,4 +3,9 @@ import Team from './Team';
import Atlas from './Atlas';
import Document from './Document';
-export { User, Team, Atlas, Document };
\ No newline at end of file
+export {
+ User,
+ Team,
+ Atlas,
+ Document,
+};
\ No newline at end of file
diff --git a/server/presenters.js b/server/presenters.js
index a9baab29a..e77a0312f 100644
--- a/server/presenters.js
+++ b/server/presenters.js
@@ -1,3 +1,5 @@
+var marked = require('marked');
+
export function presentUser(user) {
return {
id: user.id,
@@ -15,7 +17,7 @@ export function presentTeam(team) {
};
}
-export function presentAtlas(atlas) {
+export function presentAtlas(atlas, includeRecentDocuments=true) {
return {
id: atlas.id,
name: atlas.name,
@@ -23,4 +25,24 @@ export function presentAtlas(atlas) {
type: atlas.type,
recentDocuments: atlas.getRecentDocuments(),
}
-}
\ No newline at end of file
+}
+
+export async function presentDocument(document, includeAtlas=false) {
+ const data = {
+ id: document.id,
+ title: document.title,
+ text: document.text,
+ html: marked(document.text),
+ createdAt: document.createdAt,
+ updatedAt: document.updatedAt,
+ atlas: document.atlaId,
+ team: document.teamId,
+ }
+
+ if (includeAtlas) {
+ const atlas = await document.getAtlas();
+ data.atlas = presentAtlas(atlas, false);
+ }
+
+ return data;
+}
diff --git a/src/Reducers/index.js b/src/Reducers/index.js
index 0354bc2b6..ad7b94f3b 100644
--- a/src/Reducers/index.js
+++ b/src/Reducers/index.js
@@ -1,12 +1,14 @@
import { combineReducers } from 'redux';
import atlases from './atlases';
+import document from './document';
import team from './team';
import editor from './editor';
import user from './user';
export default combineReducers({
atlases,
+ document,
team,
editor,
user,
diff --git a/src/actions/DocumentActions.js b/src/actions/DocumentActions.js
new file mode 100644
index 000000000..877985382
--- /dev/null
+++ b/src/actions/DocumentActions.js
@@ -0,0 +1,63 @@
+import makeActionCreator from '../utils/actions';
+import { replace } from 'react-router-redux';
+import { client } from 'utils/ApiClient';
+
+export const FETCH_DOCUMENT_PENDING = 'FETCH_DOCUMENT_PENDING';
+export const FETCH_DOCUMENT_SUCCESS = 'FETCH_DOCUMENT_SUCCESS';
+export const FETCH_DOCUMENT_FAILURE = 'FETCH_DOCUMENT_FAILURE';
+
+const fetchDocumentPending = makeActionCreator(FETCH_DOCUMENT_PENDING);
+const fetchDocumentSuccess = makeActionCreator(FETCH_DOCUMENT_SUCCESS, 'data');
+const fetchDocumentFailure = makeActionCreator(FETCH_DOCUMENT_FAILURE, 'error');
+
+export function fetchDocumentAsync(documentId) {
+ return (dispatch) => {
+ dispatch(fetchDocumentPending());
+
+ client.post('/documents.info', {
+ id: documentId,
+ })
+ .then(data => {
+ dispatch(fetchDocumentSuccess(data.data));
+ })
+ .catch((err) => {
+ dispatch(fetchDocumentFailure(err));
+ })
+ };
+};
+
+export const SAVE_DOCUMENT_PENDING = 'SAVE_DOCUMENT_PENDING';
+export const SAVE_DOCUMENT_SUCCESS = 'SAVE_DOCUMENT_SUCCESS';
+export const SAVE_DOCUMENT_FAILURE = 'SAVE_DOCUMENT_FAILURE';
+
+const saveDocumentPending = makeActionCreator(SAVE_DOCUMENT_PENDING);
+const saveDocumentSuccess = makeActionCreator(SAVE_DOCUMENT_SUCCESS, 'data');
+const saveDocumentFailure = makeActionCreator(SAVE_DOCUMENT_FAILURE, 'error');
+
+export function saveDocumentAsync(atlasId, documentId, title, text) {
+ return (dispatch) => {
+ dispatch(saveDocumentPending());
+
+ let url;
+ if (documentId) {
+ url = '/documents.update'
+ } else {
+ url = '/documents.create'
+ }
+
+ client.post(url, {
+ atlas: atlasId,
+ document: documentId,
+ title,
+ text,
+ })
+ .then(data => {
+ dispatch(saveDocumentSuccess(data.data, data.pagination));
+ dispatch(replace(`/documents/${data.data.id}`));
+ })
+ .catch((err) => {
+ dispatch(saveDocumentFailure(err));
+ })
+ };
+};
+
diff --git a/src/index.js b/src/index.js
index feedcd4ef..bfb8342b4 100644
--- a/src/index.js
+++ b/src/index.js
@@ -23,6 +23,7 @@ import Home from 'scenes/Home';
import Editor from 'scenes/Editor';
import Dashboard from 'scenes/Dashboard';
import Atlas from 'scenes/Atlas';
+import Document from 'scenes/Document';
import SlackAuth from 'scenes/SlackAuth';
// Redux
@@ -51,8 +52,7 @@ persistStore(store, {