n8n
Version:
n8n Workflow Automation Tool
179 lines • 7.91 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildDiscoverResponse = buildDiscoverResponse;
exports._resetCache = _resetCache;
const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-schema-ref-parser"));
const is_record_1 = require("@n8n/utils/is-record");
const path_1 = __importDefault(require("path"));
const public_api_route_resolver_1 = require("../../../public-api-route-resolver");
const decorator_routes_1 = require("../../openapi-gen/decorator-routes");
const public_api_scope_lookup_1 = require("../../shared/public-api-scope-lookup");
require("../../controllers");
let cachedEndpointsPromise;
function extractRequestSchema(operation) {
if (!(0, is_record_1.isRecord)(operation.requestBody))
return undefined;
const content = operation.requestBody.content;
if (!(0, is_record_1.isRecord)(content))
return undefined;
const json = content['application/json'];
if (!(0, is_record_1.isRecord)(json))
return undefined;
const schema = json.schema;
return (0, is_record_1.isRecord)(schema) ? schema : undefined;
}
async function parseEndpointsFromSpec() {
cachedEndpointsPromise ??= buildAllEndpoints();
return await cachedEndpointsPromise;
}
async function buildAllEndpoints() {
return [...(await buildEovEndpoints()), ...buildDecoratorEndpoints()];
}
async function buildEovEndpoints() {
const specPath = path_1.default.join(__dirname, '..', '..', 'openapi.yml');
const publicApiRoot = path_1.default.join(__dirname, '..', '..', '..');
const spec = await json_schema_ref_parser_1.default.dereference(specPath);
if (!(0, is_record_1.isRecord)(spec) || !(0, is_record_1.isRecord)(spec.paths))
return [];
const endpoints = [];
const handlerCache = new Map();
for (const [pathKey, pathValue] of Object.entries(spec.paths)) {
if (!(0, is_record_1.isRecord)(pathValue))
continue;
for (const method of public_api_route_resolver_1.HTTP_METHODS) {
const operation = pathValue[method];
if (!(0, is_record_1.isRecord)(operation))
continue;
if (operation['x-decorator-routed'] === true)
continue;
const operationId = operation['x-eov-operation-id'];
const handlerPath = operation['x-eov-operation-handler'];
if (typeof operationId !== 'string' || typeof handlerPath !== 'string')
continue;
const tags = Array.isArray(operation.tags) ? operation.tags : [];
const tag = typeof tags[0] === 'string' ? tags[0] : 'Other';
let handlerModule = handlerCache.get(handlerPath);
if (!handlerModule) {
try {
const fullHandlerPath = path_1.default.join(publicApiRoot, `${handlerPath}.js`);
const imported = await import(fullHandlerPath);
if (!(0, is_record_1.isRecord)(imported))
continue;
const loaded = (0, is_record_1.isRecord)(imported.default) ? imported.default : imported;
if (!(0, is_record_1.isRecord)(loaded))
continue;
handlerModule = loaded;
handlerCache.set(handlerPath, handlerModule);
}
catch {
continue;
}
}
const middlewareChain = handlerModule[operationId];
const scope = Array.isArray(middlewareChain)
? (0, public_api_scope_lookup_1.extractScopeFromEovHandlerChain)(middlewareChain)
: undefined;
endpoints.push({
method: method.toUpperCase(),
path: `/api/v1${pathKey}`,
operationId,
tag,
scope: scope ? (0, public_api_route_resolver_1.scopeRequirementFromString)(scope) : null,
requestSchema: extractRequestSchema(operation),
});
}
}
return endpoints;
}
function buildDecoratorEndpoints() {
return (0, public_api_route_resolver_1.resolvePublicApiRoutes)().map((route) => ({
method: route.method.toUpperCase(),
path: `/api/v1${(0, public_api_route_resolver_1.toOpenApiPathTemplate)(route.path)}`,
operationId: route.handlerName,
tag: route.tags?.[0] ?? 'Other',
scope: route.apiKeyScope ?? null,
requestSchema: (0, decorator_routes_1.buildRequestBodyJsonSchema)(route),
}));
}
async function buildDiscoverResponse(callerScopes, options) {
const allEndpoints = await parseEndpointsFromSpec();
const includeSchemas = options?.includeSchemas === true;
const filtered = allEndpoints.filter((ep) => ep.scope === null || (0, public_api_route_resolver_1.apiKeyScopesSatisfy)(callerScopes, ep.scope));
const operationsOf = (scope) => scope === null
? []
: (0, public_api_route_resolver_1.scopesInRequirement)(scope)
.map((s) => s.split(':')[1])
.filter((operation) => Boolean(operation));
const resources = {};
for (const ep of filtered) {
const resourceKey = ep.tag.toLowerCase();
if (!resources[resourceKey]) {
resources[resourceKey] = { operations: [], endpoints: [] };
}
const entry = {
method: ep.method,
path: ep.path,
operationId: ep.operationId,
};
if (includeSchemas && ep.requestSchema) {
entry.requestSchema = ep.requestSchema;
}
resources[resourceKey].endpoints.push(entry);
for (const operation of operationsOf(ep.scope)) {
if (!resources[resourceKey].operations.includes(operation)) {
resources[resourceKey].operations.push(operation);
}
}
}
const resourceFilter = options?.resource?.toLowerCase();
const operationFilter = options?.operation?.toLowerCase();
let filteredResources = resources;
if (resourceFilter) {
const match = filteredResources[resourceFilter];
filteredResources = match ? { [resourceFilter]: match } : {};
}
if (operationFilter) {
const operationsByOperationId = new Map(filtered.map((f) => [
f.operationId,
new Set(operationsOf(f.scope).map((operation) => operation.toLowerCase())),
]));
const result = {};
for (const [key, info] of Object.entries(filteredResources)) {
const matchingEndpoints = info.endpoints.filter((ep) => operationsByOperationId.get(ep.operationId)?.has(operationFilter));
if (matchingEndpoints.length > 0) {
result[key] = {
operations: info.operations.filter((o) => o.toLowerCase() === operationFilter),
endpoints: matchingEndpoints,
};
}
}
filteredResources = result;
}
const allOperations = [...new Set(Object.values(resources).flatMap((r) => r.operations))];
return {
scopes: callerScopes,
resources: filteredResources,
filters: {
resource: {
description: 'Filter to a specific resource',
values: Object.keys(resources),
},
operation: {
description: 'Filter to a specific operation',
values: allOperations,
},
include: {
description: 'Include additional data',
values: ['schemas'],
},
},
specUrl: '/api/v1/openapi.yml',
};
}
function _resetCache() {
cachedEndpointsPromise = undefined;
}
//# sourceMappingURL=discover.service.js.map