From 3c780c65e81ee287dc030d47475bccfb11db93ce Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 25 Oct 2021 14:57:16 +0100 Subject: [PATCH] Merge pull request #5417 from overleaf/hb-restore-doc-from-admin Restore soft deleted docs from admin GitOrigin-RevId: 5c94539ae950c401225d2cfe7c0bc978ed258e22 --- .../Project/ProjectEntityRestoreHandler.js | 35 +++++++ .../web/scripts/restore_soft_deleted_docs.js | 45 ++------- .../ProjectEntityRestoreHandlerTests.js | 95 +++++++++++++++++++ .../ProjectEntityUpdateHandlerTests.js | 1 + 4 files changed, 138 insertions(+), 38 deletions(-) create mode 100644 services/web/app/src/Features/Project/ProjectEntityRestoreHandler.js create mode 100644 services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js diff --git a/services/web/app/src/Features/Project/ProjectEntityRestoreHandler.js b/services/web/app/src/Features/Project/ProjectEntityRestoreHandler.js new file mode 100644 index 0000000000..b9308416e3 --- /dev/null +++ b/services/web/app/src/Features/Project/ProjectEntityRestoreHandler.js @@ -0,0 +1,35 @@ +const { callbackify } = require('util') +const Path = require('path') +const ProjectEntityHandler = require('./ProjectEntityHandler') +const ProjectEntityUpdateHandler = require('./ProjectEntityUpdateHandler') + +async function restoreDeletedDoc(projectId, docId, docName, userId) { + const deletedDoc = await ProjectEntityHandler.promises.getDoc( + projectId, + docId, + { include_deleted: true } + ) + + const formattedTimestamp = new Date() + .toISOString() + .replace('T', '-') + .replace(/[^0-9-]/g, '') + const extension = Path.extname(docName) + const basename = Path.basename(docName, extension) + const deletedDocName = `${basename}-${formattedTimestamp}${extension}` + return await ProjectEntityUpdateHandler.promises.addDocWithRanges( + projectId, + null, + `${deletedDocName}`, + deletedDoc.lines, + deletedDoc.ranges, + userId + ) +} + +module.exports = { + restoreDeletedDoc: callbackify(restoreDeletedDoc), + promises: { + restoreDeletedDoc, + }, +} diff --git a/services/web/scripts/restore_soft_deleted_docs.js b/services/web/scripts/restore_soft_deleted_docs.js index 365a8e2814..07c19837d1 100644 --- a/services/web/scripts/restore_soft_deleted_docs.js +++ b/services/web/scripts/restore_soft_deleted_docs.js @@ -1,8 +1,6 @@ const { waitForDb } = require('../app/src/infrastructure/mongodb') -const ProjectEntityUpdateHandler = require('../app/src/Features/Project/ProjectEntityUpdateHandler') -const ProjectEntityHandler = require('../app/src/Features/Project/ProjectEntityHandler') +const ProjectEntityRestoreHandler = require('../app/src/Features/Project/ProjectEntityRestoreHandler') const DocstoreManager = require('../app/src/Features/Docstore/DocstoreManager') -const Path = require('path') const ARGV = process.argv.slice(2) const DEVELOPER_USER_ID = ARGV.shift() @@ -17,41 +15,12 @@ async function main() { FILE_NAMES_TO_RESTORE.includes(doc.name) ) for (const deletedDoc of docsToRestore) { - const doc = await new Promise((resolve, reject) => { - ProjectEntityHandler.getDoc( - PROJECT_ID, - deletedDoc._id, - { - include_deleted: true, - }, - (err, lines, rev, version, ranges) => { - if (err) return reject(err) - resolve({ lines, ranges }) - } - ) - }) - - const formattedTimestamp = new Date() - .toISOString() - .replace('T', '-') - .replace(/[^0-9-]/g, '') - const extension = Path.extname(deletedDoc.name) - const basename = Path.basename(deletedDoc.name, extension) - const deletedDocName = `${basename}-${formattedTimestamp}${extension}` - const newDoc = await new Promise((resolve, reject) => { - ProjectEntityUpdateHandler.addDocWithRanges( - PROJECT_ID, - null, - `${deletedDocName}`, - doc.lines, - doc.ranges, - DEVELOPER_USER_ID, - (err, doc, folderId) => { - if (err) return reject(err) - resolve({ doc, folderId }) - } - ) - }) + const newDoc = await ProjectEntityRestoreHandler.restoreDeletedDoc( + PROJECT_ID, + deletedDoc._id, + deletedDoc.id, + DEVELOPER_USER_ID + ) console.log(newDoc) } } diff --git a/services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js b/services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js new file mode 100644 index 0000000000..c8a125d11f --- /dev/null +++ b/services/web/test/unit/src/Project/ProjectEntityRestoreHandlerTests.js @@ -0,0 +1,95 @@ +const { expect } = require('chai') +const sinon = require('sinon') +const SandboxedModule = require('sandboxed-module') + +const MODULE_PATH = + '../../../../app/src/Features/Project/ProjectEntityRestoreHandler.js' + +describe('ProjectEntityRestoreHandler', function () { + beforeEach(function () { + this.project = { + _id: '123213jlkj9kdlsaj', + } + + this.user = { + _id: '588f3ddae8ebc1bac07c9fa4', + first_name: 'bjkdsjfk', + features: {}, + } + + this.docId = '4eecb1c1bffa66588e0000a2' + this.DocModel = class Doc { + constructor(options) { + this.name = options.name + this.lines = options.lines + this._id = this.docId + this.rev = 0 + } + } + + this.ProjectEntityHandler = { + promises: { + getDoc: sinon.stub(), + }, + } + + this.ProjectEntityUpdateHandler = { + promises: { + addDocWithRanges: sinon.stub(), + }, + } + + this.ProjectEntityRestoreHandler = SandboxedModule.require(MODULE_PATH, { + requires: { + './ProjectEntityHandler': this.ProjectEntityHandler, + './ProjectEntityUpdateHandler': this.ProjectEntityUpdateHandler, + }, + }) + }) + + it('should add a new doc with timestamp name and old content', async function () { + const docName = 'deletedDoc' + + this.docLines = ['line one', 'line two'] + this.rev = 3 + this.ranges = { comments: [{ id: 123 }] } + + this.newDoc = new this.DocModel({ + name: this.docName, + lines: undefined, + _id: this.docId, + rev: 0, + }) + + this.ProjectEntityHandler.promises.getDoc.resolves({ + lines: this.docLines, + rev: this.rev, + version: 'version', + ranges: this.ranges, + }) + + this.ProjectEntityUpdateHandler.addDocWithRanges = sinon + .stub() + .yields(null, this.newDoc) + + await this.ProjectEntityRestoreHandler.promises.restoreDeletedDoc( + this.project._id, + this.docId, + docName, + this.user._id + ) + + const docNameMatcher = new RegExp(docName + '-\\d{4}-\\d{2}-\\d{2}-\\d+') + + expect( + this.ProjectEntityUpdateHandler.promises.addDocWithRanges + ).to.have.been.calledWith( + this.project._id, + null, + sinon.match(docNameMatcher), + this.docLines, + this.ranges, + this.user._id + ) + }) +}) diff --git a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js index 83d3d33456..01e602d553 100644 --- a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js +++ b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js @@ -104,6 +104,7 @@ describe('ProjectEntityUpdateHandler', function () { markAsUpdated: sinon.stub(), } this.ProjectEntityHandler = { + getDoc: sinon.stub(), getDocPathByProjectIdAndDocId: sinon.stub(), getAllEntitiesFromProject: sinon.stub(), }