Merge pull request #3746 from overleaf/jpa-hard-deletion-from-deleted-files

[ProjectDeleter] hard deletion of project deletes deletedFiles entries

GitOrigin-RevId: b514c34465d5fdc66b40aae5bcdb8b66975bc350
This commit is contained in:
Jakob Ackermann 2021-04-01 12:39:48 +02:00 committed by Copybot
parent 9c38d41652
commit 23be656aec
3 changed files with 100 additions and 1 deletions

View file

@ -354,7 +354,8 @@ async function expireDeletedProject(projectId) {
deletedProject.project._id,
historyId
),
FilestoreHandler.promises.deleteProject(deletedProject.project._id)
FilestoreHandler.promises.deleteProject(deletedProject.project._id),
hardDeleteDeletedFiles(deletedProject.project._id)
])
await DeletedProject.updateOne(
@ -383,3 +384,22 @@ function filterDuplicateDeletedFilesInPlace(project) {
return true
})
}
let deletedFilesProjectIdIndexExist
async function doesDeletedFilesProjectIdIndexExist() {
if (typeof deletedFilesProjectIdIndexExist !== 'boolean') {
// Resolve this about once. No need for locking or retry handling.
deletedFilesProjectIdIndexExist = await db.deletedFiles.indexExists(
'projectId_1'
)
}
return deletedFilesProjectIdIndexExist
}
async function hardDeleteDeletedFiles(projectId) {
if (!(await doesDeletedFilesProjectIdIndexExist())) {
// Running the deletion command w/o index would kill mongo performance
return
}
return db.deletedFiles.deleteMany({ projectId })
}

View file

@ -239,6 +239,81 @@ describe('Deleting a project', function() {
})
})
describe('when the project has deleted files', function() {
beforeEach('get rootFolder id', function(done) {
this.user.getProject(this.projectId, (error, project) => {
if (error) return done(error)
this.rootFolder = project.rootFolder[0]._id
done()
})
})
let allFileIds
beforeEach('reset allFileIds', function() {
allFileIds = []
})
function createAndDeleteFile(name) {
let fileId
beforeEach(`create file ${name}`, function(done) {
this.user.uploadExampleFileInProject(
this.projectId,
this.rootFolder,
name,
(error, theFileId) => {
fileId = theFileId
allFileIds.push(theFileId)
done(error)
}
)
})
beforeEach(`delete file ${name}`, function(done) {
this.user.deleteItemInProject(this.projectId, 'file', fileId, done)
})
}
for (const name of ['a.png', 'another.png']) {
createAndDeleteFile(name)
}
it('should have two deleteFiles entries', async function() {
const files = await db.deletedFiles
.find({}, { sort: { _id: 1 } })
.toArray()
expect(files).to.have.length(2)
expect(files.map(file => file._id.toString())).to.deep.equal(allFileIds)
})
describe('When the deleted project is expired', function() {
beforeEach('soft delete the project', function(done) {
this.user.deleteProject(this.projectId, done)
})
beforeEach('hard delete the project', function(done) {
request.post(
`/internal/project/${this.projectId}/expire-deleted-project`,
{
auth: {
user: settings.apis.web.user,
pass: settings.apis.web.pass,
sendImmediately: true
}
},
(error, res) => {
expect(error).not.to.exist
expect(res.statusCode).to.equal(200)
done()
}
)
})
it('should cleanup the deleteFiles', async function() {
const files = await db.deletedFiles
.find({}, { sort: { _id: 1 } })
.toArray()
expect(files).to.deep.equal([])
})
})
})
describe('When the project has docs', function() {
beforeEach(function(done) {
this.user.getProject(this.projectId, (error, project) => {

View file

@ -103,6 +103,10 @@ describe('ProjectDeleter', function() {
}
this.db = {
deletedFiles: {
indexExists: sinon.stub().resolves(false),
deleteMany: sinon.stub()
},
projects: {
insertOne: sinon.stub().resolves()
}