diff --git a/services/web/app/src/Features/Project/ProjectDeleter.js b/services/web/app/src/Features/Project/ProjectDeleter.js index 4718081ee1..a2064d81e6 100644 --- a/services/web/app/src/Features/Project/ProjectDeleter.js +++ b/services/web/app/src/Features/Project/ProjectDeleter.js @@ -15,6 +15,7 @@ const DocstoreManager = require('../Docstore/DocstoreManager') const EditorRealTimeController = require('../Editor/EditorRealTimeController') const HistoryManager = require('../History/HistoryManager') const FilestoreHandler = require('../FileStore/FileStoreHandler') +const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender') const moment = require('moment') const { promiseMapWithLimit } = require('../../util/promises') @@ -369,6 +370,9 @@ async function expireDeletedProject(projectId) { historyId ), FilestoreHandler.promises.deleteProject(deletedProject.project._id), + TpdsUpdateSender.promises.deleteProject({ + project_id: deletedProject.project._id, + }), hardDeleteDeletedFiles(deletedProject.project._id), ]) diff --git a/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js b/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js index c65c15f726..a4ba078d93 100644 --- a/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js +++ b/services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js @@ -99,6 +99,33 @@ async function deleteEntity(options) { } } +async function deleteProject(options) { + // deletion only applies to project archiver + const projectArchiverUrl = _.get(settings, [ + 'apis', + 'project_archiver', + 'url', + ]) + // silently do nothing if project archiver url is not in settings + if (!projectArchiverUrl) { + return + } + metrics.inc('tpds.delete-project') + // send the request directly to project archiver, bypassing third-party-datastore + try { + const response = await request({ + uri: `${settings.apis.project_archiver.url}/project/${options.project_id}`, + method: 'delete', + }) + return response + } catch (err) { + logger.error( + { err, project_id: options.project_id }, + 'error deleting project in third party datastore (project_archiver)' + ) + } +} + async function enqueue(group, method, job) { const tpdsWorkerUrl = _.get(settings, ['apis', 'tpdsworker', 'url']) // silently do nothing if worker url is not in settings @@ -191,6 +218,7 @@ const TpdsUpdateSender = { addEntity: callbackify(addEntity), addFile: callbackify(addFile), deleteEntity: callbackify(deleteEntity), + deleteProject: callbackify(deleteProject), enqueue: callbackify(enqueue), moveEntity: callbackify(moveEntity), pollDropboxForUser: callbackify(pollDropboxForUser), @@ -199,6 +227,7 @@ const TpdsUpdateSender = { addEntity, addFile, deleteEntity, + deleteProject, enqueue, moveEntity, pollDropboxForUser, diff --git a/services/web/test/unit/src/Project/ProjectDeleterTests.js b/services/web/test/unit/src/Project/ProjectDeleterTests.js index 9c2cbf7373..167e2e9238 100644 --- a/services/web/test/unit/src/Project/ProjectDeleterTests.js +++ b/services/web/test/unit/src/Project/ProjectDeleterTests.js @@ -127,6 +127,11 @@ describe('ProjectDeleter', function () { deleteProject: sinon.stub().resolves(), }, } + this.TpdsUpdateSender = { + promises: { + deleteProject: sinon.stub().resolves(), + }, + } this.ProjectDeleter = SandboxedModule.require(modulePath, { requires: { @@ -138,6 +143,7 @@ describe('ProjectDeleter', function () { .DocumentUpdaterHandler, '../Tags/TagsHandler': this.TagsHandler, '../FileStore/FileStoreHandler': this.FileStoreHandler, + '../ThirdPartyDataStore/TpdsUpdateSender': this.TpdsUpdateSender, '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler, '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter, '../Docstore/DocstoreManager': this.DocstoreManager, @@ -459,6 +465,14 @@ describe('ProjectDeleter', function () { this.FileStoreHandler.promises.deleteProject ).to.have.been.calledWith(this.deletedProjects[0].project._id) }) + + it('should destroy the files in project-archiver', function () { + expect( + this.TpdsUpdateSender.promises.deleteProject + ).to.have.been.calledWith({ + project_id: this.deletedProjects[0].project._id, + }) + }) }) describe('archiveProject', function () { diff --git a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSenderTests.js b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSenderTests.js index ca3672b462..76bffd9dbe 100644 --- a/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSenderTests.js +++ b/services/web/test/unit/src/ThirdPartyDataStore/TpdsUpdateSenderTests.js @@ -20,6 +20,7 @@ const httpPass = 'pass' const siteUrl = 'http://www.localhost:3000' const httpAuthSiteUrl = `http://${httpUsername}:${httpPass}@www.localhost:3000` const filestoreUrl = 'filestore.sharelatex.com' +const projectArchiverUrl = 'project-archiver.overleaf.com' describe('TpdsUpdateSender', function () { beforeEach(function () { @@ -358,4 +359,17 @@ describe('TpdsUpdateSender', function () { job0.json.user_ids[0].should.equal(userId) }) }) + describe('deleteProject', function () { + it('should not call request if there is no project archiver url', async function () { + await this.updateSender.promises.deleteProject({ project_id: projectId }) + this.request.should.not.have.been.called + }) + it('should make a delete request to project archiver', async function () { + this.settings.apis.project_archiver = { url: projectArchiverUrl } + await this.updateSender.promises.deleteProject({ project_id: projectId }) + const { uri, method } = this.request.firstCall.args[0] + method.should.equal('delete') + uri.should.equal(`${projectArchiverUrl}/project/${projectId}`) + }) + }) })