TrackChangesClient.js 7.4 KB

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