From 65b137dacaeb5bf4ad5690b313bd8ba80d5163da Mon Sep 17 00:00:00 2001 From: Rory& Date: Sun, 2 Aug 2026 13:23:56 +0200 Subject: [PATCH] OpenAPI /api prefix for api routes --- assets/openapi.json | Bin 1041727 -> 1039905 bytes scripts/openapi.js | 6 +-- scripts/util/getRouteDescriptions.js | 61 +++++++++++++++++++-------- 3 files changed, 46 insertions(+), 21 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index add88948610260f0c4d03b815470cd485dab7e28..ad3cceefe1ac522b75f5748dca4656f385369a9d 100644 GIT binary patch delta 8502 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. +*/ + +require("module-alias/register"); + const express = require("express"); const path = require("path"); const { traverseDirectory } = require("lambert-server"); @@ -15,6 +35,7 @@ const methods = ["get", "post", "put", "delete", "patch"]; const routes = new Map(); let currentFile = ""; let currentPath = ""; +let currentRoutePrefix = ""; /* For some reason, if a route exports multiple functions, it won't be registered here! @@ -61,7 +82,7 @@ function proxy(file, apiMethod, apiPathPrefix, apiPath, ...args) { console.log(`${colorizeMethod(apiMethod).padStart("DELETE".length + 10)} ${formatPath(apiPathPrefix + apiPath)}`); opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts"); - routes.set(apiPathPrefix + apiPath + "|" + apiMethod, opts()); + routes.set(currentRoutePrefix + apiPathPrefix + apiPath + "|" + apiMethod, opts()); } express.Router = () => { @@ -76,24 +97,28 @@ RouteUtility.route = (opts) => { return func; }; +function getPrefixedRouteDescriptions(routePrefix, root) { + currentRoutePrefix = routePrefix; + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; + + currentPath = file.replace(root.slice(0, -1), ""); + currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path + currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + currentPath = currentPath.replaceAll(/%[A-Za-z0-9]{2}/g, (match) => decodeURIComponent(match)); // special case to handle .well-known for example + if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path + + try { + require(file); + } catch (e) { + console.error(e); + } + }); +} + module.exports = function getRouteDescriptions() { - const roots = [path.join(__dirname, "..", "..", "dist", "api", "routes", "/"), path.join(__dirname, "..", "..", "dist", "api", "routes_toplevel", "/")]; - for (const root of roots) - traverseDirectory({ dirname: root, recursive: true }, (file) => { - currentFile = file; - - currentPath = file.replace(root.slice(0, -1), ""); - currentPath = currentPath.split(".").slice(0, -1).join("."); // truncate .js/.ts file extension of path - currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes - currentPath = currentPath.replaceAll(/%[A-Za-z0-9]{2}/g, (match) => decodeURIComponent(match)); // special case to handle .well-known for example - if (currentPath.endsWith("/index")) currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path - - try { - require(file); - } catch (e) { - console.error(e); - } - }); + getPrefixedRouteDescriptions("/api", path.join(__dirname, "..", "..", "dist", "api", "routes", "/")); + getPrefixedRouteDescriptions("", path.join(__dirname, "..", "..", "dist", "api", "routes_toplevel", "/")); return routes; };