Merge pull request #1217 from sharelatex/ja-download-zip-version

Add option to download zip file of version in history

GitOrigin-RevId: 11ffb9a3305e1e5e0492fccf2be41be7beb67d68
This commit is contained in:
James Allen 2018-12-05 13:01:25 +01:00 committed by sharelatex
parent 28c934e8ff
commit e603afe106
7 changed files with 110 additions and 6 deletions

View file

@ -143,3 +143,37 @@ module.exports = HistoryController =
error = new Error("history api responded with non-success code: #{response.statusCode}")
logger.error err: error, "project-history api responded with non-success code: #{response.statusCode}"
callback(error)
downloadZipOfVersion: (req, res, next) ->
{project_id, version} = req.params
logger.log {project_id, version}, "got request for zip file at version"
ProjectDetailsHandler.getDetails project_id, (err, project) ->
return next(err) if err?
v1_id = project.overleaf?.history?.id
if !v1_id?
logger.err {project_id, version}, 'got request for zip version of non-v1 history project'
return res.sendStatus(402)
url = "#{settings.apis.v1_history.url}/projects/#{v1_id}/version/#{version}/zip"
logger.log {project_id, v1_id, version, url}, "proxying to history api"
getReq = request(
url: url
auth:
user: settings.apis.v1_history.user
pass: settings.apis.v1_history.pass
sendImmediately: true
)
getReq.on 'response', (response) ->
# pipe also proxies the headers, but we want to customize these ones
delete response.headers['content-disposition']
delete response.headers['content-type']
res.status response.statusCode
res.setContentDisposition(
'attachment',
{filename: "#{project.name} (Version #{version}).zip"}
)
res.contentType('application/zip')
getReq.pipe(res)
getReq.on "error", (err) ->
logger.error {err, project_id, v1_id, version}, "history API error"
next(error)

View file

@ -242,6 +242,7 @@ module.exports = class Router
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
webRouter.get "/project/:project_id/version/:version/zip", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.downloadZipOfVersion
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
webRouter.get "/project/:Project_id/labels", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.ensureProjectHistoryEnabled, HistoryController.getLabels

View file

@ -30,12 +30,13 @@
i.fa.fa-exchange
|  #{translate("compare_to_another_version")}
button.history-toolbar-btn-danger.pull-right(
tooltip-html="'<p>This feature is still in progress. Please use copy and paste to restore your files.</p><p>More restore options are coming soon.</p>'"
tooltip-placement="bottom"
a.history-toolbar-btn-danger.pull-right(
ng-hide="history.loadingFileTree || history.selection.updates.length == 0"
ng-href="/project/{{ project_id }}/version/{{ history.selection.updates[0].toV }}/zip"
target="_blank"
)
i.fa.fa-undo
| &nbsp;#{translate("restore_to_before_these_changes")}
i.fa.fa-download
| &nbsp;#{translate("download_project_at_this_version")}
.history-toolbar-entries-list(
ng-if="!history.error"

View file

@ -171,6 +171,8 @@ module.exports = settings =
pass: v1Api.pass
v1_history:
url: "http://#{process.env['V1_HISTORY_HOST'] or "localhost"}:3100/api"
user: 'staging'
pass: 'password'
templates:
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"

View file

@ -44,7 +44,6 @@
}
.history-toolbar-btn-danger {
.btn-danger;
opacity: 0.65;
}
.history-toolbar-entries-list {
flex: 0 0 @changesListWidth;

View file

@ -0,0 +1,47 @@
{expect} = require 'chai'
{db, ObjectId} = require("../../../app/js/infrastructure/mongojs")
MockV1HistoryApi = require './helpers/MockV1HistoryApi'
User = require './helpers/User'
describe 'History', ->
beforeEach (done) ->
@owner = new User()
@owner.login done
describe 'zip download of version', ->
it 'should stream the zip file of a version', (done) ->
@owner.createProject 'example-project', (error, @project_id) =>
return done(error) if error?
@v1_history_id = 42
db.projects.update {
_id: ObjectId(@project_id)
}, {
$set: {
'overleaf.history.id': @v1_history_id
}
}, (error) =>
return done(error) if error?
@owner.request "/project/#{@project_id}/version/42/zip", (error, response, body) =>
return done(error) if error?
expect(response.statusCode).to.equal 200
expect(response.headers['content-type']).to.equal 'application/zip'
expect(response.headers['content-disposition']).to.equal 'attachment; filename="example-project%20(Version%2042).zip"'
expect(body).to.equal "Mock zip for #{@v1_history_id} at version 42"
done()
it 'should return 402 for non-v2-history project', (done) ->
@owner.createProject 'non-v2-project', (error, @project_id) =>
return done(error) if error?
db.projects.update {
_id: ObjectId(@project_id)
}, {
$unset: {
'overleaf.history.id': true
}
}, (error) =>
return done(error) if error?
@owner.request "/project/#{@project_id}/version/42/zip", (error, response, body) =>
return done(error) if error?
expect(response.statusCode).to.equal 402
done()

View file

@ -0,0 +1,20 @@
_ = require 'lodash'
express = require 'express'
bodyParser = require "body-parser"
app = express()
{ObjectId} = require 'mongojs'
module.exports = MockV1HistoryApi =
run: () ->
app.get "/api/projects/:project_id/version/:version/zip", (req, res, next) =>
res.header('content-disposition', 'attachment; name=project.zip')
res.header('content-type', 'application/octet-stream')
res.send "Mock zip for #{req.params.project_id} at version #{req.params.version}"
app.listen 3100, (error) ->
throw error if error?
.on "error", (error) ->
console.error "error starting MockV1HistoryApi:", error.message
process.exit(1)
MockV1HistoryApi.run()