MongoAWS.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS207: Consider shorter variations of null checks
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. let MongoAWS;
  17. const settings = require("settings-sharelatex");
  18. const logger = require("logger-sharelatex");
  19. const AWS = require('aws-sdk');
  20. const S3S = require('s3-streams');
  21. const {db, ObjectId} = require("./mongojs");
  22. const JSONStream = require("JSONStream");
  23. const ReadlineStream = require("byline");
  24. const zlib = require("zlib");
  25. const Metrics = require("metrics-sharelatex");
  26. const DAYS = 24 * 3600 * 1000; // one day in milliseconds
  27. const createStream = function(streamConstructor, project_id, doc_id, pack_id) {
  28. const AWS_CONFIG = {
  29. accessKeyId: settings.trackchanges.s3.key,
  30. secretAccessKey: settings.trackchanges.s3.secret,
  31. endpoint: settings.trackchanges.s3.endpoint,
  32. s3ForcePathStyle: settings.trackchanges.s3.pathStyle
  33. };
  34. return streamConstructor(new AWS.S3(AWS_CONFIG), {
  35. "Bucket": settings.trackchanges.stores.doc_history,
  36. "Key": project_id+"/changes-"+doc_id+"/pack-"+pack_id
  37. });
  38. };
  39. module.exports = (MongoAWS = {
  40. archivePack(project_id, doc_id, pack_id, _callback) {
  41. if (_callback == null) { _callback = function(error) {}; }
  42. const callback = function(...args) {
  43. _callback(...Array.from(args || []));
  44. return _callback = function() {};
  45. };
  46. const query = {
  47. _id: ObjectId(pack_id),
  48. doc_id: ObjectId(doc_id)
  49. };
  50. if ((project_id == null)) { return callback(new Error("invalid project id")); }
  51. if ((doc_id == null)) { return callback(new Error("invalid doc id")); }
  52. if ((pack_id == null)) { return callback(new Error("invalid pack id")); }
  53. logger.log({project_id, doc_id, pack_id}, "uploading data to s3");
  54. const upload = createStream(S3S.WriteStream, project_id, doc_id, pack_id);
  55. return db.docHistory.findOne(query, function(err, result) {
  56. if (err != null) { return callback(err); }
  57. if ((result == null)) { return callback(new Error("cannot find pack to send to s3")); }
  58. if (result.expiresAt != null) { return callback(new Error("refusing to send pack with TTL to s3")); }
  59. const uncompressedData = JSON.stringify(result);
  60. if (uncompressedData.indexOf("\u0000") !== -1) {
  61. const error = new Error("null bytes found in upload");
  62. logger.error({err: error, project_id, doc_id, pack_id}, error.message);
  63. return callback(error);
  64. }
  65. return zlib.gzip(uncompressedData, function(err, buf) {
  66. logger.log({project_id, doc_id, pack_id, origSize: uncompressedData.length, newSize: buf.length}, "compressed pack");
  67. if (err != null) { return callback(err); }
  68. upload.on('error', err => callback(err));
  69. upload.on('finish', function() {
  70. Metrics.inc("archive-pack");
  71. logger.log({project_id, doc_id, pack_id}, "upload to s3 completed");
  72. return callback(null);
  73. });
  74. upload.write(buf);
  75. return upload.end();
  76. });
  77. });
  78. },
  79. readArchivedPack(project_id, doc_id, pack_id, _callback) {
  80. if (_callback == null) { _callback = function(error, result) {}; }
  81. const callback = function(...args) {
  82. _callback(...Array.from(args || []));
  83. return _callback = function() {};
  84. };
  85. if ((project_id == null)) { return callback(new Error("invalid project id")); }
  86. if ((doc_id == null)) { return callback(new Error("invalid doc id")); }
  87. if ((pack_id == null)) { return callback(new Error("invalid pack id")); }
  88. logger.log({project_id, doc_id, pack_id}, "downloading data from s3");
  89. const download = createStream(S3S.ReadStream, project_id, doc_id, pack_id);
  90. const inputStream = download
  91. .on('open', obj => 1).on('error', err => callback(err));
  92. const gunzip = zlib.createGunzip();
  93. gunzip.setEncoding('utf8');
  94. gunzip.on('error', function(err) {
  95. logger.log({project_id, doc_id, pack_id, err}, "error uncompressing gzip stream");
  96. return callback(err);
  97. });
  98. const outputStream = inputStream.pipe(gunzip);
  99. const parts = [];
  100. outputStream.on('error', err => callback(err));
  101. outputStream.on('end', function() {
  102. let object;
  103. logger.log({project_id, doc_id, pack_id}, "download from s3 completed");
  104. try {
  105. object = JSON.parse(parts.join(''));
  106. } catch (e) {
  107. return callback(e);
  108. }
  109. object._id = ObjectId(object._id);
  110. object.doc_id = ObjectId(object.doc_id);
  111. object.project_id = ObjectId(object.project_id);
  112. for (const op of Array.from(object.pack)) {
  113. if (op._id != null) { op._id = ObjectId(op._id); }
  114. }
  115. return callback(null, object);
  116. });
  117. return outputStream.on('data', data => parts.push(data));
  118. },
  119. unArchivePack(project_id, doc_id, pack_id, callback) {
  120. if (callback == null) { callback = function(error) {}; }
  121. return MongoAWS.readArchivedPack(project_id, doc_id, pack_id, function(err, object) {
  122. if (err != null) { return callback(err); }
  123. Metrics.inc("unarchive-pack");
  124. // allow the object to expire, we can always retrieve it again
  125. object.expiresAt = new Date(Date.now() + (7 * DAYS));
  126. logger.log({project_id, doc_id, pack_id}, "inserting object from s3");
  127. return db.docHistory.insert(object, callback);
  128. });
  129. }
  130. });