mirror of
https://github.com/spacebarchat/server.git
synced 2026-04-14 05:06:04 +00:00
✨ generate openapi documentation
This commit is contained in:
@@ -4,9 +4,9 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import * as TJS from "typescript-json-schema";
|
||||
import "missing-native-js-functions";
|
||||
const schemaPath = path.join(__dirname, "..", "assets", "responses.json");
|
||||
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||
|
||||
const settings: TJS.PartialArgs = {
|
||||
const settings = {
|
||||
required: true,
|
||||
ignoreErrors: true,
|
||||
excludePrivate: true,
|
||||
@@ -14,10 +14,13 @@ const settings: TJS.PartialArgs = {
|
||||
noExtraProps: true,
|
||||
defaultProps: false
|
||||
};
|
||||
const compilerOptions: TJS.CompilerOptions = {
|
||||
const compilerOptions = {
|
||||
strictNullChecks: true
|
||||
};
|
||||
const ExcludedSchemas = [
|
||||
const Excluded = [
|
||||
"DefaultSchema",
|
||||
"Schema",
|
||||
"EntitySchema",
|
||||
"ServerResponse",
|
||||
"Http2ServerResponse",
|
||||
"global.Express.Response",
|
||||
@@ -32,13 +35,13 @@ function main() {
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
if (!generator || !program) return;
|
||||
|
||||
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x));
|
||||
const schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x));
|
||||
console.log(schemas);
|
||||
|
||||
var definitions: any = {};
|
||||
var definitions = {};
|
||||
|
||||
for (const name of schemas) {
|
||||
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
|
||||
const part = TJS.generateSchema(program, name, settings, [], generator);
|
||||
if (!part) continue;
|
||||
|
||||
definitions = { ...definitions, [name]: { ...part } };
|
||||
@@ -47,11 +50,10 @@ function main() {
|
||||
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
||||
}
|
||||
|
||||
// #/definitions/
|
||||
main();
|
||||
|
||||
function walk(dir: string) {
|
||||
var results = [] as string[];
|
||||
function walk(dir) {
|
||||
var results = [];
|
||||
var list = fs.readdirSync(dir);
|
||||
list.forEach(function (file) {
|
||||
file = dir + "/" + file;
|
||||
@@ -1,60 +0,0 @@
|
||||
// https://mermade.github.io/openapi-gui/#
|
||||
// https://editor.swagger.io/
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import * as TJS from "typescript-json-schema";
|
||||
import "missing-native-js-functions";
|
||||
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||
|
||||
const settings: TJS.PartialArgs = {
|
||||
required: true,
|
||||
ignoreErrors: true,
|
||||
excludePrivate: true,
|
||||
defaultNumberType: "integer",
|
||||
noExtraProps: true,
|
||||
defaultProps: false
|
||||
};
|
||||
const compilerOptions: TJS.CompilerOptions = {
|
||||
strictNullChecks: true
|
||||
};
|
||||
const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"];
|
||||
|
||||
function main() {
|
||||
const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
if (!generator || !program) return;
|
||||
|
||||
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x));
|
||||
console.log(schemas);
|
||||
|
||||
var definitions: any = {};
|
||||
|
||||
for (const name of schemas) {
|
||||
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
|
||||
if (!part) continue;
|
||||
|
||||
definitions = { ...definitions, [name]: { ...part } };
|
||||
}
|
||||
|
||||
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
||||
}
|
||||
|
||||
// #/definitions/
|
||||
main();
|
||||
|
||||
function walk(dir: string) {
|
||||
var results = [] as string[];
|
||||
var list = fs.readdirSync(dir);
|
||||
list.forEach(function (file) {
|
||||
file = dir + "/" + file;
|
||||
var stat = fs.statSync(file);
|
||||
if (stat && stat.isDirectory()) {
|
||||
/* Recurse into a subdirectory */
|
||||
results = results.concat(walk(file));
|
||||
} else {
|
||||
if (!file.endsWith(".ts")) return;
|
||||
results.push(file);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
127
api/scripts/generate_openapi_schema.js
Normal file
127
api/scripts/generate_openapi_schema.js
Normal file
@@ -0,0 +1,127 @@
|
||||
// https://mermade.github.io/openapi-gui/#
|
||||
// https://editor.swagger.io/
|
||||
const getRouteDescriptions = require("../jest/getRouteDescriptions");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
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.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.map((x) => x.name), ...tags].unique().map((x) => ({ name: x }));
|
||||
|
||||
routes.forEach((route, pathAndMethod) => {
|
||||
const [p, method] = pathAndMethod.split("|");
|
||||
const path = p.replace(/:(\w+)/g, "{$1}");
|
||||
|
||||
let obj = specification.paths[path]?.[method] || {};
|
||||
if (!obj.description) {
|
||||
const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : "";
|
||||
const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : "";
|
||||
obj.description = permission + event;
|
||||
}
|
||||
if (route.body) {
|
||||
obj.requestBody = {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: { $ref: `#/components/schemas/${route.body}` }
|
||||
}
|
||||
}
|
||||
}.merge(obj.requestBody);
|
||||
}
|
||||
if (!obj.responses) {
|
||||
obj.responses = {
|
||||
default: {
|
||||
description: "not documented"
|
||||
}
|
||||
};
|
||||
}
|
||||
if (route.test?.response) {
|
||||
const status = route.test.response.status || 200;
|
||||
obj.responses = {
|
||||
[status]: {
|
||||
...(route.test.response.body
|
||||
? {
|
||||
description: obj.responses[status].description || "",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: {
|
||||
$ref: `#/components/schemas/${route.test.response.body}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
: {})
|
||||
}
|
||||
}.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();
|
||||
@@ -1,92 +0,0 @@
|
||||
// https://mermade.github.io/openapi-gui/#
|
||||
// https://editor.swagger.io/
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import * as TJS from "typescript-json-schema";
|
||||
import "missing-native-js-functions";
|
||||
|
||||
const settings: TJS.PartialArgs = {
|
||||
required: true,
|
||||
ignoreErrors: true,
|
||||
excludePrivate: true,
|
||||
defaultNumberType: "integer",
|
||||
noExtraProps: true,
|
||||
defaultProps: false
|
||||
};
|
||||
const compilerOptions: TJS.CompilerOptions = {
|
||||
strictNullChecks: false
|
||||
};
|
||||
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
||||
var specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
|
||||
|
||||
async function utilSchemas() {
|
||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
|
||||
const schemas = ["UserPublic", "UserPrivate", "PublicConnectedAccount"];
|
||||
|
||||
// @ts-ignore
|
||||
combineSchemas({ schemas, generator, program });
|
||||
}
|
||||
|
||||
function combineSchemas(opts: { program: TJS.Program; generator: TJS.JsonSchemaGenerator; schemas: string[] }) {
|
||||
var definitions: any = {};
|
||||
|
||||
for (const name of opts.schemas) {
|
||||
const part = TJS.generateSchema(opts.program, name, settings, [], opts.generator as TJS.JsonSchemaGenerator);
|
||||
if (!part) continue;
|
||||
|
||||
definitions = { ...definitions, [name]: { ...part, definitions: undefined, $schema: undefined } };
|
||||
}
|
||||
|
||||
for (const key in definitions) {
|
||||
specification.components.schemas[key] = definitions[key];
|
||||
delete definitions[key].additionalProperties;
|
||||
delete definitions[key].$schema;
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
const ExcludedSchemas = [
|
||||
"DefaultSchema",
|
||||
"Schema",
|
||||
"EntitySchema",
|
||||
"ServerResponse",
|
||||
"Http2ServerResponse",
|
||||
"global.Express.Response",
|
||||
"Response",
|
||||
"e.Response",
|
||||
"request.Response",
|
||||
"supertest.Response"
|
||||
];
|
||||
|
||||
function apiSchemas() {
|
||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
|
||||
const schemas = generator
|
||||
.getUserSymbols()
|
||||
.filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x))
|
||||
.concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)));
|
||||
|
||||
// @ts-ignore
|
||||
combineSchemas({ schemas, generator, program });
|
||||
}
|
||||
|
||||
function addDefaultResponses() {
|
||||
Object.values(specification.paths).forEach((path: any) => Object.values(path).forEach((request: any) => {}));
|
||||
}
|
||||
|
||||
function main() {
|
||||
addDefaultResponses();
|
||||
utilSchemas();
|
||||
apiSchemas();
|
||||
|
||||
fs.writeFileSync(
|
||||
openapiPath,
|
||||
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user