chore(blobstorage): refactor to typescript (#3374)

This commit is contained in:
Iain Sproat
2024-10-22 12:58:55 +01:00
committed by GitHub
parent bd00c6185b
commit d48d9dfa1d
3 changed files with 71 additions and 40 deletions
@@ -17,5 +17,5 @@ export type BlobStorageItem = {
export type BlobStorageItemInput = SetOptional<
BlobStorageItem,
'fileSize' | 'uploadStatus' | 'uploadError' | 'createdAt' | 'fileHash'
'fileSize' | 'fileType' | 'uploadStatus' | 'uploadError' | 'createdAt' | 'fileHash'
>
@@ -1,38 +1,38 @@
const Busboy = require('busboy')
const {
import Busboy from 'busboy'
import {
allowForAllRegisteredUsersOnPublicStreamsWithPublicComments,
allowForRegisteredUsersOnPublicStreamsEvenWithoutRole,
allowAnonymousUsersOnPublicStreams,
streamWritePermissionsPipelineFactory,
streamReadPermissionsPipelineFactory
} = require('@/modules/shared/authz')
const {
} from '@/modules/shared/authz'
import {
ensureStorageAccess,
storeFileStream,
getObjectStream,
deleteObject,
getObjectAttributes
} = require('@/modules/blobstorage/objectStorage')
const crs = require('crypto-random-string')
const { authMiddlewareCreator } = require('@/modules/shared/middleware')
const { isArray } = require('lodash')
} from '@/modules/blobstorage/objectStorage'
import crs from 'crypto-random-string'
import { authMiddlewareCreator } from '@/modules/shared/middleware'
import { isArray } from 'lodash'
const {
import {
NotFoundError,
ResourceMismatch,
BadRequestError
} = require('@/modules/shared/errors')
const { moduleLogger, logger } = require('@/logging/logging')
const {
} from '@/modules/shared/errors'
import { moduleLogger, logger } from '@/logging/logging'
import {
getAllStreamBlobIdsFactory,
upsertBlobFactory,
updateBlobFactory,
getBlobMetadataFactory,
getBlobMetadataCollectionFactory,
deleteBlobFactory
} = require('@/modules/blobstorage/repositories')
const { db } = require('@/db/knex')
const {
} from '@/modules/blobstorage/repositories'
import { db } from '@/db/knex'
import {
uploadFileStreamFactory,
getFileStreamFactory,
getFileSizeLimit,
@@ -40,13 +40,14 @@ const {
markUploadErrorFactory,
markUploadOverFileSizeLimitFactory,
fullyDeleteBlobFactory
} = require('@/modules/blobstorage/services/management')
const { getRolesFactory } = require('@/modules/shared/repositories/roles')
const {
getAutomationProjectFactory
} = require('@/modules/automate/repositories/automations')
const { adminOverrideEnabled } = require('@/modules/shared/helpers/envHelper')
const { getStreamFactory } = require('@/modules/core/repositories/streams')
} from '@/modules/blobstorage/services/management'
import { getRolesFactory } from '@/modules/shared/repositories/roles'
import { getAutomationProjectFactory } from '@/modules/automate/repositories/automations'
import { adminOverrideEnabled } from '@/modules/shared/helpers/envHelper'
import { getStreamFactory } from '@/modules/core/repositories/streams'
import { Request, Response } from 'express'
import { ensureError } from '@speckle/shared'
import { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
const getStream = getStreamFactory({ db })
const getAllStreamBlobIds = getAllStreamBlobIdsFactory({ db })
@@ -86,7 +87,12 @@ const ensureConditions = async () => {
}
}
const errorHandler = async (req, res, callback) => {
type ErrorHandler = (
req: Request,
res: Response,
callback: (req: Request, res: Response) => Promise<void>
) => Promise<void>
const errorHandler: ErrorHandler = async (req, res, callback) => {
try {
await callback(req, res)
} catch (err) {
@@ -95,12 +101,12 @@ const errorHandler = async (req, res, callback) => {
} else if (err instanceof ResourceMismatch || err instanceof BadRequestError) {
res.status(400).send({ error: err.message })
} else {
res.status(500).send({ error: err.message })
res.status(500).send({ error: ensureError(err, 'Unknown error').message })
}
}
}
exports.init = async (app) => {
export const init: SpeckleModule['init'] = async (app) => {
await ensureConditions()
const streamWritePermissions = streamWritePermissionsPipelineFactory({
getRoles: getRolesFactory({ db }),
@@ -127,8 +133,12 @@ exports.init = async (app) => {
req.log.debug('Uploading blob.')
// no checking of startup conditions, just dont init the endpoints if not configured right
//authorize request
const uploadOperations = {}
const finalizePromises = []
const uploadOperations: Record<string, unknown> = {}
const finalizePromises: Promise<{
uploadStatus?: number
uploadError?: Error | null | string
formKey: string
}>[] = []
const busboy = Busboy({
headers: req.headers,
limits: { fileSize: getFileSizeLimit() }
@@ -136,9 +146,14 @@ exports.init = async (app) => {
busboy.on('file', (formKey, file, info) => {
const { filename: fileName } = info
const fileType = fileName.split('.').pop().toLowerCase()
const fileType = fileName?.split('.')?.pop()?.toLowerCase()
req.log = req.log.child({ fileName, fileType })
const registerUploadResult = (processingPromise) => {
const registerUploadResult = (
processingPromise: Promise<{
uploadStatus?: number
uploadError?: Error | null | string
}>
) => {
finalizePromises.push(
processingPromise.then((resultItem) => ({ ...resultItem, formKey }))
)
@@ -159,7 +174,7 @@ exports.init = async (app) => {
uploadOperations[blobId] = uploadFileStream(
storeFileStream,
{ streamId, userId: req.context.userId },
{ blobId, fileName, fileType, fileStream: file, clientHash }
{ blobId, fileName, fileType, fileStream: file }
)
//this file level 'close' is fired when a single file upload finishes
@@ -180,7 +195,9 @@ exports.init = async (app) => {
})
file.on('error', (err) => {
registerUploadResult(markUploadError(deleteObject, blobId, err.message))
registerUploadResult(
markUploadError(deleteObject, streamId, blobId, err.message)
)
})
})
@@ -199,7 +216,12 @@ exports.init = async (app) => {
//delete all started uploads
await Promise.all(
Object.keys(uploadOperations).map((blobId) =>
markUploadError(deleteObject, streamId, blobId, err.message)
markUploadError(
deleteObject,
streamId,
blobId,
ensureError(err, 'Unknown error while uploading blob').message
)
)
)
@@ -282,12 +304,15 @@ exports.init = async (app) => {
'/api/stream/:streamId/blobs',
authMiddlewareCreator(streamWritePermissions),
async (req, res) => {
const fileName = req.query.fileName
let fileName = req.query.fileName
if (isArray(fileName)) {
fileName = fileName[0]
}
errorHandler(req, res, async (req, res) => {
const blobMetadataCollection = await getBlobMetadataCollection({
streamId: req.params.streamId,
query: fileName
query: fileName as string
})
res.status(200).send(blobMetadataCollection)
@@ -300,4 +325,4 @@ exports.init = async (app) => {
})
}
exports.finalize = () => {}
export const finalize: SpeckleModule['finalize'] = () => {}
@@ -8,6 +8,7 @@ import { BlobStorageItem } from '@/modules/blobstorage/domain/types'
import { BadRequestError } from '@/modules/shared/errors'
import { getFileSizeLimitMB } from '@/modules/shared/helpers/envHelper'
import { MaybeAsync } from '@speckle/shared'
import { Readable } from 'stream'
/**
* File size limit in bytes
@@ -19,17 +20,22 @@ export const uploadFileStreamFactory =
async (
storeFileStream: (params: {
objectKey: string
fileStream: Buffer
fileStream: Readable | Buffer
}) => Promise<{ fileHash: string }>,
params1: { streamId: string; userId: string },
params2: { blobId: string; fileName: string; fileType: string; fileStream: Buffer }
params1: { streamId: string; userId: string | undefined },
params2: {
blobId: string
fileName: string
fileType: string | undefined
fileStream: Readable | Buffer
}
) => {
const { streamId, userId } = params1
const { blobId, fileName, fileType, fileStream } = params2
if (streamId.length !== 10)
throw new BadRequestError('The stream id has to be of length 10')
if (userId.length !== 10)
if (!userId || userId.length !== 10)
throw new BadRequestError('The user id has to be of length 10')
const objectKey = `assets/${streamId}/${blobId}`