From 4fcb80df3d0d59ef6a5f042f08ba32c561f16bda Mon Sep 17 00:00:00 2001 From: bipproduction Date: Fri, 7 Nov 2025 10:41:05 +0800 Subject: [PATCH] tambahan v2 --- gen.ts | 387 ++++ gen.txt | 267 +++ gen.txt2 | 263 +++ icon.svg | 35 + openapi.json | 2432 +++++++++++++++++++++ package.json | 72 +- src/credentials/WibuApi.credentials.ts | 19 + src/index.ts | 3 - src/nodes/Wibu/Wibu.node.json | 20 - src/nodes/Wibu/Wibu.node.ts | 46 - src/nodes/Wibu/logo.svg | 2 - src/wibu/MCP Server/MCPServerApi.node.ts | 667 ++++++ src/wibu/aduan/AduanApi.node.ts | 425 ++++ src/wibu/apikey/ApikeyApi.node.ts | 425 ++++ src/wibu/auth/AuthApi.node.ts | 304 +++ src/wibu/credential/CredentialApi.node.ts | 425 ++++ src/wibu/darmasaba/DarmasabaApi.node.ts | 1042 +++++++++ src/wibu/layanan/LayananApi.node.ts | 183 ++ src/wibu/mcp/McpApi.node.ts | 1398 ++++++++++++ src/wibu/pelayanan/PelayananApi.node.ts | 425 ++++ src/wibu/pengaduan/PengaduanApi.node.ts | 546 +++++ src/wibu/user/UserApi.node.ts | 304 +++ 22 files changed, 9615 insertions(+), 75 deletions(-) create mode 100644 gen.ts create mode 100644 gen.txt create mode 100644 gen.txt2 create mode 100644 icon.svg create mode 100644 openapi.json create mode 100644 src/credentials/WibuApi.credentials.ts delete mode 100644 src/nodes/Wibu/Wibu.node.json delete mode 100644 src/nodes/Wibu/Wibu.node.ts delete mode 100644 src/nodes/Wibu/logo.svg create mode 100644 src/wibu/MCP Server/MCPServerApi.node.ts create mode 100644 src/wibu/aduan/AduanApi.node.ts create mode 100644 src/wibu/apikey/ApikeyApi.node.ts create mode 100644 src/wibu/auth/AuthApi.node.ts create mode 100644 src/wibu/credential/CredentialApi.node.ts create mode 100644 src/wibu/darmasaba/DarmasabaApi.node.ts create mode 100644 src/wibu/layanan/LayananApi.node.ts create mode 100644 src/wibu/mcp/McpApi.node.ts create mode 100644 src/wibu/pelayanan/PelayananApi.node.ts create mode 100644 src/wibu/pengaduan/PengaduanApi.node.ts create mode 100644 src/wibu/user/UserApi.node.ts diff --git a/gen.ts b/gen.ts new file mode 100644 index 0000000..a253e60 --- /dev/null +++ b/gen.ts @@ -0,0 +1,387 @@ +// tools/generate-n8n-from-openapi.ts +// Final stable generator: OpenAPI -> n8n nodes (TypeScript) +// Usage: node ./tools/generate-n8n-from-openapi.ts +/* eslint-disable @typescript-eslint/no-explicit-any */ +import fs from "fs"; +import path from "path"; + +const NAMESPACE = "wibu"; + +function pascalCase(str: string) { + return str + .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()) + .replace(/^\w/, (c) => c.toUpperCase()); +} + +function ensureDir(p: string) { + if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); +} + +function readJSON(p: string) { + return JSON.parse(fs.readFileSync(p, "utf8")); +} + +function escapeForTemplate(s: string) { + return s.replace(/`/g, "\\`").replace(/\$\{/g, "\\${"); +} + +function parsePathParams(url: string) { + const out: string[] = []; + const re = /{([^}]+)}/g; + let m: RegExpExecArray | null; + while ((m = re.exec(url))) out.push(m[1]); + return out; +} + +function detectRequestBodyType(requestBody: any) { + if (!requestBody || !requestBody.content) return null; + if (requestBody.content["multipart/form-data"]) return "formdata"; + if (requestBody.content["application/x-www-form-urlencoded"]) return "formurl"; + if (requestBody.content["application/json"]) return "json"; + const keys = Object.keys(requestBody.content || {}); + return keys.length ? keys[0] : null; +} + +function buildParamListFromOps(ops: any[]) { + const map: Record = {}; + for (const op of ops) { + const params = op.parameters || []; + for (const p of params) { + const key = `${p.in}_${p.name}`; + if (!map[key]) map[key] = p; + } + const inferred = parsePathParams(op.path); + for (const ip of inferred) { + const key = `path_${ip}`; + if (!map[key]) map[key] = { name: ip, in: "path", required: true }; + } + } + return map; +} + +function generateNode(tag: string, operations: any[], outDir: string) { + const baseFolder = path.join(outDir, NAMESPACE); + ensureDir(baseFolder); + const folder = path.join(baseFolder, tag); + ensureDir(folder); + + const className = `${pascalCase(tag)}Api`; + + const merged = buildParamListFromOps(operations); + const mergedProps = Object.keys(merged) + .map((k) => { + const p = merged[k]; + const display = `${pascalCase(p.in ?? "param")} - ${p.name}${p.required ? " *" : ""}`; + const type = p.schema && p.schema.type === "integer" ? "number" : "string"; + return `{ + displayName: "${display.replace(/"/g, '\\"')}", + name: "${k}", + type: "${type}", + default: "${(p.example ?? "") as string}" + }`; + }) + .join(","); + + const opsOptions = operations + .map((o) => { + const name = (o.summary ?? o.operationId ?? o.path).toString().replace(/"/g, '\\"'); + return `{ + name: "${name}", + value: "${o.operationId}" + }`; + }) + .join(","); + + // Build switch cases + const switchCases = operations + .map((op) => { + const opId = op.operationId; + const requestBodyTypeDetected = detectRequestBodyType(op.requestBody) ?? "null"; + const pathParams = parsePathParams(op.path); + + const replacePathCode = pathParams + .map( + (pp) => + `endpoint = endpoint.replace(new RegExp('\\\\{${pp}\\\\}','g'), encodeURIComponent(String(pathParamsValues["${pp}"] ?? "")));` + ) + .join("\n "); + + const paginationBlock = op["x-pagination"] + ? `// pagination collector + const allItems: any[] = []; + if (Array.isArray(parsed)) allItems.push(...parsed); + else if (parsed && parsed.items && Array.isArray(parsed.items)) allItems.push(...parsed.items); + else allItems.push(parsed); + + let nextUrl: string | null = (parsed && (parsed.next || (parsed.links && parsed.links.next))) || null; + const visited = new Set(); + while (nextUrl) { + if (visited.has(nextUrl)) break; + visited.add(nextUrl); + const r2 = await fetch(nextUrl).catch(() => null); + if (!r2) break; + let p2: any = null; + try { p2 = await r2.json(); } catch { p2 = await r2.text().catch(() => null); } + if (Array.isArray(p2)) allItems.push(...p2); + else if (p2 && p2.items) allItems.push(...p2.items); + else allItems.push(p2); + nextUrl = (p2 && (p2.next || (p2.links && p2.links.next))) || null; + } + returnData.push(...allItems); +` + : `returnData.push(parsed);`; + + return `case "${opId}": { + // build endpoint template and replace path params + let endpoint = \`${escapeForTemplate(op.path)}\`; + const pathParamsValues: Record = {}; + ${pathParams.map((pp) => `pathParamsValues["${pp}"] = paramsCollection["path_${pp}"] ?? undefined;`).join("\n ")} + ${replacePathCode} + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\\\{'+(creds.apiKeyName ?? 'api_key')+'\\\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = \`Bearer \${creds.oauth2.accessToken}\`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "${requestBodyTypeDetected}"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "${op.method.toUpperCase()}"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(\`HTTP \${resp.status}: \${m}\`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + ${paginationBlock} + + break; +}`; + }) + .join("\n\n"); + + const nodeContent = `import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class ${className} implements INodeType { + description: INodeTypeDescription = { + displayName: "${NAMESPACE} - ${pascalCase(tag)}", + name: "${pascalCase(tag)}", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "${NAMESPACE} - ${pascalCase(tag)}" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + ${opsOptions} + ], + default: "${operations[0].operationId}" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + ${mergedProps} + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + ${switchCases} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} +`; + + const filePath = path.join(folder, `${className}.node.ts`); + fs.writeFileSync(filePath, nodeContent, "utf8"); + return className; +} + +function writeCredentials(outDir: string) { + const credFolder = path.join(outDir, "credentials"); + ensureDir(credFolder); + const credFile = `import { ICredentialType, INodeProperties } from "n8n-workflow"; + +export class WibuApi implements ICredentialType { + name = "wibuApi"; + displayName = "Wibu API"; + + properties: INodeProperties[] = [ + { displayName: "Base URL", name: "baseUrl", type: "string", default: "", placeholder: "https://api.example.com" }, + { displayName: "Authentication Type", name: "authType", type: "options", options: [ { name: "None", value: "none" }, { name: "API Key", value: "apiKey" }, { name: "OAuth2 (scaffold)", value: "oauth2" } ], default: "apiKey" }, + { displayName: "API Key Name", name: "apiKeyName", type: "string", default: "Authorization", displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "API Key Value", name: "apiKeyValue", type: "string", default: "", typeOptions: { password: true }, displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "API Key Placement", name: "apiKeyPlacement", type: "options", options: [ { name: "Header", value: "header" }, { name: "Query", value: "query" }, { name: "Path", value: "path" } ], default: "header", displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "OAuth2 - Access Token (manual)", name: "oauth2", type: "collection", default: {}, placeholder: "OAuth2 tokens", displayOptions: { show: { authType: ["oauth2"] } }, options: [ { displayName: "Access Token", name: "accessToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Refresh Token", name: "refreshToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Expires At (ms)", name: "expiresAt", type: "number", default: 0 } ] }, + { displayName: "Additional Headers (JSON)", name: "headersJson", type: "json", default: {} }, + { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, + { displayName: "Retries", name: "retry", type: "number", default: 1 }, + { displayName: "Follow Redirects", name: "followRedirect", type: "boolean", default: true } + ]; +} +`; + fs.writeFileSync(path.join(credFolder, "WibuApi.credentials.ts"), credFile, "utf8"); +} + +export function main() { + const projectRoot = process.cwd(); + const openapiPath = path.join(projectRoot, "openapi.json"); + const outDir = path.join(projectRoot, "src"); + + if (!fs.existsSync(openapiPath)) { + console.error("❌ openapi.json not found"); + process.exit(1); + } + + const spec = readJSON(openapiPath); + const tagOps: Record = {}; + + for (const [pathKey, pathItem] of Object.entries(spec.paths)) { + for (const [method, op] of Object.entries(pathItem as any)) { + const tags = op.tags ?? ["default"]; + for (const t of tags) { + if (!tagOps[t]) tagOps[t] = []; + tagOps[t].push({ ...op, path: pathKey, method }); + } + } + } + + const generated: string[] = []; + for (const [tag, ops] of Object.entries(tagOps)) { + const node = generateNode(tag, ops, outDir); + generated.push(node); + console.log(`✅ Generated node: ${node}`); + } + + writeCredentials(outDir); + console.log(`\n✅ All done. Generated ${generated.length} nodes + credentials.`); +} + +if (require.main === module) { + main(); +} diff --git a/gen.txt b/gen.txt new file mode 100644 index 0000000..6856d1f --- /dev/null +++ b/gen.txt @@ -0,0 +1,267 @@ +// generator from open api json to n8n custom nodes +/* eslint-disable @typescript-eslint/no-explicit-any */ +import fs from "fs"; +import path from "path"; +import { pascalCase } from "pascal-case"; +import pkg from "./package.json"; + +const openApiPath = "./openapi.json"; +const baseNodesPath = "./src/nodes"; +const BASE_URL = process.env.API_BASE_URL || "https://your-api-domain.com"; + +if (!fs.existsSync(openApiPath)) { + console.error("❌ File openapi.json tidak ditemukan."); + process.exit(1); +} + +const openApi = JSON.parse(fs.readFileSync(openApiPath, "utf8")); + +const sanitizeVarName = (str: string) => str.replace(/[^a-zA-Z0-9_]/g, "_"); + +function mapTypeToN8n(type: string): string { + const typeMap: Record = { + string: "string", + number: "number", + integer: "number", + boolean: "boolean", + array: "string", + object: "json", + }; + return typeMap[type] || "string"; +} + +function buildProperties(schema: any, requiredFields: string[] = []): string { + if (!schema || !schema.properties) { + return `{ + displayName: "Data", + name: "data", + type: "json", + default: "{}", + description: "Request body sebagai JSON", + }`; + } + + return Object.entries(schema.properties) + .map(([key, prop]: [string, any]) => { + const type = mapTypeToN8n(prop.type || "string"); + const isRequired = requiredFields.includes(key); + const description = prop.description || prop.error || key; + + return `{ + displayName: "${key.charAt(0).toUpperCase() + key.slice(1)}", + name: "${key}", + type: "${type}", + default: ${type === "number" ? "0" : '""'}, + required: ${isRequired}, + description: "${description.replace(/"/g, "'")}", + }`; + }) + .join(",\n "); +} + +if (!fs.existsSync(baseNodesPath)) { + fs.mkdirSync(baseNodesPath, { recursive: true }); +} + +pkg.n8n.nodes = []; + +for (const [pathUrl, methods] of Object.entries(openApi.paths)) { + for (const [method, spec] of Object.entries(methods)) { + const tag = spec.tags?.[0] || "default"; + const rawOperationId = spec.operationId || `${method}_${pathUrl}`; + const cleanOperationId = sanitizeVarName(rawOperationId); + const className = pascalCase(cleanOperationId); + const summary = spec.summary || cleanOperationId; + const description = (spec.description || summary).replace(/"/g, "'"); + + const schema = spec.requestBody?.content?.["application/json"]?.schema || null; + const requiredFields = schema?.required || []; + + const tagFolder = path.join(baseNodesPath, tag); + if (!fs.existsSync(tagFolder)) fs.mkdirSync(tagFolder); + + const nodeFolder = path.join(tagFolder, cleanOperationId); + if (!fs.existsSync(nodeFolder)) fs.mkdirSync(nodeFolder); + + // ===== DESCRIPTION FILE ===== + fs.writeFileSync( + path.join(nodeFolder, `${cleanOperationId}.description.ts`), + `import { INodeTypeDescription } from "n8n-workflow"; + +export const ${cleanOperationId}Description: INodeTypeDescription = { + displayName: "${summary}", + name: "${cleanOperationId.toLowerCase()}", + group: ["transform"], + version: 1, + subtitle: '=${tag}', + description: "${description}", + codex: { + categories: ["BIP"], + resources: {}, + alias: ["bip", "custom", "${tag.toLowerCase()}"], + }, + defaults: { + name: "${summary}", + }, + inputs: ["main"], + outputs: ["main"], + credentials: [ + { + name: "apiAuth", + required: false, + }, + ], + properties: [ + ${buildProperties(schema, requiredFields)} + ], +}; +` + ); + + // ===== NODE FILE ===== + const hasBody = ["post", "put", "patch"].includes(method.toLowerCase()); + + // Ambil property names untuk di-hardcode + const propertyNames = schema?.properties + ? Object.keys(schema.properties).map(k => `"${k}"`).join(", ") + : ""; + + const hasProperties = hasBody && propertyNames; + + fs.writeFileSync( + path.join(nodeFolder, `${cleanOperationId}.node.ts`), + `import { + IExecuteFunctions, + INodeExecutionData, + INodeType, + INodeTypeDescription, + NodeOperationError, +} from "n8n-workflow"; +import { ${cleanOperationId}Description } from "./${cleanOperationId}.description"; + +export class ${className} implements INodeType { + description: INodeTypeDescription = ${cleanOperationId}Description; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + for (let i = 0; i < items.length; i++) { + try { + ${hasProperties ? ` + // Property names dari schema + const propertyNames = [${propertyNames}]; + const body: Record = {}; + + for (const propName of propertyNames) { + try { + const value = this.getNodeParameter(propName, i, ""); + if (value !== "") { + body[propName] = value; + } + } catch (error) { + // Property tidak ada, skip + } + }` : hasBody ? ` + // Ambil body dari parameter "data" (karena tidak ada properties) + const body = this.getNodeParameter("data", i, {}) as Record;` : ""} + + const response = await this.helpers.httpRequest({ + method: "${method.toUpperCase()}", + url: \`${BASE_URL}${pathUrl}\`, + ${hasBody ? "body," : ""} + json: true, + headers: { + "Content-Type": "application/json", + }, + }); + + returnData.push({ + json: response, + pairedItem: { item: i }, + }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + + if (this.continueOnFail()) { + returnData.push({ + json: { error: errorMessage }, + pairedItem: { item: i }, + }); + continue; + } + + throw new NodeOperationError( + this.getNode(), + \`Error executing ${summary}: \${errorMessage}\`, + { itemIndex: i } + ); + } + } + + return [returnData]; + } +} + +// ✅ Export untuk n8n (harus match dengan filename) +module.exports = { ${cleanOperationId}: ${className} }; +` + ); + + const distPath = `dist/nodes/${tag}/${cleanOperationId}/${cleanOperationId}.node.js`; + if (!pkg.n8n.nodes.includes(distPath)) { + pkg.n8n.nodes.push(distPath); + } + + console.log(`✅ Generated: ${className} (${method.toUpperCase()} ${pathUrl})`); + } +} + +// ===== GENERATE src/index.ts ===== +const indexPath = path.join("./src", "index.ts"); +const nodeExports: string[] = []; +const nodeImports: Record = {}; // tag -> [cleanOperationId] + +// Kumpulkan semua nodes berdasarkan tag +for (const [pathUrl, methods] of Object.entries(openApi.paths)) { + for (const [method, spec] of Object.entries(methods)) { + const tag = spec.tags?.[0] || "default"; + const rawOperationId = spec.operationId || `${method}_${pathUrl}`; + const cleanOperationId = sanitizeVarName(rawOperationId); + const className = pascalCase(cleanOperationId); + + if (!nodeImports[tag]) { + nodeImports[tag] = []; + } + nodeImports[tag].push(cleanOperationId); + + nodeExports.push( + ` ${className}: require('./nodes/${tag}/${cleanOperationId}/${cleanOperationId}.node.js').${cleanOperationId}` + ); + } +} + +const indexContent = `// Auto-generated index file +// Generated on: ${new Date().toISOString()} + +module.exports = { +${nodeExports.join(",\n")} +}; +`; + +fs.writeFileSync(indexPath, indexContent); + +// Update package.json +fs.writeFileSync("./package.json", JSON.stringify(pkg, null, 2)); + +console.log("\n✨ Semua node berhasil di-generate!"); +console.log(`📦 Total nodes: ${pkg.n8n.nodes.length}`); +console.log(`📄 Generated: src/index.ts`); +console.log(`\n📊 Nodes per category:`); +for (const [tag, nodes] of Object.entries(nodeImports)) { + console.log(` ${tag}: ${nodes.length} nodes`); +} +console.log(`\n⚠️ Setup yang perlu dilakukan:`); +console.log(`1. Set BASE_URL: export API_BASE_URL=https://your-api.com`); +console.log(`2. Run: npm run build`); +console.log(`3. Test: n8n start`); \ No newline at end of file diff --git a/gen.txt2 b/gen.txt2 new file mode 100644 index 0000000..bf6680e --- /dev/null +++ b/gen.txt2 @@ -0,0 +1,263 @@ +import fs from "fs"; +import path from "path"; + +const NAMESPACE = "wibu"; + +// --------------------------- +// Helper Functions +// --------------------------- + +function pascalCase(str: string) { + return str + .replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase()) + .replace(/^\w/, (c) => c.toUpperCase()); +} + +function buildPathWithParams(url: string) { + return url.replace(/{(.*?)}/g, (match, p1) => `\${encodeURIComponent(params.${p1})}`); +} + +function detectRequestBody(requestBody: any) { + if (!requestBody || !requestBody.content) return null; + if (requestBody.content["application/json"]) return "json"; + if (requestBody.content["application/x-www-form-urlencoded"]) return "form"; + return null; +} + +function buildProperties(parameters: any[], requestBodyType: string | null) { + const props: any[] = []; + + for (const p of parameters || []) { + props.push({ + displayName: `${pascalCase(p.in)} - ${p.name}`, + name: `${p.in}_${p.name}`, + type: "string", + default: "", + }); + } + + if (requestBodyType === "json") { + props.push({ + displayName: "JSON Body", + name: "bodyJson", + type: "json", + default: {}, + }); + } + + return props; +} + +// --------------------------- +// Main Node Generator +// --------------------------- + +function generateNode(tag: string, operations: any[], outDir: string) { + // Namespace folder: src/nodes/ + const baseFolder = path.join(outDir, NAMESPACE); + if (!fs.existsSync(baseFolder)) fs.mkdirSync(baseFolder, { recursive: true }); + + // Subfolder per-tag: src/nodes// + const folder = path.join(baseFolder, tag); + if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true }); + + const className = `${pascalCase(tag)}Api`; + + // Create switch-case + const switchCases = operations + .map((op) => { + const requestBodyType = detectRequestBody(op.requestBody); + + return ` + case "${op.operationId}": { + const params = this.getNodeParameter("params", i, {}) as any; + const query = this.getNodeParameter("query", i, {}) as any; + const body = this.getNodeParameter("body", i, {}) as any; + + const url = \`${buildPathWithParams(op.path)}\`; + + const qs = query; + const options: any = { + method: "${op.method.toUpperCase()}", + uri: url, + qs, + json: true, + }; + + ${requestBodyType === "json" ? `options.body = body.bodyJson;` : ""} + + const response = await this.helpers.request(options); + returnData.push(response); + break; + } + `; + }) + .join("\n"); + + // Build dynamic properties + const allProps = operations.flatMap((op: any) => { + const body = detectRequestBody(op.requestBody); + return buildProperties(op.parameters, body); + }); + + const nodeContent = ` +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class ${className} implements INodeType { + + description: INodeTypeDescription = { + displayName: "${NAMESPACE} - ${pascalCase(tag)}", + name: "${pascalCase(tag)}", + group: ["transform"], + version: 1, + description: "Auto-generated from OpenAPI", + defaults: { + name: "${NAMESPACE} - ${pascalCase(tag)}", + }, + icon: "file:../../../icon.svg", + inputs: ["main"], + outputs: ["main"], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + ${operations + .map( + (o) => ` + { + name: "${o.summary || o.operationId}", + value: "${o.operationId}" + } + ` + ) + .join(",")} + ], + default: "${operations[0].operationId}", + }, + { + displayName: "Parameters", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + ${allProps + .map( + (p) => ` + { + displayName: "${p.displayName}", + name: "${p.name}", + type: "string", + default: "", + } + ` + ) + .join(",")} + ] + }, + { + displayName: "Query", + name: "query", + type: "collection", + placeholder: "Add Query", + default: {}, + options: [] + }, + { + displayName: "Body", + name: "body", + type: "collection", + default: {}, + options: [ + { + displayName: "JSON Body", + name: "bodyJson", + type: "json", + default: {} + } + ] + } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + + switch (operation) { + ${switchCases} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} +`; + + const file = path.join(folder, `${className}.node.ts`); + fs.writeFileSync(file, nodeContent); + + return className; +} + +// --------------------------- +// Main Program +// --------------------------- + +export function main() { + const projectRoot = process.cwd(); + const openapiPath = path.join(projectRoot, "openapi.json"); + const outDir = path.join(projectRoot, "src", "nodes"); + + if (!fs.existsSync(openapiPath)) { + console.error("❌ openapi.json not found"); + process.exit(1); + } + + const openapi = JSON.parse(fs.readFileSync(openapiPath, "utf8")); + + const tagsMap: any = {}; + + // Group by tags + for (const pathKey of Object.keys(openapi.paths)) { + const pathObj = openapi.paths[pathKey]; + + for (const method of Object.keys(pathObj)) { + const op = pathObj[method]; + if (!op.tags) continue; + + const item = { + path: pathKey, + method, + ...op, + }; + + for (const t of op.tags) { + tagsMap[t] = tagsMap[t] || []; + tagsMap[t].push(item); + } + } + } + + // Generate index.ts exports + const indexExports: string[] = []; + + for (const tag of Object.keys(tagsMap)) { + const className = generateNode(tag, tagsMap[tag], outDir); + indexExports.push(`export * from "./nodes/${NAMESPACE}/${tag}/${className}.node";`); + } + + const indexPath = path.join(projectRoot, "src", "index.ts"); + fs.writeFileSync(indexPath, indexExports.join("\n")); + + console.log("✅ Generation complete"); +} + +main(); diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..e0644f5 --- /dev/null +++ b/icon.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..95958ca --- /dev/null +++ b/openapi.json @@ -0,0 +1,2432 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Elysia Documentation", + "description": "Development documentation", + "version": "0.0.0" + }, + "paths": { + "/api/pengaduan/category": { + "get": { + "operationId": "getApiPengaduanCategory", + "tags": [ + "mcp" + ], + "summary": "List Kategori Pengaduan", + "description": "tool untuk mendapatkan list kategori pengaduan", + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/category/create": { + "post": { + "parameters": [], + "operationId": "postApiPengaduanCategoryCreate", + "tags": [ + "pengaduan" + ], + "summary": "buat kategori pengaduan", + "description": "tool untuk membuat kategori pengaduan", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "name" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/category/update": { + "post": { + "parameters": [], + "operationId": "postApiPengaduanCategoryUpdate", + "tags": [ + "pengaduan" + ], + "summary": "update kategori pengaduan", + "description": "tool untuk update kategori pengaduan", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "name" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/category/delete": { + "post": { + "parameters": [], + "operationId": "postApiPengaduanCategoryDelete", + "tags": [ + "pengaduan" + ], + "summary": "delete kategori pengaduan", + "description": "tool untuk delete kategori pengaduan", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/create": { + "post": { + "parameters": [], + "operationId": "postApiPengaduanCreate", + "tags": [ + "mcp" + ], + "summary": "Create Pengaduan Warga", + "description": "tool untuk membuat pengaduan warga", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "minLength": 1, + "error": "title harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + }, + "location": { + "minLength": 1, + "error": "location harus diisi", + "type": "string" + }, + "image": {}, + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + } + }, + "required": [ + "title", + "detail", + "location", + "image", + "idCategory", + "idWarga", + "phone" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "title": { + "minLength": 1, + "error": "title harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + }, + "location": { + "minLength": 1, + "error": "location harus diisi", + "type": "string" + }, + "image": {}, + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + } + }, + "required": [ + "title", + "detail", + "location", + "image", + "idCategory", + "idWarga", + "phone" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "title": { + "minLength": 1, + "error": "title harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + }, + "location": { + "minLength": 1, + "error": "location harus diisi", + "type": "string" + }, + "image": {}, + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + } + }, + "required": [ + "title", + "detail", + "location", + "image", + "idCategory", + "idWarga", + "phone" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/update-status": { + "post": { + "parameters": [], + "operationId": "postApiPengaduanUpdate-status", + "tags": [ + "pengaduan" + ], + "summary": "Update status pengaduan", + "description": "tool untuk update status pengaduan", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": {}, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": {}, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": {}, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/detail": { + "get": { + "operationId": "getApiPengaduanDetail", + "tags": [ + "mcp" + ], + "summary": "Detail Pengaduan Warga", + "description": "tool untuk mendapatkan detail pengaduan warga / history pengaduan / mengecek status pengaduan", + "responses": { + "200": {} + } + } + }, + "/api/pengaduan/": { + "get": { + "operationId": "getApiPengaduan", + "tags": [ + "mcp" + ], + "summary": "List Pengaduan Warga", + "description": "tool untuk mendapatkan list pengaduan warga", + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/category": { + "get": { + "operationId": "getApiPelayananCategory", + "tags": [ + "mcp" + ], + "summary": "List Kategori Pelayanan Surat", + "description": "tool untuk mendapatkan list kategori pelayanan surat", + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/category/create": { + "post": { + "parameters": [], + "operationId": "postApiPelayananCategoryCreate", + "tags": [ + "pelayanan" + ], + "summary": "buat kategori pelayanan surat", + "description": "tool untuk membuat kategori pelayanan surat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "name", + "syaratDokumen", + "dataText" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "name", + "syaratDokumen", + "dataText" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "name", + "syaratDokumen", + "dataText" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/category/update": { + "post": { + "parameters": [], + "operationId": "postApiPelayananCategoryUpdate", + "tags": [ + "pelayanan" + ], + "summary": "update kategori pelayanan surat", + "description": "tool untuk update kategori pelayanan surat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "syaratDokumen", + "dataText" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "syaratDokumen", + "dataText" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "syaratDokumen": { + "type": "array", + "items": { + "minLength": 1, + "error": "syaratDokumen harus diisi", + "type": "string" + } + }, + "dataText": { + "type": "array", + "items": { + "minLength": 1, + "error": "dataText harus diisi", + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "syaratDokumen", + "dataText" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/category/delete": { + "post": { + "parameters": [], + "operationId": "postApiPelayananCategoryDelete", + "tags": [ + "pelayanan" + ], + "summary": "delete kategori pelayanan surat", + "description": "tool untuk delete kategori pelayanan surat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/": { + "get": { + "operationId": "getApiPelayanan", + "tags": [ + "mcp" + ], + "summary": "List Ajuan Pelayanan Surat", + "description": "tool untuk mendapatkan list ajuan pelayanan surat", + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/detail": { + "get": { + "parameters": [ + { + "schema": { + "type": "string", + "minLength": 1, + "error": "id harus diisi" + }, + "in": "query", + "name": "id", + "required": true + } + ], + "operationId": "getApiPelayananDetail", + "tags": [ + "mcp" + ], + "summary": "Detail Ajuan Pelayanan Surat", + "description": "tool untuk mendapatkan detail ajuan pelayanan surat", + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/create": { + "post": { + "parameters": [], + "operationId": "postApiPelayananCreate", + "tags": [ + "mcp" + ], + "summary": "Create Pengajuan Pelayanan Surat", + "description": "tool untuk membuat pengajuan pelayanan surat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "dataText": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + }, + "syaratDokumen": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + } + }, + "required": [ + "idCategory", + "idWarga", + "phone", + "dataText", + "syaratDokumen" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "dataText": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + }, + "syaratDokumen": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + } + }, + "required": [ + "idCategory", + "idWarga", + "phone", + "dataText", + "syaratDokumen" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "idCategory": { + "minLength": 1, + "error": "idCategory harus diisi", + "type": "string" + }, + "idWarga": { + "minLength": 1, + "error": "idWarga harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "dataText": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + }, + "syaratDokumen": { + "type": "array", + "items": { + "type": "object", + "properties": { + "jenis": { + "minLength": 1, + "error": "jenis harus diisi", + "type": "string" + }, + "value": { + "minLength": 1, + "error": "value harus diisi", + "type": "string" + } + }, + "required": [ + "jenis", + "value" + ] + } + } + }, + "required": [ + "idCategory", + "idWarga", + "phone", + "dataText", + "syaratDokumen" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/pelayanan/update-status": { + "post": { + "parameters": [], + "operationId": "postApiPelayananUpdate-status", + "tags": [ + "mcp" + ], + "summary": "Update status pengajuan pelayanan surat", + "description": "tool untuk update status pengajuan pelayanan surat", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": { + "minLength": 1, + "error": "keterangan harus diisi", + "type": "string" + }, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": { + "minLength": 1, + "error": "keterangan harus diisi", + "type": "string" + }, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "minLength": 1, + "error": "id harus diisi", + "type": "string" + }, + "status": { + "minLength": 1, + "error": "status harus diisi", + "type": "string" + }, + "keterangan": { + "minLength": 1, + "error": "keterangan harus diisi", + "type": "string" + }, + "idUser": { + "minLength": 1, + "error": "idUser harus diisi", + "type": "string" + } + }, + "required": [ + "id", + "status", + "keterangan", + "idUser" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/apikey/create": { + "post": { + "parameters": [], + "operationId": "postApiApikeyCreate", + "tags": [ + "apikey" + ], + "summary": "create", + "description": "create api key by user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expiredAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "description" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expiredAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "description" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "expiredAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "description" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/apikey/list": { + "get": { + "operationId": "getApiApikeyList", + "tags": [ + "apikey" + ], + "summary": "list", + "description": "get api key list by user", + "responses": { + "200": {} + } + } + }, + "/api/apikey/delete": { + "delete": { + "parameters": [], + "operationId": "deleteApiApikeyDelete", + "tags": [ + "apikey" + ], + "summary": "delete", + "description": "delete api key by id", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/repos": { + "get": { + "operationId": "getApiDarmasabaRepos", + "tags": [ + "darmasaba" + ], + "summary": "repos", + "description": "get list of repositories", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/ls": { + "get": { + "operationId": "getApiDarmasabaLs", + "tags": [ + "darmasaba" + ], + "summary": "ls", + "description": "get list of dir in darmasaba", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/ls/{dir}": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "dir", + "required": true + } + ], + "operationId": "getApiDarmasabaLsByDir", + "tags": [ + "darmasaba" + ], + "summary": "ls", + "description": "get list of files in darmasaba/", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/file/{dir}/{file_name}": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "dir", + "required": true + }, + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "file_name", + "required": true + } + ], + "operationId": "getApiDarmasabaFileByDirByFile_name", + "tags": [ + "darmasaba" + ], + "summary": "file", + "description": "get content of file in darmasaba//", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/list-pengetahuan-umum": { + "get": { + "operationId": "getApiDarmasabaList-pengetahuan-umum", + "tags": [ + "darmasaba" + ], + "summary": "list-pengetahuan-umum", + "description": "get list of files in darmasaba/pengetahuan-umum", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/pengetahuan-umum/{file_name}": { + "get": { + "parameters": [ + { + "schema": { + "type": "string" + }, + "in": "path", + "name": "file_name", + "required": true + } + ], + "operationId": "getApiDarmasabaPengetahuan-umumByFile_name", + "tags": [ + "darmasaba" + ], + "summary": "pengetahuan-umum", + "description": "get content of file in darmasaba/pengetahuan-umum/", + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/buat-pengaduan": { + "post": { + "parameters": [], + "operationId": "postApiDarmasabaBuat-pengaduan", + "tags": [ + "darmasaba" + ], + "summary": "buat-pengaduan atau pelaporan", + "description": "tool untuk membuat pengaduan atau pelaporan warga kepada desa darmasaba", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jenis_laporan": { + "minLength": 1, + "error": "jenis laporan harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + } + }, + "required": [ + "jenis_laporan", + "name", + "phone", + "detail" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "jenis_laporan": { + "minLength": 1, + "error": "jenis laporan harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + } + }, + "required": [ + "jenis_laporan", + "name", + "phone", + "detail" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "jenis_laporan": { + "minLength": 1, + "error": "jenis laporan harus diisi", + "type": "string" + }, + "name": { + "minLength": 1, + "error": "name harus diisi", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone harus diisi", + "type": "string" + }, + "detail": { + "minLength": 1, + "error": "detail harus diisi", + "type": "string" + } + }, + "required": [ + "jenis_laporan", + "name", + "phone", + "detail" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/darmasaba/status-pengaduan": { + "post": { + "parameters": [], + "operationId": "postApiDarmasabaStatus-pengaduan", + "tags": [ + "darmasaba" + ], + "summary": "lihat status pengaduan", + "description": "melikat status pengaduan dari user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "phone": { + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "phone": { + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "phone": { + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/credential/create": { + "post": { + "parameters": [], + "operationId": "postApiCredentialCreate", + "tags": [ + "credential" + ], + "summary": "create", + "description": "create credential", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/credential/list": { + "get": { + "operationId": "getApiCredentialList", + "tags": [ + "credential" + ], + "summary": "list", + "description": "get credential list", + "responses": { + "200": {} + } + } + }, + "/api/credential/rm": { + "delete": { + "parameters": [], + "operationId": "deleteApiCredentialRm", + "tags": [ + "credential" + ], + "summary": "rm", + "description": "delete credential by id", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/user/find": { + "get": { + "operationId": "getApiUserFind", + "tags": [ + "user" + ], + "summary": "find", + "description": "find user", + "responses": { + "200": {} + } + } + }, + "/api/user/upsert": { + "post": { + "parameters": [], + "operationId": "postApiUserUpsert", + "tags": [ + "user" + ], + "summary": "upsert", + "description": "upsert user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name is required", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone is required", + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name is required", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone is required", + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "name": { + "minLength": 1, + "error": "name is required", + "type": "string" + }, + "phone": { + "minLength": 1, + "error": "phone is required", + "type": "string" + } + }, + "required": [ + "name", + "phone" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/layanan/list": { + "get": { + "operationId": "getApiLayananList", + "tags": [ + "layanan" + ], + "summary": "List Layanan", + "description": "Returns the list of all available public services.", + "responses": { + "200": {} + } + } + }, + "/api/layanan/create-ktp": { + "post": { + "parameters": [], + "operationId": "postApiLayananCreate-ktp", + "tags": [ + "mcp" + ], + "summary": "Create Layanan KTP/KK", + "description": "Create a new service request for KTP or KK.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "jenis": { + "anyOf": [ + { + "const": "ktp", + "type": "string" + }, + { + "const": "kk", + "type": "string" + } + ] + }, + "nama": { + "minLength": 3, + "description": "Nama pemohon layanan", + "type": "string" + }, + "deskripsi": { + "minLength": 5, + "description": "Deskripsi singkat permohonan layanan", + "type": "string" + } + }, + "required": [ + "jenis", + "nama", + "deskripsi" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "jenis": { + "anyOf": [ + { + "const": "ktp", + "type": "string" + }, + { + "const": "kk", + "type": "string" + } + ] + }, + "nama": { + "minLength": 3, + "description": "Nama pemohon layanan", + "type": "string" + }, + "deskripsi": { + "minLength": 5, + "description": "Deskripsi singkat permohonan layanan", + "type": "string" + } + }, + "required": [ + "jenis", + "nama", + "deskripsi" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "jenis": { + "anyOf": [ + { + "const": "ktp", + "type": "string" + }, + { + "const": "kk", + "type": "string" + } + ] + }, + "nama": { + "minLength": 3, + "description": "Nama pemohon layanan", + "type": "string" + }, + "deskripsi": { + "minLength": 5, + "description": "Deskripsi singkat permohonan layanan", + "type": "string" + } + }, + "required": [ + "jenis", + "nama", + "deskripsi" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/layanan/status-ktp": { + "post": { + "parameters": [], + "operationId": "postApiLayananStatus-ktp", + "tags": [ + "mcp" + ], + "summary": "Cek Status KTP", + "description": "Retrieve the current status of a KTP/KK request by unique ID.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "uniqid": { + "description": "Unique ID layanan", + "type": "string" + } + }, + "required": [ + "uniqid" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "uniqid": { + "description": "Unique ID layanan", + "type": "string" + } + }, + "required": [ + "uniqid" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "uniqid": { + "description": "Unique ID layanan", + "type": "string" + } + }, + "required": [ + "uniqid" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/aduan/create": { + "post": { + "parameters": [], + "operationId": "postApiAduanCreate", + "tags": [ + "aduan" + ], + "summary": "create", + "description": "create aduan", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "title", + "description" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "title", + "description" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "title", + "description" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/aduan/aduan-sampah": { + "post": { + "parameters": [], + "operationId": "postApiAduanAduan-sampah", + "tags": [ + "aduan" + ], + "summary": "aduan sampah", + "description": "tool untuk membuat aduan sampah liar", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "judul": { + "type": "string" + }, + "deskripsi": { + "type": "string" + } + }, + "required": [ + "judul", + "deskripsi" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "judul": { + "type": "string" + }, + "deskripsi": { + "type": "string" + } + }, + "required": [ + "judul", + "deskripsi" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "judul": { + "type": "string" + }, + "deskripsi": { + "type": "string" + } + }, + "required": [ + "judul", + "deskripsi" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/api/aduan/list-aduan-sampah": { + "get": { + "operationId": "getApiAduanList-aduan-sampah", + "tags": [ + "aduan" + ], + "summary": "list aduan sampah", + "description": "tool untuk melihat list aduan sampah liar", + "responses": { + "200": {} + } + } + }, + "/auth/login": { + "post": { + "parameters": [], + "operationId": "postAuthLogin", + "tags": [ + "auth" + ], + "summary": "login", + "description": "Login with phone; auto-register if not found", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ] + } + }, + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ] + } + }, + "text/plain": { + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ] + } + } + } + }, + "responses": { + "200": {} + } + } + }, + "/auth/logout": { + "delete": { + "operationId": "deleteAuthLogout", + "tags": [ + "auth" + ], + "summary": "logout", + "description": "Logout (clear token cookie)", + "responses": { + "200": {} + } + } + }, + "/mcp": { + "post": { + "operationId": "postMcp", + "tags": [ + "MCP Server" + ], + "responses": { + "200": {} + } + } + }, + "/mcp/tools": { + "get": { + "operationId": "getMcpTools", + "tags": [ + "MCP Server" + ], + "responses": { + "200": {} + } + } + }, + "/mcp/status": { + "get": { + "operationId": "getMcpStatus", + "tags": [ + "MCP Server" + ], + "responses": { + "200": {} + } + } + }, + "/health": { + "get": { + "operationId": "getHealth", + "tags": [ + "MCP Server" + ], + "responses": { + "200": {} + } + } + }, + "/mcp/init": { + "get": { + "operationId": "getMcpInit", + "tags": [ + "MCP Server" + ], + "responses": { + "200": {} + } + } + } + }, + "components": { + "schemas": {} + } +} \ No newline at end of file diff --git a/package.json b/package.json index 700b95e..0d59802 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,9 @@ "version": "1.0.0", "main": "dist/index.js", "scripts": { - "build": "tsc && cp src/nodes/Wibu/logo.svg dist/nodes/Wibu/logo.svg", + "build": "rm -rf dist && tsc && cp icon.svg dist", "prepare": "npm run build", - "test": "echo \"Error: no test specified\" && exit 1" + "gen": "sudo rm -rf src/nodes && bun gen.ts && npm run build && sudo rm -rf /Users/bip/documents/projects/docker/n8n/data/n8n/custom/n8n-nodes-wibu/dist && sudo cp -r /Users/bip/Documents/projects/docker/n8n-nodes-wibu /Users/bip/documents/projects/docker/n8n/data/n8n/custom/n8n-nodes-wibu && docker restart n8n" }, "keywords": [], "author": "", @@ -23,5 +23,69 @@ "@types/ssh2": "^1.15.5", "ts-node": "^10.9.2", "typescript": "^5.9.3" - } -} + }, + "n8n": { + "nodes": [ + "dist/nodes/mcp/getApiPengaduanCategory/getApiPengaduanCategory.node.js", + "dist/nodes/pengaduan/postApiPengaduanCategoryCreate/postApiPengaduanCategoryCreate.node.js", + "dist/nodes/pengaduan/postApiPengaduanCategoryUpdate/postApiPengaduanCategoryUpdate.node.js", + "dist/nodes/pengaduan/postApiPengaduanCategoryDelete/postApiPengaduanCategoryDelete.node.js", + "dist/nodes/mcp/postApiPengaduanCreate/postApiPengaduanCreate.node.js", + "dist/nodes/pengaduan/postApiPengaduanUpdate_status/postApiPengaduanUpdate_status.node.js", + "dist/nodes/mcp/getApiPengaduanDetail/getApiPengaduanDetail.node.js", + "dist/nodes/mcp/getApiPengaduan/getApiPengaduan.node.js", + "dist/nodes/mcp/getApiPelayananCategory/getApiPelayananCategory.node.js", + "dist/nodes/pelayanan/postApiPelayananCategoryCreate/postApiPelayananCategoryCreate.node.js", + "dist/nodes/pelayanan/postApiPelayananCategoryUpdate/postApiPelayananCategoryUpdate.node.js", + "dist/nodes/pelayanan/postApiPelayananCategoryDelete/postApiPelayananCategoryDelete.node.js", + "dist/nodes/mcp/getApiPelayanan/getApiPelayanan.node.js", + "dist/nodes/mcp/getApiPelayananDetail/getApiPelayananDetail.node.js", + "dist/nodes/mcp/postApiPelayananCreate/postApiPelayananCreate.node.js", + "dist/nodes/mcp/postApiPelayananUpdate_status/postApiPelayananUpdate_status.node.js", + "dist/nodes/apikey/postApiApikeyCreate/postApiApikeyCreate.node.js", + "dist/nodes/apikey/getApiApikeyList/getApiApikeyList.node.js", + "dist/nodes/apikey/deleteApiApikeyDelete/deleteApiApikeyDelete.node.js", + "dist/nodes/darmasaba/getApiDarmasabaRepos/getApiDarmasabaRepos.node.js", + "dist/nodes/darmasaba/getApiDarmasabaLs/getApiDarmasabaLs.node.js", + "dist/nodes/darmasaba/getApiDarmasabaLsByDir/getApiDarmasabaLsByDir.node.js", + "dist/nodes/darmasaba/getApiDarmasabaFileByDirByFile_name/getApiDarmasabaFileByDirByFile_name.node.js", + "dist/nodes/darmasaba/getApiDarmasabaList_pengetahuan_umum/getApiDarmasabaList_pengetahuan_umum.node.js", + "dist/nodes/darmasaba/getApiDarmasabaPengetahuan_umumByFile_name/getApiDarmasabaPengetahuan_umumByFile_name.node.js", + "dist/nodes/darmasaba/postApiDarmasabaBuat_pengaduan/postApiDarmasabaBuat_pengaduan.node.js", + "dist/nodes/darmasaba/postApiDarmasabaStatus_pengaduan/postApiDarmasabaStatus_pengaduan.node.js", + "dist/nodes/credential/postApiCredentialCreate/postApiCredentialCreate.node.js", + "dist/nodes/credential/getApiCredentialList/getApiCredentialList.node.js", + "dist/nodes/credential/deleteApiCredentialRm/deleteApiCredentialRm.node.js", + "dist/nodes/user/getApiUserFind/getApiUserFind.node.js", + "dist/nodes/user/postApiUserUpsert/postApiUserUpsert.node.js", + "dist/nodes/layanan/getApiLayananList/getApiLayananList.node.js", + "dist/nodes/mcp/postApiLayananCreate_ktp/postApiLayananCreate_ktp.node.js", + "dist/nodes/mcp/postApiLayananStatus_ktp/postApiLayananStatus_ktp.node.js", + "dist/nodes/aduan/postApiAduanCreate/postApiAduanCreate.node.js", + "dist/nodes/aduan/postApiAduanAduan_sampah/postApiAduanAduan_sampah.node.js", + "dist/nodes/aduan/getApiAduanList_aduan_sampah/getApiAduanList_aduan_sampah.node.js", + "dist/nodes/auth/postAuthLogin/postAuthLogin.node.js", + "dist/nodes/auth/deleteAuthLogout/deleteAuthLogout.node.js", + "dist/nodes/MCP Server/postMcp/postMcp.node.js", + "dist/nodes/MCP Server/getMcpTools/getMcpTools.node.js", + "dist/nodes/MCP Server/getMcpStatus/getMcpStatus.node.js", + "dist/nodes/MCP Server/getHealth/getHealth.node.js", + "dist/nodes/MCP Server/getMcpInit/getMcpInit.node.js", + "dist/nodes/mcp/mcp.node.js", + "dist/nodes/pengaduan/pengaduan.node.js", + "dist/nodes/pelayanan/pelayanan.node.js", + "dist/nodes/apikey/apikey.node.js", + "dist/nodes/darmasaba/darmasaba.node.js", + "dist/nodes/credential/credential.node.js", + "dist/nodes/user/user.node.js", + "dist/nodes/layanan/layanan.node.js", + "dist/nodes/aduan/aduan.node.js", + "dist/nodes/auth/auth.node.js", + "dist/nodes/MCP_Server/MCP_Server.node.js", + "dist/nodes/mcp_server/mcp_server.node.js" + ] + }, + "files": [ + "icon.svg" + ] +} \ No newline at end of file diff --git a/src/credentials/WibuApi.credentials.ts b/src/credentials/WibuApi.credentials.ts new file mode 100644 index 0000000..a362499 --- /dev/null +++ b/src/credentials/WibuApi.credentials.ts @@ -0,0 +1,19 @@ +import { ICredentialType, INodeProperties } from "n8n-workflow"; + +export class WibuApi implements ICredentialType { + name = "wibuApi"; + displayName = "Wibu API"; + + properties: INodeProperties[] = [ + { displayName: "Base URL", name: "baseUrl", type: "string", default: "", placeholder: "https://api.example.com" }, + { displayName: "Authentication Type", name: "authType", type: "options", options: [ { name: "None", value: "none" }, { name: "API Key", value: "apiKey" }, { name: "OAuth2 (scaffold)", value: "oauth2" } ], default: "apiKey" }, + { displayName: "API Key Name", name: "apiKeyName", type: "string", default: "Authorization", displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "API Key Value", name: "apiKeyValue", type: "string", default: "", typeOptions: { password: true }, displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "API Key Placement", name: "apiKeyPlacement", type: "options", options: [ { name: "Header", value: "header" }, { name: "Query", value: "query" }, { name: "Path", value: "path" } ], default: "header", displayOptions: { show: { authType: ["apiKey"] } } }, + { displayName: "OAuth2 - Access Token (manual)", name: "oauth2", type: "collection", default: {}, placeholder: "OAuth2 tokens", displayOptions: { show: { authType: ["oauth2"] } }, options: [ { displayName: "Access Token", name: "accessToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Refresh Token", name: "refreshToken", type: "string", default: "", typeOptions: { password: true } }, { displayName: "Expires At (ms)", name: "expiresAt", type: "number", default: 0 } ] }, + { displayName: "Additional Headers (JSON)", name: "headersJson", type: "json", default: {} }, + { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, + { displayName: "Retries", name: "retry", type: "number", default: 1 }, + { displayName: "Follow Redirects", name: "followRedirect", type: "boolean", default: true } + ]; +} diff --git a/src/index.ts b/src/index.ts index 183aed7..e69de29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +0,0 @@ -module.exports = { - Wibu: require('./nodes/Wibu/Wibu.node.js').Wibu, -}; diff --git a/src/nodes/Wibu/Wibu.node.json b/src/nodes/Wibu/Wibu.node.json deleted file mode 100644 index 3ad4db4..0000000 --- a/src/nodes/Wibu/Wibu.node.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 1, - "description": "Node wibu untuk generate kata-kata anime", - "defaults": { - "name": "Wibu" - }, - "inputs": ["main"], - "outputs": ["main"], - "credentials": [], - "properties": [ - { - "displayName": "Text", - "name": "text", - "type": "string", - "default": "konnichiwa", - "placeholder": "Masukkan kata-kata wibu" - } - ] -} - diff --git a/src/nodes/Wibu/Wibu.node.ts b/src/nodes/Wibu/Wibu.node.ts deleted file mode 100644 index bb834a2..0000000 --- a/src/nodes/Wibu/Wibu.node.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { IExecuteFunctions } from 'n8n-workflow'; -import type { INodeType, INodeTypeDescription } from 'n8n-workflow'; - -export class Wibu implements INodeType { - - description: INodeTypeDescription = { - displayName: 'Wibu', - name: 'wibu', - icon: 'file:logo.svg', - group: ['transform'], - version: 1, - description: 'Node wibu custom', - defaults: { - name: 'Wibu', - }, - inputs: ['main'], - outputs: ['main'], - properties: [ - { - displayName: 'Text', - name: 'text', - type: 'string', - default: '', - required: true, - }, - ], - }; - - async execute(this: IExecuteFunctions) { - const returnData = []; - - const items = this.getInputData(); - - for (let i = 0; i < items.length; i++) { - const text = this.getNodeParameter('text', i) as string; - - returnData.push({ - json: { - hasil: `Halo wibu: ${text}`, - }, - }); - } - - return [returnData]; - } -} diff --git a/src/nodes/Wibu/logo.svg b/src/nodes/Wibu/logo.svg deleted file mode 100644 index 0b14444..0000000 --- a/src/nodes/Wibu/logo.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/wibu/MCP Server/MCPServerApi.node.ts b/src/wibu/MCP Server/MCPServerApi.node.ts new file mode 100644 index 0000000..5da3f35 --- /dev/null +++ b/src/wibu/MCP Server/MCPServerApi.node.ts @@ -0,0 +1,667 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class MCPServerApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - MCPServer", + name: "MCPServer", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - MCPServer" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "postMcp", + value: "postMcp" + },{ + name: "getMcpTools", + value: "getMcpTools" + },{ + name: "getMcpStatus", + value: "getMcpStatus" + },{ + name: "getHealth", + value: "getHealth" + },{ + name: "getMcpInit", + value: "getMcpInit" + } + ], + default: "postMcp" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postMcp": { + // build endpoint template and replace path params + let endpoint = `/mcp`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getMcpTools": { + // build endpoint template and replace path params + let endpoint = `/mcp/tools`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getMcpStatus": { + // build endpoint template and replace path params + let endpoint = `/mcp/status`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getHealth": { + // build endpoint template and replace path params + let endpoint = `/health`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getMcpInit": { + // build endpoint template and replace path params + let endpoint = `/mcp/init`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/aduan/AduanApi.node.ts b/src/wibu/aduan/AduanApi.node.ts new file mode 100644 index 0000000..da592be --- /dev/null +++ b/src/wibu/aduan/AduanApi.node.ts @@ -0,0 +1,425 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class AduanApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Aduan", + name: "Aduan", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Aduan" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "create", + value: "postApiAduanCreate" + },{ + name: "aduan sampah", + value: "postApiAduanAduan-sampah" + },{ + name: "list aduan sampah", + value: "getApiAduanList-aduan-sampah" + } + ], + default: "postApiAduanCreate" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postApiAduanCreate": { + // build endpoint template and replace path params + let endpoint = `/api/aduan/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiAduanAduan-sampah": { + // build endpoint template and replace path params + let endpoint = `/api/aduan/aduan-sampah`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiAduanList-aduan-sampah": { + // build endpoint template and replace path params + let endpoint = `/api/aduan/list-aduan-sampah`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/apikey/ApikeyApi.node.ts b/src/wibu/apikey/ApikeyApi.node.ts new file mode 100644 index 0000000..7510c74 --- /dev/null +++ b/src/wibu/apikey/ApikeyApi.node.ts @@ -0,0 +1,425 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class ApikeyApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Apikey", + name: "Apikey", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Apikey" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "create", + value: "postApiApikeyCreate" + },{ + name: "list", + value: "getApiApikeyList" + },{ + name: "delete", + value: "deleteApiApikeyDelete" + } + ], + default: "postApiApikeyCreate" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postApiApikeyCreate": { + // build endpoint template and replace path params + let endpoint = `/api/apikey/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiApikeyList": { + // build endpoint template and replace path params + let endpoint = `/api/apikey/list`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "deleteApiApikeyDelete": { + // build endpoint template and replace path params + let endpoint = `/api/apikey/delete`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "DELETE"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/auth/AuthApi.node.ts b/src/wibu/auth/AuthApi.node.ts new file mode 100644 index 0000000..9b5c70a --- /dev/null +++ b/src/wibu/auth/AuthApi.node.ts @@ -0,0 +1,304 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class AuthApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Auth", + name: "Auth", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Auth" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "login", + value: "postAuthLogin" + },{ + name: "logout", + value: "deleteAuthLogout" + } + ], + default: "postAuthLogin" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postAuthLogin": { + // build endpoint template and replace path params + let endpoint = `/auth/login`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "deleteAuthLogout": { + // build endpoint template and replace path params + let endpoint = `/auth/logout`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "DELETE"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/credential/CredentialApi.node.ts b/src/wibu/credential/CredentialApi.node.ts new file mode 100644 index 0000000..3cf256d --- /dev/null +++ b/src/wibu/credential/CredentialApi.node.ts @@ -0,0 +1,425 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class CredentialApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Credential", + name: "Credential", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Credential" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "create", + value: "postApiCredentialCreate" + },{ + name: "list", + value: "getApiCredentialList" + },{ + name: "rm", + value: "deleteApiCredentialRm" + } + ], + default: "postApiCredentialCreate" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postApiCredentialCreate": { + // build endpoint template and replace path params + let endpoint = `/api/credential/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiCredentialList": { + // build endpoint template and replace path params + let endpoint = `/api/credential/list`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "deleteApiCredentialRm": { + // build endpoint template and replace path params + let endpoint = `/api/credential/rm`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "DELETE"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/darmasaba/DarmasabaApi.node.ts b/src/wibu/darmasaba/DarmasabaApi.node.ts new file mode 100644 index 0000000..d7e3a7e --- /dev/null +++ b/src/wibu/darmasaba/DarmasabaApi.node.ts @@ -0,0 +1,1042 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class DarmasabaApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Darmasaba", + name: "Darmasaba", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Darmasaba" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "repos", + value: "getApiDarmasabaRepos" + },{ + name: "ls", + value: "getApiDarmasabaLs" + },{ + name: "ls", + value: "getApiDarmasabaLsByDir" + },{ + name: "file", + value: "getApiDarmasabaFileByDirByFile_name" + },{ + name: "list-pengetahuan-umum", + value: "getApiDarmasabaList-pengetahuan-umum" + },{ + name: "pengetahuan-umum", + value: "getApiDarmasabaPengetahuan-umumByFile_name" + },{ + name: "buat-pengaduan atau pelaporan", + value: "postApiDarmasabaBuat-pengaduan" + },{ + name: "lihat status pengaduan", + value: "postApiDarmasabaStatus-pengaduan" + } + ], + default: "getApiDarmasabaRepos" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + { + displayName: "Path - dir *", + name: "path_dir", + type: "string", + default: "" + },{ + displayName: "Path - file_name *", + name: "path_file_name", + type: "string", + default: "" + } + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "getApiDarmasabaRepos": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/repos`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiDarmasabaLs": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/ls`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiDarmasabaLsByDir": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/ls/{dir}`; + const pathParamsValues: Record = {}; + pathParamsValues["dir"] = paramsCollection["path_dir"] ?? undefined; + endpoint = endpoint.replace(new RegExp('\\{dir\\}','g'), encodeURIComponent(String(pathParamsValues["dir"] ?? ""))); + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiDarmasabaFileByDirByFile_name": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/file/{dir}/{file_name}`; + const pathParamsValues: Record = {}; + pathParamsValues["dir"] = paramsCollection["path_dir"] ?? undefined; + pathParamsValues["file_name"] = paramsCollection["path_file_name"] ?? undefined; + endpoint = endpoint.replace(new RegExp('\\{dir\\}','g'), encodeURIComponent(String(pathParamsValues["dir"] ?? ""))); + endpoint = endpoint.replace(new RegExp('\\{file_name\\}','g'), encodeURIComponent(String(pathParamsValues["file_name"] ?? ""))); + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiDarmasabaList-pengetahuan-umum": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/list-pengetahuan-umum`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiDarmasabaPengetahuan-umumByFile_name": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/pengetahuan-umum/{file_name}`; + const pathParamsValues: Record = {}; + pathParamsValues["file_name"] = paramsCollection["path_file_name"] ?? undefined; + endpoint = endpoint.replace(new RegExp('\\{file_name\\}','g'), encodeURIComponent(String(pathParamsValues["file_name"] ?? ""))); + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiDarmasabaBuat-pengaduan": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/buat-pengaduan`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiDarmasabaStatus-pengaduan": { + // build endpoint template and replace path params + let endpoint = `/api/darmasaba/status-pengaduan`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/layanan/LayananApi.node.ts b/src/wibu/layanan/LayananApi.node.ts new file mode 100644 index 0000000..154ceb1 --- /dev/null +++ b/src/wibu/layanan/LayananApi.node.ts @@ -0,0 +1,183 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class LayananApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Layanan", + name: "Layanan", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Layanan" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "List Layanan", + value: "getApiLayananList" + } + ], + default: "getApiLayananList" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "getApiLayananList": { + // build endpoint template and replace path params + let endpoint = `/api/layanan/list`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/mcp/McpApi.node.ts b/src/wibu/mcp/McpApi.node.ts new file mode 100644 index 0000000..10426a8 --- /dev/null +++ b/src/wibu/mcp/McpApi.node.ts @@ -0,0 +1,1398 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class McpApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Mcp", + name: "Mcp", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Mcp" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "List Kategori Pengaduan", + value: "getApiPengaduanCategory" + },{ + name: "Create Pengaduan Warga", + value: "postApiPengaduanCreate" + },{ + name: "Detail Pengaduan Warga", + value: "getApiPengaduanDetail" + },{ + name: "List Pengaduan Warga", + value: "getApiPengaduan" + },{ + name: "List Kategori Pelayanan Surat", + value: "getApiPelayananCategory" + },{ + name: "List Ajuan Pelayanan Surat", + value: "getApiPelayanan" + },{ + name: "Detail Ajuan Pelayanan Surat", + value: "getApiPelayananDetail" + },{ + name: "Create Pengajuan Pelayanan Surat", + value: "postApiPelayananCreate" + },{ + name: "Update status pengajuan pelayanan surat", + value: "postApiPelayananUpdate-status" + },{ + name: "Create Layanan KTP/KK", + value: "postApiLayananCreate-ktp" + },{ + name: "Cek Status KTP", + value: "postApiLayananStatus-ktp" + } + ], + default: "getApiPengaduanCategory" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + { + displayName: "Query - id *", + name: "query_id", + type: "string", + default: "" + } + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "getApiPengaduanCategory": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/category`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPengaduanCreate": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiPengaduanDetail": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/detail`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiPengaduan": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiPelayananCategory": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/category`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiPelayanan": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "getApiPelayananDetail": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/detail`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPelayananCreate": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPelayananUpdate-status": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/update-status`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiLayananCreate-ktp": { + // build endpoint template and replace path params + let endpoint = `/api/layanan/create-ktp`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiLayananStatus-ktp": { + // build endpoint template and replace path params + let endpoint = `/api/layanan/status-ktp`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/pelayanan/PelayananApi.node.ts b/src/wibu/pelayanan/PelayananApi.node.ts new file mode 100644 index 0000000..0bf596d --- /dev/null +++ b/src/wibu/pelayanan/PelayananApi.node.ts @@ -0,0 +1,425 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class PelayananApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Pelayanan", + name: "Pelayanan", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Pelayanan" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "buat kategori pelayanan surat", + value: "postApiPelayananCategoryCreate" + },{ + name: "update kategori pelayanan surat", + value: "postApiPelayananCategoryUpdate" + },{ + name: "delete kategori pelayanan surat", + value: "postApiPelayananCategoryDelete" + } + ], + default: "postApiPelayananCategoryCreate" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postApiPelayananCategoryCreate": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/category/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPelayananCategoryUpdate": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/category/update`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPelayananCategoryDelete": { + // build endpoint template and replace path params + let endpoint = `/api/pelayanan/category/delete`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/pengaduan/PengaduanApi.node.ts b/src/wibu/pengaduan/PengaduanApi.node.ts new file mode 100644 index 0000000..9bcc0a5 --- /dev/null +++ b/src/wibu/pengaduan/PengaduanApi.node.ts @@ -0,0 +1,546 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class PengaduanApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - Pengaduan", + name: "Pengaduan", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - Pengaduan" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "buat kategori pengaduan", + value: "postApiPengaduanCategoryCreate" + },{ + name: "update kategori pengaduan", + value: "postApiPengaduanCategoryUpdate" + },{ + name: "delete kategori pengaduan", + value: "postApiPengaduanCategoryDelete" + },{ + name: "Update status pengaduan", + value: "postApiPengaduanUpdate-status" + } + ], + default: "postApiPengaduanCategoryCreate" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "postApiPengaduanCategoryCreate": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/category/create`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPengaduanCategoryUpdate": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/category/update`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPengaduanCategoryDelete": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/category/delete`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiPengaduanUpdate-status": { + // build endpoint template and replace path params + let endpoint = `/api/pengaduan/update-status`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +} diff --git a/src/wibu/user/UserApi.node.ts b/src/wibu/user/UserApi.node.ts new file mode 100644 index 0000000..3204362 --- /dev/null +++ b/src/wibu/user/UserApi.node.ts @@ -0,0 +1,304 @@ +import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow"; + +export class UserApi implements INodeType { + description: INodeTypeDescription = { + displayName: "wibu - User", + name: "User", + icon: "file:../../../icon.svg", + group: ["transform"], + version: 1, + description: "Auto-generated node (stable generator)", + defaults: { name: "wibu - User" }, + inputs: ["main"], + outputs: ["main"], + credentials: [{ name: "wibuApi", required: true }], + properties: [ + { + displayName: "Operation", + name: "operation", + type: "options", + options: [ + { + name: "find", + value: "getApiUserFind" + },{ + name: "upsert", + value: "postApiUserUpsert" + } + ], + default: "getApiUserFind" + }, + { + displayName: "Parameters (path / query / header)", + name: "params", + type: "collection", + placeholder: "Add Parameter", + default: {}, + options: [ + + ] + }, + { displayName: "Query (quick)", name: "query", type: "collection", placeholder: "Add Query", default: {}, options: [] }, + { displayName: "Headers (quick)", name: "headers", type: "collection", placeholder: "Add Header", default: {}, options: [] }, + { displayName: "Body", name: "body", type: "collection", placeholder: "Add Body", default: {}, options: [ { displayName: "JSON Body", name: "bodyJson", type: "json", default: {} }, { displayName: "Raw String", name: "rawString", type: "string", default: "" } ] }, + { displayName: "Body Type", name: "bodyType", type: "options", options: [ { name: "None", value: "none" }, { name: "JSON", value: "json" }, { name: "Form URL Encoded", value: "formurl" }, { name: "FormData (multipart)", value: "formdata" } ], default: "none" }, + { displayName: "Advanced", name: "advanced", type: "collection", default: {}, placeholder: "Advanced", options: [ { displayName: "Timeout (ms)", name: "timeout", type: "number", default: 60000 }, { displayName: "Retries", name: "retry", type: "number", default: 1 }, { displayName: "Follow Redirect", name: "followRedirect", type: "boolean", default: true } ] } + ] + }; + + async execute(this: IExecuteFunctions) { + const returnData: any[] = []; + const items = this.getInputData(); + const creds = await this.getCredentials("wibuApi") as any; + + for (let i = 0; i < items.length; i++) { + const operation = this.getNodeParameter("operation", i) as string; + const paramsCollection = (this.getNodeParameter("params", i, {}) as any) || {}; + const queryCollection = (this.getNodeParameter("query", i, {}) as any) || {}; + const headerCollection = (this.getNodeParameter("headers", i, {}) as any) || {}; + const bodyCollection = (this.getNodeParameter("body", i, {}) as any) || {}; + + switch (operation) { + case "getApiUserFind": { + // build endpoint template and replace path params + let endpoint = `/api/user/find`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "null"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "GET"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + +case "postApiUserUpsert": { + // build endpoint template and replace path params + let endpoint = `/api/user/upsert`; + const pathParamsValues: Record = {}; + + + + // build query string + const urlSearch = new URLSearchParams(); + for (const k of Object.keys(queryCollection || {})) { + const v = queryCollection[k]; + if (v !== undefined && v !== null && v !== "") urlSearch.append(k, String(v)); + } + + // apiKey placement + if (creds && creds.authType === "apiKey" && creds.apiKeyValue) { + const placement = creds.apiKeyPlacement ?? "header"; + if (placement === "header") { + headerCollection[creds.apiKeyName ?? "Authorization"] = creds.apiKeyValue; + } else if (placement === "query") { + urlSearch.append(creds.apiKeyName ?? "api_key", creds.apiKeyValue); + } else if (placement === "path") { + endpoint = endpoint.replace(new RegExp('\\{'+(creds.apiKeyName ?? 'api_key')+'\\}','g'), encodeURIComponent(creds.apiKeyValue)); + } + } + + let url = (creds.baseUrl || "").replace(/\/$/, "") + endpoint; + const qs = urlSearch.toString(); + if (qs) url += (url.includes('?') ? '&' : '?') + qs; + + // build headers + const headers: Record = {}; + for (const hk of Object.keys(headerCollection || {})) { + const hv = headerCollection[hk]; + if (hv !== undefined && hv !== null) headers[hk] = String(hv); + } + if (creds && creds.headersJson) { + try { Object.assign(headers, creds.headersJson); } catch (e) {} + } + if (creds && creds.authType === 'oauth2' && creds.oauth2 && creds.oauth2.accessToken) { + headers['Authorization'] = `Bearer ${creds.oauth2.accessToken}`; + } + + // build body according to requestBodyType + let bodyToSend: any = undefined; + const requestBodyType: string = "formdata"; + if (requestBodyType === "formdata") { + const FD: any = (globalThis as any).FormData ?? null; + if (!FD) throw new Error("FormData not available in runtime. Use Node18+ or add polyfill."); + const fd = new FD(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + fd.append(k, v); + } + bodyToSend = fd; + } else if (requestBodyType === "formurl") { + const urlp = new URLSearchParams(); + for (const k of Object.keys(bodyCollection || {})) { + const v = bodyCollection[k]; + if (v === undefined || v === null) continue; + urlp.append(k, String(v)); + } + bodyToSend = urlp.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else if (requestBodyType === "json") { + try { bodyToSend = JSON.stringify(bodyCollection.bodyJson ?? bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } catch (e) { bodyToSend = JSON.stringify(bodyCollection ?? {}); headers['Content-Type'] = 'application/json'; } + } else if (bodyCollection && typeof bodyCollection.rawString === 'string' && bodyCollection.rawString.length) { + bodyToSend = bodyCollection.rawString; + headers['Content-Type'] = headers['Content-Type'] ?? 'text/plain'; + } + + const method = "POST"; + const init: any = { method, headers }; + if (bodyToSend !== undefined) init.body = bodyToSend; + + const GLOBAL_TIMEOUT = Number(creds && creds.timeout ? creds.timeout : 60000); + const GLOBAL_RETRY = Number(creds && creds.retry ? creds.retry : 1); + const GLOBAL_FOLLOW = creds && creds.followRedirect !== false; + + const fetchWithTimeout = async (input: string, initOpt: any) => { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), GLOBAL_TIMEOUT); + try { + const r = await fetch(input, { ...initOpt, redirect: GLOBAL_FOLLOW ? 'follow' : 'manual', signal: controller.signal }); + clearTimeout(id); + return r; + } catch (err) { + clearTimeout(id); + throw err; + } + }; + + let resp: Response | null = null; + for (let attempt = 0; attempt < GLOBAL_RETRY; attempt++) { + try { resp = await fetchWithTimeout(url, init); break; } catch (e) { if (attempt + 1 >= GLOBAL_RETRY) throw e; await new Promise(r=>setTimeout(r, 300 * (attempt+1))); } + } + if (!resp) throw new Error('No response from fetch.'); + + const contentType = resp.headers.get('content-type') || ''; + let parsed: any = null; + try { + if (contentType.includes('application/json')) parsed = await resp.json(); + else if (contentType.startsWith('text/') || contentType === '') parsed = await resp.text(); + else { const ab = await resp.arrayBuffer(); parsed = { binary: Buffer.from(new Uint8Array(ab)).toString('base64'), mime: contentType }; } + } catch (e) { parsed = await resp.text().catch(()=>null); } + + if (!resp.ok) { + const m = typeof parsed === 'string' ? parsed : JSON.stringify(parsed); + const err: any = new Error(`HTTP ${resp.status}: ${m}`); + err.statusCode = resp.status; err.response = parsed; throw err; + } + + returnData.push(parsed); + + break; +} + default: + throw new Error("Operation not implemented: " + operation); + } + } + + return [returnData]; + } +}