920099c39f
- we recently added TIME_MS helper; this applies it to existing durations
147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import zlib from 'zlib'
|
|
import { corsMiddlewareFactory } from '@/modules/core/configs/cors'
|
|
|
|
import { SpeckleObjectsStream } from '@/modules/core/rest/speckleObjectsStream'
|
|
import { pipeline, PassThrough } from 'stream'
|
|
import {
|
|
getFormattedObjectFactory,
|
|
getObjectChildrenStreamFactory
|
|
} from '@/modules/core/repositories/objects'
|
|
import { db } from '@/db/knex'
|
|
import { validatePermissionsReadStreamFactory } from '@/modules/core/services/streams/auth'
|
|
import { getStreamFactory } from '@/modules/core/repositories/streams'
|
|
import { validateScopes, authorizeResolver } from '@/modules/shared'
|
|
import type express from 'express'
|
|
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
|
|
|
export default (app: express.Express) => {
|
|
const validatePermissionsReadStream = validatePermissionsReadStreamFactory({
|
|
getStream: getStreamFactory({ db }),
|
|
validateScopes,
|
|
authorizeResolver
|
|
})
|
|
|
|
app.options('/objects/:streamId/:objectId', corsMiddlewareFactory())
|
|
|
|
app.get('/objects/:streamId/:objectId', corsMiddlewareFactory(), async (req, res) => {
|
|
const boundLogger = req.log.child({
|
|
requestId: req.id,
|
|
userId: req.context.userId || '-',
|
|
streamId: req.params.streamId,
|
|
objectId: req.params.objectId
|
|
})
|
|
const hasStreamAccess = await validatePermissionsReadStream(
|
|
req.params.streamId,
|
|
req
|
|
)
|
|
if (!hasStreamAccess.result) {
|
|
return res.status(hasStreamAccess.status).end()
|
|
}
|
|
const projectDb = await getProjectDbClient({ projectId: req.params.streamId })
|
|
const getObject = getFormattedObjectFactory({ db: projectDb })
|
|
const getObjectChildrenStream = getObjectChildrenStreamFactory({ db: projectDb })
|
|
|
|
// Populate first object (the "commit")
|
|
const obj = await getObject({
|
|
streamId: req.params.streamId,
|
|
objectId: req.params.objectId
|
|
})
|
|
|
|
if (!obj) {
|
|
return res.status(404).send('Failed to find object.')
|
|
}
|
|
|
|
const simpleText = req.headers.accept === 'text/plain'
|
|
|
|
res.writeHead(200, {
|
|
'Content-Encoding': 'gzip',
|
|
'Content-Type': simpleText ? 'text/plain; charset=UTF-8' : 'application/json'
|
|
})
|
|
|
|
const dbStream = await getObjectChildrenStream({
|
|
streamId: req.params.streamId,
|
|
objectId: req.params.objectId
|
|
})
|
|
|
|
// https://knexjs.org/faq/recipes.html#manually-closing-streams
|
|
// https://github.com/knex/knex/issues/2324
|
|
const responseCloseHandler = () => {
|
|
dbStream.end()
|
|
dbStream.destroy()
|
|
}
|
|
|
|
dbStream.on('close', () => {
|
|
res.removeListener('close', responseCloseHandler)
|
|
})
|
|
res.on('close', responseCloseHandler)
|
|
|
|
const speckleObjStream = new SpeckleObjectsStream(simpleText)
|
|
const gzipStream = zlib.createGzip()
|
|
|
|
speckleObjStream.write(obj)
|
|
|
|
pipeline(
|
|
dbStream,
|
|
speckleObjStream,
|
|
gzipStream,
|
|
new PassThrough({ highWaterMark: 16384 * 31 }),
|
|
res,
|
|
(err) => {
|
|
if (err) {
|
|
switch (err.code) {
|
|
case 'ERR_STREAM_PREMATURE_CLOSE':
|
|
boundLogger.info({ err }, 'Client closed connection early')
|
|
break
|
|
default:
|
|
boundLogger.error({ err }, 'Error downloading object from stream')
|
|
break
|
|
}
|
|
} else {
|
|
boundLogger.info(
|
|
{ megaBytesWritten: gzipStream.bytesWritten / 1_000_000 },
|
|
'Downloaded object (size: {megaBytesWritten} MB)'
|
|
)
|
|
}
|
|
}
|
|
)
|
|
})
|
|
|
|
app.options('/objects/:streamId/:objectId/single', corsMiddlewareFactory())
|
|
app.get(
|
|
'/objects/:streamId/:objectId/single',
|
|
corsMiddlewareFactory(),
|
|
async (req, res) => {
|
|
const boundLogger = req.log.child({
|
|
requestId: req.id,
|
|
userId: req.context.userId || '-',
|
|
streamId: req.params.streamId,
|
|
objectId: req.params.objectId
|
|
})
|
|
const hasStreamAccess = await validatePermissionsReadStream(
|
|
req.params.streamId,
|
|
req
|
|
)
|
|
if (!hasStreamAccess.result) {
|
|
return res.status(hasStreamAccess.status).end()
|
|
}
|
|
|
|
const projectDb = await getProjectDbClient({ projectId: req.params.streamId })
|
|
const getObject = getFormattedObjectFactory({ db: projectDb })
|
|
|
|
const obj = await getObject({
|
|
streamId: req.params.streamId,
|
|
objectId: req.params.objectId
|
|
})
|
|
|
|
if (!obj) {
|
|
boundLogger.warn('Failed to find object.')
|
|
return res.status(404).send('Failed to find object.')
|
|
}
|
|
|
|
boundLogger.info('Downloaded single object.')
|
|
|
|
res.send(obj.data)
|
|
}
|
|
)
|
|
}
|