update version
This commit is contained in:
@@ -5,6 +5,7 @@ import type {
|
|||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
INodePropertyOptions,
|
INodePropertyOptions,
|
||||||
} from "n8n-workflow";
|
} from "n8n-workflow";
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
interface OpenAPISpec {
|
interface OpenAPISpec {
|
||||||
@@ -23,20 +24,6 @@ async function loadOpenAPISpecCached(url: string): Promise<OpenAPISpec> {
|
|||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultValue(type: string | undefined, schema?: any): any {
|
|
||||||
if (schema?.example !== undefined) return schema.example;
|
|
||||||
if (schema?.default !== undefined) return schema.default;
|
|
||||||
switch (type) {
|
|
||||||
case "string": return "";
|
|
||||||
case "number":
|
|
||||||
case "integer": return 0;
|
|
||||||
case "boolean": return false;
|
|
||||||
case "array": return [];
|
|
||||||
case "object": return {};
|
|
||||||
default: return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveRef(obj: any, spec: OpenAPISpec): any {
|
function resolveRef(obj: any, spec: OpenAPISpec): any {
|
||||||
if (!obj || typeof obj !== "object") return obj;
|
if (!obj || typeof obj !== "object") return obj;
|
||||||
if (obj.$ref && typeof obj.$ref === "string" && obj.$ref.startsWith("#/")) {
|
if (obj.$ref && typeof obj.$ref === "string" && obj.$ref.startsWith("#/")) {
|
||||||
@@ -71,17 +58,31 @@ export class OpenApiNode implements INodeType {
|
|||||||
default: "",
|
default: "",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
displayName: "Tag Filter",
|
||||||
|
name: "tagFilter",
|
||||||
|
type: "options",
|
||||||
|
default: "",
|
||||||
|
description: "Filter operations based on OpenAPI tags",
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: "loadTags",
|
||||||
|
loadOptionsDependsOn: ["openapiUrl"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
displayName: "Operation",
|
displayName: "Operation",
|
||||||
name: "operation",
|
name: "operation",
|
||||||
type: "options",
|
type: "options",
|
||||||
typeOptions: {
|
typeOptions: {
|
||||||
loadOptionsMethod: "loadOperations",
|
loadOptionsMethod: "loadOperations",
|
||||||
loadOptionsDependsOn: ["openapiUrl"],
|
loadOptionsDependsOn: ["openapiUrl", "tagFilter"],
|
||||||
},
|
},
|
||||||
default: "",
|
default: "",
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
displayName: "Parameters (Form)",
|
displayName: "Parameters (Form)",
|
||||||
name: "parametersForm",
|
name: "parametersForm",
|
||||||
@@ -131,33 +132,87 @@ export class OpenApiNode implements INodeType {
|
|||||||
|
|
||||||
methods = {
|
methods = {
|
||||||
loadOptions: {
|
loadOptions: {
|
||||||
|
// ============================
|
||||||
|
// LOAD TAGS
|
||||||
|
// ============================
|
||||||
|
async loadTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const url = (this.getNodeParameter("openapiUrl", "") as string).trim();
|
||||||
|
if (!url) return [{ name: "Enter URL first", value: "" }];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const spec = await loadOpenAPISpecCached(url);
|
||||||
|
const tags = new Set<string>();
|
||||||
|
|
||||||
|
for (const path of Object.keys(spec.paths || {})) {
|
||||||
|
const pathObj = spec.paths[path];
|
||||||
|
for (const method of Object.keys(pathObj || {})) {
|
||||||
|
const op = pathObj[method];
|
||||||
|
if (Array.isArray(op.tags)) {
|
||||||
|
for (const t of op.tags) tags.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...tags].map((t) => ({
|
||||||
|
name: t,
|
||||||
|
value: t,
|
||||||
|
description: `Filter only operations with tag: ${t}`,
|
||||||
|
}));
|
||||||
|
} catch (err: any) {
|
||||||
|
return [{ name: `Error: ${err.message}`, value: "" }];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// LOAD OPERATIONS
|
||||||
|
// ============================
|
||||||
async loadOperations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
async loadOperations(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
const url = (this.getNodeParameter("openapiUrl", "") as string).trim();
|
const url = (this.getNodeParameter("openapiUrl", "") as string).trim();
|
||||||
|
const tagFilter = (this.getNodeParameter("tagFilter", "") as string).trim();
|
||||||
|
|
||||||
if (!url) return [{ name: "Provide openapiUrl first", value: "" }];
|
if (!url) return [{ name: "Provide openapiUrl first", value: "" }];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const spec = await loadOpenAPISpecCached(url);
|
const spec = await loadOpenAPISpecCached(url);
|
||||||
const ops: INodePropertyOptions[] = [];
|
const ops: INodePropertyOptions[] = [];
|
||||||
|
|
||||||
for (const path of Object.keys(spec.paths || {})) {
|
for (const path of Object.keys(spec.paths || {})) {
|
||||||
const pathObj = spec.paths[path];
|
const pathObj = spec.paths[path];
|
||||||
|
|
||||||
for (const method of Object.keys(pathObj || {})) {
|
for (const method of Object.keys(pathObj || {})) {
|
||||||
const op = pathObj[method];
|
const op = pathObj[method];
|
||||||
|
|
||||||
|
if (tagFilter && (!op.tags || !op.tags.includes(tagFilter))) {
|
||||||
|
continue; // apply tag filter
|
||||||
|
}
|
||||||
|
|
||||||
const opId =
|
const opId =
|
||||||
op.operationId ||
|
op.operationId ||
|
||||||
`${method}_${path.replace(/[{}]/g, "").replace(/\//g, "_")}`;
|
`${method}_${path.replace(/[{}]/g, "").replace(/\//g, "_")}`;
|
||||||
const title = (op.summary || opId).toString();
|
|
||||||
|
const methodLabel = method.toUpperCase();
|
||||||
|
const summary = op.summary || opId;
|
||||||
|
const description = op.description || "No description provided.";
|
||||||
|
|
||||||
ops.push({
|
ops.push({
|
||||||
name: `${title} — ${method.toUpperCase()} ${path}`,
|
name: `[${methodLabel}] ${path} — ${summary}`,
|
||||||
value: opId,
|
value: opId,
|
||||||
|
description: `${summary}\n\n${description}\n\nTags: ${
|
||||||
|
op.tags ? op.tags.join(", ") : "None"
|
||||||
|
}\nOperation ID: ${opId}\nMethod: ${methodLabel}\nPath: ${path}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ops;
|
|
||||||
|
return ops.length ? ops : [{ name: "No operations found", value: "" }];
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return [{ name: `Error: ${err.message}`, value: "" }];
|
return [{ name: `Error: ${err.message}`, value: "" }];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// LOAD PARAMETER NAMES
|
||||||
|
// ============================
|
||||||
async loadParameterNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
async loadParameterNames(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
const url = (this.getNodeParameter("openapiUrl", "") as string).trim();
|
const url = (this.getNodeParameter("openapiUrl", "") as string).trim();
|
||||||
const opId = (this.getNodeParameter("operation", "") as string).trim();
|
const opId = (this.getNodeParameter("operation", "") as string).trim();
|
||||||
@@ -193,6 +248,7 @@ export class OpenApiNode implements INodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Body fields
|
||||||
const bodySchema =
|
const bodySchema =
|
||||||
op.requestBody?.content?.["application/json"]?.schema;
|
op.requestBody?.content?.["application/json"]?.schema;
|
||||||
if (bodySchema) {
|
if (bodySchema) {
|
||||||
@@ -221,6 +277,9 @@ export class OpenApiNode implements INodeType {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================
|
||||||
|
// EXECUTE METHOD
|
||||||
|
// ============================
|
||||||
async execute(this: IExecuteFunctions) {
|
async execute(this: IExecuteFunctions) {
|
||||||
const items = this.getInputData();
|
const items = this.getInputData();
|
||||||
const returnData: any[] = [];
|
const returnData: any[] = [];
|
||||||
@@ -306,7 +365,7 @@ export class OpenApiNode implements INodeType {
|
|||||||
timeout: 30000,
|
timeout: 30000,
|
||||||
};
|
};
|
||||||
|
|
||||||
// === FIX: GET & HEAD tidak boleh punya body ===
|
// GET/HEAD must not have body
|
||||||
if (!(foundMethod === "get" || foundMethod === "head")) {
|
if (!(foundMethod === "get" || foundMethod === "head")) {
|
||||||
axiosConfig.data = Object.keys(bodyParams).length ? bodyParams : undefined;
|
axiosConfig.data = Object.keys(bodyParams).length ? bodyParams : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-nodes-openapi-node",
|
"name": "n8n-nodes-openapi-node",
|
||||||
"version": "1.0.6",
|
"version": "1.0.7",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"n8n",
|
"n8n",
|
||||||
"n8n-nodes"
|
"n8n-nodes"
|
||||||
|
|||||||
Reference in New Issue
Block a user