diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 7e1eeee150..4933e06c3b 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -277,8 +277,16 @@ module.exports = ProjectController = project: (cb)-> ProjectGetter.getProject( project_id, - { name: 1, lastUpdated: 1, track_changes: 1, owner_ref: 1, brandVariationId: 1, overleaf: 1 }, - cb + { name: 1, lastUpdated: 1, track_changes: 1, owner_ref: 1, brandVariationId: 1, overleaf: 1, tokens: 1 }, + (err, project) -> + return cb(err) if err? + return cb(null, project) unless project.overleaf?.id? and project.tokens?.readAndWrite? and Settings.projectImportingCheckMaxCreateDelta? + createDelta = (new Date().getTime() - new Date(project._id.getTimestamp()).getTime()) / 1000 + return cb(null, project) unless createDelta < Settings.projectImportingCheckMaxCreateDelta + TokenAccessHandler.getV1DocInfo project.tokens.readAndWrite, null, (err, doc_info) -> + return next err if err? + project.exporting = doc_info.exporting + cb(null, project) ) user: (cb)-> if !user_id? @@ -327,6 +335,11 @@ module.exports = ProjectController = if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE return res.sendStatus 401 + if project.exporting + res.render 'project/importing', + bodyClasses: ["editor"] + return + if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt? allowedFreeTrial = !!subscription.freeTrial.allowed || true diff --git a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee index c7be6fbc6a..5985a42129 100644 --- a/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee +++ b/services/web/app/coffee/Features/TokenAccess/TokenAccessHandler.coffee @@ -126,12 +126,18 @@ module.exports = TokenAccessHandler = return callback(null, { exists: true exported: false + exporting: false }) unless Settings.apis?.v1? - UserGetter.getUser v2UserId, { overleaf: 1 }, (err, user) -> - return callback(err) if err? - v1UserId = user.overleaf?.id - V1Api.request { url: "/api/v1/sharelatex/users/#{v1UserId}/docs/#{token}/info" }, (err, response, body) -> + if v2UserId? + UserGetter.getUser v2UserId, { overleaf: 1 }, (err, user) -> + return callback(err) if err? + v1UserId = user.overleaf?.id + V1Api.request { url: "/api/v1/sharelatex/users/#{v1UserId}/docs/#{token}/info" }, (err, response, body) -> + return callback err if err? + callback null, body + else + V1Api.request { url: "/api/v1/sharelatex/docs/#{token}/info" }, (err, response, body) -> return callback err if err? callback null, body diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index f864535706..0599801602 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -172,7 +172,7 @@ module.exports = class Router webRouter.get '/Project/:Project_id', RateLimiterMiddlewear.rateLimit({ endpointName: "open-project" params: ["Project_id"] - maxRequests: 10 + maxRequests: 15 timeInterval: 60 }), AuthorizationMiddlewear.ensureUserCanReadProject, ProjectController.loadEditor webRouter.get '/Project/:Project_id/file/:File_id', AuthorizationMiddlewear.ensureUserCanReadProject, FileStoreController.getFile @@ -439,7 +439,7 @@ module.exports = class Router webRouter.get '/read/:read_only_token([a-z]+)', RateLimiterMiddlewear.rateLimit({ endpointName: 'read-only-token', - maxRequests: 10, + maxRequests: 15, timeInterval: 60 }), TokenAccessController.readOnlyToken @@ -447,7 +447,7 @@ module.exports = class Router webRouter.get '/:read_and_write_token([0-9]+[a-z]+)', RateLimiterMiddlewear.rateLimit({ endpointName: 'read-and-write-token', - maxRequests: 10, + maxRequests: 15, timeInterval: 60 }), TokenAccessController.readAndWriteToken diff --git a/services/web/app/views/project/importing.pug b/services/web/app/views/project/importing.pug new file mode 100644 index 0000000000..e9282fdee0 --- /dev/null +++ b/services/web/app/views/project/importing.pug @@ -0,0 +1,20 @@ +extends ../layout + +block vars + - var suppressNavbar = true + - var suppressFooter = true + - var suppressSystemMessages = true + - metadata.robotsNoindexNofollow = true + +block content + .editor(ng-controller="ImportingController").full-size + .loading-screen + .loading-screen-brand-container + .loading-screen-brand( + style="height: 20%;" + ng-style="{ 'height': state.load_progress + '%' }" + ) + h3.loading-screen-label #{translate("importing")} + span.loading-screen-ellip . + span.loading-screen-ellip . + span.loading-screen-ellip . diff --git a/services/web/public/src/main.js b/services/web/public/src/main.js index 523c90da6a..ee1bc3b165 100644 --- a/services/web/public/src/main.js +++ b/services/web/public/src/main.js @@ -37,6 +37,7 @@ define([ 'main/keys', 'main/cms/blog', 'main/cms/index', + 'main/importing', 'analytics/AbTestingManager', 'directives/asyncForm', 'directives/stopPropagation', diff --git a/services/web/public/src/main/importing.js b/services/web/public/src/main/importing.js new file mode 100644 index 0000000000..9ce3c8f893 --- /dev/null +++ b/services/web/public/src/main/importing.js @@ -0,0 +1,21 @@ +define(['base'], function(App) { + App.controller('ImportingController', function( + $interval, + $scope, + $timeout, + $window + ) { + $interval(function() { + $scope.state.load_progress += 5 + if ($scope.state.load_progress > 100) { + $scope.state.load_progress = 20 + } + }, 500) + $timeout(function() { + $window.location.reload() + }, 5000) + $scope.state = { + load_progress: 20 + } + }) +}) diff --git a/services/web/test/acceptance/coffee/TokenAccessTests.coffee b/services/web/test/acceptance/coffee/TokenAccessTests.coffee index ce1a00fd7c..c6fc1b0a0e 100644 --- a/services/web/test/acceptance/coffee/TokenAccessTests.coffee +++ b/services/web/test/acceptance/coffee/TokenAccessTests.coffee @@ -1,5 +1,6 @@ expect = require("chai").expect async = require("async") +MockV1Api = require "./helpers/MockV1Api" User = require "./helpers/User" request = require "./helpers/request" settings = require "settings-sharelatex" @@ -441,3 +442,48 @@ describe 'TokenAccess', -> '/sign_in_to_v1?return_to=/read/abcd' ) , done) + + describe 'importing v1 project', -> + before (done) -> + settings.projectImportingCheckMaxCreateDelta = 3600 + settings.overleaf = + host: 'http://localhost:5000' + @owner.createProject "token-rw-test#{Math.random()}", (err, project_id) => + return done(err) if err? + @project_id = project_id + @owner.makeTokenBased @project_id, (err) => + return done(err) if err? + db.projects.update {_id: ObjectId(project_id)}, $set: overleaf: id: 1234, (err) => + return done(err) if err? + @owner.getProject @project_id, (err, project) => + return done(err) if err? + @tokens = project.tokens + MockV1Api.setDocInfo @tokens.readAndWrite, exporting: true + MockV1Api.setDocInfo @tokens.readOnly, exporting: true + done() + + after -> + delete settings.projectImportingCheckMaxCreateDelta + delete settings.overleaf + + it 'should show importing page for read and write token', (done) -> + try_read_and_write_token_access(@owner, @tokens.readAndWrite, (response, body) => + expect(response.statusCode).to.equal 200 + expect(body).to.include('ImportingController') + , done) + + it 'should show importing page for read only token', (done) -> + try_read_only_token_access(@owner, @tokens.readOnly, (response, body) => + expect(response.statusCode).to.equal 200 + expect(body).to.include('ImportingController') + , done) + + describe 'when importing check not configured', -> + before -> + delete settings.projectImportingCheckMaxCreateDelta + + it 'should load editor', (done) -> + try_read_and_write_token_access(@owner, @tokens.readAndWrite, (response, body) => + expect(response.statusCode).to.equal 200 + expect(body).to.include('IdeController') + , done) diff --git a/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee index 45b3ead39f..1697405cc1 100644 --- a/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee @@ -42,6 +42,10 @@ module.exports = MockV1Api = setAffiliations: (affiliations) -> @affiliations = affiliations + doc_info: {} + + setDocInfo: (token, info) -> @doc_info[token] = info + run: () -> app.get "/api/v1/sharelatex/users/:v1_user_id/plan_code", (req, res, next) => user = @users[req.params.v1_user_id] @@ -136,16 +140,20 @@ module.exports = MockV1Api = else res.status(404).json {} - app.listen 5000, (error) -> - throw error if error? - .on "error", (error) -> - console.error "error starting MockV1Api:", error.message - process.exit(1) - app.get '/api/v1/sharelatex/docs/:token/is_published', (req, res, next) => res.json { allow: true } app.get '/api/v1/sharelatex/users/:user_id/docs/:token/info', (req, res, next) => res.json { exported: false } + app.get '/api/v1/sharelatex/docs/:token/info', (req, res, next) => + return res.json @doc_info[req.params.token] if @doc_info[req.params.token]? + res.json { exporting: false } + + app.listen 5000, (error) -> + throw error if error? + .on "error", (error) -> + console.error "error starting MockV1Api:", error.message + process.exit(1) + MockV1Api.run() diff --git a/services/web/test/unit/coffee/TokenAccess/TokenAccessHandlerTests.coffee b/services/web/test/unit/coffee/TokenAccess/TokenAccessHandlerTests.coffee index b75942e56e..c1a2fc9994 100644 --- a/services/web/test/unit/coffee/TokenAccess/TokenAccessHandlerTests.coffee +++ b/services/web/test/unit/coffee/TokenAccess/TokenAccessHandlerTests.coffee @@ -541,6 +541,7 @@ describe "TokenAccessHandler", -> expect(@callback.calledWith null, { exists: true exported: false + exporting: false }).to.equal true describe 'when v1 api is set', -> @@ -579,6 +580,18 @@ describe "TokenAccessHandler", -> expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/users/#{@v1UserId}/docs/#{@token}/info" }).to.equal true expect(@callback.calledWith null, 'mock-data').to.equal true + describe 'when user id arg is null', -> + beforeEach -> + @v2UserId = null + @UserGetter.getUser = sinon.stub() + @V1Api.request = sinon.stub().callsArgWith(1, null, null, 'mock-data') + @TokenAccessHandler.getV1DocInfo @token, @v2UserId, @callback + + it 'should get info without user', -> + expect(@UserGetter.getUser.called).to.equal false + expect(@V1Api.request.calledWith { url: "/api/v1/sharelatex/docs/#{@token}/info" }).to.equal true + expect(@callback.calledWith null, 'mock-data').to.equal true + describe 'on V1Api.request error', -> beforeEach -> @UserGetter.getUser = sinon.stub().yields(null, {