mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
refactor(core): move node-types endpoints to a separate file n8n-4584 (#4068)
This commit is contained in:
parent
a73ac1d94f
commit
2c7ef1e550
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { readFileSync, promises } from 'fs';
|
import { readFileSync, promises } from 'fs';
|
||||||
import { readFile } from 'fs/promises';
|
|
||||||
import { exec as callbackExec } from 'child_process';
|
import { exec as callbackExec } from 'child_process';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve } from 'path';
|
||||||
|
@ -56,12 +55,9 @@ import {
|
||||||
INodeCredentialsDetails,
|
INodeCredentialsDetails,
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
|
||||||
INodeTypeNameVersion,
|
INodeTypeNameVersion,
|
||||||
ITelemetrySettings,
|
ITelemetrySettings,
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
NodeHelpers,
|
|
||||||
WebhookHttpMethod,
|
WebhookHttpMethod,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -138,6 +134,7 @@ import {
|
||||||
} from './UserManagement/UserManagementHelper';
|
} from './UserManagement/UserManagementHelper';
|
||||||
import { loadPublicApiVersions } from './PublicApi';
|
import { loadPublicApiVersions } from './PublicApi';
|
||||||
import * as telemetryScripts from './telemetry/scripts';
|
import * as telemetryScripts from './telemetry/scripts';
|
||||||
|
import { nodeTypesController } from './api/nodeTypes.api';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
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.app.get(
|
||||||
`/${this.restEndpoint}/credential-translation`,
|
`/${this.restEndpoint}/credential-translation`,
|
||||||
ResponseHelper.send(
|
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
|
// Returns node information based on node names and versions
|
||||||
this.app.get(
|
this.app.get(
|
||||||
`/${this.restEndpoint}/node-translation-headers`,
|
`/${this.restEndpoint}/node-translation-headers`,
|
||||||
|
@ -957,6 +861,8 @@ class App {
|
||||||
// Node-Types
|
// Node-Types
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
|
this.app.use(`/${this.restEndpoint}/node-types`, nodeTypesController);
|
||||||
|
|
||||||
// Returns the node icon
|
// Returns the node icon
|
||||||
this.app.get(
|
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),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,9 +26,9 @@ import {
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
WorkflowRunner,
|
WorkflowRunner,
|
||||||
ResponseHelper,
|
ResponseHelper,
|
||||||
|
IExecutionFlattedDb,
|
||||||
} from '..';
|
} from '..';
|
||||||
import * as config from '../../config';
|
import * as config from '../../config';
|
||||||
import { ExecutionEntity } from '../databases/entities/ExecutionEntity';
|
|
||||||
import { User } from '../databases/entities/User';
|
import { User } from '../databases/entities/User';
|
||||||
import { DEFAULT_EXECUTIONS_GET_ALL_LIMIT } from '../GenericHelpers';
|
import { DEFAULT_EXECUTIONS_GET_ALL_LIMIT } from '../GenericHelpers';
|
||||||
import { getLogger } from '../Logger';
|
import { getLogger } from '../Logger';
|
||||||
|
@ -137,7 +137,7 @@ executionsController.get(
|
||||||
|
|
||||||
const sharedWorkflowIds = await getSharedWorkflowIds(req.user);
|
const sharedWorkflowIds = await getSharedWorkflowIds(req.user);
|
||||||
|
|
||||||
const findOptions: FindManyOptions<ExecutionEntity> = {
|
const findOptions: FindManyOptions<IExecutionFlattedDb> = {
|
||||||
select: [
|
select: [
|
||||||
'id',
|
'id',
|
||||||
'finished',
|
'finished',
|
||||||
|
|
164
packages/cli/src/api/nodeTypes.api.ts
Normal file
164
packages/cli/src/api/nodeTypes.api.ts
Normal 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;
|
||||||
|
}),
|
||||||
|
);
|
Loading…
Reference in a new issue