DocArchiveManager.coffee 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. MongoManager = require "./MongoManager"
  2. Errors = require "./Errors"
  3. logger = require "logger-sharelatex"
  4. _ = require "underscore"
  5. async = require "async"
  6. settings = require("settings-sharelatex")
  7. request = require("request")
  8. crypto = require("crypto")
  9. RangeManager = require("./RangeManager")
  10. thirtySeconds = 30 * 1000
  11. module.exports = DocArchive =
  12. archiveAllDocs: (project_id, callback = (err, docs) ->) ->
  13. MongoManager.getProjectsDocs project_id, {include_deleted: true}, {lines: true, ranges: true, rev: true, inS3: true}, (err, docs) ->
  14. if err?
  15. return callback(err)
  16. else if !docs?
  17. return callback new Errors.NotFoundError("No docs for project #{project_id}")
  18. docs = _.filter docs, (doc)-> doc.inS3 != true
  19. jobs = _.map docs, (doc) ->
  20. (cb)->
  21. DocArchive.archiveDoc project_id, doc, cb
  22. async.parallelLimit jobs, 5, callback
  23. archiveDoc: (project_id, doc, callback)->
  24. logger.log project_id: project_id, doc_id: doc._id, "sending doc to s3"
  25. try
  26. options = DocArchive.buildS3Options(project_id+"/"+doc._id)
  27. catch e
  28. return callback e
  29. DocArchive._mongoDocToS3Doc doc, (error, json_doc) ->
  30. return callback(error) if error?
  31. options.body = json_doc
  32. options.headers =
  33. 'Content-Type': "application/json"
  34. request.put options, (err, res) ->
  35. if err? || res.statusCode != 200
  36. logger.err err:err, res:res, project_id:project_id, doc_id: doc._id, statusCode: res?.statusCode, "something went wrong archiving doc in aws"
  37. return callback new Error("Error in S3 request")
  38. md5lines = crypto.createHash("md5").update(json_doc, "utf8").digest("hex")
  39. md5response = res.headers.etag.toString().replace(/\"/g, '')
  40. if md5lines != md5response
  41. logger.err responseMD5:md5response, linesMD5:md5lines, project_id:project_id, doc_id: doc?._id, "err in response md5 from s3"
  42. return callback new Error("Error in S3 md5 response")
  43. MongoManager.markDocAsArchived doc._id, doc.rev, (err) ->
  44. return callback(err) if err?
  45. callback()
  46. unArchiveAllDocs: (project_id, callback = (err) ->) ->
  47. MongoManager.getArchivedProjectDocs project_id, (err, docs) ->
  48. if err?
  49. logger.err err:err, project_id:project_id, "error unarchiving all docs"
  50. return callback(err)
  51. else if !docs?
  52. return callback new Errors.NotFoundError("No docs for project #{project_id}")
  53. jobs = _.map docs, (doc) ->
  54. (cb)->
  55. if !doc.inS3?
  56. return cb()
  57. else
  58. DocArchive.unarchiveDoc project_id, doc._id, cb
  59. async.parallelLimit jobs, 5, callback
  60. unarchiveDoc: (project_id, doc_id, callback)->
  61. logger.log project_id: project_id, doc_id: doc_id, "getting doc from s3"
  62. try
  63. options = DocArchive.buildS3Options(project_id+"/"+doc_id)
  64. catch e
  65. return callback e
  66. options.json = true
  67. request.get options, (err, res, doc)->
  68. if err? || res.statusCode != 200
  69. logger.err err:err, res:res, project_id:project_id, doc_id:doc_id, "something went wrong unarchiving doc from aws"
  70. return callback new Errors.NotFoundError("Error in S3 request")
  71. DocArchive._s3DocToMongoDoc doc, (error, mongo_doc) ->
  72. return callback(error) if error?
  73. MongoManager.upsertIntoDocCollection project_id, doc_id.toString(), mongo_doc, (err) ->
  74. return callback(err) if err?
  75. logger.log project_id: project_id, doc_id: doc_id, "deleting doc from s3"
  76. DocArchive._deleteDocFromS3 project_id, doc_id, callback
  77. destroyAllDocs: (project_id, callback = (err) ->) ->
  78. MongoManager.getProjectsDocs project_id, {include_deleted: true}, {_id: 1}, (err, docs) ->
  79. if err?
  80. logger.err err:err, project_id:project_id, "error getting project's docs"
  81. return callback(err)
  82. else if !docs?
  83. return callback()
  84. jobs = _.map docs, (doc) ->
  85. (cb)->
  86. DocArchive.destroyDoc(project_id, doc._id, cb)
  87. async.parallelLimit jobs, 5, callback
  88. destroyDoc: (project_id, doc_id, callback)->
  89. logger.log project_id: project_id, doc_id: doc_id, "removing doc from mongo and s3"
  90. MongoManager.findDoc project_id, doc_id, {inS3: 1}, (error, doc) ->
  91. return callback error if error?
  92. return callback new Errors.NotFoundError("Doc not found in Mongo") unless doc?
  93. if doc.inS3 == true
  94. DocArchive._deleteDocFromS3 project_id, doc_id, (err) ->
  95. return err if err?
  96. MongoManager.destroyDoc doc_id, callback
  97. else
  98. MongoManager.destroyDoc doc_id, callback
  99. _deleteDocFromS3: (project_id, doc_id, callback) ->
  100. try
  101. options = DocArchive.buildS3Options(project_id+"/"+doc_id)
  102. catch e
  103. return callback e
  104. options.json = true
  105. request.del options, (err, res, body)->
  106. if err? || res.statusCode != 204
  107. logger.err err:err, res:res, project_id:project_id, doc_id:doc_id, "something went wrong deleting doc from aws"
  108. return callback new Error("Error in S3 request")
  109. callback()
  110. _s3DocToMongoDoc: (doc, callback = (error, mongo_doc) ->) ->
  111. mongo_doc = {}
  112. if doc.schema_v == 1 and doc.lines?
  113. mongo_doc.lines = doc.lines
  114. if doc.ranges?
  115. mongo_doc.ranges = RangeManager.jsonRangesToMongo(doc.ranges)
  116. else if doc instanceof Array
  117. mongo_doc.lines = doc
  118. else
  119. return callback(new Error("I don't understand the doc format in s3"))
  120. return callback null, mongo_doc
  121. _mongoDocToS3Doc: (doc, callback = (error, s3_doc) ->) ->
  122. if !doc.lines?
  123. return callback(new Error("doc has no lines"))
  124. json = JSON.stringify({
  125. lines: doc.lines
  126. ranges: doc.ranges
  127. schema_v: 1
  128. })
  129. if json.indexOf("\u0000") != -1
  130. error = new Error("null bytes detected")
  131. logger.err {err: error, doc, json}, error.message
  132. return callback(error)
  133. return callback null, json
  134. buildS3Options: (key)->
  135. if !settings.docstore.s3?
  136. throw new Error("S3 settings are not configured")
  137. return {
  138. aws:
  139. key: settings.docstore.s3.key
  140. secret: settings.docstore.s3.secret
  141. bucket: settings.docstore.s3.bucket
  142. timeout: thirtySeconds
  143. uri:"https://#{settings.docstore.s3.bucket}.s3.amazonaws.com/#{key}"
  144. }