Merge pull request #7581 from overleaf/em-docstore-decaf

Decaf cleanup in docstore

GitOrigin-RevId: 84553cec7f184d567dc796c4016d6a412fb8db99
This commit is contained in:
Eric Mc Sween 2022-04-19 08:28:47 -04:00 committed by Copybot
parent e0d5cf4b42
commit 552d6d4baa
4 changed files with 643 additions and 709 deletions

View file

@ -1,329 +1,296 @@
/* eslint-disable
camelcase,
valid-typeof,
*/
// 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 HttpController
const DocManager = require('./DocManager')
const logger = require('@overleaf/logger')
const DocArchive = require('./DocArchiveManager')
const HealthChecker = require('./HealthChecker')
const Settings = require('@overleaf/settings')
module.exports = HttpController = {
getDoc(req, res, next) {
if (next == null) {
next = function () {}
function getDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
const includeDeleted = req.query.include_deleted === 'true'
logger.log({ projectId, docId }, 'getting doc')
DocManager.getFullDoc(projectId, docId, function (error, doc) {
if (error) {
return next(error)
}
const { project_id } = req.params
const { doc_id } = req.params
const include_deleted =
(req.query != null ? req.query.include_deleted : undefined) === 'true'
logger.log({ project_id, doc_id }, 'getting doc')
return DocManager.getFullDoc(project_id, doc_id, function (error, doc) {
if (error != null) {
return next(error)
}
logger.log({ doc_id, project_id }, 'got doc')
if (doc == null) {
return res.sendStatus(404)
} else if (doc.deleted && !include_deleted) {
return res.sendStatus(404)
} else {
return res.json(HttpController._buildDocView(doc))
}
})
},
peekDoc(req, res, next) {
const { project_id } = req.params
const { doc_id } = req.params
logger.log({ project_id, doc_id }, 'peeking doc')
DocManager.peekDoc(project_id, doc_id, function (error, doc) {
if (error) {
return next(error)
}
if (doc == null) {
return res.sendStatus(404)
} else {
res.setHeader('x-doc-status', doc.inS3 ? 'archived' : 'active')
return res.json(HttpController._buildDocView(doc))
}
})
},
isDocDeleted(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
DocManager.isDocDeleted(projectId, docId, function (error, deleted) {
if (error) {
return next(error)
}
res.json({ deleted })
})
},
getRawDoc(req, res, next) {
if (next == null) {
next = function () {}
logger.log({ docId, projectId }, 'got doc')
if (doc == null) {
res.sendStatus(404)
} else if (doc.deleted && !includeDeleted) {
res.sendStatus(404)
} else {
res.json(_buildDocView(doc))
}
const { project_id } = req.params
const { doc_id } = req.params
logger.log({ project_id, doc_id }, 'getting raw doc')
return DocManager.getDocLines(project_id, doc_id, function (error, doc) {
if (error != null) {
return next(error)
}
if (doc == null) {
return res.sendStatus(404)
} else {
res.setHeader('content-type', 'text/plain')
return res.send(HttpController._buildRawDocView(doc))
}
})
},
getAllDocs(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
logger.log({ project_id }, 'getting all docs')
return DocManager.getAllNonDeletedDocs(
project_id,
{ lines: true, rev: true },
function (error, docs) {
if (docs == null) {
docs = []
}
if (error != null) {
return next(error)
}
return res.json(HttpController._buildDocsArrayView(project_id, docs))
}
)
},
getAllDeletedDocs(req, res, next) {
const { project_id } = req.params
logger.log({ project_id }, 'getting all deleted docs')
DocManager.getAllDeletedDocs(
project_id,
{ name: true, deletedAt: true },
function (error, docs) {
if (error) {
return next(error)
}
res.json(
docs.map(doc => {
return {
_id: doc._id.toString(),
name: doc.name,
deletedAt: doc.deletedAt,
}
})
)
}
)
},
getAllRanges(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
logger.log({ project_id }, 'getting all ranges')
return DocManager.getAllNonDeletedDocs(
project_id,
{ ranges: true },
function (error, docs) {
if (docs == null) {
docs = []
}
if (error != null) {
return next(error)
}
return res.json(HttpController._buildDocsArrayView(project_id, docs))
}
)
},
updateDoc(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
const { doc_id } = req.params
const lines = req.body != null ? req.body.lines : undefined
const version = req.body != null ? req.body.version : undefined
const ranges = req.body != null ? req.body.ranges : undefined
if (lines == null || !(lines instanceof Array)) {
logger.error({ project_id, doc_id }, 'no doc lines provided')
res.sendStatus(400) // Bad Request
return
}
if (version == null || typeof version === !'number') {
logger.error({ project_id, doc_id }, 'no doc version provided')
res.sendStatus(400) // Bad Request
return
}
if (ranges == null) {
logger.error({ project_id, doc_id }, 'no doc ranges provided')
res.sendStatus(400) // Bad Request
return
}
const bodyLength = lines.reduce((len, line) => line.length + len, 0)
if (bodyLength > Settings.max_doc_length) {
logger.error(
{ project_id, doc_id, bodyLength },
'document body too large'
)
res.status(413).send('document body too large')
return
}
logger.log({ project_id, doc_id }, 'got http request to update doc')
return DocManager.updateDoc(
project_id,
doc_id,
lines,
version,
ranges,
function (error, modified, rev) {
if (error != null) {
return next(error)
}
return res.json({
modified,
rev,
})
}
)
},
patchDoc(req, res, next) {
const { project_id, doc_id } = req.params
logger.log({ project_id, doc_id }, 'patching doc')
const allowedFields = ['deleted', 'deletedAt', 'name']
const meta = {}
Object.entries(req.body).forEach(([field, value]) => {
if (allowedFields.includes(field)) {
meta[field] = value
} else {
logger.fatal({ field }, 'joi validation for pathDoc is broken')
}
})
DocManager.patchDoc(project_id, doc_id, meta, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
},
_buildDocView(doc) {
const doc_view = { _id: doc._id != null ? doc._id.toString() : undefined }
for (const attribute of ['lines', 'rev', 'version', 'ranges', 'deleted']) {
if (doc[attribute] != null) {
doc_view[attribute] = doc[attribute]
}
}
return doc_view
},
_buildRawDocView(doc) {
return ((doc != null ? doc.lines : undefined) || []).join('\n')
},
_buildDocsArrayView(project_id, docs) {
const docViews = []
for (const doc of Array.from(docs)) {
if (doc != null) {
// There can end up being null docs for some reason :( (probably a race condition)
docViews.push(HttpController._buildDocView(doc))
} else {
logger.error(
{ err: new Error('null doc'), project_id },
'encountered null doc'
)
}
}
return docViews
},
archiveAllDocs(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
logger.log({ project_id }, 'archiving all docs')
return DocArchive.archiveAllDocs(project_id, function (error) {
if (error != null) {
return next(error)
}
return res.sendStatus(204)
})
},
archiveDoc(req, res, next) {
const { project_id, doc_id } = req.params
logger.log({ project_id, doc_id }, 'archiving a doc')
DocArchive.archiveDocById(project_id, doc_id, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
},
unArchiveAllDocs(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
logger.log({ project_id }, 'unarchiving all docs')
return DocArchive.unArchiveAllDocs(project_id, function (error) {
if (error != null) {
return next(error)
}
return res.sendStatus(200)
})
},
destroyAllDocs(req, res, next) {
if (next == null) {
next = function () {}
}
const { project_id } = req.params
logger.log({ project_id }, 'destroying all docs')
return DocArchive.destroyAllDocs(project_id, function (error) {
if (error != null) {
return next(error)
}
return res.sendStatus(204)
})
},
healthCheck(req, res) {
return HealthChecker.check(function (err) {
if (err != null) {
logger.err({ err }, 'error performing health check')
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
})
},
})
}
function peekDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
logger.log({ projectId, docId }, 'peeking doc')
DocManager.peekDoc(projectId, docId, function (error, doc) {
if (error) {
return next(error)
}
if (doc == null) {
res.sendStatus(404)
} else {
res.setHeader('x-doc-status', doc.inS3 ? 'archived' : 'active')
res.json(_buildDocView(doc))
}
})
}
function isDocDeleted(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
DocManager.isDocDeleted(projectId, docId, function (error, deleted) {
if (error) {
return next(error)
}
res.json({ deleted })
})
}
function getRawDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
logger.log({ projectId, docId }, 'getting raw doc')
DocManager.getDocLines(projectId, docId, function (error, doc) {
if (error) {
return next(error)
}
if (doc == null) {
res.sendStatus(404)
} else {
res.setHeader('content-type', 'text/plain')
res.send(_buildRawDocView(doc))
}
})
}
function getAllDocs(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'getting all docs')
DocManager.getAllNonDeletedDocs(
projectId,
{ lines: true, rev: true },
function (error, docs) {
if (docs == null) {
docs = []
}
if (error) {
return next(error)
}
res.json(_buildDocsArrayView(projectId, docs))
}
)
}
function getAllDeletedDocs(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'getting all deleted docs')
DocManager.getAllDeletedDocs(
projectId,
{ name: true, deletedAt: true },
function (error, docs) {
if (error) {
return next(error)
}
res.json(
docs.map(doc => ({
_id: doc._id.toString(),
name: doc.name,
deletedAt: doc.deletedAt,
}))
)
}
)
}
function getAllRanges(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'getting all ranges')
DocManager.getAllNonDeletedDocs(
projectId,
{ ranges: true },
function (error, docs) {
if (docs == null) {
docs = []
}
if (error) {
return next(error)
}
res.json(_buildDocsArrayView(projectId, docs))
}
)
}
function updateDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
const lines = req.body?.lines
const version = req.body?.version
const ranges = req.body?.ranges
if (lines == null || !(lines instanceof Array)) {
logger.error({ projectId, docId }, 'no doc lines provided')
res.sendStatus(400) // Bad Request
return
}
if (version == null || typeof version !== 'number') {
logger.error({ projectId, docId }, 'no doc version provided')
res.sendStatus(400) // Bad Request
return
}
if (ranges == null) {
logger.error({ projectId, docId }, 'no doc ranges provided')
res.sendStatus(400) // Bad Request
return
}
const bodyLength = lines.reduce((len, line) => line.length + len, 0)
if (bodyLength > Settings.max_doc_length) {
logger.error({ projectId, docId, bodyLength }, 'document body too large')
res.status(413).send('document body too large')
return
}
logger.log({ projectId, docId }, 'got http request to update doc')
DocManager.updateDoc(
projectId,
docId,
lines,
version,
ranges,
function (error, modified, rev) {
if (error) {
return next(error)
}
res.json({
modified,
rev,
})
}
)
}
function patchDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
logger.log({ projectId, docId }, 'patching doc')
const allowedFields = ['deleted', 'deletedAt', 'name']
const meta = {}
Object.entries(req.body).forEach(([field, value]) => {
if (allowedFields.includes(field)) {
meta[field] = value
} else {
logger.fatal({ field }, 'joi validation for pathDoc is broken')
}
})
DocManager.patchDoc(projectId, docId, meta, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function _buildDocView(doc) {
const docView = { _id: doc._id?.toString() }
for (const attribute of ['lines', 'rev', 'version', 'ranges', 'deleted']) {
if (doc[attribute] != null) {
docView[attribute] = doc[attribute]
}
}
return docView
}
function _buildRawDocView(doc) {
return (doc?.lines ?? []).join('\n')
}
function _buildDocsArrayView(projectId, docs) {
const docViews = []
for (const doc of docs) {
if (doc != null) {
// There can end up being null docs for some reason :( (probably a race condition)
docViews.push(_buildDocView(doc))
} else {
logger.error(
{ err: new Error('null doc'), projectId },
'encountered null doc'
)
}
}
return docViews
}
function archiveAllDocs(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'archiving all docs')
DocArchive.archiveAllDocs(projectId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function archiveDoc(req, res, next) {
const { doc_id: docId, project_id: projectId } = req.params
logger.log({ projectId, docId }, 'archiving a doc')
DocArchive.archiveDocById(projectId, docId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function unArchiveAllDocs(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'unarchiving all docs')
DocArchive.unArchiveAllDocs(projectId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(200)
})
}
function destroyAllDocs(req, res, next) {
const { project_id: projectId } = req.params
logger.log({ projectId }, 'destroying all docs')
DocArchive.destroyAllDocs(projectId, function (error) {
if (error) {
return next(error)
}
res.sendStatus(204)
})
}
function healthCheck(req, res) {
HealthChecker.check(function (err) {
if (err) {
logger.err({ err }, 'error performing health check')
res.sendStatus(500)
} else {
res.sendStatus(200)
}
})
}
module.exports = {
getDoc,
peekDoc,
isDocDeleted,
getRawDoc,
getAllDocs,
getAllDeletedDocs,
getAllRanges,
updateDoc,
patchDoc,
archiveAllDocs,
archiveDoc,
unArchiveAllDocs,
destroyAllDocs,
healthCheck,
}

View file

@ -1,15 +1,3 @@
/* eslint-disable
camelcase,
*/
// 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 MongoManager
const { db, ObjectId } = require('./mongodb')
const logger = require('@overleaf/logger')
const metrics = require('@overleaf/metrics')
@ -17,239 +5,243 @@ const Settings = require('@overleaf/settings')
const Errors = require('./Errors')
const { promisify } = require('util')
module.exports = MongoManager = {
findDoc(project_id, doc_id, filter, callback) {
if (callback == null) {
callback = function () {}
}
db.docs.findOne(
function findDoc(projectId, docId, filter, callback) {
db.docs.findOne(
{
_id: ObjectId(docId.toString()),
project_id: ObjectId(projectId.toString()),
},
{
projection: filter,
},
callback
)
}
function getProjectsDeletedDocs(projectId, filter, callback) {
db.docs
.find(
{
_id: ObjectId(doc_id.toString()),
project_id: ObjectId(project_id.toString()),
project_id: ObjectId(projectId.toString()),
deleted: true,
},
{
projection: filter,
},
callback
)
},
getProjectsDeletedDocs(project_id, filter, callback) {
db.docs
.find(
{
project_id: ObjectId(project_id.toString()),
deleted: true,
},
{
projection: filter,
sort: { deletedAt: -1 },
limit: Settings.max_deleted_docs,
}
)
.toArray(callback)
},
getProjectsDocs(project_id, options, filter, callback) {
const query = { project_id: ObjectId(project_id.toString()) }
if (!options.include_deleted) {
query.deleted = { $ne: true }
}
const queryOptions = {
projection: filter,
}
if (options.limit) {
queryOptions.limit = options.limit
}
db.docs.find(query, queryOptions).toArray(callback)
},
getArchivedProjectDocs(project_id, maxResults, callback) {
const query = {
project_id: ObjectId(project_id.toString()),
inS3: true,
}
db.docs
.find(query, { projection: { _id: 1 }, limit: maxResults })
.toArray(callback)
},
getNonArchivedProjectDocs(project_id, maxResults, callback) {
const query = {
project_id: ObjectId(project_id.toString()),
inS3: { $ne: true },
}
db.docs.find(query, { limit: maxResults }).toArray(callback)
},
getNonDeletedArchivedProjectDocs(project_id, maxResults, callback) {
const query = {
project_id: ObjectId(project_id.toString()),
deleted: { $ne: true },
inS3: true,
}
db.docs
.find(query, { projection: { _id: 1 }, limit: maxResults })
.toArray(callback)
},
upsertIntoDocCollection(project_id, doc_id, updates, callback) {
const update = {
$set: updates,
$inc: {
rev: 1,
},
$unset: {
inS3: true,
},
}
update.$set.project_id = ObjectId(project_id)
db.docs.updateOne(
{ _id: ObjectId(doc_id) },
update,
{ upsert: true },
callback
)
},
patchDoc(project_id, doc_id, meta, callback) {
db.docs.updateOne(
{
_id: ObjectId(doc_id),
project_id: ObjectId(project_id),
},
{ $set: meta },
callback
)
},
markDocAsArchived(doc_id, rev, callback) {
const update = {
$set: {},
$unset: {},
}
update.$set.inS3 = true
update.$unset.lines = true
update.$unset.ranges = true
const query = {
_id: doc_id,
rev,
}
db.docs.updateOne(query, update, callback)
},
getDocVersion(doc_id, callback) {
if (callback == null) {
callback = function () {}
}
db.docOps.findOne(
{
doc_id: ObjectId(doc_id),
},
{
projection: {
version: 1,
},
},
function (error, doc) {
if (error != null) {
return callback(error)
}
callback(null, (doc && doc.version) || 0)
sort: { deletedAt: -1 },
limit: Settings.max_deleted_docs,
}
)
},
.toArray(callback)
}
setDocVersion(doc_id, version, callback) {
if (callback == null) {
callback = function () {}
}
db.docOps.updateOne(
{
doc_id: ObjectId(doc_id),
},
{
$set: { version },
},
{
upsert: true,
},
callback
)
},
function getProjectsDocs(projectId, options, filter, callback) {
const query = { project_id: ObjectId(projectId.toString()) }
if (!options.include_deleted) {
query.deleted = { $ne: true }
}
const queryOptions = {
projection: filter,
}
if (options.limit) {
queryOptions.limit = options.limit
}
db.docs.find(query, queryOptions).toArray(callback)
}
getDocRev(doc_id, callback) {
db.docs.findOne(
{
_id: ObjectId(doc_id.toString()),
function getArchivedProjectDocs(projectId, maxResults, callback) {
const query = {
project_id: ObjectId(projectId.toString()),
inS3: true,
}
db.docs
.find(query, { projection: { _id: 1 }, limit: maxResults })
.toArray(callback)
}
function getNonArchivedProjectDocs(projectId, maxResults, callback) {
const query = {
project_id: ObjectId(projectId.toString()),
inS3: { $ne: true },
}
db.docs.find(query, { limit: maxResults }).toArray(callback)
}
function getNonDeletedArchivedProjectDocs(projectId, maxResults, callback) {
const query = {
project_id: ObjectId(projectId.toString()),
deleted: { $ne: true },
inS3: true,
}
db.docs
.find(query, { projection: { _id: 1 }, limit: maxResults })
.toArray(callback)
}
function upsertIntoDocCollection(projectId, docId, updates, callback) {
const update = {
$set: updates,
$inc: {
rev: 1,
},
$unset: {
inS3: true,
},
}
update.$set.project_id = ObjectId(projectId)
db.docs.updateOne(
{ _id: ObjectId(docId) },
update,
{ upsert: true },
callback
)
}
function patchDoc(projectId, docId, meta, callback) {
db.docs.updateOne(
{
_id: ObjectId(docId),
project_id: ObjectId(projectId),
},
{ $set: meta },
callback
)
}
function markDocAsArchived(docId, rev, callback) {
const update = {
$set: {},
$unset: {},
}
update.$set.inS3 = true
update.$unset.lines = true
update.$unset.ranges = true
const query = {
_id: docId,
rev,
}
db.docs.updateOne(query, update, callback)
}
function getDocVersion(docId, callback) {
db.docOps.findOne(
{
doc_id: ObjectId(docId),
},
{
projection: {
version: 1,
},
{
projection: { rev: 1 },
},
function (err, doc) {
if (err != null) {
return callback(err)
}
callback(null, doc && doc.rev)
},
function (error, doc) {
if (error) {
return callback(error)
}
)
},
callback(null, (doc && doc.version) || 0)
}
)
}
// Helper method to support optimistic locking. Call the provided method for
// an existing doc and return the result if the rev in mongo is unchanged when
// checked afterwards. If the rev has changed, return a DocModifiedError.
withRevCheck(doc, method, callback) {
method(doc._id, function (err, result) {
function setDocVersion(docId, version, callback) {
db.docOps.updateOne(
{
doc_id: ObjectId(docId),
},
{
$set: { version },
},
{
upsert: true,
},
callback
)
}
function getDocRev(docId, callback) {
db.docs.findOne(
{
_id: ObjectId(docId.toString()),
},
{
projection: { rev: 1 },
},
function (err, doc) {
if (err) {
return callback(err)
}
callback(null, doc && doc.rev)
}
)
}
// Helper method to support optimistic locking. Call the provided method for
// an existing doc and return the result if the rev in mongo is unchanged when
// checked afterwards. If the rev has changed, return a DocModifiedError.
function withRevCheck(doc, method, callback) {
method(doc._id, function (err, result) {
if (err) return callback(err)
getDocRev(doc._id, function (err, currentRev) {
if (err) return callback(err)
MongoManager.getDocRev(doc._id, function (err, currentRev) {
if (err) return callback(err)
if (isNaN(currentRev) || isNaN(doc.rev)) {
return callback(
new Errors.DocRevValueError('doc rev is NaN', {
doc_id: doc._id,
rev: doc.rev,
currentRev,
})
)
}
if (doc.rev !== currentRev) {
return callback(
new Errors.DocModifiedError('doc rev has changed', {
doc_id: doc._id,
rev: doc.rev,
currentRev,
})
)
}
return callback(null, result)
})
})
},
destroyDoc(doc_id, callback) {
db.docs.deleteOne(
{
_id: ObjectId(doc_id),
},
function (err) {
if (err != null) {
return callback(err)
}
db.docOps.deleteOne(
{
doc_id: ObjectId(doc_id),
},
callback
if (isNaN(currentRev) || isNaN(doc.rev)) {
return callback(
new Errors.DocRevValueError('doc rev is NaN', {
doc_id: doc._id,
rev: doc.rev,
currentRev,
})
)
}
)
},
if (doc.rev !== currentRev) {
return callback(
new Errors.DocModifiedError('doc rev has changed', {
doc_id: doc._id,
rev: doc.rev,
currentRev,
})
)
}
callback(null, result)
})
})
}
const methods = Object.getOwnPropertyNames(MongoManager)
function destroyDoc(docId, callback) {
db.docs.deleteOne(
{
_id: ObjectId(docId),
},
function (err) {
if (err) {
return callback(err)
}
db.docOps.deleteOne(
{
doc_id: ObjectId(docId),
},
callback
)
}
)
}
module.exports = {
findDoc,
getProjectsDeletedDocs,
getProjectsDocs,
getArchivedProjectDocs,
getNonArchivedProjectDocs,
getNonDeletedArchivedProjectDocs,
upsertIntoDocCollection,
patchDoc,
markDocAsArchived,
getDocVersion,
setDocVersion,
withRevCheck,
destroyDoc,
}
const methods = Object.getOwnPropertyNames(module.exports)
module.exports.promises = {}
for (const method of methods) {
metrics.timeAsyncMethod(MongoManager, method, 'mongo.MongoManager', logger)
metrics.timeAsyncMethod(module.exports, method, 'mongo.MongoManager', logger)
module.exports.promises[method] = promisify(module.exports[method])
}

View file

@ -1,14 +1,3 @@
/* eslint-disable
no-return-assign,
no-unused-vars,
*/
// 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
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const { assert, expect } = require('chai')
@ -40,46 +29,46 @@ describe('HttpController', function () {
this.res.status = sinon.stub().returns(this.res)
this.req = { query: {} }
this.next = sinon.stub()
this.project_id = 'mock-project-id'
this.doc_id = 'mock-doc-id'
this.projectId = 'mock-project-id'
this.docId = 'mock-doc-id'
this.doc = {
_id: this.doc_id,
_id: this.docId,
lines: ['mock', 'lines', ' here', '', '', ' spaces '],
version: 42,
rev: 5,
}
return (this.deletedDoc = {
this.deletedDoc = {
deleted: true,
_id: this.doc_id,
_id: this.docId,
lines: ['mock', 'lines', ' here', '', '', ' spaces '],
version: 42,
rev: 5,
})
}
})
describe('getDoc', function () {
describe('without deleted docs', function () {
beforeEach(function () {
this.req.params = {
project_id: this.project_id,
doc_id: this.doc_id,
project_id: this.projectId,
doc_id: this.docId,
}
this.DocManager.getFullDoc = sinon
.stub()
.callsArgWith(2, null, this.doc)
return this.HttpController.getDoc(this.req, this.res, this.next)
this.HttpController.getDoc(this.req, this.res, this.next)
})
it('should get the document with the version (including deleted)', function () {
return this.DocManager.getFullDoc
.calledWith(this.project_id, this.doc_id)
this.DocManager.getFullDoc
.calledWith(this.projectId, this.docId)
.should.equal(true)
})
return it('should return the doc as JSON', function () {
return this.res.json
it('should return the doc as JSON', function () {
this.res.json
.calledWith({
_id: this.doc_id,
_id: this.docId,
lines: this.doc.lines,
rev: this.doc.rev,
version: this.doc.version,
@ -88,35 +77,35 @@ describe('HttpController', function () {
})
})
return describe('which is deleted', function () {
describe('which is deleted', function () {
beforeEach(function () {
this.req.params = {
project_id: this.project_id,
doc_id: this.doc_id,
project_id: this.projectId,
doc_id: this.docId,
}
return (this.DocManager.getFullDoc = sinon
this.DocManager.getFullDoc = sinon
.stub()
.callsArgWith(2, null, this.deletedDoc))
.callsArgWith(2, null, this.deletedDoc)
})
it('should get the doc from the doc manager', function () {
this.HttpController.getDoc(this.req, this.res, this.next)
return this.DocManager.getFullDoc
.calledWith(this.project_id, this.doc_id)
this.DocManager.getFullDoc
.calledWith(this.projectId, this.docId)
.should.equal(true)
})
it('should return 404 if the query string delete is not set ', function () {
this.HttpController.getDoc(this.req, this.res, this.next)
return this.res.sendStatus.calledWith(404).should.equal(true)
this.res.sendStatus.calledWith(404).should.equal(true)
})
return it('should return the doc as JSON if include_deleted is set to true', function () {
it('should return the doc as JSON if include_deleted is set to true', function () {
this.req.query.include_deleted = 'true'
this.HttpController.getDoc(this.req, this.res, this.next)
return this.res.json
this.res.json
.calledWith({
_id: this.doc_id,
_id: this.docId,
lines: this.doc.lines,
rev: this.doc.rev,
deleted: true,
@ -130,27 +119,27 @@ describe('HttpController', function () {
describe('getRawDoc', function () {
beforeEach(function () {
this.req.params = {
project_id: this.project_id,
doc_id: this.doc_id,
project_id: this.projectId,
doc_id: this.docId,
}
this.DocManager.getDocLines = sinon.stub().callsArgWith(2, null, this.doc)
return this.HttpController.getRawDoc(this.req, this.res, this.next)
this.HttpController.getRawDoc(this.req, this.res, this.next)
})
it('should get the document without the version', function () {
return this.DocManager.getDocLines
.calledWith(this.project_id, this.doc_id)
this.DocManager.getDocLines
.calledWith(this.projectId, this.docId)
.should.equal(true)
})
it('should set the content type header', function () {
return this.res.setHeader
this.res.setHeader
.calledWith('content-type', 'text/plain')
.should.equal(true)
})
return it('should send the raw version of the doc', function () {
return assert.deepEqual(
it('should send the raw version of the doc', function () {
assert.deepEqual(
this.res.send.args[0][0],
`${this.doc.lines[0]}\n${this.doc.lines[1]}\n${this.doc.lines[2]}\n${this.doc.lines[3]}\n${this.doc.lines[4]}\n${this.doc.lines[5]}`
)
@ -160,7 +149,7 @@ describe('HttpController', function () {
describe('getAllDocs', function () {
describe('normally', function () {
beforeEach(function () {
this.req.params = { project_id: this.project_id }
this.req.params = { project_id: this.projectId }
this.docs = [
{
_id: ObjectId(),
@ -176,17 +165,17 @@ describe('HttpController', function () {
this.DocManager.getAllNonDeletedDocs = sinon
.stub()
.callsArgWith(2, null, this.docs)
return this.HttpController.getAllDocs(this.req, this.res, this.next)
this.HttpController.getAllDocs(this.req, this.res, this.next)
})
it('should get all the (non-deleted) docs', function () {
return this.DocManager.getAllNonDeletedDocs
.calledWith(this.project_id, { lines: true, rev: true })
this.DocManager.getAllNonDeletedDocs
.calledWith(this.projectId, { lines: true, rev: true })
.should.equal(true)
})
return it('should return the doc as JSON', function () {
return this.res.json
it('should return the doc as JSON', function () {
this.res.json
.calledWith([
{
_id: this.docs[0]._id.toString(),
@ -203,9 +192,9 @@ describe('HttpController', function () {
})
})
return describe('with a null doc', function () {
describe('with a null doc', function () {
beforeEach(function () {
this.req.params = { project_id: this.project_id }
this.req.params = { project_id: this.projectId }
this.docs = [
{
_id: ObjectId(),
@ -222,11 +211,11 @@ describe('HttpController', function () {
this.DocManager.getAllNonDeletedDocs = sinon
.stub()
.callsArgWith(2, null, this.docs)
return this.HttpController.getAllDocs(this.req, this.res, this.next)
this.HttpController.getAllDocs(this.req, this.res, this.next)
})
it('should return the non null docs as JSON', function () {
return this.res.json
this.res.json
.calledWith([
{
_id: this.docs[0]._id.toString(),
@ -242,12 +231,12 @@ describe('HttpController', function () {
.should.equal(true)
})
return it('should log out an error', function () {
return this.logger.error
it('should log out an error', function () {
this.logger.error
.calledWith(
{
err: sinon.match.has('message', 'null doc'),
project_id: this.project_id,
projectId: this.projectId,
},
'encountered null doc'
)
@ -257,9 +246,9 @@ describe('HttpController', function () {
})
describe('getAllRanges', function () {
return describe('normally', function () {
describe('normally', function () {
beforeEach(function () {
this.req.params = { project_id: this.project_id }
this.req.params = { project_id: this.projectId }
this.docs = [
{
_id: ObjectId(),
@ -273,17 +262,17 @@ describe('HttpController', function () {
this.DocManager.getAllNonDeletedDocs = sinon
.stub()
.callsArgWith(2, null, this.docs)
return this.HttpController.getAllRanges(this.req, this.res, this.next)
this.HttpController.getAllRanges(this.req, this.res, this.next)
})
it('should get all the (non-deleted) doc ranges', function () {
return this.DocManager.getAllNonDeletedDocs
.calledWith(this.project_id, { ranges: true })
this.DocManager.getAllNonDeletedDocs
.calledWith(this.projectId, { ranges: true })
.should.equal(true)
})
return it('should return the doc as JSON', function () {
return this.res.json
it('should return the doc as JSON', function () {
this.res.json
.calledWith([
{
_id: this.docs[0]._id.toString(),
@ -301,10 +290,10 @@ describe('HttpController', function () {
describe('updateDoc', function () {
beforeEach(function () {
return (this.req.params = {
project_id: this.project_id,
doc_id: this.doc_id,
})
this.req.params = {
project_id: this.projectId,
doc_id: this.docId,
}
})
describe('when the doc lines exist and were updated', function () {
@ -317,14 +306,14 @@ describe('HttpController', function () {
this.DocManager.updateDoc = sinon
.stub()
.yields(null, true, (this.rev = 5))
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
it('should update the document', function () {
return this.DocManager.updateDoc
this.DocManager.updateDoc
.calledWith(
this.project_id,
this.doc_id,
this.projectId,
this.docId,
this.lines,
this.version,
this.ranges
@ -332,8 +321,8 @@ describe('HttpController', function () {
.should.equal(true)
})
return it('should return a modified status', function () {
return this.res.json
it('should return a modified status', function () {
this.res.json
.calledWith({ modified: true, rev: this.rev })
.should.equal(true)
})
@ -349,11 +338,11 @@ describe('HttpController', function () {
this.DocManager.updateDoc = sinon
.stub()
.yields(null, false, (this.rev = 5))
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
return it('should return a modified status', function () {
return this.res.json
it('should return a modified status', function () {
this.res.json
.calledWith({ modified: false, rev: this.rev })
.should.equal(true)
})
@ -363,15 +352,15 @@ describe('HttpController', function () {
beforeEach(function () {
this.req.body = { version: 42, ranges: {} }
this.DocManager.updateDoc = sinon.stub().yields(null, false)
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
it('should not update the document', function () {
return this.DocManager.updateDoc.called.should.equal(false)
this.DocManager.updateDoc.called.should.equal(false)
})
return it('should return a 400 (bad request) response', function () {
return this.res.sendStatus.calledWith(400).should.equal(true)
it('should return a 400 (bad request) response', function () {
this.res.sendStatus.calledWith(400).should.equal(true)
})
})
@ -379,15 +368,15 @@ describe('HttpController', function () {
beforeEach(function () {
this.req.body = { version: 42, lines: ['hello world'] }
this.DocManager.updateDoc = sinon.stub().yields(null, false)
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
it('should not update the document', function () {
return this.DocManager.updateDoc.called.should.equal(false)
this.DocManager.updateDoc.called.should.equal(false)
})
return it('should return a 400 (bad request) response', function () {
return this.res.sendStatus.calledWith(400).should.equal(true)
it('should return a 400 (bad request) response', function () {
this.res.sendStatus.calledWith(400).should.equal(true)
})
})
@ -395,34 +384,34 @@ describe('HttpController', function () {
beforeEach(function () {
this.req.body = { lines: ['foo'], version: 42 }
this.DocManager.updateDoc = sinon.stub().yields(null, false)
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
it('should not update the document', function () {
return this.DocManager.updateDoc.called.should.equal(false)
this.DocManager.updateDoc.called.should.equal(false)
})
return it('should return a 400 (bad request) response', function () {
return this.res.sendStatus.calledWith(400).should.equal(true)
it('should return a 400 (bad request) response', function () {
this.res.sendStatus.calledWith(400).should.equal(true)
})
})
return describe('when the doc body is too large', function () {
describe('when the doc body is too large', function () {
beforeEach(function () {
this.req.body = {
lines: (this.lines = Array(2049).fill('a'.repeat(1024))),
version: (this.version = 42),
ranges: (this.ranges = { changes: 'mock' }),
}
return this.HttpController.updateDoc(this.req, this.res, this.next)
this.HttpController.updateDoc(this.req, this.res, this.next)
})
it('should return a 413 (too large) response', function () {
return sinon.assert.calledWith(this.res.status, 413)
sinon.assert.calledWith(this.res.status, 413)
})
return it('should report that the document body is too large', function () {
return sinon.assert.calledWith(this.res.send, 'document body too large')
it('should report that the document body is too large', function () {
sinon.assert.calledWith(this.res.send, 'document body too large')
})
})
})
@ -430,8 +419,8 @@ describe('HttpController', function () {
describe('patchDoc', function () {
beforeEach(function () {
this.req.params = {
project_id: this.project_id,
doc_id: this.doc_id,
project_id: this.projectId,
doc_id: this.docId,
}
this.req.body = { name: 'foo.tex' }
this.DocManager.patchDoc = sinon.stub().yields(null)
@ -440,8 +429,8 @@ describe('HttpController', function () {
it('should delete the document', function () {
expect(this.DocManager.patchDoc).to.have.been.calledWith(
this.project_id,
this.doc_id
this.projectId,
this.docId
)
})
@ -466,8 +455,8 @@ describe('HttpController', function () {
it('should not pass the invalid field along', function () {
expect(this.DocManager.patchDoc).to.have.been.calledWith(
this.project_id,
this.doc_id,
this.projectId,
this.docId,
{}
)
})
@ -476,38 +465,38 @@ describe('HttpController', function () {
describe('archiveAllDocs', function () {
beforeEach(function () {
this.req.params = { project_id: this.project_id }
this.req.params = { project_id: this.projectId }
this.DocArchiveManager.archiveAllDocs = sinon.stub().callsArg(1)
return this.HttpController.archiveAllDocs(this.req, this.res, this.next)
this.HttpController.archiveAllDocs(this.req, this.res, this.next)
})
it('should archive the project', function () {
return this.DocArchiveManager.archiveAllDocs
.calledWith(this.project_id)
this.DocArchiveManager.archiveAllDocs
.calledWith(this.projectId)
.should.equal(true)
})
return it('should return a 204 (No Content)', function () {
return this.res.sendStatus.calledWith(204).should.equal(true)
it('should return a 204 (No Content)', function () {
this.res.sendStatus.calledWith(204).should.equal(true)
})
})
return describe('destroyAllDocs', function () {
describe('destroyAllDocs', function () {
beforeEach(function () {
this.req.params = { project_id: this.project_id }
this.req.params = { project_id: this.projectId }
this.DocArchiveManager.destroyAllDocs = sinon.stub().callsArg(1)
return this.HttpController.destroyAllDocs(this.req, this.res, this.next)
this.HttpController.destroyAllDocs(this.req, this.res, this.next)
})
it('should destroy the docs', function () {
return sinon.assert.calledWith(
sinon.assert.calledWith(
this.DocArchiveManager.destroyAllDocs,
this.project_id
this.projectId
)
})
return it('should return 204', function () {
return sinon.assert.calledWith(this.res.sendStatus, 204)
it('should return 204', function () {
sinon.assert.calledWith(this.res.sendStatus, 204)
})
})
})

View file

@ -1,13 +1,3 @@
/* eslint-disable
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
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const SandboxedModule = require('sandboxed-module')
const sinon = require('sinon')
const modulePath = require('path').join(
@ -34,7 +24,7 @@ describe('MongoManager', function () {
this.project_id = ObjectId().toString()
this.doc_id = ObjectId().toString()
this.callback = sinon.stub()
return (this.stubbedErr = new Error('hello world'))
this.stubbedErr = new Error('hello world')
})
describe('findDoc', function () {
@ -42,7 +32,7 @@ describe('MongoManager', function () {
this.doc = { name: 'mock-doc' }
this.db.docs.findOne = sinon.stub().callsArgWith(2, null, this.doc)
this.filter = { lines: true }
return this.MongoManager.findDoc(
this.MongoManager.findDoc(
this.project_id,
this.doc_id,
this.filter,
@ -64,8 +54,8 @@ describe('MongoManager', function () {
.should.equal(true)
})
return it('should call the callback with the doc', function () {
return this.callback.calledWith(null, this.doc).should.equal(true)
it('should call the callback with the doc', function () {
this.callback.calledWith(null, this.doc).should.equal(true)
})
})
@ -112,7 +102,7 @@ describe('MongoManager', function () {
describe('with included_deleted = false', function () {
beforeEach(function () {
return this.MongoManager.getProjectsDocs(
this.MongoManager.getProjectsDocs(
this.project_id,
{ include_deleted: false },
this.filter,
@ -121,7 +111,7 @@ describe('MongoManager', function () {
})
it('should find the non-deleted docs via the project_id', function () {
return this.db.docs.find
this.db.docs.find
.calledWith(
{
project_id: ObjectId(this.project_id),
@ -134,16 +124,16 @@ describe('MongoManager', function () {
.should.equal(true)
})
return it('should call the callback with the docs', function () {
return this.callback
it('should call the callback with the docs', function () {
this.callback
.calledWith(null, [this.doc, this.doc3, this.doc4])
.should.equal(true)
})
})
return describe('with included_deleted = true', function () {
describe('with included_deleted = true', function () {
beforeEach(function () {
return this.MongoManager.getProjectsDocs(
this.MongoManager.getProjectsDocs(
this.project_id,
{ include_deleted: true },
this.filter,
@ -152,7 +142,7 @@ describe('MongoManager', function () {
})
it('should find all via the project_id', function () {
return this.db.docs.find
this.db.docs.find
.calledWith(
{
project_id: ObjectId(this.project_id),
@ -164,8 +154,8 @@ describe('MongoManager', function () {
.should.equal(true)
})
return it('should call the callback with the docs', function () {
return this.callback
it('should call the callback with the docs', function () {
this.callback
.calledWith(null, [this.doc, this.doc3, this.doc4])
.should.equal(true)
})
@ -218,11 +208,11 @@ describe('MongoManager', function () {
describe('upsertIntoDocCollection', function () {
beforeEach(function () {
this.db.docs.updateOne = sinon.stub().callsArgWith(3, this.stubbedErr)
return (this.oldRev = 77)
this.oldRev = 77
})
it('should upsert the document', function (done) {
return this.MongoManager.upsertIntoDocCollection(
this.MongoManager.upsertIntoDocCollection(
this.project_id,
this.doc_id,
{ lines: this.lines },
@ -233,19 +223,19 @@ describe('MongoManager', function () {
assert.equal(args[1].$set.lines, this.lines)
assert.equal(args[1].$inc.rev, 1)
assert.deepEqual(args[1].$set.project_id, ObjectId(this.project_id))
return done()
done()
}
)
})
return it('should return the error', function (done) {
return this.MongoManager.upsertIntoDocCollection(
it('should return the error', function (done) {
this.MongoManager.upsertIntoDocCollection(
this.project_id,
this.doc_id,
{ lines: this.lines },
err => {
err.should.equal(this.stubbedErr)
return done()
done()
}
)
})
@ -255,17 +245,17 @@ describe('MongoManager', function () {
beforeEach(function (done) {
this.db.docs.deleteOne = sinon.stub().yields()
this.db.docOps.deleteOne = sinon.stub().yields()
return this.MongoManager.destroyDoc('123456789012', done)
this.MongoManager.destroyDoc('123456789012', done)
})
it('should destroy the doc', function () {
return sinon.assert.calledWith(this.db.docs.deleteOne, {
sinon.assert.calledWith(this.db.docs.deleteOne, {
_id: ObjectId('123456789012'),
})
})
return it('should destroy the docOps', function () {
return sinon.assert.calledWith(this.db.docOps.deleteOne, {
it('should destroy the docOps', function () {
sinon.assert.calledWith(this.db.docOps.deleteOne, {
doc_id: ObjectId('123456789012'),
})
})
@ -276,11 +266,11 @@ describe('MongoManager', function () {
beforeEach(function () {
this.doc = { version: (this.version = 42) }
this.db.docOps.findOne = sinon.stub().callsArgWith(2, null, this.doc)
return this.MongoManager.getDocVersion(this.doc_id, this.callback)
this.MongoManager.getDocVersion(this.doc_id, this.callback)
})
it('should look for the doc in the database', function () {
return this.db.docOps.findOne
this.db.docOps.findOne
.calledWith(
{ doc_id: ObjectId(this.doc_id) },
{
@ -290,19 +280,19 @@ describe('MongoManager', function () {
.should.equal(true)
})
return it('should call the callback with the version', function () {
return this.callback.calledWith(null, this.version).should.equal(true)
it('should call the callback with the version', function () {
this.callback.calledWith(null, this.version).should.equal(true)
})
})
return describe("when the doc doesn't exist", function () {
describe("when the doc doesn't exist", function () {
beforeEach(function () {
this.db.docOps.findOne = sinon.stub().callsArgWith(2, null, null)
return this.MongoManager.getDocVersion(this.doc_id, this.callback)
this.MongoManager.getDocVersion(this.doc_id, this.callback)
})
return it('should call the callback with 0', function () {
return this.callback.calledWith(null, 0).should.equal(true)
it('should call the callback with 0', function () {
this.callback.calledWith(null, 0).should.equal(true)
})
})
})
@ -311,15 +301,11 @@ describe('MongoManager', function () {
beforeEach(function () {
this.version = 42
this.db.docOps.updateOne = sinon.stub().callsArg(3)
return this.MongoManager.setDocVersion(
this.doc_id,
this.version,
this.callback
)
this.MongoManager.setDocVersion(this.doc_id, this.version, this.callback)
})
it('should update the doc version', function () {
return this.db.docOps.updateOne
this.db.docOps.updateOne
.calledWith(
{
doc_id: ObjectId(this.doc_id),
@ -336,8 +322,8 @@ describe('MongoManager', function () {
.should.equal(true)
})
return it('should call the callback', function () {
return this.callback.called.should.equal(true)
it('should call the callback', function () {
this.callback.called.should.equal(true)
})
})