summary refs log tree commit diff
path: root/api/scripts
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-21 22:52:30 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-21 22:52:30 +0200
commitabdce76df4b6aa3a063b496e6c0575c54e9fa397 (patch)
treec135233da7c327eeb52f143a2632f5d5bd4b65b5 /api/scripts
parent:bug: fix unittests (diff)
downloadserver-abdce76df4b6aa3a063b496e6c0575c54e9fa397.tar.xz
:sparkles: generate openapi documentation
Diffstat (limited to 'api/scripts')
-rw-r--r--api/scripts/generate_body_schema.js (renamed from api/scripts/generate_test_schema.ts)22
-rw-r--r--api/scripts/generate_body_schema.ts60
-rw-r--r--api/scripts/generate_openapi_schema.js127
-rw-r--r--api/scripts/generate_openapi_schema.ts92
4 files changed, 139 insertions, 162 deletions
diff --git a/api/scripts/generate_test_schema.ts b/api/scripts/generate_body_schema.js
index eed77738..22d0b02e 100644
--- a/api/scripts/generate_test_schema.ts
+++ b/api/scripts/generate_body_schema.js
@@ -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;
diff --git a/api/scripts/generate_body_schema.ts b/api/scripts/generate_body_schema.ts
deleted file mode 100644
index 316e5a69..00000000
--- a/api/scripts/generate_body_schema.ts
+++ /dev/null
@@ -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;
-}
diff --git a/api/scripts/generate_openapi_schema.js b/api/scripts/generate_openapi_schema.js
new file mode 100644
index 00000000..eb979f14
--- /dev/null
+++ b/api/scripts/generate_openapi_schema.js
@@ -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();
diff --git a/api/scripts/generate_openapi_schema.ts b/api/scripts/generate_openapi_schema.ts
deleted file mode 100644
index c0995b6c..00000000
--- a/api/scripts/generate_openapi_schema.ts
+++ /dev/null
@@ -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();