mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-11 21:35:32 +00:00
Merge pull request #135 from overleaf/em-remove-backwards-compat
Remove backwards-compatible project updates API
This commit is contained in:
commit
df7f534440
4 changed files with 199 additions and 420 deletions
|
@ -325,28 +325,13 @@ function deleteComment(req, res, next) {
|
|||
function updateProject(req, res, next) {
|
||||
const timer = new Metrics.Timer('http.updateProject')
|
||||
const projectId = req.params.project_id
|
||||
const {
|
||||
projectHistoryId,
|
||||
userId,
|
||||
docUpdates,
|
||||
fileUpdates,
|
||||
updates,
|
||||
version
|
||||
} = req.body
|
||||
logger.log(
|
||||
{ projectId, updates, docUpdates, fileUpdates, version },
|
||||
'updating project via http'
|
||||
)
|
||||
const allUpdates = _mergeUpdates(
|
||||
docUpdates || [],
|
||||
fileUpdates || [],
|
||||
updates || []
|
||||
)
|
||||
const { projectHistoryId, userId, updates = [], version } = req.body
|
||||
logger.log({ projectId, updates, version }, 'updating project via http')
|
||||
ProjectManager.updateProjectWithLocks(
|
||||
projectId,
|
||||
projectHistoryId,
|
||||
userId,
|
||||
allUpdates,
|
||||
updates,
|
||||
version,
|
||||
(error) => {
|
||||
timer.done()
|
||||
|
@ -416,23 +401,3 @@ function flushQueuedProjects(req, res, next) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge updates from the previous project update interface (docUpdates +
|
||||
* fileUpdates) and the new update interface (updates).
|
||||
*/
|
||||
function _mergeUpdates(docUpdates, fileUpdates, updates) {
|
||||
const mergedUpdates = []
|
||||
for (const update of docUpdates) {
|
||||
const type = update.docLines != null ? 'add-doc' : 'rename-doc'
|
||||
mergedUpdates.push({ type, ...update })
|
||||
}
|
||||
for (const update of fileUpdates) {
|
||||
const type = update.url != null ? 'add-file' : 'rename-file'
|
||||
mergedUpdates.push({ type, ...update })
|
||||
}
|
||||
for (const update of updates) {
|
||||
mergedUpdates.push(update)
|
||||
}
|
||||
return mergedUpdates
|
||||
}
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
handle-callback-err,
|
||||
no-return-assign,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const sinon = require('sinon')
|
||||
const chai = require('chai')
|
||||
chai.should()
|
||||
const Settings = require('settings-sharelatex')
|
||||
const rclient_project_history = require('redis-sharelatex').createClient(
|
||||
const rclientProjectHistory = require('redis-sharelatex').createClient(
|
||||
Settings.redis.project_history
|
||||
)
|
||||
const ProjectHistoryKeys = Settings.redis.project_history.key_schema
|
||||
|
@ -28,46 +15,46 @@ const DocUpdaterApp = require('./helpers/DocUpdaterApp')
|
|||
describe("Applying updates to a project's structure", function () {
|
||||
before(function () {
|
||||
this.user_id = 'user-id-123'
|
||||
return (this.version = 1234)
|
||||
this.version = 1234
|
||||
})
|
||||
|
||||
describe('renaming a file', function () {
|
||||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.fileUpdate = {
|
||||
type: 'rename-file',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-path',
|
||||
newPathname: '/new-file-path'
|
||||
}
|
||||
this.fileUpdates = [this.fileUpdate]
|
||||
return DocUpdaterApp.ensureRunning((error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
this.updates = [this.fileUpdate]
|
||||
DocUpdaterApp.ensureRunning((error) => {
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return DocUpdaterClient.sendProjectUpdate(
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
[],
|
||||
this.fileUpdates,
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
return it('should push the applied file renames to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the applied file renames to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
const update = JSON.parse(updates[0])
|
||||
|
@ -78,21 +65,21 @@ describe("Applying updates to a project's structure", function () {
|
|||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.0`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
describe('renaming a document', function () {
|
||||
before(function () {
|
||||
this.docUpdate = {
|
||||
this.update = {
|
||||
type: 'rename-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/doc-path',
|
||||
newPathname: '/new-doc-path'
|
||||
}
|
||||
return (this.docUpdates = [this.docUpdate])
|
||||
this.updates = [this.update]
|
||||
})
|
||||
|
||||
describe('when the document is not loaded', function () {
|
||||
|
@ -101,112 +88,108 @@ describe("Applying updates to a project's structure", function () {
|
|||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
[],
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
const update = JSON.parse(updates[0])
|
||||
update.doc.should.equal(this.docUpdate.id)
|
||||
update.doc.should.equal(this.update.id)
|
||||
update.pathname.should.equal('/doc-path')
|
||||
update.new_pathname.should.equal('/new-doc-path')
|
||||
update.meta.user_id.should.equal(this.user_id)
|
||||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.0`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
return describe('when the document is loaded', function () {
|
||||
describe('when the document is loaded', function () {
|
||||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
MockWebApi.insertDoc(this.project_id, this.docUpdate.id, {})
|
||||
MockWebApi.insertDoc(this.project_id, this.update.id, {})
|
||||
DocUpdaterClient.preloadDoc(
|
||||
this.project_id,
|
||||
this.docUpdate.id,
|
||||
this.update.id,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
sinon.spy(MockWebApi, 'getDocument')
|
||||
return DocUpdaterClient.sendProjectUpdate(
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
[],
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
after(function () {
|
||||
return MockWebApi.getDocument.restore()
|
||||
MockWebApi.getDocument.restore()
|
||||
})
|
||||
|
||||
it('should update the doc', function (done) {
|
||||
DocUpdaterClient.getDoc(
|
||||
this.project_id,
|
||||
this.docUpdate.id,
|
||||
this.update.id,
|
||||
(error, res, doc) => {
|
||||
doc.pathname.should.equal(this.docUpdate.newPathname)
|
||||
return done()
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
doc.pathname.should.equal(this.update.newPathname)
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
const update = JSON.parse(updates[0])
|
||||
update.doc.should.equal(this.docUpdate.id)
|
||||
update.doc.should.equal(this.update.id)
|
||||
update.pathname.should.equal('/doc-path')
|
||||
update.new_pathname.should.equal('/new-doc-path')
|
||||
update.meta.user_id.should.equal(this.user_id)
|
||||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.0`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -214,56 +197,62 @@ describe("Applying updates to a project's structure", function () {
|
|||
describe('renaming multiple documents and files', function () {
|
||||
before(function () {
|
||||
this.docUpdate0 = {
|
||||
type: 'rename-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/doc-path0',
|
||||
newPathname: '/new-doc-path0'
|
||||
}
|
||||
this.docUpdate1 = {
|
||||
type: 'rename-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/doc-path1',
|
||||
newPathname: '/new-doc-path1'
|
||||
}
|
||||
this.docUpdates = [this.docUpdate0, this.docUpdate1]
|
||||
this.fileUpdate0 = {
|
||||
type: 'rename-file',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-path0',
|
||||
newPathname: '/new-file-path0'
|
||||
}
|
||||
this.fileUpdate1 = {
|
||||
type: 'rename-file',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-path1',
|
||||
newPathname: '/new-file-path1'
|
||||
}
|
||||
return (this.fileUpdates = [this.fileUpdate0, this.fileUpdate1])
|
||||
this.updates = [
|
||||
this.docUpdate0,
|
||||
this.docUpdate1,
|
||||
this.fileUpdate0,
|
||||
this.fileUpdate1
|
||||
]
|
||||
})
|
||||
|
||||
return describe('when the documents are not loaded', function () {
|
||||
describe('when the documents are not loaded', function () {
|
||||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
this.fileUpdates,
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the applied doc renames to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
let update = JSON.parse(updates[0])
|
||||
|
@ -298,10 +287,9 @@ describe("Applying updates to a project's structure", function () {
|
|||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.3`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -310,35 +298,34 @@ describe("Applying updates to a project's structure", function () {
|
|||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.fileUpdate = {
|
||||
type: 'add-file',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-path',
|
||||
url: 'filestore.example.com'
|
||||
}
|
||||
this.fileUpdates = [this.fileUpdate]
|
||||
this.updates = [this.fileUpdate]
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
[],
|
||||
this.fileUpdates,
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return it('should push the file addition to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the file addition to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
const update = JSON.parse(updates[0])
|
||||
|
@ -349,10 +336,9 @@ describe("Applying updates to a project's structure", function () {
|
|||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.0`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -360,35 +346,34 @@ describe("Applying updates to a project's structure", function () {
|
|||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.docUpdate = {
|
||||
type: 'add-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-path',
|
||||
docLines: 'a\nb'
|
||||
}
|
||||
this.docUpdates = [this.docUpdate]
|
||||
this.updates = [this.docUpdate]
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
this.project_id,
|
||||
this.user_id,
|
||||
this.docUpdates,
|
||||
[],
|
||||
this.updates,
|
||||
this.version,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 200)
|
||||
setTimeout(done, 200)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
return it('should push the doc addition to the project history api', function (done) {
|
||||
rclient_project_history.lrange(
|
||||
it('should push the doc addition to the project history api', function (done) {
|
||||
rclientProjectHistory.lrange(
|
||||
ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }),
|
||||
0,
|
||||
-1,
|
||||
(error, updates) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
|
||||
const update = JSON.parse(updates[0])
|
||||
|
@ -399,10 +384,9 @@ describe("Applying updates to a project's structure", function () {
|
|||
update.meta.ts.should.be.a('string')
|
||||
update.version.should.equal(`${this.version}.0`)
|
||||
|
||||
return done()
|
||||
done()
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -416,6 +400,7 @@ describe("Applying updates to a project's structure", function () {
|
|||
for (let v = 0; v <= 599; v++) {
|
||||
// Should flush after 500 ops
|
||||
updates.push({
|
||||
type: 'add-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-' + v,
|
||||
docLines: 'a\nb'
|
||||
|
@ -431,42 +416,39 @@ describe("Applying updates to a project's structure", function () {
|
|||
projectId,
|
||||
userId,
|
||||
updates.slice(0, 250),
|
||||
[],
|
||||
this.version0,
|
||||
function (error) {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return DocUpdaterClient.sendProjectUpdate(
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
projectId,
|
||||
userId,
|
||||
updates.slice(250),
|
||||
[],
|
||||
this.version1,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 2000)
|
||||
setTimeout(done, 2000)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
after(function () {
|
||||
return MockProjectHistoryApi.flushProject.restore()
|
||||
MockProjectHistoryApi.flushProject.restore()
|
||||
})
|
||||
|
||||
return it('should flush project history', function () {
|
||||
return MockProjectHistoryApi.flushProject
|
||||
it('should flush project history', function () {
|
||||
MockProjectHistoryApi.flushProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
return describe('with too few updates to flush to the history service', function () {
|
||||
describe('with too few updates to flush to the history service', function () {
|
||||
before(function (done) {
|
||||
this.project_id = DocUpdaterClient.randomId()
|
||||
this.user_id = DocUpdaterClient.randomId()
|
||||
|
@ -477,6 +459,7 @@ describe("Applying updates to a project's structure", function () {
|
|||
for (let v = 0; v <= 42; v++) {
|
||||
// Should flush after 500 ops
|
||||
updates.push({
|
||||
type: 'add-doc',
|
||||
id: DocUpdaterClient.randomId(),
|
||||
pathname: '/file-' + v,
|
||||
docLines: 'a\nb'
|
||||
|
@ -492,36 +475,33 @@ describe("Applying updates to a project's structure", function () {
|
|||
projectId,
|
||||
userId,
|
||||
updates.slice(0, 10),
|
||||
[],
|
||||
this.version0,
|
||||
function (error) {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return DocUpdaterClient.sendProjectUpdate(
|
||||
DocUpdaterClient.sendProjectUpdate(
|
||||
projectId,
|
||||
userId,
|
||||
updates.slice(10),
|
||||
[],
|
||||
this.version1,
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
throw error
|
||||
if (error) {
|
||||
return done(error)
|
||||
}
|
||||
return setTimeout(done, 2000)
|
||||
setTimeout(done, 2000)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
return null
|
||||
})
|
||||
|
||||
after(function () {
|
||||
return MockProjectHistoryApi.flushProject.restore()
|
||||
MockProjectHistoryApi.flushProject.restore()
|
||||
})
|
||||
|
||||
return it('should not flush project history', function () {
|
||||
return MockProjectHistoryApi.flushProject
|
||||
it('should not flush project history', function () {
|
||||
MockProjectHistoryApi.flushProject
|
||||
.calledWith(this.project_id)
|
||||
.should.equal(false)
|
||||
})
|
||||
|
|
|
@ -1,16 +1,3 @@
|
|||
/* eslint-disable
|
||||
camelcase,
|
||||
handle-callback-err,
|
||||
*/
|
||||
// TODO: This file was created by bulk-decaffeinate.
|
||||
// Fix any style issues and re-enable lint.
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
let DocUpdaterClient
|
||||
const Settings = require('settings-sharelatex')
|
||||
const rclient = require('redis-sharelatex').createClient(
|
||||
|
@ -20,143 +7,122 @@ const keys = Settings.redis.documentupdater.key_schema
|
|||
const request = require('request').defaults({ jar: false })
|
||||
const async = require('async')
|
||||
|
||||
const rclient_sub = require('redis-sharelatex').createClient(
|
||||
const rclientSub = require('redis-sharelatex').createClient(
|
||||
Settings.redis.pubsub
|
||||
)
|
||||
rclient_sub.subscribe('applied-ops')
|
||||
rclient_sub.setMaxListeners(0)
|
||||
rclientSub.subscribe('applied-ops')
|
||||
rclientSub.setMaxListeners(0)
|
||||
|
||||
module.exports = DocUpdaterClient = {
|
||||
randomId() {
|
||||
const chars = __range__(1, 24, true).map(
|
||||
(i) => Math.random().toString(16)[2]
|
||||
)
|
||||
return chars.join('')
|
||||
let str = ''
|
||||
for (let i = 0; i < 24; i++) {
|
||||
str += Math.floor(Math.random() * 16).toString(16)
|
||||
}
|
||||
return str
|
||||
},
|
||||
|
||||
subscribeToAppliedOps(callback) {
|
||||
if (callback == null) {
|
||||
callback = function (message) {}
|
||||
}
|
||||
return rclient_sub.on('message', callback)
|
||||
rclientSub.on('message', callback)
|
||||
},
|
||||
|
||||
sendUpdate(project_id, doc_id, update, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return rclient.rpush(
|
||||
keys.pendingUpdates({ doc_id }),
|
||||
sendUpdate(projectId, docId, update, callback) {
|
||||
rclient.rpush(
|
||||
keys.pendingUpdates({ doc_id: docId }),
|
||||
JSON.stringify(update),
|
||||
(error) => {
|
||||
if (error != null) {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
const doc_key = `${project_id}:${doc_id}`
|
||||
return rclient.sadd('DocsWithPendingUpdates', doc_key, (error) => {
|
||||
if (error != null) {
|
||||
const docKey = `${projectId}:${docId}`
|
||||
rclient.sadd('DocsWithPendingUpdates', docKey, (error) => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
return rclient.rpush('pending-updates-list', doc_key, callback)
|
||||
rclient.rpush('pending-updates-list', docKey, callback)
|
||||
})
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
sendUpdates(project_id, doc_id, updates, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return DocUpdaterClient.preloadDoc(project_id, doc_id, (error) => {
|
||||
if (error != null) {
|
||||
sendUpdates(projectId, docId, updates, callback) {
|
||||
DocUpdaterClient.preloadDoc(projectId, docId, (error) => {
|
||||
if (error) {
|
||||
return callback(error)
|
||||
}
|
||||
const jobs = []
|
||||
for (const update of Array.from(updates)) {
|
||||
;((update) =>
|
||||
jobs.push((callback) =>
|
||||
DocUpdaterClient.sendUpdate(project_id, doc_id, update, callback)
|
||||
))(update)
|
||||
}
|
||||
return async.series(jobs, (err) =>
|
||||
DocUpdaterClient.waitForPendingUpdates(project_id, doc_id, callback)
|
||||
)
|
||||
const jobs = updates.map((update) => (callback) => {
|
||||
DocUpdaterClient.sendUpdate(projectId, docId, update, callback)
|
||||
})
|
||||
async.series(jobs, (err) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
DocUpdaterClient.waitForPendingUpdates(projectId, docId, callback)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
waitForPendingUpdates(project_id, doc_id, callback) {
|
||||
return async.retry(
|
||||
waitForPendingUpdates(projectId, docId, callback) {
|
||||
async.retry(
|
||||
{ times: 30, interval: 100 },
|
||||
(cb) =>
|
||||
rclient.llen(keys.pendingUpdates({ doc_id }), (err, length) => {
|
||||
rclient.llen(keys.pendingUpdates({ doc_id: docId }), (err, length) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
if (length > 0) {
|
||||
return cb(new Error('updates still pending'))
|
||||
cb(new Error('updates still pending'))
|
||||
} else {
|
||||
return cb()
|
||||
cb()
|
||||
}
|
||||
}),
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
getDoc(project_id, doc_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error, res, body) {}
|
||||
}
|
||||
return request.get(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}`,
|
||||
getDoc(projectId, docId, callback) {
|
||||
request.get(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}`,
|
||||
(error, res, body) => {
|
||||
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
body = JSON.parse(body)
|
||||
}
|
||||
return callback(error, res, body)
|
||||
callback(error, res, body)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
getDocAndRecentOps(project_id, doc_id, fromVersion, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error, res, body) {}
|
||||
}
|
||||
return request.get(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}?fromVersion=${fromVersion}`,
|
||||
getDocAndRecentOps(projectId, docId, fromVersion, callback) {
|
||||
request.get(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}`,
|
||||
(error, res, body) => {
|
||||
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
body = JSON.parse(body)
|
||||
}
|
||||
return callback(error, res, body)
|
||||
callback(error, res, body)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
preloadDoc(project_id, doc_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return DocUpdaterClient.getDoc(project_id, doc_id, callback)
|
||||
preloadDoc(projectId, docId, callback) {
|
||||
DocUpdaterClient.getDoc(projectId, docId, callback)
|
||||
},
|
||||
|
||||
flushDoc(project_id, doc_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return request.post(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}/flush`,
|
||||
flushDoc(projectId, docId, callback) {
|
||||
request.post(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}/flush`,
|
||||
(error, res, body) => callback(error, res, body)
|
||||
)
|
||||
},
|
||||
|
||||
setDocLines(project_id, doc_id, lines, source, user_id, undoing, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return request.post(
|
||||
setDocLines(projectId, docId, lines, source, userId, undoing, callback) {
|
||||
request.post(
|
||||
{
|
||||
url: `http://localhost:3003/project/${project_id}/doc/${doc_id}`,
|
||||
url: `http://localhost:3003/project/${projectId}/doc/${docId}`,
|
||||
json: {
|
||||
lines,
|
||||
source,
|
||||
user_id,
|
||||
user_id: userId,
|
||||
undoing
|
||||
}
|
||||
},
|
||||
|
@ -164,115 +130,68 @@ module.exports = DocUpdaterClient = {
|
|||
)
|
||||
},
|
||||
|
||||
deleteDoc(project_id, doc_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return request.del(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}`,
|
||||
deleteDoc(projectId, docId, callback) {
|
||||
request.del(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}`,
|
||||
(error, res, body) => callback(error, res, body)
|
||||
)
|
||||
},
|
||||
|
||||
flushProject(project_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.post(
|
||||
`http://localhost:3003/project/${project_id}/flush`,
|
||||
callback
|
||||
)
|
||||
flushProject(projectId, callback) {
|
||||
request.post(`http://localhost:3003/project/${projectId}/flush`, callback)
|
||||
},
|
||||
|
||||
deleteProject(project_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.del(`http://localhost:3003/project/${project_id}`, callback)
|
||||
deleteProject(projectId, callback) {
|
||||
request.del(`http://localhost:3003/project/${projectId}`, callback)
|
||||
},
|
||||
|
||||
deleteProjectOnShutdown(project_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.del(
|
||||
`http://localhost:3003/project/${project_id}?background=true&shutdown=true`,
|
||||
deleteProjectOnShutdown(projectId, callback) {
|
||||
request.del(
|
||||
`http://localhost:3003/project/${projectId}?background=true&shutdown=true`,
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
flushOldProjects(callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
request.get(
|
||||
'http://localhost:3003/flush_queued_projects?min_delete_age=1',
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
acceptChange(project_id, doc_id, change_id, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.post(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}/change/${change_id}/accept`,
|
||||
acceptChange(projectId, docId, changeId, callback) {
|
||||
request.post(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}/change/${changeId}/accept`,
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
removeComment(project_id, doc_id, comment, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.del(
|
||||
`http://localhost:3003/project/${project_id}/doc/${doc_id}/comment/${comment}`,
|
||||
removeComment(projectId, docId, comment, callback) {
|
||||
request.del(
|
||||
`http://localhost:3003/project/${projectId}/doc/${docId}/comment/${comment}`,
|
||||
callback
|
||||
)
|
||||
},
|
||||
|
||||
getProjectDocs(project_id, projectStateHash, callback) {
|
||||
if (callback == null) {
|
||||
callback = function () {}
|
||||
}
|
||||
return request.get(
|
||||
`http://localhost:3003/project/${project_id}/doc?state=${projectStateHash}`,
|
||||
getProjectDocs(projectId, projectStateHash, callback) {
|
||||
request.get(
|
||||
`http://localhost:3003/project/${projectId}/doc?state=${projectStateHash}`,
|
||||
(error, res, body) => {
|
||||
if (body != null && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
body = JSON.parse(body)
|
||||
}
|
||||
return callback(error, res, body)
|
||||
callback(error, res, body)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
sendProjectUpdate(
|
||||
project_id,
|
||||
userId,
|
||||
docUpdates,
|
||||
fileUpdates,
|
||||
version,
|
||||
callback
|
||||
) {
|
||||
if (callback == null) {
|
||||
callback = function (error) {}
|
||||
}
|
||||
return request.post(
|
||||
sendProjectUpdate(projectId, userId, updates, version, callback) {
|
||||
request.post(
|
||||
{
|
||||
url: `http://localhost:3003/project/${project_id}`,
|
||||
json: { userId, docUpdates, fileUpdates, version }
|
||||
url: `http://localhost:3003/project/${projectId}`,
|
||||
json: { userId, updates, version }
|
||||
},
|
||||
(error, res, body) => callback(error, res, body)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function __range__(left, right, inclusive) {
|
||||
const range = []
|
||||
const ascending = left < right
|
||||
const end = !inclusive ? right : ascending ? right + 1 : right - 1
|
||||
for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
|
||||
range.push(i)
|
||||
}
|
||||
return range
|
||||
}
|
||||
|
|
|
@ -809,92 +809,7 @@ describe('HttpController', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('updateProject (split doc and file updates)', function () {
|
||||
beforeEach(function () {
|
||||
this.projectHistoryId = 'history-id-123'
|
||||
this.userId = 'user-id-123'
|
||||
this.docUpdates = [
|
||||
{ id: 1, pathname: 'thesis.tex', newPathname: 'book.tex' },
|
||||
{ id: 2, pathname: 'article.tex', docLines: 'hello' }
|
||||
]
|
||||
this.fileUpdates = [
|
||||
{ id: 3, pathname: 'apple.png', newPathname: 'banana.png' },
|
||||
{ id: 4, url: 'filestore.example.com/4' }
|
||||
]
|
||||
this.expectedUpdates = [
|
||||
{
|
||||
type: 'rename-doc',
|
||||
id: 1,
|
||||
pathname: 'thesis.tex',
|
||||
newPathname: 'book.tex'
|
||||
},
|
||||
{ type: 'add-doc', id: 2, pathname: 'article.tex', docLines: 'hello' },
|
||||
{
|
||||
type: 'rename-file',
|
||||
id: 3,
|
||||
pathname: 'apple.png',
|
||||
newPathname: 'banana.png'
|
||||
},
|
||||
{ type: 'add-file', id: 4, url: 'filestore.example.com/4' }
|
||||
]
|
||||
this.version = 1234567
|
||||
this.req = {
|
||||
query: {},
|
||||
body: {
|
||||
projectHistoryId: this.projectHistoryId,
|
||||
userId: this.userId,
|
||||
docUpdates: this.docUpdates,
|
||||
fileUpdates: this.fileUpdates,
|
||||
version: this.version
|
||||
},
|
||||
params: {
|
||||
project_id: this.project_id
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('successfully', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectManager.updateProjectWithLocks = sinon.stub().yields()
|
||||
this.HttpController.updateProject(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it('should accept the change', function () {
|
||||
this.ProjectManager.updateProjectWithLocks
|
||||
.calledWith(
|
||||
this.project_id,
|
||||
this.projectHistoryId,
|
||||
this.userId,
|
||||
this.expectedUpdates,
|
||||
this.version
|
||||
)
|
||||
.should.equal(true)
|
||||
})
|
||||
|
||||
it('should return a successful No Content response', function () {
|
||||
this.res.sendStatus.calledWith(204).should.equal(true)
|
||||
})
|
||||
|
||||
it('should time the request', function () {
|
||||
this.Metrics.Timer.prototype.done.called.should.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when an errors occurs', function () {
|
||||
beforeEach(function () {
|
||||
this.ProjectManager.updateProjectWithLocks = sinon
|
||||
.stub()
|
||||
.yields(new Error('oops'))
|
||||
this.HttpController.updateProject(this.req, this.res, this.next)
|
||||
})
|
||||
|
||||
it('should call next with the error', function () {
|
||||
this.next.calledWith(sinon.match.instanceOf(Error)).should.equal(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateProject (single updates parameter)', function () {
|
||||
describe('updateProject', function () {
|
||||
beforeEach(function () {
|
||||
this.projectHistoryId = 'history-id-123'
|
||||
this.userId = 'user-id-123'
|
||||
|
|
Loading…
Add table
Reference in a new issue