TrackChangesClient.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS101: Remove unnecessary use of Array.from
  11. * DS102: Remove unnecessary code created because of implicit returns
  12. * DS207: Consider shorter variations of null checks
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. let TrackChangesClient
  16. const async = require('async')
  17. const zlib = require('zlib')
  18. const request = require('request')
  19. const Settings = require('settings-sharelatex')
  20. const rclient = require('@overleaf/redis-wrapper').createClient(
  21. Settings.redis.history
  22. ) // Only works locally for now
  23. const Keys = Settings.redis.history.key_schema
  24. const { db, ObjectId } = require('../../../../app/js/mongodb')
  25. const aws = require('aws-sdk')
  26. const s3 = new aws.S3({
  27. accessKeyId: Settings.trackchanges.s3.key,
  28. secretAccessKey: Settings.trackchanges.s3.secret,
  29. endpoint: Settings.trackchanges.s3.endpoint,
  30. s3ForcePathStyle: Settings.trackchanges.s3.pathStyle
  31. })
  32. const S3_BUCKET = Settings.trackchanges.stores.doc_history
  33. module.exports = TrackChangesClient = {
  34. flushAndGetCompressedUpdates(project_id, doc_id, callback) {
  35. if (callback == null) {
  36. callback = function (error, updates) {}
  37. }
  38. return TrackChangesClient.flushDoc(project_id, doc_id, (error) => {
  39. if (error != null) {
  40. return callback(error)
  41. }
  42. return TrackChangesClient.getCompressedUpdates(doc_id, callback)
  43. })
  44. },
  45. flushDoc(project_id, doc_id, callback) {
  46. if (callback == null) {
  47. callback = function (error) {}
  48. }
  49. return request.post(
  50. {
  51. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/flush`
  52. },
  53. (error, response, body) => {
  54. response.statusCode.should.equal(204)
  55. return callback(error)
  56. }
  57. )
  58. },
  59. flushProject(project_id, callback) {
  60. if (callback == null) {
  61. callback = function (error) {}
  62. }
  63. return request.post(
  64. {
  65. url: `http://localhost:3015/project/${project_id}/flush`
  66. },
  67. (error, response, body) => {
  68. response.statusCode.should.equal(204)
  69. return callback(error)
  70. }
  71. )
  72. },
  73. getCompressedUpdates(doc_id, callback) {
  74. if (callback == null) {
  75. callback = function (error, updates) {}
  76. }
  77. return db.docHistory
  78. .find({ doc_id: ObjectId(doc_id) })
  79. .sort({ 'meta.end_ts': 1 })
  80. .toArray(callback)
  81. },
  82. getProjectMetaData(project_id, callback) {
  83. if (callback == null) {
  84. callback = function (error, updates) {}
  85. }
  86. return db.projectHistoryMetaData.findOne(
  87. {
  88. project_id: ObjectId(project_id)
  89. },
  90. callback
  91. )
  92. },
  93. setPreserveHistoryForProject(project_id, callback) {
  94. if (callback == null) {
  95. callback = function (error) {}
  96. }
  97. return db.projectHistoryMetaData.updateOne(
  98. {
  99. project_id: ObjectId(project_id)
  100. },
  101. {
  102. $set: { preserveHistory: true }
  103. },
  104. {
  105. upsert: true
  106. },
  107. callback
  108. )
  109. },
  110. pushRawUpdates(project_id, doc_id, updates, callback) {
  111. if (callback == null) {
  112. callback = function (error) {}
  113. }
  114. return rclient.sadd(
  115. Keys.docsWithHistoryOps({ project_id }),
  116. doc_id,
  117. (error) => {
  118. if (error != null) {
  119. return callback(error)
  120. }
  121. return rclient.rpush(
  122. Keys.uncompressedHistoryOps({ doc_id }),
  123. ...Array.from(Array.from(updates).map((u) => JSON.stringify(u))),
  124. callback
  125. )
  126. }
  127. )
  128. },
  129. getDiff(project_id, doc_id, from, to, callback) {
  130. if (callback == null) {
  131. callback = function (error, diff) {}
  132. }
  133. return request.get(
  134. {
  135. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/diff?from=${from}&to=${to}`
  136. },
  137. (error, response, body) => {
  138. response.statusCode.should.equal(200)
  139. return callback(null, JSON.parse(body))
  140. }
  141. )
  142. },
  143. getUpdates(project_id, options, callback) {
  144. if (callback == null) {
  145. callback = function (error, body) {}
  146. }
  147. return request.get(
  148. {
  149. url: `http://localhost:3015/project/${project_id}/updates?before=${options.before}&min_count=${options.min_count}`
  150. },
  151. (error, response, body) => {
  152. response.statusCode.should.equal(200)
  153. return callback(null, JSON.parse(body))
  154. }
  155. )
  156. },
  157. restoreDoc(project_id, doc_id, version, user_id, callback) {
  158. if (callback == null) {
  159. callback = function (error) {}
  160. }
  161. return request.post(
  162. {
  163. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/version/${version}/restore`,
  164. headers: {
  165. 'X-User-Id': user_id
  166. }
  167. },
  168. (error, response, body) => {
  169. response.statusCode.should.equal(204)
  170. return callback(null)
  171. }
  172. )
  173. },
  174. pushDocHistory(project_id, doc_id, callback) {
  175. if (callback == null) {
  176. callback = function (error) {}
  177. }
  178. return request.post(
  179. {
  180. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/push`
  181. },
  182. (error, response, body) => {
  183. response.statusCode.should.equal(204)
  184. return callback(error)
  185. }
  186. )
  187. },
  188. pullDocHistory(project_id, doc_id, callback) {
  189. if (callback == null) {
  190. callback = function (error) {}
  191. }
  192. return request.post(
  193. {
  194. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/pull`
  195. },
  196. (error, response, body) => {
  197. response.statusCode.should.equal(204)
  198. return callback(error)
  199. }
  200. )
  201. },
  202. waitForS3(done, retries) {
  203. if (retries == null) {
  204. retries = 42
  205. }
  206. if (!Settings.trackchanges.s3.endpoint) {
  207. return done()
  208. }
  209. return request.get(`${Settings.trackchanges.s3.endpoint}/`, (err, res) => {
  210. if (res && res.statusCode < 500) {
  211. return done()
  212. }
  213. if (retries === 0) {
  214. return done(err || new Error(`s3 returned ${res.statusCode}`))
  215. }
  216. return setTimeout(
  217. () => TrackChangesClient.waitForS3(done, --retries),
  218. 1000
  219. )
  220. })
  221. },
  222. getS3Doc(project_id, doc_id, pack_id, callback) {
  223. if (callback == null) {
  224. callback = function (error, body) {}
  225. }
  226. const params = {
  227. Bucket: S3_BUCKET,
  228. Key: `${project_id}/changes-${doc_id}/pack-${pack_id}`
  229. }
  230. return s3.getObject(params, (error, data) => {
  231. if (error != null) {
  232. return callback(error)
  233. }
  234. const body = data.Body
  235. if (body == null) {
  236. return callback(new Error('empty response from s3'))
  237. }
  238. return zlib.gunzip(body, (err, result) => {
  239. if (err != null) {
  240. return callback(err)
  241. }
  242. return callback(null, JSON.parse(result.toString()))
  243. })
  244. })
  245. },
  246. removeS3Doc(project_id, doc_id, callback) {
  247. if (callback == null) {
  248. callback = function (error, res, body) {}
  249. }
  250. let params = {
  251. Bucket: S3_BUCKET,
  252. Prefix: `${project_id}/changes-${doc_id}`
  253. }
  254. return s3.listObjects(params, (error, data) => {
  255. if (error != null) {
  256. return callback(error)
  257. }
  258. params = {
  259. Bucket: S3_BUCKET,
  260. Delete: {
  261. Objects: data.Contents.map((s3object) => ({ Key: s3object.Key }))
  262. }
  263. }
  264. return s3.deleteObjects(params, callback)
  265. })
  266. }
  267. }