overleaf/services/docstore/app/js/DocManager.js

354 lines
9.9 KiB
JavaScript

/* eslint-disable
camelcase,
handle-callback-err,
no-dupe-keys,
no-undef,
standard/no-callback-literal,
*/
// 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
*/
let DocManager
const MongoManager = require('./MongoManager')
const Errors = require('./Errors')
const logger = require('logger-sharelatex')
const _ = require('underscore')
const DocArchive = require('./DocArchiveManager')
const RangeManager = require('./RangeManager')
const Settings = require('settings-sharelatex')
module.exports = DocManager = {
// TODO: For historical reasons, the doc version is currently stored in the docOps
// collection (which is all that this collection contains). In future, we should
// migrate this version property to be part of the docs collection, to guarantee
// consitency between lines and version when writing/reading, and for a simpler schema.
_getDoc(project_id, doc_id, filter, callback) {
if (filter == null) {
filter = {}
}
if (callback == null) {
callback = function (error, doc) {}
}
if (filter.inS3 !== true) {
return callback('must include inS3 when getting doc')
}
return MongoManager.findDoc(project_id, doc_id, filter, function (
err,
doc
) {
if (err != null) {
return callback(err)
} else if (doc == null) {
return callback(
new Errors.NotFoundError(
`No such doc: ${doc_id} in project ${project_id}`
)
)
} else if (doc != null ? doc.inS3 : undefined) {
return DocArchive.unarchiveDoc(project_id, doc_id, function (err) {
if (err != null) {
logger.err({ err, project_id, doc_id }, 'error unarchiving doc')
return callback(err)
}
return DocManager._getDoc(project_id, doc_id, filter, callback)
})
} else {
if (filter.version) {
return MongoManager.getDocVersion(doc_id, function (error, version) {
if (error != null) {
return callback(error)
}
doc.version = version
return callback(err, doc)
})
} else {
return callback(err, doc)
}
}
})
},
checkDocExists(project_id, doc_id, callback) {
if (callback == null) {
callback = function (err, exists) {}
}
return DocManager._getDoc(
project_id,
doc_id,
{ _id: 1, inS3: true },
function (err, doc) {
if (err != null) {
return callback(err)
}
return callback(err, doc != null)
}
)
},
isDocDeleted(projectId, docId, callback) {
MongoManager.findDoc(projectId, docId, { deleted: true }, function (
err,
doc
) {
if (err) {
return callback(err)
}
if (!doc) {
return callback(
new Errors.NotFoundError(`No such project/doc: ${projectId}/${docId}`)
)
}
// `doc.deleted` is `undefined` for non deleted docs
callback(null, Boolean(doc.deleted))
})
},
getFullDoc(project_id, doc_id, callback) {
if (callback == null) {
callback = function (err, doc) {}
}
return DocManager._getDoc(
project_id,
doc_id,
{
lines: true,
rev: true,
deleted: true,
version: true,
ranges: true,
inS3: true
},
function (err, doc) {
if (err != null) {
return callback(err)
}
return callback(err, doc)
}
)
},
getDocLines(project_id, doc_id, callback) {
if (callback == null) {
callback = function (err, doc) {}
}
return DocManager._getDoc(
project_id,
doc_id,
{ lines: true, inS3: true },
function (err, doc) {
if (err != null) {
return callback(err)
}
return callback(err, doc)
}
)
},
getAllNonDeletedDocs(project_id, filter, callback) {
if (callback == null) {
callback = function (error, docs) {}
}
return DocArchive.unArchiveAllDocs(project_id, function (error) {
if (error != null) {
return callback(error)
}
return MongoManager.getProjectsDocs(
project_id,
{ include_deleted: false },
filter,
function (error, docs) {
if (typeof err !== 'undefined' && err !== null) {
return callback(error)
} else if (docs == null) {
return callback(
new Errors.NotFoundError(`No docs for project ${project_id}`)
)
} else {
return callback(null, docs)
}
}
)
})
},
updateDoc(project_id, doc_id, lines, version, ranges, callback) {
if (callback == null) {
callback = function (error, modified, rev) {}
}
if (lines == null || version == null || ranges == null) {
return callback(new Error('no lines, version or ranges provided'))
}
return DocManager._getDoc(
project_id,
doc_id,
{
version: true,
rev: true,
lines: true,
version: true,
ranges: true,
inS3: true
},
function (err, doc) {
let updateLines, updateRanges, updateVersion
if (err != null && !(err instanceof Errors.NotFoundError)) {
logger.err(
{ project_id, doc_id, err },
'error getting document for update'
)
return callback(err)
}
ranges = RangeManager.jsonRangesToMongo(ranges)
if (doc == null) {
// If the document doesn't exist, we'll make sure to create/update all parts of it.
updateLines = true
updateVersion = true
updateRanges = true
} else {
updateLines = !_.isEqual(doc.lines, lines)
updateVersion = doc.version !== version
updateRanges = RangeManager.shouldUpdateRanges(doc.ranges, ranges)
}
let modified = false
let rev = (doc != null ? doc.rev : undefined) || 0
const updateLinesAndRangesIfNeeded = function (cb) {
if (updateLines || updateRanges) {
const update = {}
if (updateLines) {
update.lines = lines
}
if (updateRanges) {
update.ranges = ranges
}
logger.log({ project_id, doc_id }, 'updating doc lines and ranges')
modified = true
rev += 1 // rev will be incremented in mongo by MongoManager.upsertIntoDocCollection
return MongoManager.upsertIntoDocCollection(
project_id,
doc_id,
update,
cb
)
} else {
logger.log(
{ project_id, doc_id },
'doc lines have not changed - not updating'
)
return cb()
}
}
const updateVersionIfNeeded = function (cb) {
if (updateVersion) {
logger.log(
{
project_id,
doc_id,
oldVersion: doc != null ? doc.version : undefined,
newVersion: version
},
'updating doc version'
)
modified = true
return MongoManager.setDocVersion(doc_id, version, cb)
} else {
logger.log(
{ project_id, doc_id, version },
'doc version has not changed - not updating'
)
return cb()
}
}
return updateLinesAndRangesIfNeeded(function (error) {
if (error != null) {
return callback(error)
}
return updateVersionIfNeeded(function (error) {
if (error != null) {
return callback(error)
}
return callback(null, modified, rev)
})
})
}
)
},
deleteDoc(project_id, doc_id, callback) {
if (callback == null) {
callback = function (error) {}
}
return DocManager.checkDocExists(project_id, doc_id, function (
error,
exists
) {
if (error != null) {
return callback(error)
}
if (!exists) {
return callback(
new Errors.NotFoundError(
`No such project/doc to delete: ${project_id}/${doc_id}`
)
)
}
if (Settings.docstore.archiveOnSoftDelete) {
// The user will not read this doc anytime soon. Flush it out of mongo.
DocArchive.archiveDocById(project_id, doc_id, (err) => {
if (err) {
logger.warn(
{ project_id, doc_id, err },
'archiving a single doc in the background failed'
)
}
})
}
return MongoManager.markDocAsDeleted(project_id, doc_id, callback)
})
},
patchDoc(project_id, doc_id, meta, callback) {
const projection = { _id: 1, deleted: true }
MongoManager.findDoc(project_id, doc_id, projection, (error, doc) => {
if (error != null) {
return callback(error)
}
if (!doc) {
return callback(
new Errors.NotFoundError(
`No such project/doc to delete: ${project_id}/${doc_id}`
)
)
}
if (meta.deleted && Settings.docstore.archiveOnSoftDelete) {
// The user will not read this doc anytime soon. Flush it out of mongo.
DocArchive.archiveDocById(project_id, doc_id, (err) => {
if (err) {
logger.warn(
{ project_id, doc_id, err },
'archiving a single doc in the background failed'
)
}
})
}
MongoManager.patchDoc(project_id, doc_id, meta, callback)
})
}
}