From 98d318fd88cc42d290add88a81b2d5d5915de019 Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Fri, 6 Jan 2023 19:30:03 +1100 Subject: [PATCH] add back openapi generation. todo: find way to keep route text descriptions in code, and find way to get usages of right/permission .hasThrow --- assets/openapi.json | Bin 113391 -> 215544 bytes package.json | 1 + scripts/openapi.js | 169 +++++++++++++++++++++++++++ scripts/util/getRouteDescriptions.js | 63 ++++++++++ 4 files changed, 233 insertions(+) create mode 100644 scripts/openapi.js create mode 100644 scripts/util/getRouteDescriptions.js diff --git a/assets/openapi.json b/assets/openapi.json index a8a657b2c83adaef7b20eba3f5bd011d1caad2d8..e1d5cfa4227242d5b69662773ec075fed7751c50 100644 GIT binary patch literal 215544 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 x !== "null"); + property.nullable = true; + } + } + } + } + } + + return definitions; +} + +function getTag(key) { + return key.match(/\/([\w-]+)/)[1]; +} + +function apiRoutes() { + const routes = getRouteDescriptions(); + + const tags = Array.from(routes.keys()).map((x) => getTag(x)); + specification.tags = specification.tags || []; + specification.tags = [...specification.tags.map((x) => x.name), ...tags] + .unique() + .map((x) => ({ name: x })); + + specification.components = specification.components || {}; + specification.components.securitySchemes = { + bearer: { + type: "http", + scheme: "bearer", + description: "Bearer/Bot prefixes are not required.", + }, + }; + + routes.forEach((route, pathAndMethod) => { + const [p, method] = pathAndMethod.split("|"); + const path = p.replace(/:(\w+)/g, "{$1}"); + + specification.paths = specification.paths || {}; + let obj = specification.paths[path]?.[method] || {}; + obj["x-right-required"] = route.right; + obj["x-permission-required"] = route.permission; + obj["x-fires-event"] = route.test?.event; + + if ( + !NO_AUTHORIZATION_ROUTES.some((x) => { + if (typeof x === "string") return path.startsWith(x); + return x.test(path); + }) + ) { + obj.security = [{ bearer: true }]; + } + + if (route.body) { + obj.requestBody = { + required: true, + content: { + "application/json": { + schema: { $ref: `#/components/schemas/${route.body}` }, + }, + }, + }.merge(obj.requestBody); + } + + if (route.test?.response) { + const status = route.test.response.status || 200; + let schema = { + allOf: [ + { + $ref: `#/components/schemas/${route.test.response.body}`, + }, + { + example: route.test.body, + }, + ], + }; + if (!route.test.body) schema = schema.allOf[0]; + + obj.responses = { + [status]: { + ...(route.test.response.body + ? { + description: + obj?.responses?.[status]?.description || "", + content: { + "application/json": { + schema: schema, + }, + }, + } + : {}), + }, + }.merge(obj.responses); + delete obj.responses.default; + } + if (p.includes(":")) { + obj.parameters = p.match(/:\w+/g)?.map((x) => ({ + name: x.replace(":", ""), + in: "path", + required: true, + schema: { type: "string" }, + description: x.replace(":", ""), + })); + } + obj.tags = [...(obj.tags || []), getTag(p)].unique(); + + specification.paths[path] = { + ...specification.paths[path], + [method]: obj, + }; + }); +} + +function main() { + combineSchemas(schemas); + apiRoutes(); + + fs.writeFileSync( + openapiPath, + JSON.stringify(specification, null, 4) + .replaceAll("#/definitions", "#/components/schemas") + .replaceAll("bigint", "number"), + ); +} + +main(); diff --git a/scripts/util/getRouteDescriptions.js b/scripts/util/getRouteDescriptions.js new file mode 100644 index 00000000..21c36ca7 --- /dev/null +++ b/scripts/util/getRouteDescriptions.js @@ -0,0 +1,63 @@ +const { traverseDirectory } = require("lambert-server"); +const path = require("path"); +const express = require("express"); +const RouteUtility = require("../../dist/api/util/handlers/route.js"); +const Router = express.Router; + +const routes = new Map(); +let currentPath = ""; +let currentFile = ""; +const methods = ["get", "post", "put", "delete", "patch"]; + +function registerPath(file, method, prefix, path, ...args) { + const urlPath = prefix + path; + const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts"); + const opts = args.find((x) => typeof x === "object"); + if (opts) { + routes.set(urlPath + "|" + method, opts); // @ts-ignore + opts.file = sourceFile; + // console.log(method, urlPath, opts); + } else { + console.log( + `${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, + ); + } +} + +function routeOptions(opts) { + return opts; +} + +// @ts-ignore +RouteUtility.route = routeOptions; + +express.Router = (opts) => { + const path = currentPath; + const file = currentFile; + const router = Router(opts); + + for (const method of methods) { + router[method] = registerPath.bind(null, file, method, path); + } + + return router; +}; + +module.exports = function getRouteDescriptions() { + const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/"); + traverseDirectory({ dirname: root, recursive: true }, (file) => { + currentFile = file; + let path = file.replace(root.slice(0, -1), ""); + path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path + path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes + if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path + currentPath = path; + + try { + require(file); + } catch (error) { + console.error("error loading file " + file, error); + } + }); + return routes; +};