diff --git a/server/api/documents.js b/server/api/documents.js index 9d8e0f94..d5985be9 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -62,7 +62,7 @@ router.post('documents.create', auth(), async (ctx) => { if (!ownerAtlas) throw httpErrors.BadRequest(); - let parentDocumentObj; + let parentDocumentObj = {}; if (parentDocument && ownerAtlas.type === 'atlas') { parentDocumentObj = await Document.findOne({ where: { @@ -77,13 +77,17 @@ router.post('documents.create', auth(), async (ctx) => { atlasId: ownerAtlas.id, teamId: user.teamId, userId: user.id, + lastModifiedById: user.id, title: title, text: text, }); + await document.createRevision(); // TODO: Move to afterSave hook if possible with imports - ownerAtlas.addNodeToNavigationTree(document); - await ownerAtlas.save(); + if (parentDocument && ownerAtlas.type === 'atlas') { + ownerAtlas.addNodeToNavigationTree(document); + await ownerAtlas.save(); + } ctx.body = { data: await presentDocument(document, true), @@ -112,7 +116,9 @@ router.post('documents.update', auth(), async (ctx) => { document.title = title; document.text = text; + document.lastModifiedById = user.id; await document.save(); + await document.createRevision(); ctx.body = { data: await presentDocument(document, true), diff --git a/server/migrations/20160626063409-unnamed-migration.js b/server/migrations/20160626063409-add-indexes.js similarity index 100% rename from server/migrations/20160626063409-unnamed-migration.js rename to server/migrations/20160626063409-add-indexes.js diff --git a/server/migrations/20160626175224-add-revisions.js b/server/migrations/20160626175224-add-revisions.js new file mode 100644 index 00000000..2e99cecd --- /dev/null +++ b/server/migrations/20160626175224-add-revisions.js @@ -0,0 +1,80 @@ +'use strict'; + +module.exports = { + up: function (queryInterface, Sequelize) { + queryInterface.createTable('revisions', { + id: { + type: 'UUID', + allowNull: false, + primaryKey: true + }, + title: + { type: 'CHARACTER VARYING', + allowNull: false, + }, + text: + { type: 'TEXT', + allowNull: true, + }, + html: + { type: 'TEXT', + allowNull: true, + }, + preview: + { type: 'TEXT', + allowNull: true, + }, + createdAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + }, + updatedAt: + { type: 'TIMESTAMP WITH TIME ZONE', + allowNull: false, + }, + userId: { + type: 'UUID', + allowNull: false, + references: { + model: 'users', + } + }, + documentId: { + type: 'UUID', + allowNull: false, + references: { + model: 'documents', + onDelete: 'CASCADE', + } + }, + }); + + queryInterface.addColumn( + 'documents', + 'lastModifiedById', + { + type: 'UUID', + allowNull: false, + references: { + model: 'users', + } + } + ); + + queryInterface.addColumn( + 'documents', + 'revisionCount', + { + type: 'INTEGER', + defaultValue: 0 + } + ); + }, + + down: function (queryInterface, Sequelize) { + queryInterface.dropTable('revisions'); + + queryInterface.removeColumn('documents', 'lastModifiedById'); + queryInterface.removeColumn('documents', 'revisionCount'); + } +}; diff --git a/server/models/Document.js b/server/models/Document.js index db3e323b..8ae990bb 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -11,6 +11,7 @@ import { truncateMarkdown, } from '../utils/truncate'; import User from './User'; +import Revision from './Revision'; slug.defaults.mode ='rfc3986'; @@ -19,6 +20,13 @@ const generateSlug = (title, urlId) => { return `${slugifiedTitle}-${urlId}`; }; +const documentBeforeSave = (doc) => { + doc.html = convertToMarkdown(doc.text); + doc.preview = truncateMarkdown(doc.text, 160); + doc.revisionCount = doc.revisionCount + 1; + return doc; +}; + const Document = sequelize.define('document', { id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, urlId: { type: DataTypes.STRING, primaryKey: true }, @@ -27,21 +35,23 @@ const Document = sequelize.define('document', { text: DataTypes.TEXT, html: DataTypes.TEXT, preview: DataTypes.TEXT, + revisionCount: { type: DataTypes.INTEGER, defaultValue: 0, }, parentDocumentId: DataTypes.UUID, + lastModifiedById: { + type: 'UUID', + allowNull: false, + references: { + model: 'users', + } + }, }, { hooks: { beforeValidate: (doc) => { doc.urlId = randomstring.generate(15); }, - beforeCreate: (doc) => { - doc.html = convertToMarkdown(doc.text); - doc.preview = truncateMarkdown(doc.text, 160); - }, - beforeUpdate: (doc) => { - doc.html = convertToMarkdown(doc.text); - doc.preview = truncateMarkdown(doc.text, 160); - }, + beforeCreate: documentBeforeSave, + beforeUpdate: documentBeforeSave, }, instanceMethods: { buildUrl() { @@ -51,6 +61,17 @@ const Document = sequelize.define('document', { getUrl() { return `/documents/${ this.id }`; }, + async createRevision() { + // Create revision of the current (latest) + await Revision.create({ + title: this.title, + text: this.text, + html: this.html, + preview: this.preview, + userId: this.lastModifiedById, + documentId: this.id, + }); + }, } }); diff --git a/server/models/Revision.js b/server/models/Revision.js new file mode 100644 index 00000000..e4e17e4a --- /dev/null +++ b/server/models/Revision.js @@ -0,0 +1,31 @@ +import { + DataTypes, + sequelize, +} from '../sequelize'; + +const Revision = sequelize.define('revision', { + id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true }, + title: DataTypes.STRING, + text: DataTypes.TEXT, + html: DataTypes.TEXT, + preview: DataTypes.TEXT, + + userId: { + type: 'UUID', + allowNull: false, + references: { + model: 'users', + } + }, + + documentId: { + type: 'UUID', + allowNull: false, + references: { + model: 'documents', + onDelete: 'CASCADE', + } + }, +}); + +export default Revision;