Files
speckle-server/packages/server/modules/blobstorage/rest/router.ts
T
2025-04-28 15:17:40 +01:00

214 lines
6.8 KiB
TypeScript

import {
allowForAllRegisteredUsersOnPublicStreamsWithPublicComments,
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
allowAnonymousUsersOnPublicStreams,
streamCommentsWritePermissionsPipelineFactory,
streamReadPermissionsPipelineFactory
} from '@/modules/shared/authz'
import { authMiddlewareCreator } from '@/modules/shared/middleware'
import { isArray } from 'lodash'
import { UnauthorizedError } from '@/modules/shared/errors'
import {
getAllStreamBlobIdsFactory,
getBlobMetadataFactory,
getBlobMetadataCollectionFactory,
deleteBlobFactory
} from '@/modules/blobstorage/repositories'
import { db } from '@/db/knex'
import {
getFileStreamFactory,
fullyDeleteBlobFactory
} from '@/modules/blobstorage/services/management'
import { Router } from 'express'
import { getProjectObjectStorage } from '@/modules/multiregion/utils/blobStorageSelector'
import {
deleteObjectFactory,
getObjectStreamFactory
} from '@/modules/blobstorage/repositories/blobs'
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
import { getStreamFactory } from '@/modules/core/repositories/streams'
import { processNewFileStreamFactory } from '@/modules/blobstorage/services/streams'
import { UserInputError } from '@/modules/core/errors/userinput'
import { createBusboy } from '@/modules/blobstorage/rest/busboy'
export const blobStorageRouterFactory = (): Router => {
const processNewFileStream = processNewFileStreamFactory()
const app = Router()
app.post(
'/api/stream/:streamId/blob',
async (req, res, next) => {
await authMiddlewareCreator(
streamCommentsWritePermissionsPipelineFactory({
getStream: getStreamFactory({ db })
})
)(req, res, next)
},
async (req, res) => {
const streamId = req.params.streamId
const userId = req.context.userId
if (!userId) throw new UnauthorizedError()
req.log = req.log.child({ streamId, userId })
req.log.debug('Uploading blob.')
const busboy = createBusboy(req)
const newFileStreamProcessor = await processNewFileStream({
busboy,
streamId,
userId,
logger: req.log,
onFinishAllFileUploads: async (uploadResults) => {
res.status(201).send({ uploadResults })
},
onError: () => {
res.contentType('application/json')
res
.status(400)
.end(
'{ "error": "Upload request error. The server logs may have more details." }'
)
}
})
req.pipe(newFileStreamProcessor)
}
)
app.post(
'/api/stream/:streamId/blob/diff',
async (req, res, next) => {
await authMiddlewareCreator([
...streamReadPermissionsPipelineFactory({
getStream: getStreamFactory({ db })
}),
allowForAllRegisteredUsersOnPublicStreamsWithPublicComments,
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
allowAnonymousUsersOnPublicStreams
])(req, res, next)
},
async (req, res) => {
if (!isArray(req.body)) {
throw new UserInputError('An array of blob IDs expected in the body.')
}
const projectDb = await getProjectDbClient({ projectId: req.params.streamId })
const getAllStreamBlobIds = getAllStreamBlobIdsFactory({ db: projectDb })
const bq = await getAllStreamBlobIds({ streamId: req.params.streamId })
const unknownBlobIds = [...req.body].filter(
(id) => bq.findIndex((bInfo) => bInfo.id === id) === -1
)
res.send(unknownBlobIds)
}
)
app.get(
'/api/stream/:streamId/blob/:blobId',
async (req, res, next) => {
await authMiddlewareCreator([
...streamReadPermissionsPipelineFactory({
getStream: getStreamFactory({ db })
}),
allowForAllRegisteredUsersOnPublicStreamsWithPublicComments,
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
allowAnonymousUsersOnPublicStreams
])(req, res, next)
},
async (req, res) => {
const streamId = req.params.streamId
const [projectDb, projectStorage] = await Promise.all([
getProjectDbClient({ projectId: streamId }),
getProjectObjectStorage({ projectId: streamId })
])
const getBlobMetadata = getBlobMetadataFactory({ db: projectDb })
const getFileStream = getFileStreamFactory({ getBlobMetadata })
const getObjectStream = getObjectStreamFactory({ storage: projectStorage })
const { fileName } = await getBlobMetadata({
streamId: req.params.streamId,
blobId: req.params.blobId
})
const fileStream = await getFileStream({
getObjectStream,
streamId: req.params.streamId,
blobId: req.params.blobId
})
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${fileName}"`
})
fileStream.pipe(res)
}
)
app.delete(
'/api/stream/:streamId/blob/:blobId',
async (req, res, next) => {
await authMiddlewareCreator(
streamCommentsWritePermissionsPipelineFactory({
getStream: getStreamFactory({ db })
})
)(req, res, next)
},
async (req, res) => {
const streamId = req.params.streamId
const [projectDb, projectStorage] = await Promise.all([
getProjectDbClient({ projectId: streamId }),
getProjectObjectStorage({ projectId: streamId })
])
const getBlobMetadata = getBlobMetadataFactory({ db: projectDb })
const deleteObject = deleteObjectFactory({ storage: projectStorage })
const deleteBlob = fullyDeleteBlobFactory({
getBlobMetadata,
deleteBlob: deleteBlobFactory({ db: projectDb }),
deleteObject
})
await deleteBlob({
streamId: req.params.streamId,
blobId: req.params.blobId
})
res.status(204).send()
}
)
app.get(
'/api/stream/:streamId/blobs',
async (req, res, next) => {
await authMiddlewareCreator(
streamReadPermissionsPipelineFactory({
getStream: getStreamFactory({ db })
})
)(req, res, next)
},
async (req, res) => {
let fileName = req.query.fileName //filename can be undefined or null, and that returns all blobs
if (isArray(fileName)) {
fileName = fileName[0]
}
const streamId = req.params.streamId
const projectDb = await getProjectDbClient({ projectId: req.params.streamId })
const getBlobMetadataCollection = getBlobMetadataCollectionFactory({
db: projectDb
})
const blobMetadataCollection = await getBlobMetadataCollection({
streamId,
query: fileName as string
})
return res.status(200).send(blobMetadataCollection)
}
)
app.delete('/api/stream/:streamId/blobs', async (_req, res) => {
return res.status(501).send('This method is not implemented yet.')
})
return app
}