diff --git a/services/docstore/app/js/HttpController.js b/services/docstore/app/js/HttpController.js index a29ec35c96..177751bddd 100644 --- a/services/docstore/app/js/HttpController.js +++ b/services/docstore/app/js/HttpController.js @@ -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, } diff --git a/services/docstore/app/js/MongoManager.js b/services/docstore/app/js/MongoManager.js index b42dc1eaa4..1f5f1b1c4e 100644 --- a/services/docstore/app/js/MongoManager.js +++ b/services/docstore/app/js/MongoManager.js @@ -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]) } diff --git a/services/docstore/test/unit/js/HttpControllerTests.js b/services/docstore/test/unit/js/HttpControllerTests.js index e60e0e705c..4e6f714438 100644 --- a/services/docstore/test/unit/js/HttpControllerTests.js +++ b/services/docstore/test/unit/js/HttpControllerTests.js @@ -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) }) }) }) diff --git a/services/docstore/test/unit/js/MongoManagerTests.js b/services/docstore/test/unit/js/MongoManagerTests.js index 40ff99f151..b2ce897fb5 100644 --- a/services/docstore/test/unit/js/MongoManagerTests.js +++ b/services/docstore/test/unit/js/MongoManagerTests.js @@ -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) }) })