refactor(core): move node-types endpoints to a separate file n8n-4584 (#4068)

This commit is contained in:
Michael Kret 2022-09-09 18:31:06 +03:00 committed by GitHub
parent a73ac1d94f
commit 2c7ef1e550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 169 additions and 154 deletions

View file

@ -31,7 +31,6 @@
import express from 'express';
import { readFileSync, promises } from 'fs';
import { readFile } from 'fs/promises';
import { exec as callbackExec } from 'child_process';
import _ from 'lodash';
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
@ -56,12 +55,9 @@ import {
INodeCredentialsDetails,
INodeParameters,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
INodeTypeNameVersion,
ITelemetrySettings,
LoggerProxy,
NodeHelpers,
WebhookHttpMethod,
WorkflowExecuteMode,
} from 'n8n-workflow';
@ -138,6 +134,7 @@ import {
} from './UserManagement/UserManagementHelper';
import { loadPublicApiVersions } from './PublicApi';
import * as telemetryScripts from './telemetry/scripts';
import { nodeTypesController } from './api/nodeTypes.api';
require('body-parser-xml')(bodyParser);
@ -816,47 +813,6 @@ class App {
),
);
// Returns all the node-types
this.app.get(
`/${this.restEndpoint}/node-types`,
ResponseHelper.send(
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
const returnData: INodeTypeDescription[] = [];
const onlyLatest = req.query.onlyLatest === 'true';
const nodeTypes = NodeTypes();
const allNodes = nodeTypes.getAll();
const getNodeDescription = (nodeType: INodeType): INodeTypeDescription => {
const nodeInfo: INodeTypeDescription = { ...nodeType.description };
if (req.query.includeProperties !== 'true') {
// @ts-ignore
delete nodeInfo.properties;
}
return nodeInfo;
};
if (onlyLatest) {
allNodes.forEach((nodeData) => {
const nodeType = NodeHelpers.getVersionedNodeType(nodeData);
const nodeInfo: INodeTypeDescription = getNodeDescription(nodeType);
returnData.push(nodeInfo);
});
} else {
allNodes.forEach((nodeData) => {
const allNodeTypes = NodeHelpers.getVersionedNodeTypeAll(nodeData);
allNodeTypes.forEach((element) => {
const nodeInfo: INodeTypeDescription = getNodeDescription(element);
returnData.push(nodeInfo);
});
});
}
return returnData;
},
),
);
this.app.get(
`/${this.restEndpoint}/credential-translation`,
ResponseHelper.send(
@ -878,58 +834,6 @@ class App {
),
);
// Returns node information based on node names and versions
this.app.post(
`/${this.restEndpoint}/node-types`,
ResponseHelper.send(
async (req: express.Request, res: express.Response): Promise<INodeTypeDescription[]> => {
const nodeInfos = _.get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[];
const { defaultLocale } = this.frontendSettings;
if (defaultLocale === 'en') {
return nodeInfos.reduce<INodeTypeDescription[]>((acc, { name, version }) => {
const { description } = NodeTypes().getByNameAndVersion(name, version);
acc.push(injectCustomApiCallOption(description));
return acc;
}, []);
}
async function populateTranslation(
name: string,
version: number,
nodeTypes: INodeTypeDescription[],
) {
const { description, sourcePath } = NodeTypes().getWithSourcePath(name, version);
const translationPath = await getNodeTranslationPath({
nodeSourcePath: sourcePath,
longNodeType: description.name,
locale: defaultLocale,
});
try {
const translation = await readFile(translationPath, 'utf8');
description.translation = JSON.parse(translation);
} catch (error) {
// ignore - no translation exists at path
}
nodeTypes.push(injectCustomApiCallOption(description));
}
const nodeTypes: INodeTypeDescription[] = [];
const promises = nodeInfos.map(async ({ name, version }) =>
populateTranslation(name, version, nodeTypes),
);
await Promise.all(promises);
return nodeTypes;
},
),
);
// Returns node information based on node names and versions
this.app.get(
`/${this.restEndpoint}/node-translation-headers`,
@ -957,6 +861,8 @@ class App {
// Node-Types
// ----------------------------------------
this.app.use(`/${this.restEndpoint}/node-types`, nodeTypesController);
// Returns the node icon
this.app.get(
[
@ -1881,58 +1787,3 @@ export async function start(): Promise<void> {
}
});
}
const CUSTOM_API_CALL_NAME = 'Custom API Call';
const CUSTOM_API_CALL_KEY = '__CUSTOM_API_CALL__';
/**
* Inject a `Custom API Call` option into `resource` and `operation`
* parameters in a node that supports proxy auth.
*/
function injectCustomApiCallOption(description: INodeTypeDescription) {
if (!supportsProxyAuth(description)) return description;
description.properties.forEach((p) => {
if (
['resource', 'operation'].includes(p.name) &&
Array.isArray(p.options) &&
p.options[p.options.length - 1].name !== CUSTOM_API_CALL_NAME
) {
p.options.push({
name: CUSTOM_API_CALL_NAME,
value: CUSTOM_API_CALL_KEY,
});
}
return p;
});
return description;
}
const credentialTypes = CredentialTypes();
/**
* Whether any of the node's credential types may be used to
* make a request from a node other than itself.
*/
function supportsProxyAuth(description: INodeTypeDescription) {
if (!description.credentials) return false;
return description.credentials.some(({ name }) => {
const credType = credentialTypes.getByName(name);
if (credType.authenticate !== undefined) return true;
return isOAuth(credType);
});
}
function isOAuth(credType: ICredentialType) {
return (
Array.isArray(credType.extends) &&
credType.extends.some((parentType) =>
['oAuth2Api', 'googleOAuth2Api', 'oAuth1Api'].includes(parentType),
)
);
}

View file

@ -26,9 +26,9 @@ import {
NodeTypes,
WorkflowRunner,
ResponseHelper,
IExecutionFlattedDb,
} from '..';
import * as config from '../../config';
import { ExecutionEntity } from '../databases/entities/ExecutionEntity';
import { User } from '../databases/entities/User';
import { DEFAULT_EXECUTIONS_GET_ALL_LIMIT } from '../GenericHelpers';
import { getLogger } from '../Logger';
@ -137,7 +137,7 @@ executionsController.get(
const sharedWorkflowIds = await getSharedWorkflowIds(req.user);
const findOptions: FindManyOptions<ExecutionEntity> = {
const findOptions: FindManyOptions<IExecutionFlattedDb> = {
select: [
'id',
'finished',

View file

@ -0,0 +1,164 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-cycle */
import express from 'express';
import { readFile } from 'fs/promises';
import _ from 'lodash';
import {
ICredentialType,
INodeType,
INodeTypeDescription,
INodeTypeNameVersion,
NodeHelpers,
} from 'n8n-workflow';
import { CredentialTypes, NodeTypes, ResponseHelper } from '..';
import config from '../../config';
import { getNodeTranslationPath } from '../TranslationHelpers';
function isOAuth(credType: ICredentialType) {
return (
Array.isArray(credType.extends) &&
credType.extends.some((parentType) =>
['oAuth2Api', 'googleOAuth2Api', 'oAuth1Api'].includes(parentType),
)
);
}
/**
* Whether any of the node's credential types may be used to
* make a request from a node other than itself.
*/
function supportsProxyAuth(description: INodeTypeDescription) {
if (!description.credentials) return false;
const credentialTypes = CredentialTypes();
return description.credentials.some(({ name }) => {
const credType = credentialTypes.getByName(name);
if (credType.authenticate !== undefined) return true;
return isOAuth(credType);
});
}
const CUSTOM_API_CALL_NAME = 'Custom API Call';
const CUSTOM_API_CALL_KEY = '__CUSTOM_API_CALL__';
/**
* Inject a `Custom API Call` option into `resource` and `operation`
* parameters in a node that supports proxy auth.
*/
function injectCustomApiCallOption(description: INodeTypeDescription) {
if (!supportsProxyAuth(description)) return description;
description.properties.forEach((p) => {
if (
['resource', 'operation'].includes(p.name) &&
Array.isArray(p.options) &&
p.options[p.options.length - 1].name !== CUSTOM_API_CALL_NAME
) {
p.options.push({
name: CUSTOM_API_CALL_NAME,
value: CUSTOM_API_CALL_KEY,
});
}
return p;
});
return description;
}
export const nodeTypesController = express.Router();
// Returns all the node-types
nodeTypesController.get(
'/',
ResponseHelper.send(async (req: express.Request): Promise<INodeTypeDescription[]> => {
const returnData: INodeTypeDescription[] = [];
const onlyLatest = req.query.onlyLatest === 'true';
const nodeTypes = NodeTypes();
const allNodes = nodeTypes.getAll();
const getNodeDescription = (nodeType: INodeType): INodeTypeDescription => {
const nodeInfo: INodeTypeDescription = { ...nodeType.description };
if (req.query.includeProperties !== 'true') {
// @ts-ignore
delete nodeInfo.properties;
}
return nodeInfo;
};
if (onlyLatest) {
allNodes.forEach((nodeData) => {
const nodeType = NodeHelpers.getVersionedNodeType(nodeData);
const nodeInfo: INodeTypeDescription = getNodeDescription(nodeType);
returnData.push(nodeInfo);
});
} else {
allNodes.forEach((nodeData) => {
const allNodeTypes = NodeHelpers.getVersionedNodeTypeAll(nodeData);
allNodeTypes.forEach((element) => {
const nodeInfo: INodeTypeDescription = getNodeDescription(element);
returnData.push(nodeInfo);
});
});
}
return returnData;
}),
);
// Returns node information based on node names and versions
nodeTypesController.post(
'/',
ResponseHelper.send(async (req: express.Request): Promise<INodeTypeDescription[]> => {
const nodeInfos = _.get(req, 'body.nodeInfos', []) as INodeTypeNameVersion[];
const defaultLocale = config.getEnv('defaultLocale');
if (defaultLocale === 'en') {
return nodeInfos.reduce<INodeTypeDescription[]>((acc, { name, version }) => {
const { description } = NodeTypes().getByNameAndVersion(name, version);
acc.push(injectCustomApiCallOption(description));
return acc;
}, []);
}
async function populateTranslation(
name: string,
version: number,
nodeTypes: INodeTypeDescription[],
) {
const { description, sourcePath } = NodeTypes().getWithSourcePath(name, version);
const translationPath = await getNodeTranslationPath({
nodeSourcePath: sourcePath,
longNodeType: description.name,
locale: defaultLocale,
});
try {
const translation = await readFile(translationPath, 'utf8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
description.translation = JSON.parse(translation);
} catch (error) {
// ignore - no translation exists at path
}
nodeTypes.push(injectCustomApiCallOption(description));
}
const nodeTypes: INodeTypeDescription[] = [];
const promises = nodeInfos.map(async ({ name, version }) =>
populateTranslation(name, version, nodeTypes),
);
await Promise.all(promises);
return nodeTypes;
}),
);