diff --git a/package.json b/package.json index f0346afd0..6404b580f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build:webpack": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --progress", "build:analyze": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --json | webpack-bundle-size-analyzer", "build": "npm run clean && npm run build:webpack", - "start": "cross-env NODE_ENV=development DEBUG=1 ./node_modules/.bin/nodemon --watch server index.js", + "start": "cross-env NODE_ENV=development DEBUG=sql ./node_modules/.bin/nodemon --watch server index.js", "lint": "eslint frontend", "deploy": "git push heroku master", "heroku-postbuild": "npm run build && npm run sequelize db:migrate", diff --git a/server/api/documents.js b/server/api/documents.js index f0366477f..c8fa8f21d 100644 --- a/server/api/documents.js +++ b/server/api/documents.js @@ -52,7 +52,8 @@ router.post('documents.search', auth(), async (ctx) => { const sql = ` SELECT * FROM documents WHERE "searchVector" @@ plainto_tsquery('english', :query) AND - "teamId" = '${user.teamId}'::uuid + "teamId" = '${user.teamId}'::uuid AND + "deletedAt" IS NULL ORDER BY ts_rank(documents."searchVector", plainto_tsquery('english', :query)) DESC; `; @@ -199,12 +200,13 @@ router.post('documents.delete', auth(), async (ctx) => { } catch (e) { throw httpErrors.BadRequest('Error while deleting'); } - } else { - try { - await document.destroy(); - } catch (e) { - throw httpErrors.BadRequest('Error while deleting'); - } + } + + // Delete the actual document + try { + await document.destroy(); + } catch (e) { + throw httpErrors.BadRequest('Error while deleting document'); } ctx.body = { diff --git a/server/migrations/20160726061511-atlas-creator.js b/server/migrations/20160726061511-atlas-creator.js index d8afeda71..1d6285863 100644 --- a/server/migrations/20160726061511-atlas-creator.js +++ b/server/migrations/20160726061511-atlas-creator.js @@ -13,6 +13,6 @@ module.exports = { }, down: function (queryInterface, Sequelize) { - queryInterface.removeColumn('documents', 'creatorId'); + queryInterface.removeColumn('atlases', 'creatorId'); } }; diff --git a/server/migrations/20160812145029-document-atlas-soft-delete.js b/server/migrations/20160812145029-document-atlas-soft-delete.js new file mode 100644 index 000000000..a2081fc46 --- /dev/null +++ b/server/migrations/20160812145029-document-atlas-soft-delete.js @@ -0,0 +1,28 @@ +'use strict'; + +module.exports = { + up: function (queryInterface, Sequelize) { + queryInterface.addColumn( + 'atlases', + 'deletedAt', + { + type: Sequelize.DATE, + allowNull: true, + } + ); + + queryInterface.addColumn( + 'documents', + 'deletedAt', + { + type: Sequelize.DATE, + allowNull: true, + } + ); + }, + + down: function (queryInterface, Sequelize) { + queryInterface.removeColumn('atlases', 'deletedAt'); + queryInterface.removeColumn('documents', 'deletedAt'); + } +}; diff --git a/server/migrations/20160814083127-paranoia-indeces.js b/server/migrations/20160814083127-paranoia-indeces.js new file mode 100644 index 000000000..b488d56f3 --- /dev/null +++ b/server/migrations/20160814083127-paranoia-indeces.js @@ -0,0 +1,41 @@ +'use strict'; + +module.exports = { + up: function (queryInterface, Sequelize) { + // Remove old indeces + queryInterface.removeIndex('documents', ['urlId']); + queryInterface.removeIndex('documents', ['id', 'atlasId']); + queryInterface.removeIndex('documents', ['id', 'teamId']); + queryInterface.removeIndex('documents', ['parentDocumentId', 'atlasId']); + + queryInterface.removeIndex('atlases', ['id', 'teamId']); + + // Add new ones + queryInterface.addIndex('documents', ['id', 'deletedAt']); + queryInterface.addIndex('documents', ['urlId', 'deletedAt']); + queryInterface.addIndex('documents', ['id', 'atlasId', 'deletedAt']); + queryInterface.addIndex('documents', ['id', 'teamId', 'deletedAt']); + queryInterface.addIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']); + + queryInterface.addIndex('atlases', ['id', 'deletedAt']); + queryInterface.addIndex('atlases', ['id', 'teamId', 'deletedAt']); + }, + + down: function (queryInterface, Sequelize) { + queryInterface.addIndex('documents', ['urlId']); + queryInterface.addIndex('documents', ['id', 'atlasId']); + queryInterface.addIndex('documents', ['id', 'teamId']); + queryInterface.addIndex('documents', ['parentDocumentId', 'atlasId']); + + queryInterface.addIndex('atlases', ['id', 'teamId']); + + queryInterface.removeIndex('documents', ['id', 'deletedAt']); + queryInterface.removeIndex('documents', ['urlId', 'deletedAt']); + queryInterface.removeIndex('documents', ['id', 'atlasId', 'deletedAt']); + queryInterface.removeIndex('documents', ['id', 'teamId', 'deletedAt']); + queryInterface.removeIndex('documents', ['parentDocumentId', 'atlasId', 'deletedAt']); + + queryInterface.removeIndex('atlases', ['id', 'deletedAt']); + queryInterface.removeIndex('atlases', ['id', 'teamId', 'deletedAt']); + } +}; diff --git a/server/models/Atlas.js b/server/models/Atlas.js index c20b6b407..bf717105c 100644 --- a/server/models/Atlas.js +++ b/server/models/Atlas.js @@ -18,26 +18,28 @@ const Atlas = sequelize.define('atlas', { navigationTree: DataTypes.JSONB, }, { tableName: 'atlases', + paranoid: true, hooks: { - afterCreate: async (atlas) => { - if (atlas.type !== 'atlas') return; + afterCreate: async (collection) => { + if (collection.type !== 'atlas') return; await Document.create({ parentDocumentId: null, - atlasId: atlas.id, - teamId: atlas.teamId, - userId: atlas.creatorId, - lastModifiedById: atlas.creatorId, + atlasId: collection.id, + teamId: collection.teamId, + userId: collection.creatorId, + lastModifiedById: collection.creatorId, title: 'Introduction', text: '# Introduction', }); + await collection.buildStructure(); + await collection.save(); }, }, instanceMethods: { - async getStructure() { - if (this.navigationTree) { - return this.navigationTree; - } + async buildStructure() { + console.log('start'); + if (this.navigationTree) return this.navigationTree; const getNodeForDocument = async (document) => { const children = await Document.findAll({ where: { @@ -47,7 +49,7 @@ const Atlas = sequelize.define('atlas', { const childNodes = []; await Promise.all(children.map(async (child) => { - childNodes.push(await getNodeForDocument(child)); + return childNodes.push(await getNodeForDocument(child)); })); return { @@ -65,11 +67,8 @@ const Atlas = sequelize.define('atlas', { }, }); - if (rootDocument) { - return await getNodeForDocument(rootDocument); - } else { - return true; // TODO should create a root doc - } + this.navigationTree = await getNodeForDocument(rootDocument); + return this.navigationTree; }, async updateNavigationTree(tree = this.navigationTree) { const nodeIds = []; diff --git a/server/models/Document.js b/server/models/Document.js index 8570dde94..60ce96eda 100644 --- a/server/models/Document.js +++ b/server/models/Document.js @@ -58,6 +58,7 @@ const Document = sequelize.define('document', { }, }, }, { + paranoid: true, hooks: { beforeValidate: (doc) => { doc.urlId = randomstring.generate(15); diff --git a/server/presenters.js b/server/presenters.js index 6c6b80fde..da98ea6f2 100644 --- a/server/presenters.js +++ b/server/presenters.js @@ -59,9 +59,7 @@ export function presentCollection(collection, includeRecentDocuments=false) { type: collection.type, }; - if (collection.type === 'atlas') { - data.navigationTree = await collection.getStructure(); - } + if (collection.type === 'atlas') data.navigationTree = collection.navigationTree; if (includeRecentDocuments) { const documents = await Document.findAll({