mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #151 from overleaf/jpa-bulk-dependency-upgrades
[misc] bulk dependency upgrades
This commit is contained in:
commit
9d790f4eda
33 changed files with 1475 additions and 1568 deletions
|
@ -3,9 +3,9 @@
|
|||
// https://github.com/sharelatex/sharelatex-dev-environment
|
||||
{
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"standard",
|
||||
"prettier",
|
||||
"prettier/standard"
|
||||
"prettier"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2018
|
||||
|
@ -20,6 +20,19 @@
|
|||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
// TODO(das7pad): remove overrides after fixing all the violations manually (https://github.com/overleaf/issues/issues/3882#issuecomment-878999671)
|
||||
// START of temporary overrides
|
||||
"array-callback-return": "off",
|
||||
"no-dupe-else-if": "off",
|
||||
"no-var": "off",
|
||||
"no-empty": "off",
|
||||
"node/handle-callback-err": "off",
|
||||
"no-loss-of-precision": "off",
|
||||
"node/no-callback-literal": "off",
|
||||
"node/no-path-concat": "off",
|
||||
"prefer-regex-literals": "off",
|
||||
// END of temporary overrides
|
||||
|
||||
// Swap the no-unused-expressions rule with a more chai-friendly one
|
||||
"no-unused-expressions": 0,
|
||||
"chai-friendly/no-unused-expressions": "error",
|
||||
|
|
2
services/filestore/.github/dependabot.yml
vendored
2
services/filestore/.github/dependabot.yml
vendored
|
@ -20,4 +20,4 @@ updates:
|
|||
# future if we reorganise teams
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "Team-Magma"
|
||||
- "type:maintenance"
|
||||
|
|
|
@ -1 +1 @@
|
|||
12.20.1
|
||||
12.22.3
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
# Instead run bin/update_build_scripts from
|
||||
# https://github.com/sharelatex/sharelatex-dev-environment
|
||||
{
|
||||
"arrowParens": "avoid",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Instead run bin/update_build_scripts from
|
||||
# https://github.com/sharelatex/sharelatex-dev-environment
|
||||
|
||||
FROM node:12.20.1 as base
|
||||
FROM node:12.22.3 as base
|
||||
|
||||
WORKDIR /app
|
||||
COPY install_deps.sh /app
|
||||
|
|
|
@ -4,7 +4,7 @@ Metrics.initialize(process.env.METRICS_APP_NAME || 'filestore')
|
|||
const logger = require('logger-sharelatex')
|
||||
logger.initialize(process.env.METRICS_APP_NAME || 'filestore')
|
||||
|
||||
const settings = require('settings-sharelatex')
|
||||
const settings = require('@overleaf/settings')
|
||||
const express = require('express')
|
||||
const bodyParser = require('body-parser')
|
||||
|
||||
|
@ -140,7 +140,7 @@ const host = '0.0.0.0'
|
|||
|
||||
if (!module.parent) {
|
||||
// Called directly
|
||||
app.listen(port, host, (error) => {
|
||||
app.listen(port, host, error => {
|
||||
if (error) {
|
||||
logger.error('Error starting Filestore', error)
|
||||
throw error
|
||||
|
@ -153,7 +153,7 @@ process
|
|||
.on('unhandledRejection', (reason, p) => {
|
||||
logger.err(reason, 'Unhandled Rejection at Promise', p)
|
||||
})
|
||||
.on('uncaughtException', (err) => {
|
||||
.on('uncaughtException', err => {
|
||||
logger.err(err, 'Uncaught Exception thrown')
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ class FailedCommandError extends OError {
|
|||
constructor(command, code, stdout, stderr) {
|
||||
super('command failed with error exit code', {
|
||||
command,
|
||||
code
|
||||
code,
|
||||
})
|
||||
this.stdout = stdout
|
||||
this.stderr = stderr
|
||||
|
@ -26,5 +26,5 @@ module.exports = {
|
|||
HealthCheckError,
|
||||
TimeoutError,
|
||||
InvalidParametersError,
|
||||
...Errors
|
||||
...Errors,
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = {
|
|||
copyFile,
|
||||
deleteFile,
|
||||
deleteProject,
|
||||
directorySize
|
||||
directorySize,
|
||||
}
|
||||
|
||||
function getFile(req, res, next) {
|
||||
|
@ -24,7 +24,7 @@ function getFile(req, res, next) {
|
|||
key,
|
||||
bucket,
|
||||
format,
|
||||
style
|
||||
style,
|
||||
}
|
||||
|
||||
metrics.inc('getFile')
|
||||
|
@ -34,7 +34,7 @@ function getFile(req, res, next) {
|
|||
bucket,
|
||||
format,
|
||||
style,
|
||||
cacheWarm: req.query.cacheWarm
|
||||
cacheWarm: req.query.cacheWarm,
|
||||
})
|
||||
|
||||
if (req.headers.range) {
|
||||
|
@ -70,7 +70,7 @@ function getFile(req, res, next) {
|
|||
return res.sendStatus(200).end()
|
||||
}
|
||||
|
||||
pipeline(fileStream, res, (err) => {
|
||||
pipeline(fileStream, res, err => {
|
||||
if (err && err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
res.end()
|
||||
} else if (err) {
|
||||
|
@ -134,13 +134,13 @@ function copyFile(req, res, next) {
|
|||
key,
|
||||
bucket,
|
||||
oldProject_id: oldProjectId,
|
||||
oldFile_id: oldFileId
|
||||
oldFile_id: oldFileId,
|
||||
})
|
||||
req.requestLogger.setMessage('copying file')
|
||||
|
||||
PersistorManager.copyObject(bucket, `${oldProjectId}/${oldFileId}`, key)
|
||||
.then(() => res.sendStatus(200))
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
if (err) {
|
||||
if (err instanceof Errors.NotFoundError) {
|
||||
res.sendStatus(404)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const metrics = require('@overleaf/metrics')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { callbackify } = require('util')
|
||||
|
||||
const safeExec = require('./SafeExec').promises
|
||||
|
@ -16,8 +16,8 @@ module.exports = {
|
|||
promises: {
|
||||
convert,
|
||||
thumbnail,
|
||||
preview
|
||||
}
|
||||
preview,
|
||||
},
|
||||
}
|
||||
|
||||
async function convert(sourcePath, requestedFormat) {
|
||||
|
@ -29,7 +29,7 @@ async function convert(sourcePath, requestedFormat) {
|
|||
'-flatten',
|
||||
'-density',
|
||||
'300',
|
||||
`${sourcePath}[0]`
|
||||
`${sourcePath}[0]`,
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ async function thumbnail(sourcePath) {
|
|||
`pdf:fit-page=${width}`,
|
||||
`${sourcePath}[0]`,
|
||||
'-resize',
|
||||
width
|
||||
width,
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -63,14 +63,14 @@ async function preview(sourcePath) {
|
|||
`pdf:fit-page=${width}`,
|
||||
`${sourcePath}[0]`,
|
||||
'-resize',
|
||||
width
|
||||
width,
|
||||
])
|
||||
}
|
||||
|
||||
async function _convert(sourcePath, requestedFormat, command) {
|
||||
if (!APPROVED_FORMATS.includes(requestedFormat)) {
|
||||
throw new ConversionError('invalid format requested', {
|
||||
format: requestedFormat
|
||||
format: requestedFormat,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ async function _convert(sourcePath, requestedFormat, command) {
|
|||
try {
|
||||
await safeExec(command, {
|
||||
killSignal: KILL_SIGNAL,
|
||||
timeout: FOURTY_SECONDS
|
||||
timeout: FOURTY_SECONDS,
|
||||
})
|
||||
} catch (err) {
|
||||
throw new ConversionError(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { callbackify } = require('util')
|
||||
const fs = require('fs')
|
||||
const PersistorManager = require('./PersistorManager')
|
||||
|
@ -23,8 +23,8 @@ module.exports = {
|
|||
deleteFile,
|
||||
deleteProject,
|
||||
getFileSize,
|
||||
getDirectorySize
|
||||
}
|
||||
getDirectorySize,
|
||||
},
|
||||
}
|
||||
|
||||
async function insertFile(bucket, key, stream) {
|
||||
|
@ -33,7 +33,7 @@ async function insertFile(bucket, key, stream) {
|
|||
throw new InvalidParametersError('key does not match validation regex', {
|
||||
bucket,
|
||||
key,
|
||||
convertedKey
|
||||
convertedKey,
|
||||
})
|
||||
}
|
||||
if (Settings.enableConversions) {
|
||||
|
@ -48,7 +48,7 @@ async function deleteFile(bucket, key) {
|
|||
throw new InvalidParametersError('key does not match validation regex', {
|
||||
bucket,
|
||||
key,
|
||||
convertedKey
|
||||
convertedKey,
|
||||
})
|
||||
}
|
||||
const jobs = [PersistorManager.deleteObject(bucket, key)]
|
||||
|
@ -62,7 +62,7 @@ async function deleteProject(bucket, key) {
|
|||
if (!key.match(/^[0-9a-f]{24}\//i)) {
|
||||
throw new InvalidParametersError('key does not match validation regex', {
|
||||
bucket,
|
||||
key
|
||||
key,
|
||||
})
|
||||
}
|
||||
await PersistorManager.deleteDirectory(bucket, key)
|
||||
|
@ -172,7 +172,7 @@ async function _convertFile(bucket, originalKey, opts) {
|
|||
throw new ConversionError('invalid file conversion options', {
|
||||
bucket,
|
||||
originalKey,
|
||||
opts
|
||||
opts,
|
||||
})
|
||||
}
|
||||
let destPath
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const streamBuffers = require('stream-buffers')
|
||||
const { promisify } = require('util')
|
||||
const Stream = require('stream')
|
||||
|
@ -24,7 +24,7 @@ async function checkCanGetFiles() {
|
|||
const bucket = Settings.filestore.stores.user_files
|
||||
|
||||
const buffer = new streamBuffers.WritableStreamBuffer({
|
||||
initialSize: 100
|
||||
initialSize: 100,
|
||||
})
|
||||
|
||||
const sourceStream = await FileHandler.getFile(bucket, key, {})
|
||||
|
@ -62,8 +62,8 @@ module.exports = {
|
|||
check(req, res, next) {
|
||||
Promise.all([checkCanGetFiles(), checkFileConvert()])
|
||||
.then(() => res.sendStatus(200))
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
next(err)
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ const safeExec = require('./SafeExec').promises
|
|||
module.exports = {
|
||||
compressPng: callbackify(compressPng),
|
||||
promises: {
|
||||
compressPng
|
||||
}
|
||||
compressPng,
|
||||
},
|
||||
}
|
||||
|
||||
async function compressPng(localPath, callback) {
|
||||
|
@ -15,7 +15,7 @@ async function compressPng(localPath, callback) {
|
|||
const args = ['optipng', localPath]
|
||||
const opts = {
|
||||
timeout: 30 * 1000,
|
||||
killSignal: 'SIGKILL'
|
||||
killSignal: 'SIGKILL',
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const settings = require('settings-sharelatex')
|
||||
const settings = require('@overleaf/settings')
|
||||
|
||||
module.exports = {
|
||||
getConvertedFolderKey,
|
||||
|
@ -8,7 +8,7 @@ module.exports = {
|
|||
publicFileKeyMiddleware,
|
||||
publicProjectKeyMiddleware,
|
||||
bucketFileKeyMiddleware,
|
||||
templateFileKeyMiddleware
|
||||
templateFileKeyMiddleware,
|
||||
}
|
||||
|
||||
function getConvertedFolderKey(key) {
|
||||
|
@ -68,7 +68,7 @@ function templateFileKeyMiddleware(req, res, next) {
|
|||
template_id: templateId,
|
||||
format,
|
||||
version,
|
||||
sub_type: subType
|
||||
sub_type: subType,
|
||||
} = req.params
|
||||
|
||||
req.key = `${templateId}/v/${version}/${format}`
|
||||
|
|
|
@ -4,16 +4,16 @@ const path = require('path')
|
|||
const Stream = require('stream')
|
||||
const { callbackify, promisify } = require('util')
|
||||
const metrics = require('@overleaf/metrics')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { WriteError } = require('./Errors')
|
||||
|
||||
module.exports = {
|
||||
promises: {
|
||||
writeStream,
|
||||
deleteFile
|
||||
deleteFile,
|
||||
},
|
||||
writeStream: callbackify(writeStream),
|
||||
deleteFile: callbackify(deleteFile)
|
||||
deleteFile: callbackify(deleteFile),
|
||||
}
|
||||
|
||||
const pipeline = promisify(Stream.pipeline)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const settings = require('settings-sharelatex')
|
||||
const settings = require('@overleaf/settings')
|
||||
|
||||
const persistorSettings = settings.filestore
|
||||
persistorSettings.Metrics = require('@overleaf/metrics')
|
||||
|
|
|
@ -38,7 +38,7 @@ class RequestLogger {
|
|||
metrics.timing('http_request', responseTime, null, {
|
||||
method: req.method,
|
||||
status_code: res.statusCode,
|
||||
path: routePath.replace(/\//g, '_').replace(/:/g, '').slice(1)
|
||||
path: routePath.replace(/\//g, '_').replace(/:/g, '').slice(1),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -57,14 +57,14 @@ class RequestLogger {
|
|||
req.socket.socket &&
|
||||
req.socket.socket.remoteAddress),
|
||||
'user-agent': req.headers['user-agent'],
|
||||
'content-length': req.headers['content-length']
|
||||
'content-length': req.headers['content-length'],
|
||||
},
|
||||
res: {
|
||||
'content-length': res._headers['content-length'],
|
||||
statusCode: res.statusCode,
|
||||
'response-time': responseTime
|
||||
'response-time': responseTime,
|
||||
},
|
||||
info: req.requestLogger._logInfo
|
||||
info: req.requestLogger._logInfo,
|
||||
},
|
||||
req.requestLogger._logMessage
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const lodashOnce = require('lodash.once')
|
||||
const childProcess = require('child_process')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const { ConversionsDisabledError, FailedCommandError } = require('./Errors')
|
||||
|
||||
// execute a command in the same way as 'exec' but with a timeout that
|
||||
|
@ -45,7 +45,7 @@ function safeExec(command, options, callback) {
|
|||
new FailedCommandError('failed to kill process after timeout', {
|
||||
command,
|
||||
options,
|
||||
pid: child.pid
|
||||
pid: child.pid,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -62,13 +62,13 @@ function safeExec(command, options, callback) {
|
|||
cleanup()
|
||||
})
|
||||
|
||||
child.on('error', (err) => {
|
||||
child.on('error', err => {
|
||||
cleanup(err)
|
||||
})
|
||||
child.stdout.on('data', (chunk) => {
|
||||
child.stdout.on('data', chunk => {
|
||||
stdout += chunk
|
||||
})
|
||||
child.stderr.on('data', (chunk) => {
|
||||
child.stderr.on('data', chunk => {
|
||||
stderr += chunk
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,6 @@ filestore
|
|||
--docker-repos=gcr.io/overleaf-ops
|
||||
--env-add=ENABLE_CONVERSIONS="true",USE_PROM_METRICS="true",AWS_S3_USER_FILES_BUCKET_NAME=fake_user_files,AWS_S3_TEMPLATE_FILES_BUCKET_NAME=fake_template_files,AWS_S3_PUBLIC_FILES_BUCKET_NAME=fake_public_files,GCS_USER_FILES_BUCKET_NAME=fake_userfiles,GCS_TEMPLATE_FILES_BUCKET_NAME=fake_templatefiles,GCS_PUBLIC_FILES_BUCKET_NAME=fake_publicfiles
|
||||
--env-pass-through=
|
||||
--node-version=12.20.1
|
||||
--node-version=12.22.3
|
||||
--public-repo=True
|
||||
--script-version=3.8.0
|
||||
--script-version=3.11.0
|
||||
|
|
|
@ -40,8 +40,8 @@ const settings = {
|
|||
internal: {
|
||||
filestore: {
|
||||
port: 3009,
|
||||
host: process.env.LISTEN_ADDRESS || 'localhost'
|
||||
}
|
||||
host: process.env.LISTEN_ADDRESS || 'localhost',
|
||||
},
|
||||
},
|
||||
|
||||
filestore: {
|
||||
|
@ -57,13 +57,13 @@ const settings = {
|
|||
? {
|
||||
apiEndpoint: process.env.GCS_API_ENDPOINT,
|
||||
apiScheme: process.env.GCS_API_SCHEME,
|
||||
projectId: process.env.GCS_PROJECT_ID
|
||||
projectId: process.env.GCS_PROJECT_ID,
|
||||
}
|
||||
: undefined,
|
||||
unlockBeforeDelete: process.env.GCS_UNLOCK_BEFORE_DELETE === 'true', // unlock an event-based hold before deleting. default false
|
||||
deletedBucketSuffix: process.env.GCS_DELETED_BUCKET_SUFFIX, // if present, copy file to another bucket on delete. default null
|
||||
deleteConcurrency: parseInt(process.env.GCS_DELETE_CONCURRENCY) || 50,
|
||||
signedUrlExpiryInMs: parseInt(process.env.LINK_EXPIRY_TIMEOUT || 60000)
|
||||
signedUrlExpiryInMs: parseInt(process.env.LINK_EXPIRY_TIMEOUT || 60000),
|
||||
},
|
||||
|
||||
s3:
|
||||
|
@ -76,7 +76,7 @@ const settings = {
|
|||
partSize: process.env.AWS_S3_PARTSIZE || 100 * 1024 * 1024,
|
||||
bucketCreds: process.env.S3_BUCKET_CREDENTIALS
|
||||
? JSON.parse(process.env.S3_BUCKET_CREDENTIALS)
|
||||
: undefined
|
||||
: undefined,
|
||||
}
|
||||
: undefined,
|
||||
|
||||
|
@ -86,7 +86,7 @@ const settings = {
|
|||
stores: {
|
||||
user_files: process.env.USER_FILES_BUCKET_NAME,
|
||||
template_files: process.env.TEMPLATE_FILES_BUCKET_NAME,
|
||||
public_files: process.env.PUBLIC_FILES_BUCKET_NAME
|
||||
public_files: process.env.PUBLIC_FILES_BUCKET_NAME,
|
||||
},
|
||||
|
||||
fallback: process.env.FALLBACK_BACKEND
|
||||
|
@ -95,28 +95,28 @@ const settings = {
|
|||
// mapping of bucket names on the fallback, to bucket names on the primary.
|
||||
// e.g. { myS3UserFilesBucketName: 'myGoogleUserFilesBucketName' }
|
||||
buckets: JSON.parse(process.env.FALLBACK_BUCKET_MAPPING || '{}'),
|
||||
copyOnMiss: process.env.COPY_ON_MISS === 'true'
|
||||
copyOnMiss: process.env.COPY_ON_MISS === 'true',
|
||||
}
|
||||
: undefined,
|
||||
|
||||
allowRedirects: process.env.ALLOW_REDIRECTS === 'true'
|
||||
allowRedirects: process.env.ALLOW_REDIRECTS === 'true',
|
||||
},
|
||||
|
||||
path: {
|
||||
// eslint-disable-next-line no-path-concat
|
||||
uploadFolder: Path.resolve(__dirname + '/../uploads')
|
||||
uploadFolder: Path.resolve(__dirname + '/../uploads'),
|
||||
},
|
||||
|
||||
commands: {
|
||||
// Any commands to wrap the convert utility in, for example ["nice"], or ["firejail", "--profile=/etc/firejail/convert.profile"]
|
||||
convertCommandPrefix: []
|
||||
convertCommandPrefix: [],
|
||||
},
|
||||
|
||||
enableConversions: process.env.ENABLE_CONVERSIONS === 'true',
|
||||
|
||||
sentry: {
|
||||
dsn: process.env.SENTRY_DSN
|
||||
}
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
},
|
||||
}
|
||||
|
||||
// Filestore health check
|
||||
|
@ -125,7 +125,7 @@ const settings = {
|
|||
if (process.env.HEALTH_CHECK_PROJECT_ID && process.env.HEALTH_CHECK_FILE_ID) {
|
||||
settings.health_check = {
|
||||
project_id: process.env.HEALTH_CHECK_PROJECT_ID,
|
||||
file_id: process.env.HEALTH_CHECK_FILE_ID
|
||||
file_id: process.env.HEALTH_CHECK_FILE_ID,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2523
services/filestore/package-lock.json
generated
2523
services/filestore/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -13,17 +13,20 @@
|
|||
"test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP",
|
||||
"start": "node $NODE_APP_OPTIONS app.js",
|
||||
"nodemon": "nodemon --config nodemon.json",
|
||||
"lint": "node_modules/.bin/eslint --max-warnings 0 .",
|
||||
"format": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --list-different",
|
||||
"format:fix": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --write",
|
||||
"lint": "eslint --max-warnings 0 --format unix .",
|
||||
"format": "prettier --list-different $PWD/'**/*.js'",
|
||||
"format:fix": "prettier --write $PWD/'**/*.js'",
|
||||
"test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js",
|
||||
"test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js"
|
||||
"test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js",
|
||||
"lint:fix": "eslint --fix ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@overleaf/metrics": "^3.5.1",
|
||||
"@overleaf/o-error": "^3.0.0",
|
||||
"@overleaf/object-persistor": "https://github.com/overleaf/object-persistor/archive/8fbc9ed03206bfb54368578d22b7ac4f285baa25.tar.gz",
|
||||
"@overleaf/settings": "^2.1.1",
|
||||
"body-parser": "^1.19.0",
|
||||
"bunyan": "^1.8.15",
|
||||
"express": "^4.17.1",
|
||||
"fast-crc32c": "^2.0.0",
|
||||
"glob": "^7.1.6",
|
||||
|
@ -33,34 +36,29 @@
|
|||
"range-parser": "^1.2.1",
|
||||
"request": "^2.88.2",
|
||||
"request-promise-native": "^1.0.8",
|
||||
"settings-sharelatex": "^1.1.0",
|
||||
"stream-buffers": "~0.2.6",
|
||||
"tiny-async-pool": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@google-cloud/storage": "^5.1.2",
|
||||
"aws-sdk": "^2.718.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"bunyan": "^1.8.14",
|
||||
"chai": "4.2.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"disrequire": "^1.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-prettier": "^6.10.0",
|
||||
"eslint-config-standard": "^14.1.0",
|
||||
"eslint-plugin-chai-expect": "^2.1.0",
|
||||
"eslint-plugin-chai-friendly": "^0.5.0",
|
||||
"eslint-plugin-import": "^2.20.1",
|
||||
"eslint-plugin-mocha": "^6.3.0",
|
||||
"eslint-plugin-node": "^11.0.0",
|
||||
"eslint": "^7.21.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-chai-expect": "^2.2.0",
|
||||
"eslint-plugin-chai-friendly": "^0.6.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-mocha": "^8.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.2",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"mocha": "7.2.0",
|
||||
"mocha": "^8.3.2",
|
||||
"mongodb": "^3.5.9",
|
||||
"prettier": "^2.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-eslint": "^9.0.2",
|
||||
"prettier-eslint-cli": "^5.0.0",
|
||||
"sandboxed-module": "2.0.4",
|
||||
"sinon": "9.0.2",
|
||||
"sinon-chai": "^3.5.0",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const logger = require('logger-sharelatex')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const fs = require('fs')
|
||||
const Path = require('path')
|
||||
const { promisify } = require('util')
|
||||
|
@ -33,7 +33,7 @@ class FilestoreApp {
|
|||
this.server = this.app.listen(
|
||||
Settings.internal.filestore.port,
|
||||
'localhost',
|
||||
(err) => {
|
||||
err => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class FilestoreApp {
|
|||
secretAccessKey: Settings.filestore.s3.secret,
|
||||
endpoint: Settings.filestore.s3.endpoint,
|
||||
s3ForcePathStyle: true,
|
||||
signatureVersion: 'v4'
|
||||
signatureVersion: 'v4',
|
||||
})
|
||||
|
||||
while (true) {
|
||||
|
@ -91,7 +91,7 @@ class FilestoreApp {
|
|||
.putObject({
|
||||
Key: 'startup',
|
||||
Body: '42',
|
||||
Bucket: Settings.filestore.stores.user_files
|
||||
Bucket: Settings.filestore.stores.user_files,
|
||||
})
|
||||
.promise()
|
||||
} catch (err) {
|
||||
|
@ -110,7 +110,7 @@ class FilestoreApp {
|
|||
// unload the app, as we may be doing this on multiple runs with
|
||||
// different settings, which affect startup in some cases
|
||||
const files = await fsReaddir(Path.resolve(__dirname, '../../../app/js'))
|
||||
files.forEach((file) => {
|
||||
files.forEach(file => {
|
||||
disrequire(Path.resolve(__dirname, '../../../app/js', file))
|
||||
})
|
||||
disrequire(Path.resolve(__dirname, '../../../app'))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
const chai = require('chai')
|
||||
const { expect } = chai
|
||||
const fs = require('fs')
|
||||
const Settings = require('settings-sharelatex')
|
||||
const Settings = require('@overleaf/settings')
|
||||
const Path = require('path')
|
||||
const FilestoreApp = require('./FilestoreApp')
|
||||
const TestHelper = require('./TestHelper')
|
||||
const rp = require('request-promise-native').defaults({
|
||||
resolveWithFullResponse: true
|
||||
resolveWithFullResponse: true,
|
||||
})
|
||||
const S3 = require('aws-sdk/clients/s3')
|
||||
const Stream = require('stream')
|
||||
|
@ -29,7 +29,7 @@ if (!process.env.AWS_ACCESS_KEY_ID) {
|
|||
throw new Error('please provide credentials for the AWS S3 test server')
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (e) => {
|
||||
process.on('unhandledRejection', e => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('** Unhandled Promise Rejection **\n', e)
|
||||
throw e
|
||||
|
@ -51,7 +51,7 @@ describe('Filestore', function () {
|
|||
|
||||
const badSockets = []
|
||||
for (const socket of stdout.split('\n')) {
|
||||
const fields = socket.split(' ').filter((part) => part !== '')
|
||||
const fields = socket.split(' ').filter(part => part !== '')
|
||||
if (
|
||||
fields.length > 2 &&
|
||||
parseInt(fields[1]) &&
|
||||
|
@ -79,7 +79,7 @@ describe('Filestore', function () {
|
|||
}
|
||||
|
||||
// redefine the test suite for every available backend
|
||||
Object.keys(BackendSettings).forEach((backend) => {
|
||||
Object.keys(BackendSettings).forEach(backend => {
|
||||
describe(backend, function () {
|
||||
let app, previousEgress, previousIngress, metricPrefix, projectId
|
||||
|
||||
|
@ -150,7 +150,7 @@ describe('Filestore', function () {
|
|||
constantFileContent = [
|
||||
'hello world',
|
||||
`line 2 goes here ${Math.random()}`,
|
||||
'there are 3 lines in all'
|
||||
'there are 3 lines in all',
|
||||
].join('\n')
|
||||
|
||||
await fsWriteFile(localFileReadPath, constantFileContent)
|
||||
|
@ -204,8 +204,8 @@ describe('Filestore', function () {
|
|||
const options = {
|
||||
uri: fileUrl,
|
||||
headers: {
|
||||
Range: 'bytes=0-8'
|
||||
}
|
||||
Range: 'bytes=0-8',
|
||||
},
|
||||
}
|
||||
const res = await rp.get(options)
|
||||
expect(res.body).to.equal('hello wor')
|
||||
|
@ -215,8 +215,8 @@ describe('Filestore', function () {
|
|||
const options = {
|
||||
uri: fileUrl,
|
||||
headers: {
|
||||
Range: 'bytes=4-10'
|
||||
}
|
||||
Range: 'bytes=4-10',
|
||||
},
|
||||
}
|
||||
const res = await rp.get(options)
|
||||
expect(res.body).to.equal('o world')
|
||||
|
@ -240,9 +240,9 @@ describe('Filestore', function () {
|
|||
json: {
|
||||
source: {
|
||||
project_id: projectId,
|
||||
file_id: fileId
|
||||
}
|
||||
}
|
||||
file_id: fileId,
|
||||
},
|
||||
},
|
||||
}
|
||||
let response = await rp(opts)
|
||||
expect(response.statusCode).to.equal(200)
|
||||
|
@ -288,8 +288,8 @@ describe('Filestore', function () {
|
|||
const options = {
|
||||
uri: fileUrl,
|
||||
headers: {
|
||||
Range: 'bytes=0-8'
|
||||
}
|
||||
Range: 'bytes=0-8',
|
||||
},
|
||||
}
|
||||
await rp.get(options)
|
||||
const metric = await TestHelper.getMetric(
|
||||
|
@ -305,25 +305,25 @@ describe('Filestore', function () {
|
|||
let fileIds, fileUrls, projectUrl
|
||||
const localFileReadPaths = [
|
||||
'/tmp/filestore_acceptance_tests_file_read_1.txt',
|
||||
'/tmp/filestore_acceptance_tests_file_read_2.txt'
|
||||
'/tmp/filestore_acceptance_tests_file_read_2.txt',
|
||||
]
|
||||
const constantFileContents = [
|
||||
[
|
||||
'hello world',
|
||||
`line 2 goes here ${Math.random()}`,
|
||||
'there are 3 lines in all'
|
||||
'there are 3 lines in all',
|
||||
].join('\n'),
|
||||
[
|
||||
`for reference: ${Math.random()}`,
|
||||
'cats are the best animals',
|
||||
'wombats are a close second'
|
||||
].join('\n')
|
||||
'wombats are a close second',
|
||||
].join('\n'),
|
||||
]
|
||||
|
||||
before(async function () {
|
||||
return Promise.all([
|
||||
fsWriteFile(localFileReadPaths[0], constantFileContents[0]),
|
||||
fsWriteFile(localFileReadPaths[1], constantFileContents[1])
|
||||
fsWriteFile(localFileReadPaths[1], constantFileContents[1]),
|
||||
])
|
||||
})
|
||||
|
||||
|
@ -332,25 +332,25 @@ describe('Filestore', function () {
|
|||
fileIds = [ObjectId().toString(), ObjectId().toString()]
|
||||
fileUrls = [
|
||||
`${projectUrl}/file/${fileIds[0]}`,
|
||||
`${projectUrl}/file/${fileIds[1]}`
|
||||
`${projectUrl}/file/${fileIds[1]}`,
|
||||
]
|
||||
|
||||
const writeStreams = [
|
||||
request.post(fileUrls[0]),
|
||||
request.post(fileUrls[1])
|
||||
request.post(fileUrls[1]),
|
||||
]
|
||||
const readStreams = [
|
||||
fs.createReadStream(localFileReadPaths[0]),
|
||||
fs.createReadStream(localFileReadPaths[1])
|
||||
fs.createReadStream(localFileReadPaths[1]),
|
||||
]
|
||||
// hack to consume the result to ensure the http request has been fully processed
|
||||
const resultStreams = [
|
||||
fs.createWriteStream('/dev/null'),
|
||||
fs.createWriteStream('/dev/null')
|
||||
fs.createWriteStream('/dev/null'),
|
||||
]
|
||||
return Promise.all([
|
||||
pipeline(readStreams[0], writeStreams[0], resultStreams[0]),
|
||||
pipeline(readStreams[1], writeStreams[1], resultStreams[1])
|
||||
pipeline(readStreams[1], writeStreams[1], resultStreams[1]),
|
||||
])
|
||||
})
|
||||
|
||||
|
@ -433,7 +433,7 @@ describe('Filestore', function () {
|
|||
for (let i = 0; i < 5; i++) {
|
||||
// test is not 100% reliable, so repeat
|
||||
// create a new connection and have it time out before reading any data
|
||||
await new Promise((resolve) => {
|
||||
await new Promise(resolve => {
|
||||
const streamThatHangs = new Stream.PassThrough()
|
||||
const stream = request({ url: fileUrl, timeout: 1000 })
|
||||
stream.pipe(streamThatHangs)
|
||||
|
@ -461,24 +461,24 @@ describe('Filestore', function () {
|
|||
const s3ClientSettings = {
|
||||
credentials: {
|
||||
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
||||
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
endpoint: process.env.AWS_S3_ENDPOINT,
|
||||
sslEnabled: false,
|
||||
s3ForcePathStyle: true
|
||||
s3ForcePathStyle: true,
|
||||
}
|
||||
|
||||
const s3 = new S3(s3ClientSettings)
|
||||
await s3
|
||||
.createBucket({
|
||||
Bucket: bucketName
|
||||
Bucket: bucketName,
|
||||
})
|
||||
.promise()
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: bucketName,
|
||||
Key: fileId,
|
||||
Body: constantFileContent
|
||||
Body: constantFileContent,
|
||||
})
|
||||
.promise()
|
||||
})
|
||||
|
@ -648,9 +648,9 @@ describe('Filestore', function () {
|
|||
json: {
|
||||
source: {
|
||||
project_id: projectId,
|
||||
file_id: fileId
|
||||
}
|
||||
}
|
||||
file_id: fileId,
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -752,9 +752,8 @@ describe('Filestore', function () {
|
|||
describe('when sending a file', function () {
|
||||
beforeEach(async function () {
|
||||
const writeStream = request.post(fileUrl)
|
||||
const readStream = streamifier.createReadStream(
|
||||
constantFileContent
|
||||
)
|
||||
const readStream =
|
||||
streamifier.createReadStream(constantFileContent)
|
||||
// hack to consume the result to ensure the http request has been fully processed
|
||||
const resultStream = fs.createWriteStream('/dev/null')
|
||||
await pipeline(readStream, writeStream, resultStream)
|
||||
|
|
|
@ -7,7 +7,7 @@ function s3Config() {
|
|||
secret: process.env.AWS_SECRET_ACCESS_KEY,
|
||||
endpoint: process.env.AWS_S3_ENDPOINT,
|
||||
pathStyle: true,
|
||||
partSize: 100 * 1024 * 1024
|
||||
partSize: 100 * 1024 * 1024,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ function s3Stores() {
|
|||
return {
|
||||
user_files: process.env.AWS_S3_USER_FILES_BUCKET_NAME,
|
||||
template_files: process.env.AWS_S3_TEMPLATE_FILES_BUCKET_NAME,
|
||||
public_files: process.env.AWS_S3_PUBLIC_FILES_BUCKET_NAME
|
||||
public_files: process.env.AWS_S3_PUBLIC_FILES_BUCKET_NAME,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,11 +24,11 @@ function gcsConfig() {
|
|||
endpoint: {
|
||||
apiEndpoint: process.env.GCS_API_ENDPOINT,
|
||||
apiScheme: process.env.GCS_API_SCHEME,
|
||||
projectId: 'fake'
|
||||
projectId: 'fake',
|
||||
},
|
||||
directoryKeyRegex: new RegExp('^[0-9a-fA-F]{24}/[0-9a-fA-F]{24}'),
|
||||
unlockBeforeDelete: false, // fake-gcs does not support this
|
||||
deletedBucketSuffix: '-deleted'
|
||||
deletedBucketSuffix: '-deleted',
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ function gcsStores() {
|
|||
return {
|
||||
user_files: process.env.GCS_USER_FILES_BUCKET_NAME,
|
||||
template_files: process.env.GCS_TEMPLATE_FILES_BUCKET_NAME,
|
||||
public_files: process.env.GCS_PUBLIC_FILES_BUCKET_NAME
|
||||
public_files: process.env.GCS_PUBLIC_FILES_BUCKET_NAME,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ function fsStores() {
|
|||
return {
|
||||
user_files: Path.resolve(__dirname, '../../../user_files'),
|
||||
public_files: Path.resolve(__dirname, '../../../public_files'),
|
||||
template_files: Path.resolve(__dirname, '../../../template_files')
|
||||
template_files: Path.resolve(__dirname, '../../../template_files'),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,24 +52,24 @@ function fallbackStores(primaryConfig, fallbackConfig) {
|
|||
return {
|
||||
[primaryConfig.user_files]: fallbackConfig.user_files,
|
||||
[primaryConfig.public_files]: fallbackConfig.public_files,
|
||||
[primaryConfig.template_files]: fallbackConfig.template_files
|
||||
[primaryConfig.template_files]: fallbackConfig.template_files,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
FSPersistor: {
|
||||
backend: 'fs',
|
||||
stores: fsStores()
|
||||
stores: fsStores(),
|
||||
},
|
||||
S3Persistor: {
|
||||
backend: 's3',
|
||||
s3: s3Config(),
|
||||
stores: s3Stores()
|
||||
stores: s3Stores(),
|
||||
},
|
||||
GcsPersistor: {
|
||||
backend: 'gcs',
|
||||
gcs: gcsConfig(),
|
||||
stores: gcsStores()
|
||||
stores: gcsStores(),
|
||||
},
|
||||
FallbackS3ToFSPersistor: {
|
||||
backend: 's3',
|
||||
|
@ -77,8 +77,8 @@ module.exports = {
|
|||
stores: s3Stores(),
|
||||
fallback: {
|
||||
backend: 'fs',
|
||||
buckets: fallbackStores(s3Stores(), fsStores())
|
||||
}
|
||||
buckets: fallbackStores(s3Stores(), fsStores()),
|
||||
},
|
||||
},
|
||||
FallbackFSToS3Persistor: {
|
||||
backend: 'fs',
|
||||
|
@ -86,8 +86,8 @@ module.exports = {
|
|||
stores: fsStores(),
|
||||
fallback: {
|
||||
backend: 's3',
|
||||
buckets: fallbackStores(fsStores(), s3Stores())
|
||||
}
|
||||
buckets: fallbackStores(fsStores(), s3Stores()),
|
||||
},
|
||||
},
|
||||
FallbackGcsToS3Persistor: {
|
||||
backend: 'gcs',
|
||||
|
@ -96,8 +96,8 @@ module.exports = {
|
|||
s3: s3Config(),
|
||||
fallback: {
|
||||
backend: 's3',
|
||||
buckets: fallbackStores(gcsStores(), s3Stores())
|
||||
}
|
||||
buckets: fallbackStores(gcsStores(), s3Stores()),
|
||||
},
|
||||
},
|
||||
FallbackS3ToGcsPersistor: {
|
||||
backend: 's3',
|
||||
|
@ -107,7 +107,7 @@ module.exports = {
|
|||
gcs: gcsConfig(),
|
||||
fallback: {
|
||||
backend: 'gcs',
|
||||
buckets: fallbackStores(s3Stores(), gcsStores())
|
||||
}
|
||||
}
|
||||
buckets: fallbackStores(s3Stores(), gcsStores()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const streamifier = require('streamifier')
|
||||
const rp = require('request-promise-native').defaults({
|
||||
resolveWithFullResponse: true
|
||||
resolveWithFullResponse: true,
|
||||
})
|
||||
|
||||
const { expect } = require('chai')
|
||||
|
@ -11,7 +11,7 @@ module.exports = {
|
|||
expectPersistorToHaveFile,
|
||||
expectPersistorNotToHaveFile,
|
||||
streamToString,
|
||||
getMetric
|
||||
getMetric,
|
||||
}
|
||||
|
||||
async function getMetric(filestoreUrl, metric) {
|
||||
|
@ -25,7 +25,7 @@ async function getMetric(filestoreUrl, metric) {
|
|||
function streamToString(stream) {
|
||||
const chunks = []
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', (chunk) => chunks.push(chunk))
|
||||
stream.on('data', chunk => chunks.push(chunk))
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
|
||||
stream.resume()
|
||||
|
|
|
@ -17,9 +17,9 @@ describe('FileController', function () {
|
|||
const settings = {
|
||||
s3: {
|
||||
buckets: {
|
||||
user_files: 'user_files'
|
||||
}
|
||||
}
|
||||
user_files: 'user_files',
|
||||
},
|
||||
},
|
||||
}
|
||||
const fileSize = 1234
|
||||
const fileStream = 'fileStream'
|
||||
|
@ -33,7 +33,7 @@ describe('FileController', function () {
|
|||
PersistorManager = {
|
||||
sendStream: sinon.stub().yields(),
|
||||
copyObject: sinon.stub().resolves(),
|
||||
deleteObject: sinon.stub().yields()
|
||||
deleteObject: sinon.stub().yields(),
|
||||
}
|
||||
|
||||
FileHandler = {
|
||||
|
@ -43,12 +43,12 @@ describe('FileController', function () {
|
|||
deleteProject: sinon.stub().yields(),
|
||||
insertFile: sinon.stub().yields(),
|
||||
getDirectorySize: sinon.stub().yields(null, fileSize),
|
||||
getRedirectUrl: sinon.stub().yields(null, null)
|
||||
getRedirectUrl: sinon.stub().yields(null, null),
|
||||
}
|
||||
|
||||
LocalFileWriter = {}
|
||||
stream = {
|
||||
pipeline: sinon.stub()
|
||||
pipeline: sinon.stub(),
|
||||
}
|
||||
|
||||
FileController = SandboxedModule.require(modulePath, {
|
||||
|
@ -58,12 +58,12 @@ describe('FileController', function () {
|
|||
'./PersistorManager': PersistorManager,
|
||||
'./Errors': Errors,
|
||||
stream: stream,
|
||||
'settings-sharelatex': settings,
|
||||
'@overleaf/settings': settings,
|
||||
'@overleaf/metrics': {
|
||||
inc() {}
|
||||
}
|
||||
inc() {},
|
||||
},
|
||||
globals: { console }
|
||||
},
|
||||
globals: { console },
|
||||
})
|
||||
|
||||
req = {
|
||||
|
@ -73,19 +73,19 @@ describe('FileController', function () {
|
|||
query: {},
|
||||
params: {
|
||||
project_id: projectId,
|
||||
file_id: fileId
|
||||
file_id: fileId,
|
||||
},
|
||||
headers: {},
|
||||
requestLogger: {
|
||||
setMessage: sinon.stub(),
|
||||
addFields: sinon.stub()
|
||||
}
|
||||
addFields: sinon.stub(),
|
||||
},
|
||||
}
|
||||
|
||||
res = {
|
||||
set: sinon.stub().returnsThis(),
|
||||
sendStatus: sinon.stub().returnsThis(),
|
||||
status: sinon.stub().returnsThis()
|
||||
status: sinon.stub().returnsThis(),
|
||||
}
|
||||
|
||||
next = sinon.stub()
|
||||
|
@ -104,7 +104,7 @@ describe('FileController', function () {
|
|||
|
||||
it('should send a 200 if the cacheWarm param is true', function (done) {
|
||||
req.query.cacheWarm = true
|
||||
res.sendStatus = (statusCode) => {
|
||||
res.sendStatus = statusCode => {
|
||||
statusCode.should.equal(200)
|
||||
done()
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ describe('FileController', function () {
|
|||
bucket,
|
||||
key,
|
||||
format: undefined,
|
||||
style: undefined
|
||||
style: undefined,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -220,7 +220,7 @@ describe('FileController', function () {
|
|||
new Errors.NotFoundError({ message: 'not found', info: {} })
|
||||
)
|
||||
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
expect(code).to.equal(404)
|
||||
done()
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ describe('FileController', function () {
|
|||
|
||||
describe('insertFile', function () {
|
||||
it('should send bucket name key and res to PersistorManager', function (done) {
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
expect(FileHandler.insertFile).to.have.been.calledWith(bucket, key, req)
|
||||
expect(code).to.equal(200)
|
||||
done()
|
||||
|
@ -256,13 +256,13 @@ describe('FileController', function () {
|
|||
req.body = {
|
||||
source: {
|
||||
project_id: oldProjectId,
|
||||
file_id: oldFileId
|
||||
}
|
||||
file_id: oldFileId,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
it('should send bucket name and both keys to PersistorManager', function (done) {
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
code.should.equal(200)
|
||||
expect(PersistorManager.copyObject).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -278,7 +278,7 @@ describe('FileController', function () {
|
|||
PersistorManager.copyObject.rejects(
|
||||
new Errors.NotFoundError({ message: 'not found', info: {} })
|
||||
)
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
code.should.equal(404)
|
||||
done()
|
||||
}
|
||||
|
@ -287,7 +287,7 @@ describe('FileController', function () {
|
|||
|
||||
it('should send an error if there was an error', function (done) {
|
||||
PersistorManager.copyObject.rejects(error)
|
||||
FileController.copyFile(req, res, (err) => {
|
||||
FileController.copyFile(req, res, err => {
|
||||
expect(err).to.equal(error)
|
||||
done()
|
||||
})
|
||||
|
@ -296,7 +296,7 @@ describe('FileController', function () {
|
|||
|
||||
describe('delete file', function () {
|
||||
it('should tell the file handler', function (done) {
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
code.should.equal(204)
|
||||
expect(FileHandler.deleteFile).to.have.been.calledWith(bucket, key)
|
||||
done()
|
||||
|
@ -313,7 +313,7 @@ describe('FileController', function () {
|
|||
|
||||
describe('delete project', function () {
|
||||
it('should tell the file handler', function (done) {
|
||||
res.sendStatus = (code) => {
|
||||
res.sendStatus = code => {
|
||||
code.should.equal(204)
|
||||
expect(FileHandler.deleteProject).to.have.been.calledWith(bucket, key)
|
||||
done()
|
||||
|
@ -331,10 +331,10 @@ describe('FileController', function () {
|
|||
describe('directorySize', function () {
|
||||
it('should return total directory size bytes', function (done) {
|
||||
FileController.directorySize(req, {
|
||||
json: (result) => {
|
||||
json: result => {
|
||||
expect(result['total bytes']).to.equal(fileSize)
|
||||
done()
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -14,13 +14,13 @@ describe('FileConverter', function () {
|
|||
const errorMessage = 'guru meditation error'
|
||||
const Settings = {
|
||||
commands: {
|
||||
convertCommandPrefix: []
|
||||
}
|
||||
convertCommandPrefix: [],
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
SafeExec = {
|
||||
promises: sinon.stub().resolves(destPath)
|
||||
promises: sinon.stub().resolves(destPath),
|
||||
}
|
||||
|
||||
const ObjectPersistor = { Errors }
|
||||
|
@ -30,11 +30,11 @@ describe('FileConverter', function () {
|
|||
'./SafeExec': SafeExec,
|
||||
'@overleaf/metrics': {
|
||||
inc: sinon.stub(),
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() })
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() }),
|
||||
},
|
||||
'@overleaf/settings': Settings,
|
||||
'@overleaf/object-persistor': ObjectPersistor,
|
||||
},
|
||||
'settings-sharelatex': Settings,
|
||||
'@overleaf/object-persistor': ObjectPersistor
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('FileHandler', function () {
|
|||
const redirectUrl = 'https://wombat.potato/giraffe'
|
||||
const readStream = {
|
||||
stream: 'readStream',
|
||||
on: sinon.stub()
|
||||
on: sinon.stub(),
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
|
@ -41,35 +41,35 @@ describe('FileHandler', function () {
|
|||
sendStream: sinon.stub().resolves(),
|
||||
insertFile: sinon.stub().resolves(),
|
||||
sendFile: sinon.stub().resolves(),
|
||||
directorySize: sinon.stub().resolves()
|
||||
directorySize: sinon.stub().resolves(),
|
||||
}
|
||||
LocalFileWriter = {
|
||||
// the callback style is used for detached cleanup calls
|
||||
deleteFile: sinon.stub().yields(),
|
||||
promises: {
|
||||
writeStream: sinon.stub().resolves(),
|
||||
deleteFile: sinon.stub().resolves()
|
||||
}
|
||||
deleteFile: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
FileConverter = {
|
||||
promises: {
|
||||
convert: sinon.stub().resolves(),
|
||||
thumbnail: sinon.stub().resolves(),
|
||||
preview: sinon.stub().resolves()
|
||||
}
|
||||
preview: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
KeyBuilder = {
|
||||
addCachingToKey: sinon.stub().returns(convertedKey),
|
||||
getConvertedFolderKey: sinon.stub().returns(convertedFolderKey)
|
||||
getConvertedFolderKey: sinon.stub().returns(convertedFolderKey),
|
||||
}
|
||||
ImageOptimiser = {
|
||||
promises: {
|
||||
compressPng: sinon.stub().resolves()
|
||||
}
|
||||
compressPng: sinon.stub().resolves(),
|
||||
},
|
||||
}
|
||||
Settings = {}
|
||||
fs = {
|
||||
createReadStream: sinon.stub().returns(readStream)
|
||||
createReadStream: sinon.stub().returns(readStream),
|
||||
}
|
||||
|
||||
const ObjectPersistor = { Errors }
|
||||
|
@ -81,11 +81,11 @@ describe('FileHandler', function () {
|
|||
'./FileConverter': FileConverter,
|
||||
'./KeyBuilder': KeyBuilder,
|
||||
'./ImageOptimiser': ImageOptimiser,
|
||||
'settings-sharelatex': Settings,
|
||||
'@overleaf/settings': Settings,
|
||||
'@overleaf/object-persistor': ObjectPersistor,
|
||||
fs: fs
|
||||
fs: fs,
|
||||
},
|
||||
globals: { console }
|
||||
globals: { console },
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -93,7 +93,7 @@ describe('FileHandler', function () {
|
|||
const stream = 'stream'
|
||||
|
||||
it('should send file to the filestore', function (done) {
|
||||
FileHandler.insertFile(bucket, key, stream, (err) => {
|
||||
FileHandler.insertFile(bucket, key, stream, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.sendStream).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -105,7 +105,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('should not make a delete request for the convertedKey folder', function (done) {
|
||||
FileHandler.insertFile(bucket, key, stream, (err) => {
|
||||
FileHandler.insertFile(bucket, key, stream, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteDirectory).not.to.have.been.called
|
||||
done()
|
||||
|
@ -116,7 +116,7 @@ describe('FileHandler', function () {
|
|||
KeyBuilder.getConvertedFolderKey.returns(
|
||||
'5ecba29f1a294e007d0bccb4/v/0/pdf'
|
||||
)
|
||||
FileHandler.insertFile(bucket, key, stream, (err) => {
|
||||
FileHandler.insertFile(bucket, key, stream, err => {
|
||||
expect(err).not.to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -124,7 +124,7 @@ describe('FileHandler', function () {
|
|||
|
||||
it('should throw an error when the key is in the wrong format', function (done) {
|
||||
KeyBuilder.getConvertedFolderKey.returns('wombat')
|
||||
FileHandler.insertFile(bucket, key, stream, (err) => {
|
||||
FileHandler.insertFile(bucket, key, stream, err => {
|
||||
expect(err).to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -136,7 +136,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('should delete the convertedKey folder', function (done) {
|
||||
FileHandler.insertFile(bucket, key, stream, (err) => {
|
||||
FileHandler.insertFile(bucket, key, stream, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteDirectory).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -150,7 +150,7 @@ describe('FileHandler', function () {
|
|||
|
||||
describe('deleteFile', function () {
|
||||
it('should tell the filestore manager to delete the file', function (done) {
|
||||
FileHandler.deleteFile(bucket, key, (err) => {
|
||||
FileHandler.deleteFile(bucket, key, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteObject).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -161,7 +161,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('should not tell the filestore manager to delete the cached folder', function (done) {
|
||||
FileHandler.deleteFile(bucket, key, (err) => {
|
||||
FileHandler.deleteFile(bucket, key, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteDirectory).not.to.have.been.called
|
||||
done()
|
||||
|
@ -172,7 +172,7 @@ describe('FileHandler', function () {
|
|||
KeyBuilder.getConvertedFolderKey.returns(
|
||||
'5ecba29f1a294e007d0bccb4/v/0/pdf'
|
||||
)
|
||||
FileHandler.deleteFile(bucket, key, (err) => {
|
||||
FileHandler.deleteFile(bucket, key, err => {
|
||||
expect(err).not.to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -180,7 +180,7 @@ describe('FileHandler', function () {
|
|||
|
||||
it('should throw an error when the key is in the wrong format', function (done) {
|
||||
KeyBuilder.getConvertedFolderKey.returns('wombat')
|
||||
FileHandler.deleteFile(bucket, key, (err) => {
|
||||
FileHandler.deleteFile(bucket, key, err => {
|
||||
expect(err).to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -192,7 +192,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('should delete the convertedKey folder', function (done) {
|
||||
FileHandler.deleteFile(bucket, key, (err) => {
|
||||
FileHandler.deleteFile(bucket, key, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteDirectory).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -206,7 +206,7 @@ describe('FileHandler', function () {
|
|||
|
||||
describe('deleteProject', function () {
|
||||
it('should tell the filestore manager to delete the folder', function (done) {
|
||||
FileHandler.deleteProject(bucket, projectKey, (err) => {
|
||||
FileHandler.deleteProject(bucket, projectKey, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.deleteDirectory).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -217,7 +217,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('should throw an error when the key is in the wrong format', function (done) {
|
||||
FileHandler.deleteProject(bucket, 'wombat', (err) => {
|
||||
FileHandler.deleteProject(bucket, 'wombat', err => {
|
||||
expect(err).to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -235,7 +235,7 @@ describe('FileHandler', function () {
|
|||
|
||||
it('should pass options through to PersistorManager', function (done) {
|
||||
const options = { start: 0, end: 8 }
|
||||
FileHandler.getFile(bucket, key, options, (err) => {
|
||||
FileHandler.getFile(bucket, key, options, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.getObjectStream).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
@ -305,7 +305,7 @@ describe('FileHandler', function () {
|
|||
|
||||
describe('when a style is defined', function () {
|
||||
it('generates a thumbnail when requested', function (done) {
|
||||
FileHandler.getFile(bucket, key, { style: 'thumbnail' }, (err) => {
|
||||
FileHandler.getFile(bucket, key, { style: 'thumbnail' }, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(FileConverter.promises.thumbnail).to.have.been.called
|
||||
expect(FileConverter.promises.preview).not.to.have.been.called
|
||||
|
@ -314,7 +314,7 @@ describe('FileHandler', function () {
|
|||
})
|
||||
|
||||
it('generates a preview when requested', function (done) {
|
||||
FileHandler.getFile(bucket, key, { style: 'preview' }, (err) => {
|
||||
FileHandler.getFile(bucket, key, { style: 'preview' }, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(FileConverter.promises.thumbnail).not.to.have.been.called
|
||||
expect(FileConverter.promises.preview).to.have.been.called
|
||||
|
@ -329,8 +329,8 @@ describe('FileHandler', function () {
|
|||
Settings.filestore = {
|
||||
allowRedirects: true,
|
||||
stores: {
|
||||
userFiles: bucket
|
||||
}
|
||||
userFiles: bucket,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -385,7 +385,7 @@ describe('FileHandler', function () {
|
|||
|
||||
describe('getDirectorySize', function () {
|
||||
it('should call the filestore manager to get directory size', function (done) {
|
||||
FileHandler.getDirectorySize(bucket, key, (err) => {
|
||||
FileHandler.getDirectorySize(bucket, key, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(PersistorManager.directorySize).to.have.been.calledWith(
|
||||
bucket,
|
||||
|
|
|
@ -11,29 +11,29 @@ describe('ImageOptimiser', function () {
|
|||
|
||||
beforeEach(function () {
|
||||
SafeExec = {
|
||||
promises: sinon.stub().resolves()
|
||||
promises: sinon.stub().resolves(),
|
||||
}
|
||||
logger = {
|
||||
warn: sinon.stub()
|
||||
warn: sinon.stub(),
|
||||
}
|
||||
ImageOptimiser = SandboxedModule.require(modulePath, {
|
||||
requires: {
|
||||
'./SafeExec': SafeExec,
|
||||
'logger-sharelatex': logger,
|
||||
'@overleaf/metrics': {
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() })
|
||||
}
|
||||
}
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() }),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('compressPng', function () {
|
||||
it('should convert the file', function (done) {
|
||||
ImageOptimiser.compressPng(sourcePath, (err) => {
|
||||
ImageOptimiser.compressPng(sourcePath, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(SafeExec.promises).to.have.been.calledWith([
|
||||
'optipng',
|
||||
sourcePath
|
||||
sourcePath,
|
||||
])
|
||||
done()
|
||||
})
|
||||
|
@ -41,7 +41,7 @@ describe('ImageOptimiser', function () {
|
|||
|
||||
it('should return the error', function (done) {
|
||||
SafeExec.promises.rejects('wombat herding failure')
|
||||
ImageOptimiser.compressPng(sourcePath, (err) => {
|
||||
ImageOptimiser.compressPng(sourcePath, err => {
|
||||
expect(err.toString()).to.equal('wombat herding failure')
|
||||
done()
|
||||
})
|
||||
|
@ -54,7 +54,7 @@ describe('ImageOptimiser', function () {
|
|||
|
||||
beforeEach(function (done) {
|
||||
SafeExec.promises.rejects(expectedError)
|
||||
ImageOptimiser.compressPng(sourcePath, (err) => {
|
||||
ImageOptimiser.compressPng(sourcePath, err => {
|
||||
error = err
|
||||
done()
|
||||
})
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('KeybuilderTests', function () {
|
|||
|
||||
beforeEach(function () {
|
||||
KeyBuilder = SandboxedModule.require(modulePath, {
|
||||
requires: { 'settings-sharelatex': {} }
|
||||
requires: { '@overleaf/settings': {} },
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -28,7 +28,7 @@ describe('KeybuilderTests', function () {
|
|||
it('should add format first, then style', function () {
|
||||
const opts = {
|
||||
style: 'thumbnail',
|
||||
format: 'png'
|
||||
format: 'png',
|
||||
}
|
||||
const newKey = KeyBuilder.addCachingToKey(key, opts)
|
||||
newKey.should.equal(`${key}-converted-cache/format-png-style-thumbnail`)
|
||||
|
|
|
@ -17,10 +17,10 @@ describe('LocalFileWriter', function () {
|
|||
beforeEach(function () {
|
||||
fs = {
|
||||
createWriteStream: sinon.stub().returns(writeStream),
|
||||
unlink: sinon.stub().yields()
|
||||
unlink: sinon.stub().yields(),
|
||||
}
|
||||
stream = {
|
||||
pipeline: sinon.stub().yields()
|
||||
pipeline: sinon.stub().yields(),
|
||||
}
|
||||
|
||||
const ObjectPersistor = { Errors }
|
||||
|
@ -29,13 +29,13 @@ describe('LocalFileWriter', function () {
|
|||
requires: {
|
||||
fs,
|
||||
stream,
|
||||
'settings-sharelatex': settings,
|
||||
'@overleaf/settings': settings,
|
||||
'@overleaf/metrics': {
|
||||
inc: sinon.stub(),
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() })
|
||||
Timer: sinon.stub().returns({ done: sinon.stub() }),
|
||||
},
|
||||
'@overleaf/object-persistor': ObjectPersistor,
|
||||
},
|
||||
'@overleaf/object-persistor': ObjectPersistor
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -57,7 +57,7 @@ describe('LocalFileWriter', function () {
|
|||
})
|
||||
|
||||
it('should wrap the error', function () {
|
||||
LocalFileWriter.writeStream(readStream, filename, (err) => {
|
||||
LocalFileWriter.writeStream(readStream, filename, err => {
|
||||
expect(err).to.exist
|
||||
expect(err.cause).to.equal(error)
|
||||
})
|
||||
|
@ -73,7 +73,7 @@ describe('LocalFileWriter', function () {
|
|||
|
||||
describe('deleteFile', function () {
|
||||
it('should unlink the file', function (done) {
|
||||
LocalFileWriter.deleteFile(fsPath, (err) => {
|
||||
LocalFileWriter.deleteFile(fsPath, err => {
|
||||
expect(err).not.to.exist
|
||||
expect(fs.unlink).to.have.been.calledWith(fsPath)
|
||||
done()
|
||||
|
@ -81,7 +81,7 @@ describe('LocalFileWriter', function () {
|
|||
})
|
||||
|
||||
it('should not call unlink with an empty path', function (done) {
|
||||
LocalFileWriter.deleteFile('', (err) => {
|
||||
LocalFileWriter.deleteFile('', err => {
|
||||
expect(err).not.to.exist
|
||||
expect(fs.unlink).not.to.have.been.called
|
||||
done()
|
||||
|
@ -92,7 +92,7 @@ describe('LocalFileWriter', function () {
|
|||
const error = new Error('file not found')
|
||||
error.code = 'ENOENT'
|
||||
fs.unlink = sinon.stub().yields(error)
|
||||
LocalFileWriter.deleteFile(fsPath, (err) => {
|
||||
LocalFileWriter.deleteFile(fsPath, err => {
|
||||
expect(err).not.to.exist
|
||||
done()
|
||||
})
|
||||
|
@ -101,7 +101,7 @@ describe('LocalFileWriter', function () {
|
|||
it('should wrap the error', function (done) {
|
||||
const error = new Error('failed to reticulate splines')
|
||||
fs.unlink = sinon.stub().yields(error)
|
||||
LocalFileWriter.deleteFile(fsPath, (err) => {
|
||||
LocalFileWriter.deleteFile(fsPath, err => {
|
||||
expect(err).to.exist
|
||||
expect(err.cause).to.equal(error)
|
||||
done()
|
||||
|
|
|
@ -17,9 +17,9 @@ describe('SafeExec', function () {
|
|||
safeExec = SandboxedModule.require(modulePath, {
|
||||
globals: { process },
|
||||
requires: {
|
||||
'settings-sharelatex': settings,
|
||||
'@overleaf/object-persistor': ObjectPersistor
|
||||
}
|
||||
'@overleaf/settings': settings,
|
||||
'@overleaf/object-persistor': ObjectPersistor,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -35,14 +35,14 @@ describe('SafeExec', function () {
|
|||
|
||||
it('should error when conversions are disabled', function (done) {
|
||||
settings.enableConversions = false
|
||||
safeExec(['/bin/echo', 'hello'], options, (err) => {
|
||||
safeExec(['/bin/echo', 'hello'], options, err => {
|
||||
expect(err).to.exist
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it('should execute a command with non-zero exit status', function (done) {
|
||||
safeExec(['/usr/bin/env', 'false'], options, (err) => {
|
||||
safeExec(['/usr/bin/env', 'false'], options, err => {
|
||||
expect(err).to.exist
|
||||
expect(err.name).to.equal('FailedCommandError')
|
||||
expect(err.code).to.equal(1)
|
||||
|
@ -53,7 +53,7 @@ describe('SafeExec', function () {
|
|||
})
|
||||
|
||||
it('should handle an invalid command', function (done) {
|
||||
safeExec(['/bin/foobar'], options, (err) => {
|
||||
safeExec(['/bin/foobar'], options, err => {
|
||||
err.code.should.equal('ENOENT')
|
||||
done()
|
||||
})
|
||||
|
@ -63,7 +63,7 @@ describe('SafeExec', function () {
|
|||
safeExec(
|
||||
['/bin/sleep', '10'],
|
||||
{ timeout: 500, killSignal: 'SIGTERM' },
|
||||
(err) => {
|
||||
err => {
|
||||
expect(err).to.exist
|
||||
expect(err.name).to.equal('FailedCommandError')
|
||||
expect(err.code).to.equal('SIGTERM')
|
||||
|
|
|
@ -8,12 +8,12 @@ describe('Settings', function () {
|
|||
const s3Settings = {
|
||||
bucket1: {
|
||||
auth_key: 'bucket1_key',
|
||||
auth_secret: 'bucket1_secret'
|
||||
}
|
||||
auth_secret: 'bucket1_secret',
|
||||
},
|
||||
}
|
||||
process.env.S3_BUCKET_CREDENTIALS = JSON.stringify(s3Settings)
|
||||
const settings = SandboxedModule.require('settings-sharelatex', {
|
||||
globals: { console, process }
|
||||
const settings = SandboxedModule.require('@overleaf/settings', {
|
||||
globals: { console, process },
|
||||
})
|
||||
expect(settings.filestore.s3.bucketCreds).to.deep.equal(s3Settings)
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue