fix
This commit is contained in:
+52
-45
@@ -1,5 +1,4 @@
|
||||
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";
|
||||
@@ -38,11 +37,25 @@ interface ResourceRoute {
|
||||
}
|
||||
|
||||
const app = express();
|
||||
app.set("trust proxy", true);
|
||||
const port = Number(process.env.PORT || 3000);
|
||||
const rootPath = process.env.ROOT_PATH || "";
|
||||
const rootPath = normalizeRootPath(process.env.ROOT_PATH || "");
|
||||
const serviceName = "SAP Business One";
|
||||
const serviceId = "sap-bo";
|
||||
|
||||
function normalizeRootPath(value: string): string {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const withLeadingSlash = value.startsWith("/") ? value : "/" + value;
|
||||
return withLeadingSlash.endsWith("/") ? withLeadingSlash.slice(0, -1) : withLeadingSlash;
|
||||
}
|
||||
|
||||
function withRootPath(path: string): string {
|
||||
return rootPath + path;
|
||||
}
|
||||
|
||||
const resourceRoutes: ResourceRoute[] = [
|
||||
{
|
||||
slug: "business-partners",
|
||||
@@ -581,31 +594,42 @@ function openApiDocument(basePath = "") {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveBasePath(req: Request): string {
|
||||
// Behind the AppFactory reverse proxy the public prefix is /apps/<app-id>, but the
|
||||
// handle_path route strips it before the request reaches this container, so it cannot be
|
||||
// read from the request path. ROOT_PATH is the sanctioned source; X-Forwarded-Prefix is a
|
||||
// fallback for proxies that forward it. Empty when running without a proxy.
|
||||
const forwardedPrefix = getHeader(req, "x-forwarded-prefix");
|
||||
return (rootPath || forwardedPrefix || "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function swaggerUiOptions() {
|
||||
return {
|
||||
customSiteTitle: "SAP Business One connector API",
|
||||
swaggerOptions: {
|
||||
// Relative endpoint (same approach as the sibling idoklad service): the browser resolves
|
||||
// it against the docs page, i.e. {prefix}/docs/openapi.json, so the spec loads locally and
|
||||
// behind the AppFactory proxy without hardcoding the /apps/<app-id> prefix.
|
||||
url: "openapi.json",
|
||||
displayRequestDuration: true,
|
||||
function renderDocsHtml(): string {
|
||||
// Same approach as the sibling google-service: serve the Swagger UI page directly at /docs
|
||||
// (no static-file middleware, so no /docs -> /docs/ redirect that would drop the proxy prefix),
|
||||
// load the UI assets from CDN, and point the spec URL at ROOT_PATH + /openapi.json so it resolves
|
||||
// to the public /apps/<app-id>/openapi.json behind the AppFactory reverse proxy.
|
||||
const specUrl = withRootPath("/openapi.json");
|
||||
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 API</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
||||
<style>
|
||||
body { margin: 0; background: #f7f7f7; }
|
||||
.topbar { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||
<script>
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: ${JSON.stringify(specUrl)},
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
persistAuthorization: true,
|
||||
tryItOutEnabled: true,
|
||||
filter: true,
|
||||
displayRequestDuration: true,
|
||||
tagsSorter: "alpha",
|
||||
operationsSorter: "method"
|
||||
}
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
app.get("/", (_req, res) => {
|
||||
@@ -616,33 +640,16 @@ app.get("/health", (_req, res) => {
|
||||
res.json({ status: "ok" });
|
||||
});
|
||||
|
||||
// OpenAPI document served under the same /docs prefix as the UI so the relative UI endpoint
|
||||
// resolves correctly locally and behind the reverse-proxy prefix. servers[0].url advertises the
|
||||
// public prefix (ROOT_PATH) so Swagger UI "Try it out" targets {prefix}/api/..., not the host root.
|
||||
app.get("/docs/openapi.json", (req, res) => {
|
||||
res.json(openApiDocument(resolveBasePath(req)));
|
||||
app.get("/docs", (_req, res) => {
|
||||
res.type("html").send(renderDocsHtml());
|
||||
});
|
||||
|
||||
// Documented root alias for direct access to the OpenAPI document.
|
||||
app.get("/openapi.json", (req, res) => {
|
||||
res.json(openApiDocument(resolveBasePath(req)));
|
||||
// servers[0].url advertises the public prefix (ROOT_PATH) so Swagger UI "Try it out" targets
|
||||
// {prefix}/api/..., not the host root.
|
||||
app.get("/openapi.json", (_req, res) => {
|
||||
res.json(openApiDocument(rootPath));
|
||||
});
|
||||
|
||||
// swagger-ui-express's static handler redirects /docs -> absolute "/docs/", which behind the
|
||||
// AppFactory handle_path proxy drops the /apps/<app-id> prefix and breaks the page. Intercept
|
||||
// the no-slash form first and redirect to the ROOT_PATH-prefixed URL (mirrors idoklad's
|
||||
// UsePathBase), so the trailing-slash form keeps the public prefix.
|
||||
app.get("/docs", (req, res, next) => {
|
||||
// Express non-strict routing matches both /docs and /docs/ here; only the no-slash form
|
||||
// needs the redirect (otherwise /docs/ would redirect to itself in a loop).
|
||||
if (req.path.endsWith("/")) {
|
||||
return next();
|
||||
}
|
||||
res.redirect(301, `${resolveBasePath(req)}/docs/`);
|
||||
});
|
||||
|
||||
app.use("/docs", swaggerUi.serve, swaggerUi.setup(undefined, swaggerUiOptions()));
|
||||
|
||||
addResourceEndpoints();
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
Reference in New Issue
Block a user