mirror of
https://github.com/spacebarchat/server.git
synced 2024-11-22 10:22:39 +01:00
add back openapi generation. todo: find way to keep route text descriptions in code, and find way to get usages of right/permission .hasThrow
This commit is contained in:
parent
1c9f2ecdce
commit
885c635d85
11484
assets/openapi.json
11484
assets/openapi.json
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@
|
||||
"generate:client": "node scripts/client.js",
|
||||
"generate:changelog": "node scripts/changelog.js",
|
||||
"generate:migration": "node -r dotenv/config -r module-alias/register node_modules/typeorm/cli.js migration:generate -d dist/util/util/Database.js",
|
||||
"generate:openapi": "node scripts/openapi.js",
|
||||
"migrate-from-staging": "node -r dotenv/config -r module-alias/register scripts/stagingMigration/index.js"
|
||||
},
|
||||
"main": "dist/bundle/index.js",
|
||||
|
169
scripts/openapi.js
Normal file
169
scripts/openapi.js
Normal file
@ -0,0 +1,169 @@
|
||||
require("module-alias/register");
|
||||
const getRouteDescriptions = require("./util/getRouteDescriptions");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const {
|
||||
NO_AUTHORIZATION_ROUTES,
|
||||
} = require("../dist/api/middlewares/Authentication");
|
||||
require("missing-native-js-functions");
|
||||
|
||||
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
||||
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
||||
const specification = JSON.parse(
|
||||
fs.readFileSync(openapiPath, { encoding: "utf8" }),
|
||||
);
|
||||
|
||||
function combineSchemas(schemas) {
|
||||
var definitions = {};
|
||||
|
||||
for (const name in schemas) {
|
||||
definitions = {
|
||||
...definitions,
|
||||
...schemas[name].definitions,
|
||||
[name]: {
|
||||
...schemas[name],
|
||||
definitions: undefined,
|
||||
$schema: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
for (const key in definitions) {
|
||||
specification.components = specification.components || {};
|
||||
specification.components.schemas =
|
||||
specification.components.schemas || {};
|
||||
specification.components.schemas[key] = definitions[key];
|
||||
delete definitions[key].additionalProperties;
|
||||
delete definitions[key].$schema;
|
||||
const definition = definitions[key];
|
||||
|
||||
if (typeof definition.properties === "object") {
|
||||
for (const property of Object.values(definition.properties)) {
|
||||
if (Array.isArray(property.type)) {
|
||||
if (property.type.includes("null")) {
|
||||
property.type = property.type.find((x) => 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();
|
63
scripts/util/getRouteDescriptions.js
Normal file
63
scripts/util/getRouteDescriptions.js
Normal file
@ -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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user