TrackChangesClient.js 7.7 KB

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