ProjectHistoryRedisManager.js 5.0 KB

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