Sfoglia il codice sorgente

Merge pull request #5417 from overleaf/hb-restore-doc-from-admin

Restore soft deleted docs from admin

GitOrigin-RevId: 5c94539ae950c401225d2cfe7c0bc978ed258e22
Brian Gough 5 anni fa
parent
commit
3c780c65e8

+ 35 - 0
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,
+  },
+}

+ 7 - 38
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)
   }
 }

+ 95 - 0
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
+    )
+  })
+})

+ 1 - 0
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(),
     }