diff --git a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js index b5b975641f..dc7735e60e 100644 --- a/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js +++ b/services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js @@ -877,14 +877,84 @@ const ProjectEntityUpdateHandler = { if (folder == null) { return callback(new Error("Couldn't find folder")) } - let existingFile = null - for (let fileRef of folder.fileRefs) { - if (fileRef.name === fileName) { - existingFile = fileRef - break - } - } - if (existingFile != null) { + const existingFile = folder.fileRefs.find( + ({ name }) => name === fileName + ) + const existingDoc = folder.docs.find(({ name }) => name === fileName) + + if (existingDoc) { + ProjectLocator.findElement( + { + project_id: projectId, + element_id: existingDoc._id, + type: 'doc' + }, + (err, doc, path) => { + if (err) { + return callback(new Error('coudnt find existing file')) + } + ProjectEntityMongoUpdateHandler.replaceDocWithFile( + projectId, + existingDoc._id, + newFileRef, + (err, project) => { + if (err) { + return callback(err) + } + const projectHistoryId = + project.overleaf && + project.overleaf.history && + project.overleaf.history.id + TpdsUpdateSender.addFile( + { + project_id: project._id, + file_id: newFileRef._id, + path: path.fileSystem, + rev: newFileRef.rev, + project_name: project.name + }, + err => { + if (err) { + return callback(err) + } + DocumentUpdaterHandler.updateProjectStructure( + projectId, + projectHistoryId, + userId, + { + oldDocs: [ + { doc: existingDoc, path: path.fileSystem } + ], + + newFiles: [ + { + file: newFileRef, + path: path.fileSystem, + url: fileStoreUrl + } + ], + newProject: project + }, + err => { + if (err) { + return callback(err) + } + EditorRealTimeController.emitToRoom( + projectId, + 'removeEntity', + existingDoc._id, + 'convertDocToFile' + ) + callback(null, newFileRef, true, existingFile) + } + ) + } + ) + } + ) + } + ) + } else if (existingFile) { // this calls directly into the replaceFile main task (without the beforeLock part) return ProjectEntityUpdateHandler.replaceFile.mainTask( projectId, diff --git a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js index 3aebca8e06..f4766d5765 100644 --- a/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js +++ b/services/web/test/unit/src/Project/ProjectEntityUpdateHandlerTests.js @@ -1020,7 +1020,7 @@ describe('ProjectEntityUpdateHandler', function() { describe('updating an existing file', function() { beforeEach(function() { this.existingFile = { _id: fileId, name: this.fileName } - this.folder = { _id: folderId, fileRefs: [this.existingFile] } + this.folder = { _id: folderId, fileRefs: [this.existingFile], docs: [] } this.ProjectLocator.findElement.yields(null, this.folder) this.ProjectEntityUpdateHandler.replaceFile = { mainTask: sinon.stub().yields(null, this.newFile) @@ -1038,15 +1038,15 @@ describe('ProjectEntityUpdateHandler', function() { }) it('replaces the file', function() { - this.ProjectEntityUpdateHandler.replaceFile.mainTask - .calledWith( - projectId, - fileId, - this.fileSystemPath, - this.linkedFileData, - userId - ) - .should.equal(true) + expect( + this.ProjectEntityUpdateHandler.replaceFile.mainTask + ).to.be.calledWith( + projectId, + fileId, + this.fileSystemPath, + this.linkedFileData, + userId + ) }) it('returns the file', function() { @@ -1056,7 +1056,7 @@ describe('ProjectEntityUpdateHandler', function() { describe('creating a new file', function() { beforeEach(function() { - this.folder = { _id: folderId, fileRefs: [] } + this.folder = { _id: folderId, fileRefs: [], docs: [] } this.newFile = { _id: fileId } this.ProjectLocator.findElement.yields(null, this.folder) this.ProjectEntityUpdateHandler.addFile = { @@ -1127,6 +1127,104 @@ describe('ProjectEntityUpdateHandler', function() { this.callback.calledWithMatch(errorMatcher).should.equal(true) }) }) + + describe('upserting file on top of a doc', function() { + beforeEach(function(done) { + this.path = '/path/to/doc' + this.existingDoc = { _id: new ObjectId(), name: this.fileName } + this.folder = { + _id: folderId, + fileRefs: [], + docs: [this.existingDoc] + } + this.ProjectLocator.findElement + .withArgs({ + project_id: this.project._id.toString(), + element_id: folderId, + type: 'folder' + }) + .yields(null, this.folder) + this.ProjectLocator.findElement + .withArgs({ + project_id: this.project._id.toString(), + element_id: this.existingDoc._id, + type: 'doc' + }) + .yields(null, this.existingDoc, { fileSystem: this.path }) + + this.newFileUrl = 'new-file-url' + this.newFile = { + _id: newFileId, + name: 'dummy-upload-filename', + rev: 0, + linkedFileData: this.linkedFileData + } + this.newProject = { + name: 'new project', + overleaf: { history: { id: projectHistoryId } } + } + this.FileStoreHandler.uploadFileFromDisk.yields( + null, + this.newFileUrl, + this.newFile + ) + this.ProjectEntityMongoUpdateHandler.replaceDocWithFile.yields( + null, + this.newProject + ) + + this.ProjectEntityUpdateHandler.upsertFile( + projectId, + folderId, + this.fileName, + this.fileSystemPath, + this.linkedFileData, + userId, + done + ) + }) + + it('replaces the existing doc with a file', function() { + expect( + this.ProjectEntityMongoUpdateHandler.replaceDocWithFile + ).to.have.been.calledWith(projectId, this.existingDoc._id, this.newFile) + }) + + it('updates the doc structure', function() { + const oldDocs = [ + { + doc: this.existingDoc, + path: this.path + } + ] + const newFiles = [ + { + file: this.newFile, + path: this.path, + url: this.newFileUrl + } + ] + const updates = { + oldDocs, + newFiles, + newProject: this.newProject + } + expect( + this.DocumentUpdaterHandler.updateProjectStructure + ).to.have.been.calledWith(projectId, projectHistoryId, userId, updates) + }) + + it('tells everyone in the room the doc is removed', function() { + expect( + this.EditorRealTimeController.emitToRoom + ).to.have.been.calledWith( + projectId, + 'removeEntity', + this.existingDoc._id, + 'convertDocToFile' + ) + }) + }) }) describe('upsertDocWithPath', function() {