diff --git a/services/web/app/coffee/Features/Documents/DocumentController.coffee b/services/web/app/coffee/Features/Documents/DocumentController.coffee index f32262a02c..b687e74966 100644 --- a/services/web/app/coffee/Features/Documents/DocumentController.coffee +++ b/services/web/app/coffee/Features/Documents/DocumentController.coffee @@ -38,9 +38,9 @@ module.exports = setDocument: (req, res, next = (error) ->) -> project_id = req.params.Project_id doc_id = req.params.doc_id - {lines, version, ranges} = req.body + {lines, version, ranges, lastUpdatedAt, lastUpdatedBy} = req.body logger.log doc_id:doc_id, project_id:project_id, "receiving set document request from api (docupdater)" - ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, lines, version, ranges, (error) -> + ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, (error) -> if error? logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding element for getDocument" return next(error) diff --git a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee index 9bdfa0e18d..5023094941 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee @@ -81,7 +81,7 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(error) if error? callback null, fileRef, folder_id - updateDocLines: (project_id, doc_id, lines, version, ranges, callback = (error) ->)-> + updateDocLines: (project_id, doc_id, lines, version, ranges, lastUpdatedAt, lastUpdatedBy, callback = (error) ->)-> ProjectGetter.getProjectWithoutDocLines project_id, (err, project)-> return callback(err) if err? return callback(new Errors.NotFoundError("project not found")) if !project? @@ -113,7 +113,7 @@ module.exports = ProjectEntityUpdateHandler = self = # path will only be present if the doc is not deleted if modified && !isDeletedDoc # Don't need to block for marking as updated - ProjectUpdateHandler.markAsUpdated project_id + ProjectUpdateHandler.markAsUpdated project_id, lastUpdatedAt, lastUpdatedBy TpdsUpdateSender.addDoc {project_id:project_id, path:path.fileSystem, doc_id:doc_id, project_name:project.name, rev:rev}, callback else callback() diff --git a/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee index ba3b3b6295..e0f2c5f7af 100644 --- a/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectUpdateHandler.coffee @@ -2,12 +2,18 @@ Project = require('../../models/Project').Project logger = require('logger-sharelatex') module.exports = - markAsUpdated : (project_id, callback)-> - conditions = {_id:project_id} - update = {lastUpdated:Date.now()} - Project.update conditions, update, {}, (err)-> - if callback? - callback() + markAsUpdated : (projectId, lastUpdatedAt, lastUpdatedBy, callback = () ->)-> + lastUpdatedAt ?= new Date() + + conditions = + _id: projectId + lastUpdated: { $lt: lastUpdatedAt } + + update = { + lastUpdated: lastUpdatedAt or (new Date()).getTime() + lastUpdatedBy: lastUpdatedBy + } + Project.update conditions, update, {}, callback markAsOpened : (project_id, callback)-> conditions = {_id:project_id} @@ -28,4 +34,4 @@ module.exports = update = {active:true} Project.update conditions, update, {}, (err)-> if callback? - callback() \ No newline at end of file + callback() diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee index a63aea3609..c5a75d3cd8 100644 --- a/services/web/app/coffee/models/Project.coffee +++ b/services/web/app/coffee/models/Project.coffee @@ -19,6 +19,7 @@ DeletedFileSchema = new Schema ProjectSchema = new Schema name : {type:String, default:'new project'} lastUpdated : {type:Date, default: () -> new Date()} + lastUpdatedBy : {type:ObjectId, ref: 'User'} lastOpened : {type:Date} active : { type: Boolean, default: true } owner_ref : {type:ObjectId, ref:'User'} diff --git a/services/web/test/unit/coffee/Documents/DocumentControllerTests.coffee b/services/web/test/unit/coffee/Documents/DocumentControllerTests.coffee index d9d2e542ef..08cc7f1f83 100644 --- a/services/web/test/unit/coffee/Documents/DocumentControllerTests.coffee +++ b/services/web/test/unit/coffee/Documents/DocumentControllerTests.coffee @@ -28,6 +28,8 @@ describe "DocumentController", -> @version = 42 @ranges = {"mock": "ranges"} @pathname = '/a/b/c/file.tex' + @lastUpdatedAt = (new Date()).getTime() + @lastUpdatedBy = 'fake-last-updater-id' @rev = 5 describe "getDocument", -> @@ -120,12 +122,21 @@ describe "DocumentController", -> lines: @doc_lines version: @version ranges: @ranges + lastUpdatedAt: @lastUpdatedAt + lastUpdatedBy: @lastUpdatedBy @DocumentController.setDocument(@req, @res, @next) it "should update the document in Mongo", -> - @ProjectEntityUpdateHandler.updateDocLines - .calledWith(@project_id, @doc_id, @doc_lines, @version, @ranges) - .should.equal true + sinon.assert.calledWith( + @ProjectEntityUpdateHandler.updateDocLines, + @project_id, + @doc_id, + @doc_lines, + @version, + @ranges, + @lastUpdatedAt, + @lastUpdatedBy + ) it "should return a successful response", -> @res.success.should.equal true diff --git a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee index 7accaa13e6..f83a6c2959 100644 --- a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee @@ -160,6 +160,8 @@ describe 'ProjectEntityUpdateHandler', -> } @version = 42 @ranges = {"mock":"ranges"} + @lastUpdatedAt = (new Date()).getTime() + @lastUpdatedBy = 'fake-last-updater-id' @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project) @ProjectLocator.findElement = sinon.stub().yields(null, @doc, {fileSystem: @path}) @TpdsUpdateSender.addDoc = sinon.stub().yields() @@ -169,7 +171,7 @@ describe 'ProjectEntityUpdateHandler', -> describe "when the doc has been modified", -> beforeEach -> @DocstoreManager.updateDoc = sinon.stub().yields(null, true, @rev = 5) - @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback + @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback it "should get the project without doc lines", -> @ProjectGetter.getProjectWithoutDocLines @@ -191,9 +193,12 @@ describe 'ProjectEntityUpdateHandler', -> .should.equal true it "should mark the project as updated", -> - @ProjectUpdater.markAsUpdated - .calledWith(project_id) - .should.equal true + sinon.assert.calledWith( + @ProjectUpdater.markAsUpdated, + project_id, + @lastUpdatedAt, + @lastUpdatedBy + ) it "should send the doc the to the TPDS", -> @TpdsUpdateSender.addDoc @@ -212,7 +217,7 @@ describe 'ProjectEntityUpdateHandler', -> describe "when the doc has not been modified", -> beforeEach -> @DocstoreManager.updateDoc = sinon.stub().yields(null, false, @rev = 5) - @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback + @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback it "should not mark the project as updated", -> @ProjectUpdater.markAsUpdated.called.should.equal false @@ -229,7 +234,7 @@ describe 'ProjectEntityUpdateHandler', -> @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields(null, @project) @ProjectLocator.findElement = sinon.stub().yields(new Errors.NotFoundError) @DocstoreManager.updateDoc = sinon.stub().yields() - @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback + @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback it "should update the doc in the docstore", -> @DocstoreManager.updateDoc @@ -248,7 +253,7 @@ describe 'ProjectEntityUpdateHandler', -> describe "when the doc is not related to the project", -> beforeEach -> @ProjectLocator.findElement = sinon.stub().yields() - @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback + @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback it "should log out the error", -> @logger.error @@ -266,7 +271,7 @@ describe 'ProjectEntityUpdateHandler', -> describe "when the project is not found", -> beforeEach -> @ProjectGetter.getProjectWithoutDocLines = sinon.stub().yields() - @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @callback + @ProjectEntityUpdateHandler.updateDocLines project_id, doc_id, @docLines, @version, @ranges, @lastUpdatedAt, @lastUpdatedBy, @callback it "should return a not found error", -> @callback.calledWith(new Errors.NotFoundError()).should.equal true diff --git a/services/web/test/unit/coffee/Project/ProjectUpdateHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectUpdateHandlerTests.coffee index a68a9be1f1..6f8408ce7f 100644 --- a/services/web/test/unit/coffee/Project/ProjectUpdateHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectUpdateHandlerTests.coffee @@ -6,6 +6,10 @@ SandboxedModule = require('sandboxed-module') describe 'ProjectUpdateHandler', -> + before -> + @fakeTime = new Date() + @clock = sinon.useFakeTimers(@fakeTime.getTime()) + beforeEach -> @ProjectModel = class Project @ProjectModel.update = sinon.stub().callsArg(3) @@ -13,15 +17,43 @@ describe 'ProjectUpdateHandler', -> '../../models/Project':{Project:@ProjectModel} 'logger-sharelatex' : { log: sinon.stub() } + after -> + @clock.restore() + describe 'marking a project as recently updated', -> + beforeEach -> + @project_id = "project_id" + @lastUpdatedAt = 987654321 + @lastUpdatedBy = 'fake-last-updater-id' + it 'should send an update to mongo', (done)-> - project_id = "project_id" - @handler.markAsUpdated project_id, (err)=> - args = @ProjectModel.update.args[0] - args[0]._id.should.equal project_id - date = args[1].lastUpdated+"" - now = Date.now()+"" - date.substring(0,5).should.equal now.substring(0,5) + @handler.markAsUpdated @project_id, @lastUpdatedAt, @lastUpdatedBy, (err) => + sinon.assert.calledWith( + @ProjectModel.update, + { + _id: @project_id, + lastUpdated: { $lt: @lastUpdatedAt } + }, + { + lastUpdated: @lastUpdatedAt, + lastUpdatedBy: @lastUpdatedBy + } + ) + done() + + it 'should set smart fallbacks', (done)-> + @handler.markAsUpdated @project_id, null, null, (err) => + sinon.assert.calledWithMatch( + @ProjectModel.update, + { + _id: @project_id, + lastUpdated: { $lt: @fakeTime } + }, + { + lastUpdated: @fakeTime + lastUpdatedBy: null + } + ) done() describe "markAsOpened", ->