mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-08 00:29:04 +02:00
367e8d53b2
add script for redacting unwanted blobs GitOrigin-RevId: cddbeb4d27546b7cb98634ab364cc8dad0ada76c
145 lines
4.1 KiB
JavaScript
145 lines
4.1 KiB
JavaScript
import fs from 'node:fs'
|
|
import { Readable } from 'node:stream'
|
|
import { createRequire } from 'node:module'
|
|
import * as readline from 'node:readline/promises'
|
|
import commandLineArgs from 'command-line-args'
|
|
import { makeProjectKey } from '../lib/blob_store/index.js'
|
|
import { client } from '../lib/mongodb.js'
|
|
import knex from '../lib/knex.js'
|
|
import redis from '../lib/redis.js'
|
|
|
|
const require = createRequire(import.meta.url)
|
|
const config = require('config')
|
|
const persistor = require('../lib/persistor.js')
|
|
const { Errors } = require('@overleaf/object-persistor')
|
|
|
|
const optionDefinitions = [
|
|
{ name: 'historyId', alias: 'p', type: String },
|
|
{ name: 'blob', alias: 'b', type: String },
|
|
{ name: 'file', alias: 'f', type: String },
|
|
{ name: 'empty', alias: 'e', type: Boolean },
|
|
{ name: 'delete', alias: 'd', type: Boolean },
|
|
{ name: 'yes', alias: 'y', type: Boolean },
|
|
{ name: 'message', alias: 'm', type: String },
|
|
]
|
|
|
|
async function replaceBlob(historyId, blobHash, options) {
|
|
const bucket = config.get('blobStore.projectBucket')
|
|
const key = makeProjectKey(historyId, blobHash)
|
|
|
|
// 1. Check existence
|
|
let originalSize
|
|
try {
|
|
originalSize = await persistor.getObjectSize(bucket, key)
|
|
console.log(`Found blob ${blobHash} of size ${originalSize} bytes`)
|
|
} catch (err) {
|
|
if (
|
|
err instanceof Errors.NotFoundError ||
|
|
err.code === 'NoSuchKey' ||
|
|
err.name === 'NoSuchKey'
|
|
) {
|
|
throw new Error(`Blob ${blobHash} not found in project ${historyId}`)
|
|
}
|
|
throw err
|
|
}
|
|
|
|
// 2. Prepare action
|
|
let stream
|
|
let streamSize
|
|
let actionDesc
|
|
if (!options.delete) {
|
|
if (options.empty) {
|
|
stream = Readable.from([])
|
|
streamSize = 0
|
|
actionDesc = 'empty file'
|
|
} else if (options.file) {
|
|
const stat = fs.statSync(options.file)
|
|
stream = fs.createReadStream(options.file)
|
|
streamSize = stat.size
|
|
actionDesc = `file ${options.file}`
|
|
} else {
|
|
const baseMessage = options.message || 'REDACTED'
|
|
const msg = `${baseMessage} ${new Date().toISOString()}`
|
|
const buf = Buffer.from(msg, 'utf8')
|
|
stream = Readable.from([buf])
|
|
streamSize = buf.length
|
|
actionDesc = `message "${msg}"`
|
|
}
|
|
}
|
|
|
|
const actionLog = options.delete
|
|
? `Deleting blob ${blobHash} in ${historyId}`
|
|
: `Replacing blob ${blobHash} in ${historyId} with ${actionDesc} (${streamSize} bytes)`
|
|
|
|
console.log(actionLog)
|
|
|
|
if (!options.yes) {
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
})
|
|
const answer = await rl.question('Proceed (Y/N)? ')
|
|
rl.close()
|
|
if (answer.toLowerCase() !== 'y') {
|
|
console.log('Aborted.')
|
|
return
|
|
}
|
|
}
|
|
|
|
// 3. Execute action
|
|
if (options.delete) {
|
|
await persistor.deleteObject(bucket, key)
|
|
console.log('Blob deleted successfully.')
|
|
} else {
|
|
await persistor.sendStream(bucket, key, stream, {
|
|
contentType: 'application/octet-stream',
|
|
contentLength: streamSize,
|
|
})
|
|
console.log('Blob replaced successfully.')
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const options = commandLineArgs(optionDefinitions)
|
|
if (!options.historyId) {
|
|
console.error('Error: --historyId is required.')
|
|
process.exit(1)
|
|
}
|
|
if (!options.blob) {
|
|
console.error('Error: --blob is required.')
|
|
process.exit(1)
|
|
}
|
|
|
|
const activeModes = [
|
|
options.delete ? '--delete' : null,
|
|
options.empty ? '--empty' : null,
|
|
options.file ? '--file' : null,
|
|
options.message !== undefined ? '--message' : null,
|
|
].filter(Boolean)
|
|
|
|
if (activeModes.length > 1) {
|
|
console.error(
|
|
`Error: Conflicting options provided (${activeModes.join(
|
|
', '
|
|
)}). Please select exactly one redaction mode.`
|
|
)
|
|
process.exit(1)
|
|
}
|
|
|
|
await replaceBlob(options.historyId, options.blob, options)
|
|
}
|
|
|
|
main()
|
|
.then(() => console.log('Done.'))
|
|
.catch(err => {
|
|
console.error('Error:', err.message)
|
|
process.exit(1)
|
|
})
|
|
.finally(() => {
|
|
knex.destroy().catch(err => console.error('Error closing Postgres:', err))
|
|
client.close().catch(err => console.error('Error closing MongoDB:', err))
|
|
redis
|
|
.disconnect()
|
|
.catch(err => console.error('Error disconnecting Redis:', err))
|
|
})
|