mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Add HTTP end point for accepting changes
This commit is contained in:
parent
65f4360738
commit
be19532a1d
8 changed files with 187 additions and 7 deletions
|
@ -45,6 +45,7 @@ app.post '/project/:project_id/doc/:doc_id/flush', HttpController.flushDocIfLo
|
||||||
app.delete '/project/:project_id/doc/:doc_id', HttpController.flushAndDeleteDoc
|
app.delete '/project/:project_id/doc/:doc_id', HttpController.flushAndDeleteDoc
|
||||||
app.delete '/project/:project_id', HttpController.deleteProject
|
app.delete '/project/:project_id', HttpController.deleteProject
|
||||||
app.post '/project/:project_id/flush', HttpController.flushProject
|
app.post '/project/:project_id/flush', HttpController.flushProject
|
||||||
|
app.post '/project/:project_id/doc/:doc_id/change/:change_id/accept', HttpController.acceptChange
|
||||||
|
|
||||||
app.get '/total', (req, res)->
|
app.get '/total', (req, res)->
|
||||||
timer = new Metrics.Timer("http.allDocList")
|
timer = new Metrics.Timer("http.allDocList")
|
||||||
|
|
|
@ -5,6 +5,8 @@ logger = require "logger-sharelatex"
|
||||||
Metrics = require "./Metrics"
|
Metrics = require "./Metrics"
|
||||||
HistoryManager = require "./HistoryManager"
|
HistoryManager = require "./HistoryManager"
|
||||||
WebRedisManager = require "./WebRedisManager"
|
WebRedisManager = require "./WebRedisManager"
|
||||||
|
Errors = require "./Errors"
|
||||||
|
RangesManager = require "./RangesManager"
|
||||||
|
|
||||||
module.exports = DocumentManager =
|
module.exports = DocumentManager =
|
||||||
getDoc: (project_id, doc_id, _callback = (error, lines, version, alreadyLoaded) ->) ->
|
getDoc: (project_id, doc_id, _callback = (error, lines, version, alreadyLoaded) ->) ->
|
||||||
|
@ -83,7 +85,6 @@ module.exports = DocumentManager =
|
||||||
DocumentManager.flushAndDeleteDoc project_id, doc_id, (error) ->
|
DocumentManager.flushAndDeleteDoc project_id, doc_id, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null
|
callback null
|
||||||
|
|
||||||
|
|
||||||
flushDocIfLoaded: (project_id, doc_id, _callback = (error) ->) ->
|
flushDocIfLoaded: (project_id, doc_id, _callback = (error) ->) ->
|
||||||
timer = new Metrics.Timer("docManager.flushDocIfLoaded")
|
timer = new Metrics.Timer("docManager.flushDocIfLoaded")
|
||||||
|
@ -119,6 +120,22 @@ module.exports = DocumentManager =
|
||||||
RedisManager.removeDocFromMemory project_id, doc_id, (error) ->
|
RedisManager.removeDocFromMemory project_id, doc_id, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null
|
callback null
|
||||||
|
|
||||||
|
acceptChange: (project_id, doc_id, change_id, _callback = (error) ->) ->
|
||||||
|
timer = new Metrics.Timer("docManager.acceptChange")
|
||||||
|
callback = (args...) ->
|
||||||
|
timer.done()
|
||||||
|
_callback(args...)
|
||||||
|
|
||||||
|
DocumentManager.getDoc project_id, doc_id, (error, lines, version, ranges) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
if !lines? or !version?
|
||||||
|
return callback(new Errors.NotFoundError("document not found: #{doc_id}"))
|
||||||
|
RangesManager.acceptChange change_id, ranges, (error, new_ranges) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
RedisManager.updateDocument doc_id, lines, version, [], new_ranges, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback()
|
||||||
|
|
||||||
getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
|
getDocWithLock: (project_id, doc_id, callback = (error, lines, version) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
|
@ -139,3 +156,7 @@ module.exports = DocumentManager =
|
||||||
flushAndDeleteDocWithLock: (project_id, doc_id, callback = (error) ->) ->
|
flushAndDeleteDocWithLock: (project_id, doc_id, callback = (error) ->) ->
|
||||||
UpdateManager = require "./UpdateManager"
|
UpdateManager = require "./UpdateManager"
|
||||||
UpdateManager.lockUpdatesAndDo DocumentManager.flushAndDeleteDoc, project_id, doc_id, callback
|
UpdateManager.lockUpdatesAndDo DocumentManager.flushAndDeleteDoc, project_id, doc_id, callback
|
||||||
|
|
||||||
|
acceptChangeWithLock: (project_id, doc_id, change_id, callback = (error) ->) ->
|
||||||
|
UpdateManager = require "./UpdateManager"
|
||||||
|
UpdateManager.lockUpdatesAndDo DocumentManager.acceptChange, project_id, doc_id, change_id, callback
|
||||||
|
|
|
@ -97,4 +97,15 @@ module.exports = HttpController =
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
logger.log project_id: project_id, "deleted project via http"
|
logger.log project_id: project_id, "deleted project via http"
|
||||||
res.send 204 # No Content
|
res.send 204 # No Content
|
||||||
|
|
||||||
|
acceptChange: (req, res, next = (error) ->) ->
|
||||||
|
{project_id, doc_id, change_id} = req.params
|
||||||
|
logger.log {project_id, doc_id, change_id}, "accepting change via http"
|
||||||
|
timer = new Metrics.Timer("http.acceptChange")
|
||||||
|
DocumentManager.acceptChangeWithLock project_id, doc_id, change_id, (error) ->
|
||||||
|
timer.done()
|
||||||
|
return next(error) if error?
|
||||||
|
logger.log {project_id, doc_id, change_id}, "accepted change via http"
|
||||||
|
res.send 204 # No Content
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ logger = require "logger-sharelatex"
|
||||||
module.exports = RangesManager =
|
module.exports = RangesManager =
|
||||||
applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
|
applyUpdate: (project_id, doc_id, entries = {}, updates = [], callback = (error, new_entries) ->) ->
|
||||||
{changes, comments} = entries
|
{changes, comments} = entries
|
||||||
logger.log {changes, comments, updates}, "appliyng updates to ranges"
|
logger.log {changes, comments, updates}, "applying updates to ranges"
|
||||||
rangesTracker = new RangesTracker(changes, comments)
|
rangesTracker = new RangesTracker(changes, comments)
|
||||||
for update in updates
|
for update in updates
|
||||||
rangesTracker.track_changes = !!update.meta.tc
|
rangesTracker.track_changes = !!update.meta.tc
|
||||||
|
@ -12,7 +12,20 @@ module.exports = RangesManager =
|
||||||
rangesTracker.setIdSeed(update.meta.tc)
|
rangesTracker.setIdSeed(update.meta.tc)
|
||||||
for op in update.op
|
for op in update.op
|
||||||
rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
|
rangesTracker.applyOp(op, { user_id: update.meta?.user_id })
|
||||||
|
|
||||||
|
response = RangesManager._getRanges rangesTracker
|
||||||
|
logger.log {response}, "applied updates to ranges"
|
||||||
|
callback null, response
|
||||||
|
|
||||||
|
acceptChange: (change_id, ranges, callback = (error, ranges) ->) ->
|
||||||
|
{changes, comments} = ranges
|
||||||
|
logger.log {changes, comments, change_id}, "accepting change in ranges"
|
||||||
|
rangesTracker = new RangesTracker(changes, comments)
|
||||||
|
rangesTracker.removeChangeId(change_id)
|
||||||
|
response = RangesManager._getRanges(rangesTracker)
|
||||||
|
callback null, response
|
||||||
|
|
||||||
|
_getRanges: (rangesTracker) ->
|
||||||
# Return the minimal data structure needed, since most documents won't have any
|
# Return the minimal data structure needed, since most documents won't have any
|
||||||
# changes or comments
|
# changes or comments
|
||||||
response = {}
|
response = {}
|
||||||
|
@ -22,5 +35,4 @@ module.exports = RangesManager =
|
||||||
if rangesTracker.comments?.length > 0
|
if rangesTracker.comments?.length > 0
|
||||||
response ?= {}
|
response ?= {}
|
||||||
response.comments = rangesTracker.comments
|
response.comments = rangesTracker.comments
|
||||||
logger.log {response}, "applied updates to ranges"
|
return response
|
||||||
callback null, response
|
|
|
@ -1,6 +1,7 @@
|
||||||
sinon = require "sinon"
|
sinon = require "sinon"
|
||||||
chai = require("chai")
|
chai = require("chai")
|
||||||
chai.should()
|
chai.should()
|
||||||
|
expect = chai.expect
|
||||||
async = require "async"
|
async = require "async"
|
||||||
rclient = require("redis").createClient()
|
rclient = require("redis").createClient()
|
||||||
|
|
||||||
|
@ -182,3 +183,45 @@ describe "Ranges", ->
|
||||||
changes[0].op.should.deep.equal { i: "123", p: 1 }
|
changes[0].op.should.deep.equal { i: "123", p: 1 }
|
||||||
changes[1].op.should.deep.equal { i: "456", p: 5 }
|
changes[1].op.should.deep.equal { i: "456", p: 5 }
|
||||||
done()
|
done()
|
||||||
|
|
||||||
|
describe "accepting a change", ->
|
||||||
|
before (done) ->
|
||||||
|
@project_id = DocUpdaterClient.randomId()
|
||||||
|
@user_id = DocUpdaterClient.randomId()
|
||||||
|
@id_seed = "587357bd35e64f6157"
|
||||||
|
@doc = {
|
||||||
|
id: DocUpdaterClient.randomId()
|
||||||
|
lines: ["aaa"]
|
||||||
|
}
|
||||||
|
@update = {
|
||||||
|
doc: @doc.id
|
||||||
|
op: [{ i: "456", p: 1 }]
|
||||||
|
v: 0
|
||||||
|
meta: { user_id: @user_id, tc: @id_seed }
|
||||||
|
}
|
||||||
|
MockWebApi.insertDoc @project_id, @doc.id, {
|
||||||
|
lines: @doc.lines
|
||||||
|
version: 0
|
||||||
|
}
|
||||||
|
DocUpdaterClient.preloadDoc @project_id, @doc.id, (error) =>
|
||||||
|
throw error if error?
|
||||||
|
DocUpdaterClient.sendUpdate @project_id, @doc.id, @update, (error) =>
|
||||||
|
throw error if error?
|
||||||
|
setTimeout () =>
|
||||||
|
DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
|
||||||
|
throw error if error?
|
||||||
|
ranges = data.ranges
|
||||||
|
change = ranges.changes[0]
|
||||||
|
change.op.should.deep.equal { i: "456", p: 1 }
|
||||||
|
change.id.should.equal @id_seed + "000001"
|
||||||
|
change.metadata.user_id.should.equal @user_id
|
||||||
|
done()
|
||||||
|
, 200
|
||||||
|
|
||||||
|
it "should remove the change after accepting", (done) ->
|
||||||
|
DocUpdaterClient.acceptChange @project_id, @doc.id, @id_seed + "000001", (error) =>
|
||||||
|
throw error if error?
|
||||||
|
DocUpdaterClient.getDoc @project_id, @doc.id, (error, res, data) =>
|
||||||
|
throw error if error?
|
||||||
|
expect(data.ranges.changes).to.be.undefined
|
||||||
|
done()
|
|
@ -72,3 +72,6 @@ module.exports = DocUpdaterClient =
|
||||||
|
|
||||||
deleteProject: (project_id, callback = () ->) ->
|
deleteProject: (project_id, callback = () ->) ->
|
||||||
request.del "http://localhost:3003/project/#{project_id}", callback
|
request.del "http://localhost:3003/project/#{project_id}", callback
|
||||||
|
|
||||||
|
acceptChange: (project_id, doc_id, change_id, callback = () ->) ->
|
||||||
|
request.post "http://localhost:3003/project/#{project_id}/doc/#{doc_id}/change/#{change_id}/accept", callback
|
|
@ -3,6 +3,7 @@ chai = require('chai')
|
||||||
should = chai.should()
|
should = chai.should()
|
||||||
modulePath = "../../../../app/js/DocumentManager.js"
|
modulePath = "../../../../app/js/DocumentManager.js"
|
||||||
SandboxedModule = require('sandboxed-module')
|
SandboxedModule = require('sandboxed-module')
|
||||||
|
Errors = require "../../../../app/js/Errors"
|
||||||
|
|
||||||
describe "DocumentManager", ->
|
describe "DocumentManager", ->
|
||||||
beforeEach ->
|
beforeEach ->
|
||||||
|
@ -18,6 +19,7 @@ describe "DocumentManager", ->
|
||||||
"./WebRedisManager": @WebRedisManager = {}
|
"./WebRedisManager": @WebRedisManager = {}
|
||||||
"./DiffCodec": @DiffCodec = {}
|
"./DiffCodec": @DiffCodec = {}
|
||||||
"./UpdateManager": @UpdateManager = {}
|
"./UpdateManager": @UpdateManager = {}
|
||||||
|
"./RangesManager": @RangesManager = {}
|
||||||
@project_id = "project-id-123"
|
@project_id = "project-id-123"
|
||||||
@doc_id = "doc-id-123"
|
@doc_id = "doc-id-123"
|
||||||
@callback = sinon.stub()
|
@callback = sinon.stub()
|
||||||
|
@ -259,4 +261,50 @@ describe "DocumentManager", ->
|
||||||
@callback.calledWith(new Error("No lines were passed to setDoc"))
|
@callback.calledWith(new Error("No lines were passed to setDoc"))
|
||||||
|
|
||||||
it "should not try to get the doc lines", ->
|
it "should not try to get the doc lines", ->
|
||||||
@DocumentManager.getDoc.called.should.equal false
|
@DocumentManager.getDoc.called.should.equal false
|
||||||
|
|
||||||
|
describe "acceptChanges", ->
|
||||||
|
beforeEach ->
|
||||||
|
@change_id = "mock-change-id"
|
||||||
|
@version = 34
|
||||||
|
@lines = ["original", "lines"]
|
||||||
|
@ranges = { entries: "mock", comments: "mock" }
|
||||||
|
@updated_ranges = { entries: "updated", comments: "updated" }
|
||||||
|
@DocumentManager.getDoc = sinon.stub().yields(null, @lines, @version, @ranges)
|
||||||
|
@RangesManager.acceptChange = sinon.stub().yields(null, @updated_ranges)
|
||||||
|
@RedisManager.updateDocument = sinon.stub().yields()
|
||||||
|
|
||||||
|
describe "successfully", ->
|
||||||
|
beforeEach ->
|
||||||
|
@DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
|
||||||
|
|
||||||
|
it "should get the document's current ranges", ->
|
||||||
|
@DocumentManager.getDoc
|
||||||
|
.calledWith(@project_id, @doc_id)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should apply the accept change to the ranges", ->
|
||||||
|
@RangesManager.acceptChange
|
||||||
|
.calledWith(@change_id, @ranges)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should save the updated ranges", ->
|
||||||
|
@RedisManager.updateDocument
|
||||||
|
.calledWith(@doc_id, @lines, @version, [], @updated_ranges)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should call the callback", ->
|
||||||
|
@callback.called.should.equal true
|
||||||
|
|
||||||
|
describe "when the doc is not found", ->
|
||||||
|
beforeEach ->
|
||||||
|
@DocumentManager.getDoc = sinon.stub().yields(null, null, null, null)
|
||||||
|
@DocumentManager.acceptChange @project_id, @doc_id, @change_id, @callback
|
||||||
|
|
||||||
|
it "should not save anything", ->
|
||||||
|
@RedisManager.updateDocument.called.should.equal false
|
||||||
|
|
||||||
|
it "should call the callback with a not found error", ->
|
||||||
|
error = new Errors.NotFoundError("document not found: #{@doc_id}")
|
||||||
|
@callback.calledWith(error).should.equal true
|
||||||
|
|
|
@ -332,4 +332,45 @@ describe "HttpController", ->
|
||||||
it "should call next with the error", ->
|
it "should call next with the error", ->
|
||||||
@next
|
@next
|
||||||
.calledWith(new Error("oops"))
|
.calledWith(new Error("oops"))
|
||||||
.should.equal true
|
.should.equal true
|
||||||
|
|
||||||
|
describe "acceptChange", ->
|
||||||
|
beforeEach ->
|
||||||
|
@req =
|
||||||
|
params:
|
||||||
|
project_id: @project_id
|
||||||
|
doc_id: @doc_id
|
||||||
|
change_id: @change_id = "mock-change-od-1"
|
||||||
|
|
||||||
|
describe "successfully", ->
|
||||||
|
beforeEach ->
|
||||||
|
@DocumentManager.acceptChangeWithLock = sinon.stub().callsArgWith(3)
|
||||||
|
@HttpController.acceptChange(@req, @res, @next)
|
||||||
|
|
||||||
|
it "should accept the change", ->
|
||||||
|
@DocumentManager.acceptChangeWithLock
|
||||||
|
.calledWith(@project_id, @doc_id, @change_id)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should return a successful No Content response", ->
|
||||||
|
@res.send
|
||||||
|
.calledWith(204)
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should log the request", ->
|
||||||
|
@logger.log
|
||||||
|
.calledWith({@project_id, @doc_id, @change_id}, "accepting change via http")
|
||||||
|
.should.equal true
|
||||||
|
|
||||||
|
it "should time the request", ->
|
||||||
|
@Metrics.Timer::done.called.should.equal true
|
||||||
|
|
||||||
|
describe "when an errors occurs", ->
|
||||||
|
beforeEach ->
|
||||||
|
@DocumentManager.acceptChangeWithLock = sinon.stub().callsArgWith(3, new Error("oops"))
|
||||||
|
@HttpController.acceptChange(@req, @res, @next)
|
||||||
|
|
||||||
|
it "should call next with the error", ->
|
||||||
|
@next
|
||||||
|
.calledWith(new Error("oops"))
|
||||||
|
.should.equal true
|
||||||
|
|
Loading…
Reference in a new issue