DocManager.js 10.0 KB

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