mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-14 21:25:21 +00:00
Merge pull request #2724 from overleaf/ns-doc-over-file
allow upload of doc over existing file GitOrigin-RevId: 13578bf4ab6d54686077402488399db0379cc761
This commit is contained in:
parent
be900488a9
commit
3a1ab63cce
4 changed files with 237 additions and 13 deletions
|
@ -43,6 +43,7 @@ module.exports = {
|
|||
'newProject'
|
||||
]),
|
||||
replaceDocWithFile: callbackify(replaceDocWithFile),
|
||||
replaceFileWithDoc: callbackify(replaceFileWithDoc),
|
||||
mkdirp: callbackifyMultiResult(wrapWithLock(mkdirp), [
|
||||
'newFolders',
|
||||
'folder'
|
||||
|
@ -78,6 +79,7 @@ module.exports = {
|
|||
addFolder: wrapWithLock(addFolder),
|
||||
replaceFileWithNew: wrapWithLock(replaceFileWithNew),
|
||||
replaceDocWithFile: wrapWithLock(replaceDocWithFile),
|
||||
replaceFileWithDoc: wrapWithLock(replaceFileWithDoc),
|
||||
mkdirp: wrapWithLock(mkdirp),
|
||||
moveEntity: wrapWithLock(moveEntity),
|
||||
deleteEntity: wrapWithLock(deleteEntity),
|
||||
|
@ -210,6 +212,33 @@ async function replaceDocWithFile(projectId, docId, fileRef) {
|
|||
return newProject
|
||||
}
|
||||
|
||||
async function replaceFileWithDoc(projectId, fileId, newDoc) {
|
||||
const project = await ProjectGetter.promises.getProjectWithoutLock(
|
||||
projectId,
|
||||
{ rootFolder: true, name: true, overleaf: true }
|
||||
)
|
||||
const { path } = await ProjectLocator.promises.findElement({
|
||||
project,
|
||||
element_id: fileId,
|
||||
type: 'file'
|
||||
})
|
||||
const folderMongoPath = _getParentMongoPath(path.mongo)
|
||||
const newProject = await Project.findOneAndUpdate(
|
||||
{ _id: project._id },
|
||||
{
|
||||
$pull: {
|
||||
[`${folderMongoPath}.fileRefs`]: { _id: fileId }
|
||||
},
|
||||
$push: {
|
||||
[`${folderMongoPath}.docs`]: newDoc
|
||||
},
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{ new: true }
|
||||
).exec()
|
||||
return newProject
|
||||
}
|
||||
|
||||
async function mkdirp(projectId, path, options = {}) {
|
||||
// defaults to case insensitive paths, use options {exactCaseMatch:true}
|
||||
// to make matching case-sensitive
|
||||
|
|
|
@ -754,21 +754,92 @@ const ProjectEntityUpdateHandler = {
|
|||
}
|
||||
ProjectLocator.findElement(
|
||||
{ project_id: projectId, element_id: folderId, type: 'folder' },
|
||||
(error, folder) => {
|
||||
(error, folder, path) => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
if (folder == null) {
|
||||
return callback(new Error("Couldn't find folder"))
|
||||
}
|
||||
let existingDoc = null
|
||||
for (let doc of folder.docs) {
|
||||
if (doc.name === docName) {
|
||||
existingDoc = doc
|
||||
break
|
||||
}
|
||||
}
|
||||
if (existingDoc != null) {
|
||||
const existingDoc = folder.docs.find(({ name }) => name === docName)
|
||||
const existingFile = folder.fileRefs.find(
|
||||
({ name }) => name === docName
|
||||
)
|
||||
if (existingFile) {
|
||||
const doc = new Doc({ name: docName })
|
||||
DocstoreManager.updateDoc(
|
||||
projectId.toString(),
|
||||
doc._id.toString(),
|
||||
docLines,
|
||||
0,
|
||||
{},
|
||||
(err, modified, rev) => {
|
||||
if (err != null) {
|
||||
return callback(err)
|
||||
}
|
||||
ProjectEntityMongoUpdateHandler.replaceFileWithDoc(
|
||||
projectId,
|
||||
existingFile._id,
|
||||
doc,
|
||||
(err, project) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
TpdsUpdateSender.addDoc(
|
||||
{
|
||||
project_id: projectId,
|
||||
doc_id: doc._id,
|
||||
path: path.fileSystem,
|
||||
project_name: project.name,
|
||||
rev: existingFile.rev + 1
|
||||
},
|
||||
err => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
const docPath = path.fileSystem
|
||||
const projectHistoryId =
|
||||
project.overleaf &&
|
||||
project.overleaf.history &&
|
||||
project.overleaf.history.id
|
||||
const newDocs = [
|
||||
{
|
||||
doc,
|
||||
path: docPath,
|
||||
docLines: docLines.join('\n')
|
||||
}
|
||||
]
|
||||
const oldFiles = [
|
||||
{
|
||||
file: existingFile,
|
||||
path: Path.join(path.fileSystem, existingFile.name)
|
||||
}
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
{ oldFiles, newDocs, newProject: project },
|
||||
error => {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
}
|
||||
EditorRealTimeController.emitToRoom(
|
||||
projectId,
|
||||
'removeEntity',
|
||||
existingFile._id,
|
||||
'convertFileToDoc'
|
||||
)
|
||||
callback(null, doc, true)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
} else if (existingDoc) {
|
||||
DocumentUpdaterHandler.setDocument(
|
||||
projectId,
|
||||
existingDoc._id,
|
||||
|
|
|
@ -1113,4 +1113,27 @@ describe('ProjectEntityMongoUpdateHandler', function() {
|
|||
this.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
|
||||
describe('replaceFileWithDoc', function() {
|
||||
it('should simultaneously remove the file and add the doc', async function() {
|
||||
this.ProjectMock.expects('findOneAndUpdate')
|
||||
.withArgs(
|
||||
{ _id: this.project._id },
|
||||
{
|
||||
$pull: { 'rootFolder.0.fileRefs': { _id: this.file._id } },
|
||||
$push: { 'rootFolder.0.docs': this.doc },
|
||||
$inc: { version: 1 }
|
||||
},
|
||||
{ new: true }
|
||||
)
|
||||
.chain('exec')
|
||||
.resolves(this.project)
|
||||
await this.subject.promises.replaceFileWithDoc(
|
||||
this.project._id,
|
||||
this.file._id,
|
||||
this.doc
|
||||
)
|
||||
this.ProjectMock.verify()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -126,7 +126,8 @@ describe('ProjectEntityUpdateHandler', function() {
|
|||
moveEntity: sinon.stub(),
|
||||
renameEntity: sinon.stub(),
|
||||
deleteEntity: sinon.stub(),
|
||||
replaceDocWithFile: sinon.stub()
|
||||
replaceDocWithFile: sinon.stub(),
|
||||
replaceFileWithDoc: sinon.stub()
|
||||
}
|
||||
this.TpdsUpdateSender = {
|
||||
addFile: sinon.stub().yields(),
|
||||
|
@ -865,7 +866,12 @@ describe('ProjectEntityUpdateHandler', function() {
|
|||
describe('updating an existing doc', function() {
|
||||
beforeEach(function() {
|
||||
this.existingDoc = { _id: docId, name: this.docName }
|
||||
this.folder = { _id: folderId, docs: [this.existingDoc] }
|
||||
this.existingFile = { _id: fileId, name: this.fileName }
|
||||
this.folder = {
|
||||
_id: folderId,
|
||||
docs: [this.existingDoc],
|
||||
fileRefs: [this.existingFile]
|
||||
}
|
||||
this.ProjectLocator.findElement.yields(null, this.folder)
|
||||
this.DocumentUpdaterHandler.setDocument.yields()
|
||||
|
||||
|
@ -915,7 +921,7 @@ describe('ProjectEntityUpdateHandler', function() {
|
|||
|
||||
describe('creating a new doc', function() {
|
||||
beforeEach(function() {
|
||||
this.folder = { _id: folderId, docs: [] }
|
||||
this.folder = { _id: folderId, docs: [], fileRefs: [] }
|
||||
this.newDoc = { _id: docId }
|
||||
this.ProjectLocator.findElement.yields(null, this.folder)
|
||||
this.ProjectEntityUpdateHandler.addDocWithRanges = {
|
||||
|
@ -963,7 +969,7 @@ describe('ProjectEntityUpdateHandler', function() {
|
|||
|
||||
describe('upserting a new doc with an invalid name', function() {
|
||||
beforeEach(function() {
|
||||
this.folder = { _id: folderId, docs: [] }
|
||||
this.folder = { _id: folderId, docs: [], fileRefs: [] }
|
||||
this.newDoc = { _id: docId }
|
||||
this.ProjectLocator.findElement.yields(null, this.folder)
|
||||
this.ProjectEntityUpdateHandler.addDocWithRanges = {
|
||||
|
@ -986,6 +992,101 @@ describe('ProjectEntityUpdateHandler', function() {
|
|||
this.callback.calledWithMatch(errorMatcher).should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('upserting a doc on top of a file', function() {
|
||||
beforeEach(function() {
|
||||
this.newProject = {
|
||||
name: 'new project',
|
||||
overleaf: { history: { id: projectHistoryId } }
|
||||
}
|
||||
this.existingFile = { _id: fileId, name: 'foo.tex', rev: 12 }
|
||||
this.folder = { _id: folderId, docs: [], fileRefs: [this.existingFile] }
|
||||
this.newDoc = { _id: docId }
|
||||
this.docLines = ['line one', 'line two']
|
||||
this.path = 'path/to/file'
|
||||
this.ProjectLocator.findElement.yields(null, this.folder, {
|
||||
fileSystem: this.path
|
||||
})
|
||||
this.DocstoreManager.updateDoc.yields()
|
||||
this.ProjectEntityMongoUpdateHandler.replaceFileWithDoc.yields(
|
||||
null,
|
||||
this.newProject
|
||||
)
|
||||
this.TpdsUpdateSender.addDoc.yields()
|
||||
|
||||
this.ProjectEntityUpdateHandler.upsertDoc(
|
||||
projectId,
|
||||
folderId,
|
||||
'foo.tex',
|
||||
this.docLines,
|
||||
this.source,
|
||||
userId,
|
||||
this.callback
|
||||
)
|
||||
})
|
||||
|
||||
it('notifies docstore of the new doc', function() {
|
||||
expect(this.DocstoreManager.updateDoc).to.have.been.calledWith(
|
||||
projectId,
|
||||
this.newDoc._id,
|
||||
this.docLines
|
||||
)
|
||||
})
|
||||
|
||||
it('adds the new doc and removes the file in one go', function() {
|
||||
expect(
|
||||
this.ProjectEntityMongoUpdateHandler.replaceFileWithDoc
|
||||
).to.have.been.calledWithMatch(
|
||||
projectId,
|
||||
this.existingFile._id,
|
||||
this.newDoc
|
||||
)
|
||||
})
|
||||
|
||||
it('sends the doc to TPDS', function() {
|
||||
expect(this.TpdsUpdateSender.addDoc).to.have.been.calledWith({
|
||||
project_id: projectId,
|
||||
doc_id: this.newDoc._id,
|
||||
path: this.path,
|
||||
project_name: this.newProject.name,
|
||||
rev: this.existingFile.rev + 1
|
||||
})
|
||||
})
|
||||
|
||||
it('sends the updates to the doc updater', function() {
|
||||
const oldFiles = [
|
||||
{
|
||||
file: this.existingFile,
|
||||
path: `${this.path}/foo.tex`
|
||||
}
|
||||
]
|
||||
const newDocs = [
|
||||
{
|
||||
doc: sinon.match(this.newDoc),
|
||||
path: this.path,
|
||||
docLines: this.docLines.join('\n')
|
||||
}
|
||||
]
|
||||
expect(
|
||||
this.DocumentUpdaterHandler.updateProjectStructure
|
||||
).to.have.been.calledWith(projectId, projectHistoryId, userId, {
|
||||
oldFiles,
|
||||
newDocs,
|
||||
newProject: this.newProject
|
||||
})
|
||||
})
|
||||
|
||||
it('should notify everyone of the file deletion', function() {
|
||||
expect(
|
||||
this.EditorRealTimeController.emitToRoom
|
||||
).to.have.been.calledWith(
|
||||
projectId,
|
||||
'removeEntity',
|
||||
this.existingFile._id,
|
||||
'convertFileToDoc'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('upsertFile', function() {
|
||||
|
|
Loading…
Add table
Reference in a new issue