refactor(core): Create controller for binary data (no-changelog) (#7363)
This PR adds a controller for binary data + integration tests.
This commit is contained in:
@@ -6,7 +6,7 @@ import type { Request, Response } from 'express';
|
||||
import { parse, stringify } from 'flatted';
|
||||
import picocolors from 'picocolors';
|
||||
import { ErrorReporterProxy as ErrorReporter, NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import { Readable } from 'node:stream';
|
||||
import type {
|
||||
IExecutionDb,
|
||||
IExecutionFlatted,
|
||||
@@ -101,6 +101,11 @@ export function sendSuccessResponse(
|
||||
res.header(responseHeader);
|
||||
}
|
||||
|
||||
if (data instanceof Readable) {
|
||||
data.pipe(res);
|
||||
return;
|
||||
}
|
||||
|
||||
if (raw === true) {
|
||||
if (typeof data === 'string') {
|
||||
res.send(data);
|
||||
|
||||
@@ -26,13 +26,11 @@ import type { RequestOptions } from 'oauth-1.0a';
|
||||
import clientOAuth1 from 'oauth-1.0a';
|
||||
|
||||
import {
|
||||
BinaryDataService,
|
||||
Credentials,
|
||||
LoadMappingOptions,
|
||||
LoadNodeParameterOptions,
|
||||
LoadNodeListSearch,
|
||||
UserSettings,
|
||||
FileNotFoundError,
|
||||
} from 'n8n-core';
|
||||
|
||||
import type {
|
||||
@@ -74,7 +72,6 @@ import {
|
||||
import { credentialsController } from '@/credentials/credentials.controller';
|
||||
import { oauth2CredentialController } from '@/credentials/oauth2Credential.api';
|
||||
import type {
|
||||
BinaryDataRequest,
|
||||
CurlHelper,
|
||||
ExecutionRequest,
|
||||
NodeListSearchRequest,
|
||||
@@ -99,6 +96,7 @@ import {
|
||||
WorkflowStatisticsController,
|
||||
} from '@/controllers';
|
||||
|
||||
import { BinaryDataController } from './controllers/binaryData.controller';
|
||||
import { ExternalSecretsController } from '@/ExternalSecrets/ExternalSecrets.controller.ee';
|
||||
import { executionsController } from '@/executions/executions.controller';
|
||||
import { isApiEnabled, loadPublicApiVersions } from '@/PublicApi';
|
||||
@@ -208,8 +206,6 @@ export class Server extends AbstractServer {
|
||||
|
||||
push: Push;
|
||||
|
||||
binaryDataService: BinaryDataService;
|
||||
|
||||
constructor() {
|
||||
super('main');
|
||||
|
||||
@@ -374,7 +370,6 @@ export class Server extends AbstractServer {
|
||||
this.endpointPresetCredentials = config.getEnv('credentials.overwrite.endpoint');
|
||||
|
||||
this.push = Container.get(Push);
|
||||
this.binaryDataService = Container.get(BinaryDataService);
|
||||
|
||||
await super.start();
|
||||
LoggerProxy.debug(`Server ID: ${this.uniqueInstanceId}`);
|
||||
@@ -581,6 +576,7 @@ export class Server extends AbstractServer {
|
||||
Container.get(ExternalSecretsController),
|
||||
Container.get(OrchestrationController),
|
||||
Container.get(WorkflowHistoryController),
|
||||
Container.get(BinaryDataController),
|
||||
];
|
||||
|
||||
if (isLdapEnabled()) {
|
||||
@@ -1442,50 +1438,6 @@ export class Server extends AbstractServer {
|
||||
}),
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
// Binary data
|
||||
// ----------------------------------------
|
||||
|
||||
// View or download binary file
|
||||
this.app.get(
|
||||
`/${this.restEndpoint}/data`,
|
||||
async (req: BinaryDataRequest, res: express.Response): Promise<void> => {
|
||||
const { id: binaryDataId, action } = req.query;
|
||||
let { fileName, mimeType } = req.query;
|
||||
const [mode] = binaryDataId.split(':') as ['filesystem' | 's3', string];
|
||||
|
||||
try {
|
||||
const binaryPath = this.binaryDataService.getPath(binaryDataId);
|
||||
|
||||
if (!fileName || !mimeType) {
|
||||
try {
|
||||
const metadata = await this.binaryDataService.getMetadata(binaryDataId);
|
||||
fileName = metadata.fileName;
|
||||
mimeType = metadata.mimeType;
|
||||
res.setHeader('Content-Length', metadata.fileSize);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (mimeType) res.setHeader('Content-Type', mimeType);
|
||||
|
||||
if (action === 'download') {
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
|
||||
}
|
||||
|
||||
if (mode === 's3') {
|
||||
const readStream = await this.binaryDataService.getAsStream(binaryDataId);
|
||||
readStream.pipe(res);
|
||||
return;
|
||||
} else {
|
||||
res.sendFile(binaryPath);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof FileNotFoundError) res.writeHead(404).end();
|
||||
else throw error;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ----------------------------------------
|
||||
// Settings
|
||||
// ----------------------------------------
|
||||
|
||||
54
packages/cli/src/controllers/binaryData.controller.ts
Normal file
54
packages/cli/src/controllers/binaryData.controller.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Service } from 'typedi';
|
||||
import express from 'express';
|
||||
import { BinaryDataService, FileNotFoundError, isValidNonDefaultMode } from 'n8n-core';
|
||||
import { Get, RestController } from '@/decorators';
|
||||
import { BinaryDataRequest } from '@/requests';
|
||||
|
||||
@RestController('/binary-data')
|
||||
@Service()
|
||||
export class BinaryDataController {
|
||||
constructor(private readonly binaryDataService: BinaryDataService) {}
|
||||
|
||||
@Get('/')
|
||||
async get(req: BinaryDataRequest, res: express.Response) {
|
||||
const { id: binaryDataId, action } = req.query;
|
||||
|
||||
if (!binaryDataId) {
|
||||
return res.status(400).end('Missing binary data ID');
|
||||
}
|
||||
|
||||
if (!binaryDataId.includes(':')) {
|
||||
return res.status(400).end('Missing binary data mode');
|
||||
}
|
||||
|
||||
const [mode] = binaryDataId.split(':');
|
||||
|
||||
if (!isValidNonDefaultMode(mode)) {
|
||||
return res.status(400).end('Invalid binary data mode');
|
||||
}
|
||||
|
||||
let { fileName, mimeType } = req.query;
|
||||
|
||||
try {
|
||||
if (!fileName || !mimeType) {
|
||||
try {
|
||||
const metadata = await this.binaryDataService.getMetadata(binaryDataId);
|
||||
fileName = metadata.fileName;
|
||||
mimeType = metadata.mimeType;
|
||||
res.setHeader('Content-Length', metadata.fileSize);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (mimeType) res.setHeader('Content-Type', mimeType);
|
||||
|
||||
if (action === 'download') {
|
||||
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);
|
||||
}
|
||||
|
||||
return await this.binaryDataService.getAsStream(binaryDataId);
|
||||
} catch (error) {
|
||||
if (error instanceof FileNotFoundError) return res.writeHead(404).end();
|
||||
else throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user