TrackChangesClient.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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/mongojs");
  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) { callback = function(error, updates) {}; }
  34. return TrackChangesClient.flushDoc(project_id, doc_id, (error) => {
  35. if (error != null) { return callback(error); }
  36. return TrackChangesClient.getCompressedUpdates(doc_id, callback);
  37. });
  38. },
  39. flushDoc(project_id, doc_id, callback) {
  40. if (callback == null) { callback = function(error) {}; }
  41. return request.post({
  42. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/flush`
  43. }, (error, response, body) => {
  44. response.statusCode.should.equal(204);
  45. return callback(error);
  46. });
  47. },
  48. flushProject(project_id, callback) {
  49. if (callback == null) { callback = function(error) {}; }
  50. return request.post({
  51. url: `http://localhost:3015/project/${project_id}/flush`
  52. }, (error, response, body) => {
  53. response.statusCode.should.equal(204);
  54. return callback(error);
  55. });
  56. },
  57. getCompressedUpdates(doc_id, callback) {
  58. if (callback == null) { callback = function(error, updates) {}; }
  59. return db.docHistory
  60. .find({doc_id: ObjectId(doc_id)})
  61. .sort({"meta.end_ts": 1})
  62. .toArray(callback);
  63. },
  64. getProjectMetaData(project_id, callback) {
  65. if (callback == null) { callback = function(error, updates) {}; }
  66. return db.projectHistoryMetaData
  67. .find({
  68. project_id: ObjectId(project_id)
  69. },
  70. (error, projects) => callback(error, projects[0]));
  71. },
  72. setPreserveHistoryForProject(project_id, callback) {
  73. if (callback == null) { callback = function(error) {}; }
  74. return db.projectHistoryMetaData.update({
  75. project_id: ObjectId(project_id)
  76. }, {
  77. $set: { preserveHistory: true }
  78. }, {
  79. upsert: true
  80. }, callback);
  81. },
  82. pushRawUpdates(project_id, doc_id, updates, callback) {
  83. if (callback == null) { callback = function(error) {}; }
  84. return rclient.sadd(Keys.docsWithHistoryOps({project_id}), doc_id, (error) => {
  85. if (error != null) { return callback(error); }
  86. return rclient.rpush(Keys.uncompressedHistoryOps({doc_id}), ...Array.from(((Array.from(updates).map((u) => JSON.stringify(u))))), callback);
  87. });
  88. },
  89. getDiff(project_id, doc_id, from, to, callback) {
  90. if (callback == null) { callback = function(error, diff) {}; }
  91. return request.get({
  92. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/diff?from=${from}&to=${to}`
  93. }, (error, response, body) => {
  94. response.statusCode.should.equal(200);
  95. return callback(null, JSON.parse(body));
  96. });
  97. },
  98. getUpdates(project_id, options, callback) {
  99. if (callback == null) { callback = function(error, body) {}; }
  100. return request.get({
  101. url: `http://localhost:3015/project/${project_id}/updates?before=${options.before}&min_count=${options.min_count}`
  102. }, (error, response, body) => {
  103. response.statusCode.should.equal(200);
  104. return callback(null, JSON.parse(body));
  105. });
  106. },
  107. restoreDoc(project_id, doc_id, version, user_id, callback) {
  108. if (callback == null) { callback = function(error) {}; }
  109. return request.post({
  110. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/version/${version}/restore`,
  111. headers: {
  112. "X-User-Id": user_id
  113. }
  114. }, (error, response, body) => {
  115. response.statusCode.should.equal(204);
  116. return callback(null);
  117. });
  118. },
  119. pushDocHistory(project_id, doc_id, callback) {
  120. if (callback == null) { callback = function(error) {}; }
  121. return request.post({
  122. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/push`
  123. }, (error, response, body) => {
  124. response.statusCode.should.equal(204);
  125. return callback(error);
  126. });
  127. },
  128. pullDocHistory(project_id, doc_id, callback) {
  129. if (callback == null) { callback = function(error) {}; }
  130. return request.post({
  131. url: `http://localhost:3015/project/${project_id}/doc/${doc_id}/pull`
  132. }, (error, response, body) => {
  133. response.statusCode.should.equal(204);
  134. return callback(error);
  135. });
  136. },
  137. waitForS3(done, retries) {
  138. if (retries == null) { retries = 42; }
  139. if (!Settings.trackchanges.s3.endpoint) {
  140. return done();
  141. }
  142. return request.get(`${Settings.trackchanges.s3.endpoint}/`, (err, res) => {
  143. if (res && (res.statusCode < 500)) {
  144. return done();
  145. }
  146. if (retries === 0) {
  147. return done(err || new Error(`s3 returned ${res.statusCode}`));
  148. }
  149. return setTimeout(() => TrackChangesClient.waitForS3(done, --retries)
  150. , 1000);
  151. });
  152. },
  153. getS3Doc(project_id, doc_id, pack_id, callback) {
  154. if (callback == null) { callback = function(error, body) {}; }
  155. const params = {
  156. Bucket: S3_BUCKET,
  157. Key: `${project_id}/changes-${doc_id}/pack-${pack_id}`
  158. };
  159. return s3.getObject(params, (error, data) => {
  160. if (error != null) { return callback(error); }
  161. const body = data.Body;
  162. if ((body == null)) { return callback(new Error("empty response from s3")); }
  163. return zlib.gunzip(body, (err, result) => {
  164. if (err != null) { return callback(err); }
  165. return callback(null, JSON.parse(result.toString()));
  166. });
  167. });
  168. },
  169. removeS3Doc(project_id, doc_id, callback) {
  170. if (callback == null) { callback = function(error, res, body) {}; }
  171. let params = {
  172. Bucket: S3_BUCKET,
  173. Prefix: `${project_id}/changes-${doc_id}`
  174. };
  175. return s3.listObjects(params, (error, data) => {
  176. if (error != null) { return callback(error); }
  177. params = {
  178. Bucket: S3_BUCKET,
  179. Delete: {
  180. Objects: data.Contents.map(s3object => ({Key: s3object.Key}))
  181. }
  182. };
  183. return s3.deleteObjects(params, callback);
  184. });
  185. }
  186. });