mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
First pass at restore end point
This commit is contained in:
parent
8ff1492962
commit
beee86f1ce
7 changed files with 127 additions and 7 deletions
|
@ -6,6 +6,7 @@ Errors = require "../Errors/Errors"
|
|||
HistoryManager = require "./HistoryManager"
|
||||
ProjectDetailsHandler = require "../Project/ProjectDetailsHandler"
|
||||
ProjectEntityUpdateHandler = require "../Project/ProjectEntityUpdateHandler"
|
||||
RestoreManager = require "./RestoreManager"
|
||||
|
||||
module.exports = HistoryController =
|
||||
selectHistoryApi: (req, res, next = (error) ->) ->
|
||||
|
@ -71,3 +72,11 @@ module.exports = HistoryController =
|
|||
return res.sendStatus(404) if error instanceof Errors.ProjectHistoryDisabledError
|
||||
return next(error) if error?
|
||||
res.sendStatus 204
|
||||
|
||||
restoreFile: (req, res, next) ->
|
||||
{project_id} = req.params
|
||||
{version, pathname} = req.body
|
||||
user_id = AuthenticationController.getLoggedInUserId req
|
||||
RestoreManager.restoreFile user_id, project_id, version, pathname, (error) ->
|
||||
return next(error) if error?
|
||||
res.send 204
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
Settings = require 'settings-sharelatex'
|
||||
Path = require 'path'
|
||||
FileWriter = require '../../infrastructure/FileWriter'
|
||||
FileSystemImportManager = require '../Uploads/FileSystemImportManager'
|
||||
ProjectLocator = require '../Project/ProjectLocator'
|
||||
|
||||
module.exports = RestoreManager =
|
||||
restoreFile: (user_id, project_id, version, pathname, callback = (error) ->) ->
|
||||
RestoreManager._writeFileVersionToDisk project_id, version, pathname, (error, fsPath) ->
|
||||
return callback(error) if error?
|
||||
basename = Path.basename(pathname)
|
||||
dirname = Path.dirname(pathname)
|
||||
if dirname == '.' # no directory
|
||||
dirname = ''
|
||||
ProjectLocator.findElementByPath {project_id, path: dirname}, (error, element, type) ->
|
||||
return callback(error) if error?
|
||||
# We're going to try to recover the file into the folder it was in previously,
|
||||
# but this is historical, so the folder may not exist anymore. Fallback to the
|
||||
# root folder if not (parent_folder_id == null will default to this)
|
||||
if type == 'folder' and element?
|
||||
parent_folder_id = element._id
|
||||
else
|
||||
parent_folder_id = null
|
||||
# TODO if we get a name conflict error from here, then retry with a timestamp appended
|
||||
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, basename, fsPath, false, callback
|
||||
|
||||
_writeFileVersionToDisk: (project_id, version, pathname, callback = (error, fsPath) ->) ->
|
||||
url = "#{Settings.apis.project_history.url}/project/#{project_id}/version/#{version}/#{pathname}"
|
||||
FileWriter.writeUrlToDisk project_id, url, callback
|
|
@ -3,8 +3,9 @@ logger = require 'logger-sharelatex'
|
|||
uuid = require 'uuid'
|
||||
_ = require 'underscore'
|
||||
Settings = require 'settings-sharelatex'
|
||||
request = require 'request'
|
||||
|
||||
module.exports =
|
||||
module.exports = FileWriter =
|
||||
writeStreamToDisk: (identifier, stream, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
||||
|
@ -21,3 +22,14 @@ module.exports =
|
|||
writeStream.on "finish", ->
|
||||
logger.log {identifier, fsPath}, "[writeStreamToDisk] write stream finished"
|
||||
callback null, fsPath
|
||||
|
||||
writeUrlToDisk: (identifier, url, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
stream = request.get(url)
|
||||
stream.on 'response', (response) ->
|
||||
if 200 <= response.statusCode < 300
|
||||
FileWriter.writeStreamToDisk identifier, stream, callback
|
||||
else
|
||||
err = new Error("bad response from url: #{response.statusCode}")
|
||||
logger.err {err, identifier, url}, err.message
|
||||
callback(err)
|
|
@ -201,6 +201,7 @@ module.exports = class Router
|
|||
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.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
|
||||
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
|
||||
|
||||
webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject
|
||||
|
|
|
@ -138,15 +138,19 @@ div#history(ng-show="ui.view == 'history'")
|
|||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right
|
||||
.toolbar-right(ng-if="!history.isV2")
|
||||
a.btn.btn-danger.btn-sm(
|
||||
href,
|
||||
ng-if="!history.isV2"
|
||||
ng-click="openRestoreDiffModal()"
|
||||
) #{translate("restore_to_before_these_changes")}
|
||||
.deleted-warning(
|
||||
ng-show="history.selection.docs[history.selection.pathname].deleted"
|
||||
) This file was deleted
|
||||
.toolbar-right(ng-if="history.isV2")
|
||||
button.btn.btn-danger.btn-sm(
|
||||
href,
|
||||
ng-if="history.selection.docs[history.selection.pathname].deleted"
|
||||
ng-click=""
|
||||
)
|
||||
i.fa.fa-fw.fa-step-backward
|
||||
| Restore this deleted file
|
||||
.diff-editor.hide-ace-cursor(
|
||||
ace-editor="history",
|
||||
theme="settings.theme",
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
async = require "async"
|
||||
expect = require("chai").expect
|
||||
|
||||
ProjectGetter = require "../../../app/js/Features/Project/ProjectGetter.js"
|
||||
|
||||
User = require "./helpers/User"
|
||||
MockProjectHistoryApi = require "./helpers/MockProjectHistoryApi"
|
||||
MockDocstoreApi = require "./helpers/MockDocstoreApi"
|
||||
|
||||
describe "RestoringFiles", ->
|
||||
before (done) ->
|
||||
@owner = new User()
|
||||
@owner.login (error) =>
|
||||
throw error if error?
|
||||
@owner.createProject "example-project", {template: "example"}, (error, @project_id) =>
|
||||
throw error if error?
|
||||
done()
|
||||
|
||||
describe "restoring a text file", ->
|
||||
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", ->
|
||||
@owner.getProject @project_id, (error, project) =>
|
||||
throw error if error?
|
||||
doc = _.find project.rootFolder[0].docs, (doc) ->
|
||||
doc.name == 'foo.tex'
|
||||
doc = MockDocstoreApi.docs[@project_id][doc._id]
|
||||
expect(doc.lines).to.deep.equal [
|
||||
"hello world, this is foo.tex!"
|
||||
]
|
||||
done()
|
||||
|
||||
describe "restoring a binary file", ->
|
||||
it "should have created a file"
|
||||
|
||||
describe "restoring to a directory that no longer exists", ->
|
||||
it "should have created the file in the root folder"
|
||||
|
||||
describe "restoring to a filename that already exists", ->
|
||||
it "should have created the file with a timestamp appended"
|
|
@ -4,10 +4,24 @@ app = express()
|
|||
module.exports = MockProjectHistoryApi =
|
||||
docs: {}
|
||||
|
||||
oldFiles: {}
|
||||
|
||||
addOldFile: (project_id, version, pathname, content) ->
|
||||
@oldFiles["#{project_id}:#{version}:#{pathname}"] = content
|
||||
|
||||
run: () ->
|
||||
app.post "/project", (req, res, next) =>
|
||||
res.json project: id: 1
|
||||
|
||||
app.get "/project/:project_id/version/:version/:pathname", (req, res, next) =>
|
||||
{project_id, version, pathname} = req.params
|
||||
key = "#{project_id}:#{version}:#{pathname}"
|
||||
console.log key, @oldFiles, @oldFiles[key]
|
||||
if @oldFiles[key]?
|
||||
res.send @oldFiles[key]
|
||||
else
|
||||
res.send 404
|
||||
|
||||
app.listen 3054, (error) ->
|
||||
throw error if error?
|
||||
.on "error", (error) ->
|
||||
|
|
Loading…
Reference in a new issue