This commit is contained in:
JiriUhlir
2026-06-29 10:01:03 +02:00
parent a7aedfce10
commit 5281d56998
4 changed files with 72 additions and 146 deletions
+25 -145
View File
@@ -1,4 +1,5 @@
import express, { Request, Response } from "express";
import swaggerUi from "swagger-ui-express";
import { SapBusinessOneServiceLayer } from "./SapBusinessOneServiceLayer";
import { SapB1Client, SapB1ClientOptions } from "./client/SapB1Client";
import { buildODataParams, extractNextLink } from "./client/odata";
@@ -531,6 +532,14 @@ function openApiDocument(basePath = "") {
"HTTP API and TypeScript connector for SAP Business One Service Layer. SAP credentials are supplied per request in X-SAP-B1-* headers."
},
servers: [{ url: basePath || "/" }],
security: [
{
SapB1BaseUrl: [],
SapB1CompanyDB: [],
SapB1Username: [],
SapB1Password: []
}
],
tags: [
{ name: "Service" },
{ name: "Session" },
@@ -572,143 +581,19 @@ function openApiDocument(basePath = "") {
};
}
function docsHtml(basePath = "") {
const openApiUrl = `${basePath}/openapi.json`.replace("//", "/");
const resources = resourceRoutes
.map(
(route) => `<tr>
<td>${route.tag}</td>
<td><code>GET ${basePath}/api/${route.slug}</code></td>
<td><code>GET ${basePath}/api/${route.slug}/{id}</code></td>
<td><code>POST ${basePath}/api/${route.slug}</code></td>
<td><code>PATCH ${basePath}/api/${route.slug}/{id}</code></td>
<td>${route.deleteSupported ? `<code>DELETE ${basePath}/api/${route.slug}/{id}</code>` : "not generally safe"}</td>
</tr>`
)
.join("");
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SAP Business One connector docs</title>
<style>
body { font-family: Arial, sans-serif; margin: 2rem; line-height: 1.5; color: #1f2937; }
code, pre { background: #f3f4f6; border-radius: 4px; padding: 0.2rem 0.35rem; }
a, button { color: #0f766e; }
table { border-collapse: collapse; width: 100%; margin: 1rem 0; }
th, td { border: 1px solid #d1d5db; padding: 0.5rem; text-align: left; vertical-align: top; }
th { background: #f9fafb; }
input, textarea, select, button { font: inherit; margin: 0.25rem 0; padding: 0.35rem; }
textarea { width: min(760px, 100%); height: 9rem; display: block; }
.row { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: end; }
.field { display: grid; gap: 0.2rem; }
</style>
</head>
<body>
<h1>SAP Business One connector</h1>
<p>HTTP API over SAP Business One Service Layer. SAP credentials are supplied in X-SAP-B1-* request headers and are never logged or returned.</p>
<p><a href="${openApiUrl}">OpenAPI JSON</a></p>
<h2>SAP credentials headers</h2>
<div class="row">
<label class="field">Base URL
<input id="sap-base-url" size="36" placeholder="https://sap-host:50000">
</label>
<label class="field">Company DB
<input id="sap-company-db" size="18" placeholder="SBODEMO">
</label>
<label class="field">Username
<input id="sap-username" size="18">
</label>
<label class="field">Password
<input id="sap-password" type="password" size="18">
</label>
<label class="field">Language
<input id="sap-language" size="10">
</label>
<label class="field">Reject unauthorized
<select id="sap-reject-unauthorized">
<option value="true">true</option>
<option value="false">false</option>
</select>
</label>
<label class="field">Timeout ms
<input id="sap-timeout-ms" size="10" value="30000">
</label>
</div>
<h2>Session</h2>
<button type="button" data-method="POST" data-url="${basePath}/api/session/login">Login</button>
<button type="button" data-method="POST" data-url="${basePath}/api/session/logout">Logout</button>
<button type="button" data-method="GET" data-url="${basePath}/health">Health</button>
<h2>Resources</h2>
<table>
<thead>
<tr><th>Resource</th><th>List</th><th>Get</th><th>Create</th><th>Update</th><th>Delete</th></tr>
</thead>
<tbody>${resources}</tbody>
</table>
<h2>Try request</h2>
<div class="row">
<label class="field">Method
<select id="method">
<option>GET</option>
<option>POST</option>
<option>PATCH</option>
<option>DELETE</option>
</select>
</label>
<label class="field">Path
<input id="path" size="48" value="${basePath}/api/business-partners?$top=10">
</label>
<button type="button" id="send">Send</button>
</div>
<label>JSON body</label>
<textarea id="body">{}</textarea>
<pre id="result" aria-live="polite"></pre>
<script>
const result = document.getElementById("result");
const method = document.getElementById("method");
const path = document.getElementById("path");
const body = document.getElementById("body");
function sapHeaders() {
const headers = {
"X-SAP-B1-BaseUrl": document.getElementById("sap-base-url").value,
"X-SAP-B1-CompanyDB": document.getElementById("sap-company-db").value,
"X-SAP-B1-Username": document.getElementById("sap-username").value,
"X-SAP-B1-Password": document.getElementById("sap-password").value,
"X-SAP-B1-Reject-Unauthorized": document.getElementById("sap-reject-unauthorized").value,
"X-SAP-B1-Timeout-Ms": document.getElementById("sap-timeout-ms").value
};
const language = document.getElementById("sap-language").value;
if (language) {
headers["X-SAP-B1-Language"] = language;
}
return headers;
}
async function sendRequest(requestMethod, requestUrl, requestBody) {
const options = { method: requestMethod, headers: sapHeaders() };
if (requestMethod !== "GET" && requestMethod !== "DELETE") {
options.headers["content-type"] = "application/json";
options.body = requestBody || "{}";
}
const response = await fetch(requestUrl, options);
const contentType = response.headers.get("content-type") || "";
const payload = response.status === 204 ? null : contentType.includes("application/json") ? await response.json() : await response.text();
result.textContent = JSON.stringify({ status: response.status, body: payload }, null, 2);
}
document.querySelectorAll("[data-method][data-url]").forEach((button) => {
button.addEventListener("click", () => sendRequest(button.dataset.method, button.dataset.url));
});
document.getElementById("send").addEventListener("click", () => sendRequest(method.value, path.value, body.value));
</script>
</body>
</html>`;
function swaggerOptions(basePath = "") {
return {
customSiteTitle: "SAP Business One connector API",
swaggerOptions: {
url: `${basePath}/openapi.json`.replace("//", "/"),
displayRequestDuration: true,
persistAuthorization: true,
tryItOutEnabled: true,
filter: true,
tagsSorter: "alpha",
operationsSorter: "method"
}
};
}
app.get("/", (_req, res) => {
@@ -723,12 +608,10 @@ app.get("/openapi.json", (_req, res) => {
res.json(openApiDocument(rootPath));
});
app.get("/docs", (_req, res) => {
res.type("html").send(docsHtml(rootPath));
});
addResourceEndpoints();
app.use("/docs", swaggerUi.serveFiles(undefined, swaggerOptions("")), swaggerUi.setup(undefined, swaggerOptions("")));
if (rootPath) {
app.get(rootPath, (_req, res) => {
res.json(serviceMetadata());
@@ -742,11 +625,8 @@ if (rootPath) {
res.json(openApiDocument(rootPath));
});
app.get(rootPath + "/docs", (_req, res) => {
res.type("html").send(docsHtml(rootPath));
});
addResourceEndpoints(rootPath);
app.use(rootPath + "/docs", swaggerUi.serveFiles(undefined, swaggerOptions(rootPath)), swaggerUi.setup(undefined, swaggerOptions(rootPath)));
}
if (require.main === module) {