MongoManager.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. const { db, ObjectId } = require('./mongodb')
  2. const logger = require('@overleaf/logger')
  3. const metrics = require('@overleaf/metrics')
  4. const Settings = require('@overleaf/settings')
  5. const Errors = require('./Errors')
  6. const { promisify } = require('util')
  7. function findDoc(projectId, docId, filter, callback) {
  8. db.docs.findOne(
  9. {
  10. _id: ObjectId(docId.toString()),
  11. project_id: ObjectId(projectId.toString()),
  12. },
  13. {
  14. projection: filter,
  15. },
  16. callback
  17. )
  18. }
  19. function getProjectsDeletedDocs(projectId, filter, callback) {
  20. db.docs
  21. .find(
  22. {
  23. project_id: ObjectId(projectId.toString()),
  24. deleted: true,
  25. },
  26. {
  27. projection: filter,
  28. sort: { deletedAt: -1 },
  29. limit: Settings.max_deleted_docs,
  30. }
  31. )
  32. .toArray(callback)
  33. }
  34. function getProjectsDocs(projectId, options, filter, callback) {
  35. const query = { project_id: ObjectId(projectId.toString()) }
  36. if (!options.include_deleted) {
  37. query.deleted = { $ne: true }
  38. }
  39. const queryOptions = {
  40. projection: filter,
  41. }
  42. if (options.limit) {
  43. queryOptions.limit = options.limit
  44. }
  45. db.docs.find(query, queryOptions).toArray(callback)
  46. }
  47. function getArchivedProjectDocs(projectId, maxResults, callback) {
  48. const query = {
  49. project_id: ObjectId(projectId.toString()),
  50. inS3: true,
  51. }
  52. db.docs
  53. .find(query, { projection: { _id: 1 }, limit: maxResults })
  54. .toArray(callback)
  55. }
  56. function getNonArchivedProjectDocs(projectId, maxResults, callback) {
  57. const query = {
  58. project_id: ObjectId(projectId.toString()),
  59. inS3: { $ne: true },
  60. }
  61. db.docs.find(query, { limit: maxResults }).toArray(callback)
  62. }
  63. function getNonDeletedArchivedProjectDocs(projectId, maxResults, callback) {
  64. const query = {
  65. project_id: ObjectId(projectId.toString()),
  66. deleted: { $ne: true },
  67. inS3: true,
  68. }
  69. db.docs
  70. .find(query, { projection: { _id: 1 }, limit: maxResults })
  71. .toArray(callback)
  72. }
  73. function upsertIntoDocCollection(projectId, docId, updates, callback) {
  74. const update = {
  75. $set: updates,
  76. $inc: {
  77. rev: 1,
  78. },
  79. $unset: {
  80. inS3: true,
  81. },
  82. }
  83. update.$set.project_id = ObjectId(projectId)
  84. db.docs.updateOne(
  85. { _id: ObjectId(docId) },
  86. update,
  87. { upsert: true },
  88. callback
  89. )
  90. }
  91. function patchDoc(projectId, docId, meta, callback) {
  92. db.docs.updateOne(
  93. {
  94. _id: ObjectId(docId),
  95. project_id: ObjectId(projectId),
  96. },
  97. { $set: meta },
  98. callback
  99. )
  100. }
  101. function markDocAsArchived(docId, rev, callback) {
  102. const update = {
  103. $set: {},
  104. $unset: {},
  105. }
  106. update.$set.inS3 = true
  107. update.$unset.lines = true
  108. update.$unset.ranges = true
  109. const query = {
  110. _id: docId,
  111. rev,
  112. }
  113. db.docs.updateOne(query, update, callback)
  114. }
  115. function getDocVersion(docId, callback) {
  116. db.docOps.findOne(
  117. {
  118. doc_id: ObjectId(docId),
  119. },
  120. {
  121. projection: {
  122. version: 1,
  123. },
  124. },
  125. function (error, doc) {
  126. if (error) {
  127. return callback(error)
  128. }
  129. callback(null, (doc && doc.version) || 0)
  130. }
  131. )
  132. }
  133. function setDocVersion(docId, version, callback) {
  134. db.docOps.updateOne(
  135. {
  136. doc_id: ObjectId(docId),
  137. },
  138. {
  139. $set: { version },
  140. },
  141. {
  142. upsert: true,
  143. },
  144. callback
  145. )
  146. }
  147. function getDocRev(docId, callback) {
  148. db.docs.findOne(
  149. {
  150. _id: ObjectId(docId.toString()),
  151. },
  152. {
  153. projection: { rev: 1 },
  154. },
  155. function (err, doc) {
  156. if (err) {
  157. return callback(err)
  158. }
  159. callback(null, doc && doc.rev)
  160. }
  161. )
  162. }
  163. // Helper method to support optimistic locking. Call the provided method for
  164. // an existing doc and return the result if the rev in mongo is unchanged when
  165. // checked afterwards. If the rev has changed, return a DocModifiedError.
  166. function withRevCheck(doc, method, callback) {
  167. method(doc._id, function (err, result) {
  168. if (err) return callback(err)
  169. getDocRev(doc._id, function (err, currentRev) {
  170. if (err) return callback(err)
  171. if (isNaN(currentRev) || isNaN(doc.rev)) {
  172. return callback(
  173. new Errors.DocRevValueError('doc rev is NaN', {
  174. doc_id: doc._id,
  175. rev: doc.rev,
  176. currentRev,
  177. })
  178. )
  179. }
  180. if (doc.rev !== currentRev) {
  181. return callback(
  182. new Errors.DocModifiedError('doc rev has changed', {
  183. doc_id: doc._id,
  184. rev: doc.rev,
  185. currentRev,
  186. })
  187. )
  188. }
  189. callback(null, result)
  190. })
  191. })
  192. }
  193. function destroyDoc(docId, callback) {
  194. db.docs.deleteOne(
  195. {
  196. _id: ObjectId(docId),
  197. },
  198. function (err) {
  199. if (err) {
  200. return callback(err)
  201. }
  202. db.docOps.deleteOne(
  203. {
  204. doc_id: ObjectId(docId),
  205. },
  206. callback
  207. )
  208. }
  209. )
  210. }
  211. module.exports = {
  212. findDoc,
  213. getProjectsDeletedDocs,
  214. getProjectsDocs,
  215. getArchivedProjectDocs,
  216. getNonArchivedProjectDocs,
  217. getNonDeletedArchivedProjectDocs,
  218. upsertIntoDocCollection,
  219. patchDoc,
  220. markDocAsArchived,
  221. getDocVersion,
  222. setDocVersion,
  223. withRevCheck,
  224. destroyDoc,
  225. }
  226. const methods = Object.getOwnPropertyNames(module.exports)
  227. module.exports.promises = {}
  228. for (const method of methods) {
  229. metrics.timeAsyncMethod(module.exports, method, 'mongo.MongoManager', logger)
  230. module.exports.promises[method] = promisify(module.exports[method])
  231. }