DocManager.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-dupe-keys,
  5. no-undef,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let DocManager
  16. const MongoManager = require('./MongoManager')
  17. const Errors = require('./Errors')
  18. const logger = require('logger-sharelatex')
  19. const _ = require('underscore')
  20. const DocArchive = require('./DocArchiveManager')
  21. const RangeManager = require('./RangeManager')
  22. const Settings = require('@overleaf/settings')
  23. module.exports = DocManager = {
  24. // TODO: For historical reasons, the doc version is currently stored in the docOps
  25. // collection (which is all that this collection contains). In future, we should
  26. // migrate this version property to be part of the docs collection, to guarantee
  27. // consitency between lines and version when writing/reading, and for a simpler schema.
  28. _getDoc(project_id, doc_id, filter, callback) {
  29. if (filter == null) {
  30. filter = {}
  31. }
  32. if (callback == null) {
  33. callback = function (error, doc) {}
  34. }
  35. if (filter.inS3 !== true) {
  36. return callback('must include inS3 when getting doc')
  37. }
  38. return MongoManager.findDoc(
  39. project_id,
  40. doc_id,
  41. filter,
  42. function (err, doc) {
  43. if (err != null) {
  44. return callback(err)
  45. } else if (doc == null) {
  46. return callback(
  47. new Errors.NotFoundError(
  48. `No such doc: ${doc_id} in project ${project_id}`
  49. )
  50. )
  51. } else if (doc != null ? doc.inS3 : undefined) {
  52. return DocArchive.unarchiveDoc(project_id, doc_id, function (err) {
  53. if (err != null) {
  54. logger.err({ err, project_id, doc_id }, 'error unarchiving doc')
  55. return callback(err)
  56. }
  57. return DocManager._getDoc(project_id, doc_id, filter, callback)
  58. })
  59. } else {
  60. if (filter.version) {
  61. return MongoManager.getDocVersion(
  62. doc_id,
  63. function (error, version) {
  64. if (error != null) {
  65. return callback(error)
  66. }
  67. doc.version = version
  68. return callback(err, doc)
  69. }
  70. )
  71. } else {
  72. return callback(err, doc)
  73. }
  74. }
  75. }
  76. )
  77. },
  78. isDocDeleted(projectId, docId, callback) {
  79. MongoManager.findDoc(
  80. projectId,
  81. docId,
  82. { deleted: true },
  83. function (err, doc) {
  84. if (err) {
  85. return callback(err)
  86. }
  87. if (!doc) {
  88. return callback(
  89. new Errors.NotFoundError(
  90. `No such project/doc: ${projectId}/${docId}`
  91. )
  92. )
  93. }
  94. // `doc.deleted` is `undefined` for non deleted docs
  95. callback(null, Boolean(doc.deleted))
  96. }
  97. )
  98. },
  99. getFullDoc(project_id, doc_id, callback) {
  100. if (callback == null) {
  101. callback = function (err, doc) {}
  102. }
  103. return DocManager._getDoc(
  104. project_id,
  105. doc_id,
  106. {
  107. lines: true,
  108. rev: true,
  109. deleted: true,
  110. version: true,
  111. ranges: true,
  112. inS3: true,
  113. },
  114. function (err, doc) {
  115. if (err != null) {
  116. return callback(err)
  117. }
  118. return callback(err, doc)
  119. }
  120. )
  121. },
  122. // returns the doc without any version information
  123. _peekRawDoc(project_id, doc_id, callback) {
  124. MongoManager.findDoc(
  125. project_id,
  126. doc_id,
  127. {
  128. lines: true,
  129. rev: true,
  130. deleted: true,
  131. version: true,
  132. ranges: true,
  133. inS3: true,
  134. },
  135. (err, doc) => {
  136. if (err) return callback(err)
  137. if (doc == null) {
  138. return callback(
  139. new Errors.NotFoundError(
  140. `No such doc: ${doc_id} in project ${project_id}`
  141. )
  142. )
  143. }
  144. if (doc && !doc.inS3) {
  145. return callback(null, doc)
  146. }
  147. // skip the unarchiving to mongo when getting a doc
  148. DocArchive.getDoc(project_id, doc_id, function (err, archivedDoc) {
  149. if (err != null) {
  150. logger.err(
  151. { err, project_id, doc_id },
  152. 'error getting doc from archive'
  153. )
  154. return callback(err)
  155. }
  156. doc = _.extend(doc, archivedDoc)
  157. callback(null, doc)
  158. })
  159. }
  160. )
  161. },
  162. // get the doc from mongo if possible, or from the persistent store otherwise,
  163. // without unarchiving it (avoids unnecessary writes to mongo)
  164. peekDoc(project_id, doc_id, callback) {
  165. DocManager._peekRawDoc(project_id, doc_id, (err, doc) => {
  166. if (err) {
  167. return callback(err)
  168. }
  169. MongoManager.withRevCheck(
  170. doc,
  171. MongoManager.getDocVersion,
  172. function (error, version) {
  173. // If the doc has been modified while we were retrieving it, we
  174. // will get a DocModified error
  175. if (error != null) {
  176. return callback(error)
  177. }
  178. doc.version = version
  179. return callback(err, doc)
  180. }
  181. )
  182. })
  183. },
  184. getDocLines(project_id, doc_id, callback) {
  185. if (callback == null) {
  186. callback = function (err, doc) {}
  187. }
  188. return DocManager._getDoc(
  189. project_id,
  190. doc_id,
  191. { lines: true, inS3: true },
  192. function (err, doc) {
  193. if (err != null) {
  194. return callback(err)
  195. }
  196. return callback(err, doc)
  197. }
  198. )
  199. },
  200. getAllDeletedDocs(project_id, filter, callback) {
  201. MongoManager.getProjectsDeletedDocs(project_id, filter, callback)
  202. },
  203. getAllNonDeletedDocs(project_id, filter, callback) {
  204. if (callback == null) {
  205. callback = function (error, docs) {}
  206. }
  207. return DocArchive.unArchiveAllDocs(project_id, function (error) {
  208. if (error != null) {
  209. return callback(error)
  210. }
  211. return MongoManager.getProjectsDocs(
  212. project_id,
  213. { include_deleted: false },
  214. filter,
  215. function (error, docs) {
  216. if (typeof err !== 'undefined' && err !== null) {
  217. return callback(error)
  218. } else if (docs == null) {
  219. return callback(
  220. new Errors.NotFoundError(`No docs for project ${project_id}`)
  221. )
  222. } else {
  223. return callback(null, docs)
  224. }
  225. }
  226. )
  227. })
  228. },
  229. updateDoc(project_id, doc_id, lines, version, ranges, callback) {
  230. if (callback == null) {
  231. callback = function (error, modified, rev) {}
  232. }
  233. if (lines == null || version == null || ranges == null) {
  234. return callback(new Error('no lines, version or ranges provided'))
  235. }
  236. return DocManager._getDoc(
  237. project_id,
  238. doc_id,
  239. {
  240. version: true,
  241. rev: true,
  242. lines: true,
  243. version: true,
  244. ranges: true,
  245. inS3: true,
  246. },
  247. function (err, doc) {
  248. let updateLines, updateRanges, updateVersion
  249. if (err != null && !(err instanceof Errors.NotFoundError)) {
  250. logger.err(
  251. { project_id, doc_id, err },
  252. 'error getting document for update'
  253. )
  254. return callback(err)
  255. }
  256. ranges = RangeManager.jsonRangesToMongo(ranges)
  257. if (doc == null) {
  258. // If the document doesn't exist, we'll make sure to create/update all parts of it.
  259. updateLines = true
  260. updateVersion = true
  261. updateRanges = true
  262. } else {
  263. updateLines = !_.isEqual(doc.lines, lines)
  264. updateVersion = doc.version !== version
  265. updateRanges = RangeManager.shouldUpdateRanges(doc.ranges, ranges)
  266. }
  267. let modified = false
  268. let rev = (doc != null ? doc.rev : undefined) || 0
  269. const updateLinesAndRangesIfNeeded = function (cb) {
  270. if (updateLines || updateRanges) {
  271. const update = {}
  272. if (updateLines) {
  273. update.lines = lines
  274. }
  275. if (updateRanges) {
  276. update.ranges = ranges
  277. }
  278. logger.log({ project_id, doc_id }, 'updating doc lines and ranges')
  279. modified = true
  280. rev += 1 // rev will be incremented in mongo by MongoManager.upsertIntoDocCollection
  281. return MongoManager.upsertIntoDocCollection(
  282. project_id,
  283. doc_id,
  284. update,
  285. cb
  286. )
  287. } else {
  288. logger.log(
  289. { project_id, doc_id },
  290. 'doc lines have not changed - not updating'
  291. )
  292. return cb()
  293. }
  294. }
  295. const updateVersionIfNeeded = function (cb) {
  296. if (updateVersion) {
  297. logger.log(
  298. {
  299. project_id,
  300. doc_id,
  301. oldVersion: doc != null ? doc.version : undefined,
  302. newVersion: version,
  303. },
  304. 'updating doc version'
  305. )
  306. modified = true
  307. return MongoManager.setDocVersion(doc_id, version, cb)
  308. } else {
  309. logger.log(
  310. { project_id, doc_id, version },
  311. 'doc version has not changed - not updating'
  312. )
  313. return cb()
  314. }
  315. }
  316. return updateLinesAndRangesIfNeeded(function (error) {
  317. if (error != null) {
  318. return callback(error)
  319. }
  320. return updateVersionIfNeeded(function (error) {
  321. if (error != null) {
  322. return callback(error)
  323. }
  324. return callback(null, modified, rev)
  325. })
  326. })
  327. }
  328. )
  329. },
  330. patchDoc(project_id, doc_id, meta, callback) {
  331. const projection = { _id: 1, deleted: true }
  332. MongoManager.findDoc(project_id, doc_id, projection, (error, doc) => {
  333. if (error != null) {
  334. return callback(error)
  335. }
  336. if (!doc) {
  337. return callback(
  338. new Errors.NotFoundError(
  339. `No such project/doc to delete: ${project_id}/${doc_id}`
  340. )
  341. )
  342. }
  343. if (meta.deleted && Settings.docstore.archiveOnSoftDelete) {
  344. // The user will not read this doc anytime soon. Flush it out of mongo.
  345. DocArchive.archiveDocById(project_id, doc_id, err => {
  346. if (err) {
  347. logger.warn(
  348. { project_id, doc_id, err },
  349. 'archiving a single doc in the background failed'
  350. )
  351. }
  352. })
  353. }
  354. MongoManager.patchDoc(project_id, doc_id, meta, callback)
  355. })
  356. },
  357. }