Files
speckle-server/packages/server/modules/blobstorage/rest/router.ts
T
Kristaps Fabians Geikins bde148f286 chore(server): migrating fully to ESM (#5042)
* wip

* some extra fixes

* stuff kinda works?

* need to figure out mocks

* need to figure out mocks

* fix db listener

* gqlgen fix

* minor gqlgen watch adjustment

* lint fixes

* delete old codegen file

* converting migrations to ESM

* getModuleDIrectory

* vitest sort of works

* added back ts-vitest

* resolve gql double load

* fixing test timeout configs

* TSC lint fix

* fix automate tests

* moar debugging

* debugging

* more debugging

* codegen update

* server works

* yargs migrated

* chore(server): getting rid of global mocks for Server ESM (#5046)

* got rid of email mock

* got rid of comment mocks

* got rid of multi region mocks

* got rid of stripe mock

* admin override mock updated

* removed final mock

* fixing import.meta.resolve calls

* another import.meta.resolve fix

* added requested test

* nyc ESM fix

* removed unneeded deps + linting

* yarn lock forgot to commit

* tryna fix flakyness

* email capture util fix

* sendEmail fix

* fix TSX check

* sender transporter fix + CR comments

* merge main fix

* test fixx

* circleci fix

* gqlgen bigint fix

* error formatter fix

* more error formatting improvements

* esmloader added to Dockerfile

* more dockerfile fixes

* bg jobs fix
2025-07-14 10:26:19 +03:00

217 lines
6.9 KiB
TypeScript

import {
allowForAllRegisteredUsersOnPublicStreamsWithPublicComments,
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
allowAnonymousUsersOnPublicStreams,
streamCommentsWritePermissionsPipelineFactory,
streamReadPermissionsPipelineFactory
} from '@/modules/shared/authz'
import { authMiddlewareCreator } from '@/modules/shared/middleware'
import { isArray } from 'lodash-es'
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'
import contentDisposition from 'content-disposition'
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.private
})
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': contentDisposition(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.private })
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
}