ProjectHistoryRedisManager.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /* eslint-disable
  2. camelcase,
  3. */
  4. // TODO: This file was created by bulk-decaffeinate.
  5. // Fix any style issues and re-enable lint.
  6. /*
  7. * decaffeinate suggestions:
  8. * DS101: Remove unnecessary use of Array.from
  9. * DS102: Remove unnecessary code created because of implicit returns
  10. * DS201: Simplify complex destructure assignments
  11. * DS207: Consider shorter variations of null checks
  12. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  13. */
  14. let ProjectHistoryRedisManager
  15. const Settings = require('@overleaf/settings')
  16. const projectHistoryKeys = Settings.redis?.project_history?.key_schema
  17. const rclient = require('@overleaf/redis-wrapper').createClient(
  18. Settings.redis.project_history
  19. )
  20. const logger = require('@overleaf/logger')
  21. const metrics = require('./Metrics')
  22. const { docIsTooLarge } = require('./Limits')
  23. module.exports = ProjectHistoryRedisManager = {
  24. queueOps(project_id, ...rest) {
  25. // Record metric for ops pushed onto queue
  26. const callback = rest.pop()
  27. const ops = rest
  28. for (const op of Array.from(ops)) {
  29. metrics.summary('redis.projectHistoryOps', op.length, { status: 'push' })
  30. }
  31. const multi = rclient.multi()
  32. // Push the ops onto the project history queue
  33. multi.rpush(
  34. projectHistoryKeys.projectHistoryOps({ project_id }),
  35. ...Array.from(ops)
  36. )
  37. // To record the age of the oldest op on the queue set a timestamp if not
  38. // already present (SETNX).
  39. multi.setnx(
  40. projectHistoryKeys.projectHistoryFirstOpTimestamp({ project_id }),
  41. Date.now()
  42. )
  43. return multi.exec(function (error, result) {
  44. if (error != null) {
  45. return callback(error)
  46. }
  47. // return the number of entries pushed onto the project history queue
  48. return callback(null, result[0])
  49. })
  50. },
  51. queueRenameEntity(
  52. project_id,
  53. projectHistoryId,
  54. entity_type,
  55. entity_id,
  56. user_id,
  57. projectUpdate,
  58. callback
  59. ) {
  60. projectUpdate = {
  61. pathname: projectUpdate.pathname,
  62. new_pathname: projectUpdate.newPathname,
  63. meta: {
  64. user_id,
  65. ts: new Date(),
  66. },
  67. version: projectUpdate.version,
  68. projectHistoryId,
  69. }
  70. projectUpdate[entity_type] = entity_id
  71. logger.debug(
  72. { project_id, projectUpdate },
  73. 'queue rename operation to project-history'
  74. )
  75. const jsonUpdate = JSON.stringify(projectUpdate)
  76. return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
  77. },
  78. queueAddEntity(
  79. project_id,
  80. projectHistoryId,
  81. entity_type,
  82. entitiy_id,
  83. user_id,
  84. projectUpdate,
  85. callback
  86. ) {
  87. if (callback == null) {
  88. callback = function () {}
  89. }
  90. projectUpdate = {
  91. pathname: projectUpdate.pathname,
  92. docLines: projectUpdate.docLines,
  93. url: projectUpdate.url,
  94. meta: {
  95. user_id,
  96. ts: new Date(),
  97. },
  98. version: projectUpdate.version,
  99. projectHistoryId,
  100. }
  101. projectUpdate[entity_type] = entitiy_id
  102. logger.debug(
  103. { project_id, projectUpdate },
  104. 'queue add operation to project-history'
  105. )
  106. const jsonUpdate = JSON.stringify(projectUpdate)
  107. return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
  108. },
  109. queueResyncProjectStructure(
  110. project_id,
  111. projectHistoryId,
  112. docs,
  113. files,
  114. callback
  115. ) {
  116. logger.debug({ project_id, docs, files }, 'queue project structure resync')
  117. const projectUpdate = {
  118. resyncProjectStructure: { docs, files },
  119. projectHistoryId,
  120. meta: {
  121. ts: new Date(),
  122. },
  123. }
  124. const jsonUpdate = JSON.stringify(projectUpdate)
  125. return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
  126. },
  127. queueResyncDocContent(
  128. project_id,
  129. projectHistoryId,
  130. doc_id,
  131. lines,
  132. version,
  133. pathname,
  134. callback
  135. ) {
  136. logger.debug(
  137. { project_id, doc_id, lines, version, pathname },
  138. 'queue doc content resync'
  139. )
  140. const projectUpdate = {
  141. resyncDocContent: {
  142. content: lines.join('\n'),
  143. version,
  144. },
  145. projectHistoryId,
  146. path: pathname,
  147. doc: doc_id,
  148. meta: {
  149. ts: new Date(),
  150. },
  151. }
  152. const jsonUpdate = JSON.stringify(projectUpdate)
  153. // Do an optimised size check on the docLines using the serialised
  154. // project update length as an upper bound
  155. const sizeBound = jsonUpdate.length
  156. if (docIsTooLarge(sizeBound, lines, Settings.max_doc_length)) {
  157. const err = new Error(
  158. 'blocking resync doc content insert into project history queue: doc is too large'
  159. )
  160. logger.error({ project_id, doc_id, err, docSize: sizeBound }, err.message)
  161. return callback(err)
  162. }
  163. return ProjectHistoryRedisManager.queueOps(project_id, jsonUpdate, callback)
  164. },
  165. }