Fix restore of docs from old deleted docs

This commit is contained in:
James Allen 2018-03-16 12:21:07 +00:00
parent b4fa47d664
commit 1e8439a2c6
16 changed files with 242 additions and 243 deletions

View file

@ -180,7 +180,7 @@ clean_css:
rm -f public/stylesheets/*.css* rm -f public/stylesheets/*.css*
clean_ci: clean_ci:
docker-compose down docker-compose down -v
test: test_unit test_frontend test_acceptance test: test_unit test_frontend test_acceptance
@ -221,7 +221,7 @@ test_acceptance_module: $(MODULE_MAKEFILES)
fi fi
test_clean: test_clean:
docker-compose ${DOCKER_COMPOSE_FLAGS} down docker-compose ${DOCKER_COMPOSE_FLAGS} down -v
ci: ci:
MOCHA_ARGS="--reporter tap" \ MOCHA_ARGS="--reporter tap" \

View file

@ -57,26 +57,9 @@ module.exports = EditorHttpController =
privilegeLevel privilegeLevel
) )
restoreDoc: (req, res, next) ->
project_id = req.params.Project_id
doc_id = req.params.doc_id
name = req.body.name
if !name?
return res.sendStatus 400 # Malformed request
logger.log project_id: project_id, doc_id: doc_id, "restoring doc"
ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, name, (err, doc, folder_id) =>
return next(error) if error?
EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc)
res.json {
doc_id: doc._id
}
_nameIsAcceptableLength: (name)-> _nameIsAcceptableLength: (name)->
return name? and name.length < 150 and name.length != 0 return name? and name.length < 150 and name.length != 0
addDoc: (req, res, next) -> addDoc: (req, res, next) ->
project_id = req.params.Project_id project_id = req.params.Project_id
name = req.body.name name = req.body.name

View file

@ -14,8 +14,6 @@ module.exports =
webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc webRouter.delete '/project/:Project_id/doc/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteDoc
webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder webRouter.delete '/project/:Project_id/folder/:entity_id', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.deleteFolder
webRouter.post '/project/:Project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, EditorHttpController.restoreDoc
# Called by the real-time API to load up the current project state. # Called by the real-time API to load up the current project state.
# This is a post request because it's more than just a getting of data. We take actions # This is a post request because it's more than just a getting of data. We take actions
# whenever a user joins a project, like updating the deleted status. # whenever a user joins a project, like updating the deleted status.

View file

@ -73,13 +73,28 @@ module.exports = HistoryController =
return next(error) if error? return next(error) if error?
res.sendStatus 204 res.sendStatus 204
restoreFile: (req, res, next) -> restoreFileFromV2: (req, res, next) ->
{project_id} = req.params {project_id} = req.params
{version, pathname} = req.body {version, pathname} = req.body
user_id = AuthenticationController.getLoggedInUserId req user_id = AuthenticationController.getLoggedInUserId req
RestoreManager.restoreFile user_id, project_id, version, pathname, (error, entity) -> logger.log {project_id, version, pathname}, "restoring file from v2"
RestoreManager.restoreFileFromV2 user_id, project_id, version, pathname, (error, entity) ->
return next(error) if error? return next(error) if error?
res.json { res.json {
type: entity.type, type: entity.type,
id: entity._id id: entity._id
} }
restoreDocFromDeletedDoc: (req, res, next) ->
{project_id, doc_id} = req.params
{name} = req.body
user_id = AuthenticationController.getLoggedInUserId(req)
if !name?
return res.sendStatus 400 # Malformed request
logger.log {project_id, doc_id, user_id}, "restoring doc from v1 deleted doc"
RestoreManager.restoreDocFromDeletedDoc user_id, project_id, doc_id, name, (err, doc) =>
return next(error) if error?
res.json {
doc_id: doc._id
}

View file

@ -2,12 +2,23 @@ Settings = require 'settings-sharelatex'
Path = require 'path' Path = require 'path'
FileWriter = require '../../infrastructure/FileWriter' FileWriter = require '../../infrastructure/FileWriter'
FileSystemImportManager = require '../Uploads/FileSystemImportManager' FileSystemImportManager = require '../Uploads/FileSystemImportManager'
ProjectEntityHandler = require '../Project/ProjectEntityHandler'
ProjectLocator = require '../Project/ProjectLocator' ProjectLocator = require '../Project/ProjectLocator'
EditorController = require '../Editor/EditorController'
Errors = require '../Errors/Errors' Errors = require '../Errors/Errors'
moment = require 'moment' moment = require 'moment'
module.exports = RestoreManager = module.exports = RestoreManager =
restoreFile: (user_id, project_id, version, pathname, callback = (error, entity) ->) -> restoreDocFromDeletedDoc: (user_id, project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
# getDoc will return the deleted doc's lines, but we don't actually remove
# the deleted doc, just create a new one from its lines.
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
return callback(error) if error?
addDocWithName = (name, callback) ->
EditorController.addDoc project_id, null, name, lines, 'restore', user_id, callback
RestoreManager._addEntityWithUniqueName addDocWithName, name, callback
restoreFileFromV2: (user_id, project_id, version, pathname, callback = (error, entity) ->) ->
RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) -> RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) ->
return callback(error) if error? return callback(error) if error?
basename = Path.basename(pathname) basename = Path.basename(pathname)
@ -16,7 +27,9 @@ module.exports = RestoreManager =
dirname = '' dirname = ''
RestoreManager._findFolderOrRootFolderId project_id, dirname, (error, parent_folder_id) -> RestoreManager._findFolderOrRootFolderId project_id, dirname, (error, parent_folder_id) ->
return callback(error) if error? return callback(error) if error?
RestoreManager._addEntityWithUniqueName user_id, project_id, parent_folder_id, basename, fsPath, callback addEntityWithName = (name, callback) ->
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, name, fsPath, false, callback
RestoreManager._addEntityWithUniqueName addEntityWithName, basename, callback
_findFolderOrRootFolderId: (project_id, dirname, callback = (error, folder_id) ->) -> _findFolderOrRootFolderId: (project_id, dirname, callback = (error, folder_id) ->) ->
# We're going to try to recover the file into the folder it was in previously, # We're going to try to recover the file into the folder it was in previously,
@ -30,8 +43,8 @@ module.exports = RestoreManager =
else else
return callback(null, null) return callback(null, null)
_addEntityWithUniqueName: (user_id, project_id, parent_folder_id, basename, fsPath, callback = (error) ->) -> _addEntityWithUniqueName: (addEntityWithName, basename, callback = (error) ->) ->
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, (error, entity) -> addEntityWithName basename, (error, entity) ->
if error? if error?
if error instanceof Errors.InvalidNameError if error instanceof Errors.InvalidNameError
# likely a duplicate name, so try with a prefix # likely a duplicate name, so try with a prefix
@ -42,7 +55,7 @@ module.exports = RestoreManager =
basename = "#{basename} (Restored on #{date})" basename = "#{basename} (Restored on #{date})"
if extension != '' if extension != ''
basename = "#{basename}#{extension}" basename = "#{basename}#{extension}"
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, callback addEntityWithName basename, callback
else else
callback(error) callback(error)
else else

View file

@ -108,15 +108,6 @@ module.exports = ProjectEntityUpdateHandler = self =
logger.log project_id: project_id, "removing root doc" logger.log project_id: project_id, "removing root doc"
Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback
restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
if not SafePath.isCleanFilename name
return callback new Errors.InvalidNameError("invalid element name")
# getDoc will return the deleted doc's lines, but we don't actually remove
# the deleted doc, just create a new one from its lines.
ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
return callback(error) if error?
self.addDoc project_id, null, name, lines, callback
addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=> addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) -> self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
return callback(error) if error? return callback(error) if error?

View file

@ -10,18 +10,25 @@ module.exports = FileWriter =
callback = _.once(callback) callback = _.once(callback)
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}" fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
writeStream = fs.createWriteStream(fsPath) stream.pause()
stream.pipe(writeStream) fs.mkdir Settings.path.dumpFolder, (error) ->
stream.resume()
if error? and error.code != 'EEXIST'
# Ignore error about already existing
return callback(error)
stream.on 'error', (err)-> writeStream = fs.createWriteStream(fsPath)
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream" stream.pipe(writeStream)
callback(err)
writeStream.on 'error', (err)-> stream.on 'error', (err)->
logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk" logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with incoming stream"
callback(err) callback(err)
writeStream.on "finish", -> writeStream.on 'error', (err)->
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished" logger.err {err, identifier, fsPath}, "[writeStreamToDisk] something went wrong with writing to disk"
callback null, fsPath callback(err)
writeStream.on "finish", ->
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
callback null, fsPath
writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) -> writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) ->
callback = _.once(callback) callback = _.once(callback)

View file

@ -201,9 +201,11 @@ module.exports = class Router
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFile webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects

View file

@ -1,4 +1,4 @@
.diff-panel.full-size(ng-if="history.isV2", ng-controller="HistoryDiffController") .diff-panel.full-size(ng-if="!history.isV2", ng-controller="HistoryDiffController")
.diff( .diff(
ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary" ng-if="!!history.diff && !history.diff.loading && !history.diff.deleted && !history.diff.error && !history.diff.binary"
) )

View file

@ -18,137 +18,175 @@ describe "RestoringFiles", ->
throw error if error? throw error if error?
done() done()
describe "restoring a text file", -> describe "restoring a deleted doc", ->
beforeEach (done) -> beforeEach (done) ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "foo.tex", "hello world, this is foo.tex!")
@owner.request {
method: "POST",
url: "/project/#{@project_id}/restore_file",
json:
pathname: "foo.tex"
version: 42
}, (error, response, body) ->
throw error if error?
expect(response.statusCode).to.equal 204
done()
it "should have created a doc", (done) ->
@owner.getProject @project_id, (error, project) => @owner.getProject @project_id, (error, project) =>
throw error if error? throw error if error?
doc = _.find project.rootFolder[0].docs, (doc) -> @doc = _.find project.rootFolder[0].docs, (doc) ->
doc.name == 'foo.tex' doc.name == 'main.tex'
doc = MockDocstoreApi.docs[@project_id][doc._id] @owner.request {
expect(doc.lines).to.deep.equal [ method: "DELETE",
"hello world, this is foo.tex!" url: "/project/#{@project_id}/doc/#{@doc._id}",
] }, (error, response, body) =>
done() throw error if error?
expect(response.statusCode).to.equal 204
@owner.request {
method: "POST",
url: "/project/#{@project_id}/doc/#{@doc._id}/restore"
json:
name: "main.tex"
}, (error, response, body) =>
throw error if error?
expect(response.statusCode).to.equal 200
expect(body.doc_id).to.exist
@restored_doc_id = body.doc_id
done()
describe "restoring a binary file", -> it 'should have restored the doc', (done) ->
beforeEach (done) ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "image.png", "Mock image.png content")
@owner.request {
method: "POST",
url: "/project/#{@project_id}/restore_file",
json:
pathname: "image.png"
version: 42
}, (error, response, body) ->
throw error if error?
expect(response.statusCode).to.equal 204
done()
it "should have created a file", (done) ->
@owner.getProject @project_id, (error, project) => @owner.getProject @project_id, (error, project) =>
throw error if error? throw error if error?
file = _.find project.rootFolder[0].fileRefs, (file) -> restored_doc = _.find project.rootFolder[0].docs, (doc) ->
file.name == 'image.png' doc.name == 'main.tex'
file = MockFileStoreApi.files[@project_id][file._id] expect(restored_doc._id.toString()).to.equal @restored_doc_id
expect(file.content).to.equal "Mock image.png content" expect(@doc._id).to.not.equal @restored_doc_id
# console.log @doc_id, @restored_doc_id, MockDocstoreApi.docs[@project_id]
expect(MockDocstoreApi.docs[@project_id][@restored_doc_id].lines).to.deep.equal(
MockDocstoreApi.docs[@project_id][@doc._id].lines
)
done() done()
describe "restoring to a directory that exists", -> describe "restoring from v2 history", ->
beforeEach (done) -> describe "restoring a text file", ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "foldername/foo2.tex", "hello world, this is foo-2.tex!") beforeEach (done) ->
@owner.request.post { MockProjectHistoryApi.addOldFile(@project_id, 42, "foo.tex", "hello world, this is foo.tex!")
uri: "project/#{@project_id}/folder",
json:
name: 'foldername'
}, (error, response, body) =>
throw error if error?
expect(response.statusCode).to.equal 200
@owner.request { @owner.request {
method: "POST", method: "POST",
url: "/project/#{@project_id}/restore_file", url: "/project/#{@project_id}/restore_file",
json: json:
pathname: "foldername/foo2.tex" pathname: "foo.tex"
version: 42 version: 42
}, (error, response, body) -> }, (error, response, body) ->
throw error if error? throw error if error?
expect(response.statusCode).to.equal 204 expect(response.statusCode).to.equal 200
done() done()
it "should have created the doc in the named folder", (done) -> it "should have created a doc", (done) ->
@owner.getProject @project_id, (error, project) => @owner.getProject @project_id, (error, project) =>
throw error if error? throw error if error?
folder = _.find project.rootFolder[0].folders, (folder) -> doc = _.find project.rootFolder[0].docs, (doc) ->
folder.name == 'foldername' doc.name == 'foo.tex'
doc = _.find folder.docs, (doc) -> doc = MockDocstoreApi.docs[@project_id][doc._id]
doc.name == 'foo2.tex' expect(doc.lines).to.deep.equal [
doc = MockDocstoreApi.docs[@project_id][doc._id] "hello world, this is foo.tex!"
expect(doc.lines).to.deep.equal [ ]
"hello world, this is foo-2.tex!" done()
]
done()
describe "restoring to a directory that no longer exists", -> describe "restoring a binary file", ->
beforeEach (done) -> beforeEach (done) ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "nothere/foo3.tex", "hello world, this is foo-3.tex!") MockProjectHistoryApi.addOldFile(@project_id, 42, "image.png", "Mock image.png content")
@owner.request { @owner.request {
method: "POST", method: "POST",
url: "/project/#{@project_id}/restore_file", url: "/project/#{@project_id}/restore_file",
json: json:
pathname: "nothere/foo3.tex" pathname: "image.png"
version: 42 version: 42
}, (error, response, body) -> }, (error, response, body) ->
throw error if error? throw error if error?
expect(response.statusCode).to.equal 204 expect(response.statusCode).to.equal 200
done() done()
it "should have created the doc in the root folder", (done) -> it "should have created a file", (done) ->
@owner.getProject @project_id, (error, project) => @owner.getProject @project_id, (error, project) =>
throw error if error? throw error if error?
doc = _.find project.rootFolder[0].docs, (doc) -> file = _.find project.rootFolder[0].fileRefs, (file) ->
doc.name == 'foo3.tex' file.name == 'image.png'
doc = MockDocstoreApi.docs[@project_id][doc._id] file = MockFileStoreApi.files[@project_id][file._id]
expect(doc.lines).to.deep.equal [ expect(file.content).to.equal "Mock image.png content"
"hello world, this is foo-3.tex!" done()
]
done()
describe "restoring to a filename that already exists", -> describe "restoring to a directory that exists", ->
it "should have created the file with a timestamp appended", -> beforeEach (done) ->
beforeEach (done) -> MockProjectHistoryApi.addOldFile(@project_id, 42, "foldername/foo2.tex", "hello world, this is foo-2.tex!")
MockProjectHistoryApi.addOldFile(@project_id, 42, "main.tex", "hello world, this is main.tex!") @owner.request.post {
@owner.request { uri: "project/#{@project_id}/folder",
method: "POST", json:
url: "/project/#{@project_id}/restore_file", name: 'foldername'
json: }, (error, response, body) =>
pathname: "main.tex" throw error if error?
version: 42 expect(response.statusCode).to.equal 200
}, (error, response, body) -> @owner.request {
throw error if error? method: "POST",
expect(response.statusCode).to.equal 204 url: "/project/#{@project_id}/restore_file",
done() json:
pathname: "foldername/foo2.tex"
version: 42
}, (error, response, body) ->
throw error if error?
expect(response.statusCode).to.equal 200
done()
it "should have created the doc in the root folder", (done) -> it "should have created the doc in the named folder", (done) ->
@owner.getProject @project_id, (error, project) => @owner.getProject @project_id, (error, project) =>
throw error if error? throw error if error?
doc = _.find project.rootFolder[0].docs, (doc) -> folder = _.find project.rootFolder[0].folders, (folder) ->
doc.name.match(/main \(Restored on/) folder.name == 'foldername'
expect(doc).to.exist doc = _.find folder.docs, (doc) ->
doc = MockDocstoreApi.docs[@project_id][doc._id] doc.name == 'foo2.tex'
expect(doc.lines).to.deep.equal [ doc = MockDocstoreApi.docs[@project_id][doc._id]
"hello world, this is main.tex!" expect(doc.lines).to.deep.equal [
] "hello world, this is foo-2.tex!"
done() ]
done()
describe "restoring to a directory that no longer exists", ->
beforeEach (done) ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "nothere/foo3.tex", "hello world, this is foo-3.tex!")
@owner.request {
method: "POST",
url: "/project/#{@project_id}/restore_file",
json:
pathname: "nothere/foo3.tex"
version: 42
}, (error, response, body) ->
throw error if error?
expect(response.statusCode).to.equal 200
done()
it "should have created the doc in the root folder", (done) ->
@owner.getProject @project_id, (error, project) =>
throw error if error?
doc = _.find project.rootFolder[0].docs, (doc) ->
doc.name == 'foo3.tex'
doc = MockDocstoreApi.docs[@project_id][doc._id]
expect(doc.lines).to.deep.equal [
"hello world, this is foo-3.tex!"
]
done()
describe "restoring to a filename that already exists", ->
it "should have created the file with a timestamp appended", ->
beforeEach (done) ->
MockProjectHistoryApi.addOldFile(@project_id, 42, "main.tex", "hello world, this is main.tex!")
@owner.request {
method: "POST",
url: "/project/#{@project_id}/restore_file",
json:
pathname: "main.tex"
version: 42
}, (error, response, body) ->
throw error if error?
expect(response.statusCode).to.equal 200
done()
it "should have created the doc in the root folder", (done) ->
@owner.getProject @project_id, (error, project) =>
throw error if error?
doc = _.find project.rootFolder[0].docs, (doc) ->
doc.name.match(/main \(Restored on/)
expect(doc).to.exist
doc = MockDocstoreApi.docs[@project_id][doc._id]
expect(doc.lines).to.deep.equal [
"hello world, this is main.tex!"
]
done()

View file

@ -36,7 +36,7 @@ module.exports = MockDocUpdaterApi =
res.sendStatus 200 res.sendStatus 200
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) => app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
res.send 204 res.sendStatus 204
app.listen 3003, (error) -> app.listen 3003, (error) ->
throw error if error? throw error if error?

View file

@ -23,15 +23,23 @@ module.exports = MockDocStoreApi =
docs = (doc for doc_id, doc of @docs[req.params.project_id]) docs = (doc for doc_id, doc of @docs[req.params.project_id])
res.send JSON.stringify docs res.send JSON.stringify docs
app.get "/project/:project_id/doc/:doc_id", (req, res, next) =>
{project_id, doc_id} = req.params
doc = @docs[project_id][doc_id]
if doc.deleted and !req.query.include_deleted
res.sendStatus 404
else
res.send JSON.stringify doc
app.delete "/project/:project_id/doc/:doc_id", (req, res, next) => app.delete "/project/:project_id/doc/:doc_id", (req, res, next) =>
{project_id, doc_id} = req.params {project_id, doc_id} = req.params
if !@docs[project_id]? if !@docs[project_id]?
res.send 404 res.sendStatus 404
else if !@docs[project_id][doc_id]? else if !@docs[project_id][doc_id]?
res.send 404 res.sendStatus 404
else else
@docs[project_id][doc_id] = undefined @docs[project_id][doc_id].deleted = true
res.send 204 res.sendStatus 204
app.listen 3016, (error) -> app.listen 3016, (error) ->
throw error if error? throw error if error?

View file

@ -164,35 +164,6 @@ describe "EditorHttpController", ->
it "should return false in the callback", -> it "should return false in the callback", ->
@callback.calledWith(null, null, false).should.equal true @callback.calledWith(null, null, false).should.equal true
describe "restoreDoc", ->
beforeEach ->
@req.params =
Project_id: @project_id
doc_id: @doc_id
@req.body =
name: @name = "doc-name"
@ProjectEntityUpdateHandler.restoreDoc = sinon.stub().callsArgWith(3, null,
@doc = { "mock": "doc", _id: @new_doc_id = "new-doc-id" }
@folder_id = "mock-folder-id"
)
@EditorRealTimeController.emitToRoom = sinon.stub()
@EditorHttpController.restoreDoc @req, @res
it "should restore the doc", ->
@ProjectEntityUpdateHandler.restoreDoc
.calledWith(@project_id, @doc_id, @name)
.should.equal true
it "should the real-time clients about the new doc", ->
@EditorRealTimeController.emitToRoom
.calledWith(@project_id, 'reciveNewDoc', @folder_id, @doc)
.should.equal true
it "should return the new doc id", ->
@res.json
.calledWith(doc_id: @new_doc_id)
.should.equal true
describe "addDoc", -> describe "addDoc", ->
beforeEach -> beforeEach ->
@doc = { "mock": "doc" } @doc = { "mock": "doc" }

View file

@ -22,6 +22,7 @@ describe "HistoryController", ->
"./HistoryManager": @HistoryManager = {} "./HistoryManager": @HistoryManager = {}
"../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {} "../Project/ProjectDetailsHandler": @ProjectDetailsHandler = {}
"../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {} "../Project/ProjectEntityUpdateHandler": @ProjectEntityUpdateHandler = {}
"./RestoreManager": @RestoreManager = {}
@settings.apis = @settings.apis =
trackchanges: trackchanges:
enabled: false enabled: false

View file

@ -15,6 +15,8 @@ describe 'RestoreManager', ->
'../Uploads/FileSystemImportManager': @FileSystemImportManager = {} '../Uploads/FileSystemImportManager': @FileSystemImportManager = {}
'../Project/ProjectLocator': @ProjectLocator = {} '../Project/ProjectLocator': @ProjectLocator = {}
'../Errors/Errors': Errors '../Errors/Errors': Errors
'../Project/ProjectEntityHandler': @ProjectEntityHandler = {}
'../Editor/EditorController': @EditorController = {}
'logger-sharelatex': @logger = {log: sinon.stub(), err: sinon.stub()} 'logger-sharelatex': @logger = {log: sinon.stub(), err: sinon.stub()}
@user_id = 'mock-user-id' @user_id = 'mock-user-id'
@project_id = 'mock-project-id' @project_id = 'mock-project-id'
@ -25,16 +27,16 @@ describe 'RestoreManager', ->
afterEach -> afterEach ->
tk.reset() tk.reset()
describe 'restoreFile', -> describe 'restoreFileFromV2', ->
beforeEach -> beforeEach ->
@RestoreManager._writeFileVersionToDisk = sinon.stub().yields(null, @fsPath = "/tmp/path/on/disk") @RestoreManager._writeFileVersionToDisk = sinon.stub().yields(null, @fsPath = "/tmp/path/on/disk")
@RestoreManager._findFolderOrRootFolderId = sinon.stub().yields(null, @folder_id = 'mock-folder-id') @RestoreManager._findFolderOrRootFolderId = sinon.stub().yields(null, @folder_id = 'mock-folder-id')
@RestoreManager._addEntityWithUniqueName = sinon.stub().yields(null, @entity = 'mock-entity') @FileSystemImportManager.addEntity = sinon.stub().yields(null, @entity = 'mock-entity')
describe "with a file not in a folder", -> describe "with a file not in a folder", ->
beforeEach -> beforeEach ->
@pathname = 'foo.tex' @pathname = 'foo.tex'
@RestoreManager.restoreFile @user_id, @project_id, @version, @pathname, @callback @RestoreManager.restoreFileFromV2 @user_id, @project_id, @version, @pathname, @callback
it 'should write the file version to disk', -> it 'should write the file version to disk', ->
@RestoreManager._writeFileVersionToDisk @RestoreManager._writeFileVersionToDisk
@ -47,18 +49,17 @@ describe 'RestoreManager', ->
.should.equal true .should.equal true
it 'should add the entity', -> it 'should add the entity', ->
@RestoreManager._addEntityWithUniqueName @FileSystemImportManager.addEntity
.calledWith(@user_id, @project_id, @folder_id, 'foo.tex', @fsPath) .calledWith(@user_id, @project_id, @folder_id, 'foo.tex', @fsPath, false)
.should.equal true .should.equal true
it 'should call the callback with the entity', -> it 'should call the callback with the entity', ->
@callback.calledWith(null, @entity).should.equal true @callback.calledWith(null, @entity).should.equal true
describe "with a file in a folder", -> describe "with a file in a folder", ->
beforeEach -> beforeEach ->
@pathname = 'foo/bar.tex' @pathname = 'foo/bar.tex'
@RestoreManager.restoreFile @user_id, @project_id, @version, @pathname, @callback @RestoreManager.restoreFileFromV2 @user_id, @project_id, @version, @pathname, @callback
it 'should find the folder', -> it 'should find the folder', ->
@RestoreManager._findFolderOrRootFolderId @RestoreManager._findFolderOrRootFolderId
@ -66,8 +67,8 @@ describe 'RestoreManager', ->
.should.equal true .should.equal true
it 'should add the entity by its basename', -> it 'should add the entity by its basename', ->
@RestoreManager._addEntityWithUniqueName @FileSystemImportManager.addEntity
.calledWith(@user_id, @project_id, @folder_id, 'bar.tex', @fsPath) .calledWith(@user_id, @project_id, @folder_id, 'bar.tex', @fsPath, false)
.should.equal true .should.equal true
describe '_findFolderOrRootFolderId', -> describe '_findFolderOrRootFolderId', ->
@ -94,40 +95,32 @@ describe 'RestoreManager', ->
describe '_addEntityWithUniqueName', -> describe '_addEntityWithUniqueName', ->
beforeEach -> beforeEach ->
@parent_folder_id = 'mock-folder-id' @addEntityWithName = sinon.stub()
@fsPath = '/tmp/file/on/disk'
@name = 'foo.tex' @name = 'foo.tex'
describe 'with a valid name', -> describe 'with a valid name', ->
beforeEach -> beforeEach ->
@FileSystemImportManager.addEntity = sinon.stub().yields(null, @entity = 'mock-entity') @addEntityWithName.yields(null, @entity = 'mock-entity')
@RestoreManager._addEntityWithUniqueName @user_id, @project_id, @parent_folder_id, @name, @fsPath, @callback @RestoreManager._addEntityWithUniqueName @addEntityWithName, @name, @callback
it 'should add the entity', -> it 'should add the entity', ->
@FileSystemImportManager.addEntity @addEntityWithName.calledWith(@name).should.equal true
.calledWith(@user_id, @project_id, @parent_folder_id, @name, @fsPath, false)
.should.equal true
it 'should return the entity', -> it 'should return the entity', ->
@callback.calledWith(null, @entity).should.equal true @callback.calledWith(null, @entity).should.equal true
describe "with an invalid name", -> describe "with an invalid name", ->
beforeEach -> beforeEach ->
@FileSystemImportManager.addEntity = sinon.stub() @addEntityWithName.onFirstCall().yields(new Errors.InvalidNameError())
@FileSystemImportManager.addEntity.onFirstCall().yields(new Errors.InvalidNameError()) @addEntityWithName.onSecondCall().yields(null, @entity = 'mock-entity')
@FileSystemImportManager.addEntity.onSecondCall().yields(null, @entity = 'mock-entity') @RestoreManager._addEntityWithUniqueName @addEntityWithName, @name, @callback
@RestoreManager._addEntityWithUniqueName @user_id, @project_id, @parent_folder_id, @name, @fsPath, @callback
it 'should try to add the entity with its original name', -> it 'should try to add the entity with its original name', ->
@FileSystemImportManager.addEntity @addEntityWithName.calledWith('foo.tex').should.equal true
.calledWith(@user_id, @project_id, @parent_folder_id, 'foo.tex', @fsPath, false)
.should.equal true
it 'should try to add the entity with a unique name', -> it 'should try to add the entity with a unique name', ->
date = moment(new Date()).format('Do MMM YY H:mm:ss') date = moment(new Date()).format('Do MMM YY H:mm:ss')
@FileSystemImportManager.addEntity @addEntityWithName.calledWith("foo (Restored on #{date}).tex").should.equal true
.calledWith(@user_id, @project_id, @parent_folder_id, "foo (Restored on #{date}).tex", @fsPath, false)
.should.equal true
it 'should return the entity', -> it 'should return the entity', ->
@callback.calledWith(null, @entity).should.equal true @callback.calledWith(null, @entity).should.equal true

View file

@ -246,27 +246,6 @@ describe 'ProjectEntityUpdateHandler', ->
.calledWith({_id : project_id}, {$unset : {rootDoc_id: true}}) .calledWith({_id : project_id}, {$unset : {rootDoc_id: true}})
.should.equal true .should.equal true
describe "restoreDoc", ->
beforeEach ->
@doc = { "mock": "doc" }
@ProjectEntityHandler.getDoc = sinon.stub().yields(null, @docLines)
@ProjectEntityUpdateHandler.addDoc = sinon.stub().yields(null, @doc, folder_id)
@ProjectEntityUpdateHandler.restoreDoc project_id, doc_id, @docName, @callback
it 'should get the doc lines', ->
@ProjectEntityHandler.getDoc
.calledWith(project_id, doc_id, include_deleted: true)
.should.equal true
it "should add a new doc with these doc lines", ->
@ProjectEntityUpdateHandler.addDoc
.calledWith(project_id, null, @docName, @docLines)
.should.equal true
it "should call the callback with the new folder and doc", ->
@callback.calledWith(null, @doc, folder_id).should.equal true
describe 'addDoc', -> describe 'addDoc', ->
beforeEach -> beforeEach ->
@path = "/path/to/doc" @path = "/path/to/doc"