DocManager.js 11 KB

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