2020-02-17 17:34:21 +00:00
|
|
|
/* eslint-disable
|
|
|
|
no-return-assign,
|
|
|
|
no-unused-vars,
|
|
|
|
*/
|
|
|
|
// TODO: This file was created by bulk-decaffeinate.
|
|
|
|
// Fix any style issues and re-enable lint.
|
2020-02-17 17:34:04 +00:00
|
|
|
/*
|
|
|
|
* decaffeinate suggestions:
|
|
|
|
* DS101: Remove unnecessary use of Array.from
|
|
|
|
* DS102: Remove unnecessary code created because of implicit returns
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
2020-02-17 17:34:28 +00:00
|
|
|
let MongoAWS
|
2021-07-12 16:47:16 +00:00
|
|
|
const settings = require('@overleaf/settings')
|
2021-12-14 13:00:35 +00:00
|
|
|
const logger = require('@overleaf/logger')
|
2020-02-17 17:34:28 +00:00
|
|
|
const AWS = require('aws-sdk')
|
|
|
|
const S3S = require('s3-streams')
|
2020-09-10 11:58:06 +00:00
|
|
|
const { db, ObjectId } = require('./mongodb')
|
2020-02-17 17:34:28 +00:00
|
|
|
const JSONStream = require('JSONStream')
|
|
|
|
const ReadlineStream = require('byline')
|
|
|
|
const zlib = require('zlib')
|
2020-11-25 11:57:20 +00:00
|
|
|
const Metrics = require('@overleaf/metrics')
|
2020-02-17 17:34:28 +00:00
|
|
|
|
|
|
|
const DAYS = 24 * 3600 * 1000 // one day in milliseconds
|
2020-02-17 17:34:04 +00:00
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
const createStream = function (streamConstructor, projectId, docId, packId) {
|
2020-02-17 17:34:28 +00:00
|
|
|
const AWS_CONFIG = {
|
|
|
|
accessKeyId: settings.trackchanges.s3.key,
|
|
|
|
secretAccessKey: settings.trackchanges.s3.secret,
|
|
|
|
endpoint: settings.trackchanges.s3.endpoint,
|
2021-07-13 11:04:43 +00:00
|
|
|
s3ForcePathStyle: settings.trackchanges.s3.pathStyle,
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return streamConstructor(new AWS.S3(AWS_CONFIG), {
|
|
|
|
Bucket: settings.trackchanges.stores.doc_history,
|
2023-03-21 12:21:51 +00:00
|
|
|
Key: projectId + '/changes-' + docId + '/pack-' + packId,
|
2020-02-17 17:34:28 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = MongoAWS = {
|
2023-03-21 12:21:51 +00:00
|
|
|
archivePack(projectId, docId, packId, _callback) {
|
2020-02-17 17:34:28 +00:00
|
|
|
if (_callback == null) {
|
2021-10-27 09:49:18 +00:00
|
|
|
_callback = function () {}
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
2020-06-04 08:24:21 +00:00
|
|
|
const callback = function (...args) {
|
2020-02-17 17:34:28 +00:00
|
|
|
_callback(...Array.from(args || []))
|
2020-06-04 08:24:21 +00:00
|
|
|
return (_callback = function () {})
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const query = {
|
2023-03-21 12:21:51 +00:00
|
|
|
_id: ObjectId(packId),
|
|
|
|
doc_id: ObjectId(docId),
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
if (projectId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid project id'))
|
|
|
|
}
|
2023-03-21 12:21:51 +00:00
|
|
|
if (docId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid doc id'))
|
|
|
|
}
|
2023-03-21 12:21:51 +00:00
|
|
|
if (packId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid pack id'))
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.debug({ projectId, docId, packId }, 'uploading data to s3')
|
2020-02-17 17:34:28 +00:00
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
const upload = createStream(S3S.WriteStream, projectId, docId, packId)
|
2020-02-17 17:34:28 +00:00
|
|
|
|
2020-06-04 08:24:21 +00:00
|
|
|
return db.docHistory.findOne(query, function (err, result) {
|
2020-02-17 17:34:28 +00:00
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
if (result == null) {
|
|
|
|
return callback(new Error('cannot find pack to send to s3'))
|
|
|
|
}
|
|
|
|
if (result.expiresAt != null) {
|
|
|
|
return callback(new Error('refusing to send pack with TTL to s3'))
|
|
|
|
}
|
|
|
|
const uncompressedData = JSON.stringify(result)
|
|
|
|
if (uncompressedData.indexOf('\u0000') !== -1) {
|
|
|
|
const error = new Error('null bytes found in upload')
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.error({ err: error, projectId, docId, packId }, error.message)
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(error)
|
|
|
|
}
|
2020-06-04 08:24:21 +00:00
|
|
|
return zlib.gzip(uncompressedData, function (err, buf) {
|
2022-05-16 12:38:18 +00:00
|
|
|
logger.debug(
|
2020-02-17 17:34:28 +00:00
|
|
|
{
|
2023-03-21 12:21:51 +00:00
|
|
|
projectId,
|
|
|
|
docId,
|
|
|
|
packId,
|
2020-02-17 17:34:28 +00:00
|
|
|
origSize: uncompressedData.length,
|
2021-07-13 11:04:43 +00:00
|
|
|
newSize: buf.length,
|
2020-02-17 17:34:28 +00:00
|
|
|
},
|
|
|
|
'compressed pack'
|
|
|
|
)
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2021-07-13 11:04:43 +00:00
|
|
|
upload.on('error', err => callback(err))
|
2020-06-04 08:24:21 +00:00
|
|
|
upload.on('finish', function () {
|
2020-02-17 17:34:28 +00:00
|
|
|
Metrics.inc('archive-pack')
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.debug({ projectId, docId, packId }, 'upload to s3 completed')
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(null)
|
|
|
|
})
|
|
|
|
upload.write(buf)
|
|
|
|
return upload.end()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
readArchivedPack(projectId, docId, packId, _callback) {
|
2020-02-17 17:34:28 +00:00
|
|
|
if (_callback == null) {
|
2021-10-27 09:49:18 +00:00
|
|
|
_callback = function () {}
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
2020-06-04 08:24:21 +00:00
|
|
|
const callback = function (...args) {
|
2020-02-17 17:34:28 +00:00
|
|
|
_callback(...Array.from(args || []))
|
2020-06-04 08:24:21 +00:00
|
|
|
return (_callback = function () {})
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
if (projectId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid project id'))
|
|
|
|
}
|
2023-03-21 12:21:51 +00:00
|
|
|
if (docId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid doc id'))
|
|
|
|
}
|
2023-03-21 12:21:51 +00:00
|
|
|
if (packId == null) {
|
2020-02-17 17:34:28 +00:00
|
|
|
return callback(new Error('invalid pack id'))
|
|
|
|
}
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.debug({ projectId, docId, packId }, 'downloading data from s3')
|
2020-02-17 17:34:28 +00:00
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
const download = createStream(S3S.ReadStream, projectId, docId, packId)
|
2020-02-17 17:34:28 +00:00
|
|
|
|
|
|
|
const inputStream = download
|
2021-07-13 11:04:43 +00:00
|
|
|
.on('open', obj => 1)
|
|
|
|
.on('error', err => callback(err))
|
2020-02-17 17:34:28 +00:00
|
|
|
|
|
|
|
const gunzip = zlib.createGunzip()
|
|
|
|
gunzip.setEncoding('utf8')
|
2020-06-04 08:24:21 +00:00
|
|
|
gunzip.on('error', function (err) {
|
2022-05-16 12:38:18 +00:00
|
|
|
logger.debug(
|
2023-03-21 12:21:51 +00:00
|
|
|
{ projectId, docId, packId, err },
|
2020-02-17 17:34:28 +00:00
|
|
|
'error uncompressing gzip stream'
|
|
|
|
)
|
|
|
|
return callback(err)
|
|
|
|
})
|
|
|
|
|
|
|
|
const outputStream = inputStream.pipe(gunzip)
|
|
|
|
const parts = []
|
2021-07-13 11:04:43 +00:00
|
|
|
outputStream.on('error', err => callback(err))
|
2020-06-04 08:24:21 +00:00
|
|
|
outputStream.on('end', function () {
|
2020-02-17 17:34:28 +00:00
|
|
|
let object
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.debug({ projectId, docId, packId }, 'download from s3 completed')
|
2020-02-17 17:34:28 +00:00
|
|
|
try {
|
|
|
|
object = JSON.parse(parts.join(''))
|
|
|
|
} catch (e) {
|
|
|
|
return callback(e)
|
|
|
|
}
|
|
|
|
object._id = ObjectId(object._id)
|
|
|
|
object.doc_id = ObjectId(object.doc_id)
|
|
|
|
object.project_id = ObjectId(object.project_id)
|
|
|
|
for (const op of Array.from(object.pack)) {
|
|
|
|
if (op._id != null) {
|
|
|
|
op._id = ObjectId(op._id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return callback(null, object)
|
|
|
|
})
|
2021-07-13 11:04:43 +00:00
|
|
|
return outputStream.on('data', data => parts.push(data))
|
2020-02-17 17:34:28 +00:00
|
|
|
},
|
|
|
|
|
2023-03-21 12:21:51 +00:00
|
|
|
unArchivePack(projectId, docId, packId, callback) {
|
2020-02-17 17:34:28 +00:00
|
|
|
if (callback == null) {
|
2021-10-27 09:49:18 +00:00
|
|
|
callback = function () {}
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
2021-07-13 11:04:43 +00:00
|
|
|
return MongoAWS.readArchivedPack(
|
2023-03-21 12:21:51 +00:00
|
|
|
projectId,
|
|
|
|
docId,
|
|
|
|
packId,
|
2021-07-13 11:04:43 +00:00
|
|
|
function (err, object) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
Metrics.inc('unarchive-pack')
|
|
|
|
// allow the object to expire, we can always retrieve it again
|
|
|
|
object.expiresAt = new Date(Date.now() + 7 * DAYS)
|
2023-03-21 12:21:51 +00:00
|
|
|
logger.debug({ projectId, docId, packId }, 'inserting object from s3')
|
2021-07-13 11:04:43 +00:00
|
|
|
return db.docHistory.insertOne(object, (err, confirmation) => {
|
|
|
|
if (err) return callback(err)
|
|
|
|
object._id = confirmation.insertedId
|
|
|
|
callback(null, object)
|
|
|
|
})
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|
2021-07-13 11:04:43 +00:00
|
|
|
)
|
|
|
|
},
|
2020-02-17 17:34:28 +00:00
|
|
|
}
|