c99f40bb20
Release pipeline / Get version (push) Has been cancelled
Release pipeline / Get Chart Name (push) Has been cancelled
Release pipeline / tests (push) Has been cancelled
Release pipeline / builds (push) Has been cancelled
Release pipeline / builds-ghcr (push) Has been cancelled
Release pipeline / test-deployments (push) Has been cancelled
Release pipeline / deploy (push) Has been cancelled
Release pipeline / Helm chart oci (push) Has been cancelled
Release pipeline / npm (push) Has been cancelled
Release pipeline / snyk (push) Has been cancelled
171 lines
6.1 KiB
TypeScript
171 lines
6.1 KiB
TypeScript
import { Router } from 'express'
|
|
import {
|
|
insertNewUploadAndNotifyFactory,
|
|
insertNewUploadAndNotifyFactoryV2
|
|
} from '@/modules/fileuploads/services/management'
|
|
import { authMiddlewareCreator } from '@/modules/shared/middleware'
|
|
import {
|
|
saveUploadFileFactory,
|
|
saveUploadFileFactoryV2
|
|
} from '@/modules/fileuploads/repositories/fileUploads'
|
|
import { db } from '@/db/knex'
|
|
import { streamWritePermissionsPipelineFactory } from '@/modules/shared/authz'
|
|
import { getStreamBranchByNameFactory } from '@/modules/core/repositories/branches'
|
|
import { getStreamFactory } from '@/modules/core/repositories/streams'
|
|
import { getProjectDbClient } from '@/modules/multiregion/utils/dbSelector'
|
|
import { createBusboy } from '@/modules/blobstorage/rest/busboy'
|
|
import { processNewFileStreamFactory } from '@/modules/blobstorage/services/streams'
|
|
import { UnauthorizedError } from '@/modules/shared/errors'
|
|
import type { Nullable } from '@speckle/shared'
|
|
import { ensureError } from '@speckle/shared'
|
|
import { UploadRequestErrorMessage } from '@/modules/fileuploads/helpers/rest'
|
|
import { getEventBus } from '@/modules/shared/services/eventBus'
|
|
import { getFeatureFlags } from '@speckle/shared/environment'
|
|
import { BranchNotFoundError } from '@/modules/core/errors/branch'
|
|
import { fileImportQueues } from '@/modules/fileuploads/queues/fileimports'
|
|
import { pushJobToFileImporterFactory } from '@/modules/fileuploads/services/createFileImport'
|
|
import {
|
|
fileImportServiceShouldUsePrivateObjectsServerUrl,
|
|
getPrivateObjectsServerOrigin,
|
|
getServerOrigin
|
|
} from '@/modules/shared/helpers/envHelper'
|
|
import { createAppTokenFactory } from '@/modules/core/services/tokens'
|
|
import {
|
|
storeApiTokenFactory,
|
|
storeTokenResourceAccessDefinitionsFactory,
|
|
storeTokenScopesFactory,
|
|
storeUserServerAppTokenFactory
|
|
} from '@/modules/core/repositories/tokens'
|
|
|
|
const { FF_NEXT_GEN_FILE_IMPORTER_ENABLED } = getFeatureFlags()
|
|
|
|
export const fileuploadRouterFactory = (): Router => {
|
|
const processNewFileStream = processNewFileStreamFactory()
|
|
|
|
const app = Router()
|
|
|
|
/**
|
|
* @deprecated use POST /graphql (mutation.fileUploadMutations.generateUploadUrl), then PUT (to the provided url), then POST /graphql (mutation.fileUploadMutations.startFileImport)
|
|
*/
|
|
app.post(
|
|
'/api/file/:fileType/:streamId/:branchName?',
|
|
authMiddlewareCreator(
|
|
streamWritePermissionsPipelineFactory({
|
|
getStream: getStreamFactory({ db })
|
|
})
|
|
),
|
|
async (req, res) => {
|
|
const branchName = req.params.branchName || 'main'
|
|
const projectId = req.params.streamId
|
|
const userId = req.context.userId || 'anonymous-user-id'
|
|
|
|
// Bypass user authentication check for public uploads
|
|
// if (!userId) {
|
|
// throw new UnauthorizedError('User not authenticated.')
|
|
// }
|
|
const logger = req.log.child({
|
|
projectId,
|
|
streamId: projectId, //legacy
|
|
userId,
|
|
branchName
|
|
})
|
|
|
|
const projectDb = await getProjectDbClient({ projectId })
|
|
const getStreamBranchByName = getStreamBranchByNameFactory({ db: projectDb })
|
|
const branch = await getStreamBranchByName(projectId, branchName)
|
|
if (!branch) {
|
|
throw new BranchNotFoundError('Branch {branchName} was not found', {
|
|
info: { branchName }
|
|
})
|
|
}
|
|
|
|
const insertNewUploadAndNotify = insertNewUploadAndNotifyFactory({
|
|
saveUploadFile: saveUploadFileFactory({ db: projectDb }),
|
|
emit: getEventBus().emit
|
|
})
|
|
|
|
const pushJobToFileImporter = pushJobToFileImporterFactory({
|
|
getServerOrigin: fileImportServiceShouldUsePrivateObjectsServerUrl()
|
|
? getPrivateObjectsServerOrigin
|
|
: getServerOrigin,
|
|
createAppToken: createAppTokenFactory({
|
|
storeApiToken: storeApiTokenFactory({ db }),
|
|
storeTokenScopes: storeTokenScopesFactory({ db }),
|
|
storeTokenResourceAccessDefinitions:
|
|
storeTokenResourceAccessDefinitionsFactory({ db }),
|
|
storeUserServerAppToken: storeUserServerAppTokenFactory({ db })
|
|
})
|
|
})
|
|
|
|
const insertNewUploadAndNotifyV2 = insertNewUploadAndNotifyFactoryV2({
|
|
queues: fileImportQueues,
|
|
pushJobToFileImporter,
|
|
saveUploadFile: saveUploadFileFactoryV2({ db: projectDb }),
|
|
emit: getEventBus().emit
|
|
})
|
|
|
|
const saveFileUploads = async ({
|
|
uploadResults
|
|
}: {
|
|
uploadResults: Array<{
|
|
blobId: string
|
|
fileName: string
|
|
fileSize: Nullable<number>
|
|
}>
|
|
}) => {
|
|
await Promise.all(
|
|
uploadResults.map(async (upload) => {
|
|
await (FF_NEXT_GEN_FILE_IMPORTER_ENABLED
|
|
? insertNewUploadAndNotifyV2
|
|
: insertNewUploadAndNotify)({
|
|
fileId: upload.blobId,
|
|
streamId: projectId, //legacy
|
|
projectId,
|
|
branchName: branch.name || branchName, //legacy
|
|
userId,
|
|
fileName: upload.fileName,
|
|
fileType: upload.fileName?.split('.').pop() || '', //FIXME
|
|
fileSize: upload.fileSize,
|
|
modelName: branch.name || branchName,
|
|
modelId: branch.id
|
|
})
|
|
})
|
|
)
|
|
}
|
|
|
|
const busboy = createBusboy(req)
|
|
const newFileStreamProcessor = await processNewFileStream({
|
|
busboy,
|
|
streamId: projectId,
|
|
userId,
|
|
logger,
|
|
onFinishAllFileUploads: async (uploadResults) => {
|
|
try {
|
|
await saveFileUploads({
|
|
uploadResults
|
|
})
|
|
} catch (err) {
|
|
logger.error(ensureError(err), 'File importer handling error @deprecated')
|
|
res.status(500)
|
|
}
|
|
|
|
res.setHeader(
|
|
'Warning',
|
|
'Deprecated API; use POST /graphql (mutation.fileUploadMutations.generateUploadUrl), then PUT (to the provided url), then POST /graphql (mutation.fileUploadMutations.startFileImport)'
|
|
)
|
|
|
|
res.status(201).send({ uploadResults })
|
|
},
|
|
onError: () => {
|
|
res.contentType('application/json')
|
|
res.status(400).end(UploadRequestErrorMessage)
|
|
}
|
|
})
|
|
|
|
req.pipe(newFileStreamProcessor)
|
|
}
|
|
)
|
|
|
|
return app
|
|
}
|