Merge pull request #3745 from overleaf/jpa-project-restore-handle-deleted-files

[ProjectDeleter] restore project.deletedFiles into own collection

GitOrigin-RevId: cb34e0e22609a49c676ebfe0753e650699b96f5d
This commit is contained in:
Jakob Ackermann 2021-03-26 10:51:49 +01:00 committed by Copybot
parent a5637651b5
commit f66fa58a7c
3 changed files with 110 additions and 0 deletions

View file

@ -303,6 +303,20 @@ async function undeleteProject(projectId, options = {}) {
)
restored.archived = undefined
if (restored.deletedFiles && restored.deletedFiles.length > 0) {
filterDuplicateDeletedFilesInPlace(restored)
const deletedFiles = restored.deletedFiles.map(file => {
// break free from the model
file = file.toObject()
// add projectId
file.projectId = projectId
return file
})
await db.deletedFiles.insertMany(deletedFiles)
restored.deletedFiles = []
}
// we can't use Mongoose to re-insert the project, as it won't
// create a new document with an _id already specified. We need to
// insert it directly into the collection
@ -359,3 +373,13 @@ async function expireDeletedProject(projectId) {
throw error
}
}
function filterDuplicateDeletedFilesInPlace(project) {
const fileIds = new Set()
project.deletedFiles = project.deletedFiles.filter(file => {
const id = file._id.toString()
if (fileIds.has(id)) return false
fileIds.add(id)
return true
})
}

View file

@ -362,4 +362,69 @@ describe('Deleting a project', function() {
})
})
})
describe('when the deleted project has deletedFiles', function() {
beforeEach('delete project', function(done) {
this.user.deleteProject(this.projectId, done)
})
let fileId1, fileId2
beforeEach('create files', function() {
// take a short cut and just allocate file ids
fileId1 = ObjectId()
fileId2 = ObjectId()
})
const otherFileDetails = {
name: 'universe.jpg',
linkedFileData: null,
hash: 'ed19e7d6779b47d8c63f6fa5a21954dcfb6cac00',
deletedAt: new Date()
}
beforeEach('insert deletedFiles', async function() {
const deletedFiles = [
{ _id: fileId1, ...otherFileDetails },
{ _id: fileId2, ...otherFileDetails },
// duplicate entry
{ _id: fileId1, ...otherFileDetails }
]
await db.deletedProjects.updateOne(
{ 'deleterData.deletedProjectId': ObjectId(this.projectId) },
{ $set: { 'project.deletedFiles': deletedFiles } }
)
})
describe('when undelete the project', function() {
let admin
beforeEach('create admin', function(done) {
admin = new User()
async.series(
[
cb => admin.ensureUserExists(cb),
cb => admin.ensureAdmin(cb),
cb => admin.login(cb)
],
done
)
})
beforeEach('undelete project', function(done) {
admin.undeleteProject(this.projectId, done)
})
it('should not insert deletedFiles into the projects collection', function(done) {
this.user.getProject(this.projectId, (error, project) => {
if (error) return done(error)
expect(project.deletedFiles).to.deep.equal([])
done()
})
})
it('should insert unique entries into the deletedFiles collection', async function() {
const docs = await db.deletedFiles
.find({}, { sort: { _id: 1 } })
.toArray()
expect(docs).to.deep.equal([
{ _id: fileId1, projectId: this.projectId, ...otherFileDetails },
{ _id: fileId2, projectId: this.projectId, ...otherFileDetails }
])
})
})
})
})

View file

@ -356,6 +356,27 @@ class User {
)
}
undeleteProject(projectId, callback) {
this.request.post(
{
url: `/admin/project/${projectId}/undelete`
},
(error, response) => {
if (error) {
return callback(error)
}
if (response.statusCode !== 204) {
return callback(
new Error(
`Non-success response when undeleting project: ${response.statusCode}`
)
)
}
callback(null)
}
)
}
deleteProjects(callback) {
db.projects.deleteMany({ owner_ref: ObjectId(this.id) }, callback)
}