summary refs log tree commit diff
path: root/scripts
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2023-07-28 08:24:15 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2023-07-28 08:24:15 +1000
commit8a3989c29776ad7eba8077bf7cc9c56e28b9b8c3 (patch)
treee71d68052e6bf5ddfad64c643a8fb2d04c2e183c /scripts
parentMerge branch 'master' into feat/refactorIdentify (diff)
parentMerge pull request #1075 from SpecificProtagonist/get_messages_around (diff)
downloadserver-8a3989c29776ad7eba8077bf7cc9c56e28b9b8c3.tar.xz
Merge branch 'master' into feat/refactorIdentify
Diffstat (limited to 'scripts')
-rw-r--r--scripts/openapi.js186
-rw-r--r--scripts/schema.js48
-rw-r--r--scripts/util/getRouteDescriptions.js98
3 files changed, 160 insertions, 172 deletions
diff --git a/scripts/openapi.js b/scripts/openapi.js
index 49d5dfde..8258a76c 100644
--- a/scripts/openapi.js
+++ b/scripts/openapi.js
@@ -27,34 +27,46 @@ require("missing-native-js-functions");
 
 const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
 const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
-let schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
-
-for (var schema in schemas) {
-	const part = schemas[schema];
-	for (var key in part.properties) {
-		if (part.properties[key].anyOf) {
-			const nullIndex = part.properties[key].anyOf.findIndex(
-				(x) => x.type == "null",
-			);
-			if (nullIndex != -1) {
-				part.properties[key].nullable = true;
-				part.properties[key].anyOf.splice(nullIndex, 1);
-
-				if (part.properties[key].anyOf.length == 1) {
-					Object.assign(
-						part.properties[key],
-						part.properties[key].anyOf[0],
-					);
-					delete part.properties[key].anyOf;
-				}
-			}
-		}
-	}
-}
-
-const specification = JSON.parse(
-	fs.readFileSync(openapiPath, { encoding: "utf8" }),
-);
+const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
+// const specification = JSON.parse(
+// 	fs.readFileSync(openapiPath, { encoding: "utf8" }),
+// );
+let specification = {
+	openapi: "3.1.0",
+	info: {
+		title: "Spacebar Server",
+		description:
+			"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
+		license: {
+			name: "AGPLV3",
+			url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
+		},
+		version: "1.0.0",
+	},
+	externalDocs: {
+		description: "Spacebar Docs",
+		url: "https://docs.spacebar.chat",
+	},
+	servers: [
+		{
+			url: "https://old.server.spacebar.chat/api/",
+			description: "Official Spacebar Instance",
+		},
+	],
+	components: {
+		securitySchemes: {
+			bearer: {
+				type: "http",
+				scheme: "bearer",
+				description: "Bearer/Bot prefixes are not required.",
+				bearerFormat: "JWT",
+				in: "header",
+			},
+		},
+	},
+	tags: [],
+	paths: {},
+};
 
 function combineSchemas(schemas) {
 	var definitions = {};
@@ -72,6 +84,11 @@ function combineSchemas(schemas) {
 	}
 
 	for (const key in definitions) {
+		const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm");
+		if (!reg.test(key)) {
+			console.error(`Invalid schema name: ${key} (${reg.test(key)})`);
+			continue;
+		}
 		specification.components = specification.components || {};
 		specification.components.schemas =
 			specification.components.schemas || {};
@@ -102,30 +119,20 @@ function getTag(key) {
 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.",
-		},
-	};
+	// populate tags
+	const tags = Array.from(routes.keys())
+		.map((x) => getTag(x))
+		.sort((a, b) => a.localeCompare(b));
+	specification.tags = tags.unique().map((x) => ({ name: x }));
 
 	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;
+		obj["x-fires-event"] = route.event;
 
 		if (
 			!NO_AUTHORIZATION_ROUTES.some((x) => {
@@ -136,48 +143,56 @@ function apiRoutes() {
 			obj.security = [{ bearer: [] }];
 		}
 
-		if (route.body) {
+		if (route.description) obj.description = route.description;
+		if (route.summary) obj.summary = route.summary;
+		if (route.deprecated) obj.deprecated = route.deprecated;
+
+		if (route.requestBody) {
 			obj.requestBody = {
 				required: true,
 				content: {
 					"application/json": {
-						schema: { $ref: `#/components/schemas/${route.body}` },
+						schema: {
+							$ref: `#/components/schemas/${route.requestBody}`,
+						},
 					},
 				},
 			}.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.responses) {
+			for (const [k, v] of Object.entries(route.responses)) {
+				let schema = {
+					$ref: `#/components/schemas/${v.body}`,
+				};
+
+				obj.responses = {
+					[k]: {
+						...(v.body
+							? {
+									description:
+										obj?.responses?.[k]?.description || "",
+									content: {
+										"application/json": {
+											schema: schema,
+										},
+									},
+							  }
+							: {
+									description: "No description available",
+							  }),
 					},
-				],
-			};
-			if (!route.test.body) schema = schema.allOf[0];
-
+				}.merge(obj.responses);
+			}
+		} else {
 			obj.responses = {
-				[status]: {
-					...(route.test.response.body
-						? {
-								description:
-									obj?.responses?.[status]?.description || "",
-								content: {
-									"application/json": {
-										schema: schema,
-									},
-								},
-						  }
-						: {}),
+				default: {
+					description: "No description available",
 				},
-			}.merge(obj.responses);
-			delete obj.responses.default;
+			};
 		}
+
+		// handles path parameters
 		if (p.includes(":")) {
 			obj.parameters = p.match(/:\w+/g)?.map((x) => ({
 				name: x.replace(":", ""),
@@ -187,16 +202,33 @@ function apiRoutes() {
 				description: x.replace(":", ""),
 			}));
 		}
+
+		if (route.query) {
+			// map to array
+			const query = Object.entries(route.query).map(([k, v]) => ({
+				name: k,
+				in: "query",
+				required: v.required,
+				schema: { type: v.type },
+				description: v.description,
+			}));
+
+			obj.parameters = [...(obj.parameters || []), ...query];
+		}
+
 		obj.tags = [...(obj.tags || []), getTag(p)].unique();
 
-		specification.paths[path] = {
-			...specification.paths[path],
-			[method]: obj,
-		};
+		specification.paths[path] = Object.assign(
+			specification.paths[path] || {},
+			{
+				[method]: obj,
+			},
+		);
 	});
 }
 
 function main() {
+	console.log("Generating OpenAPI Specification...");
 	combineSchemas(schemas);
 	apiRoutes();
 
diff --git a/scripts/schema.js b/scripts/schema.js
index c29d5bab..ff3280ac 100644
--- a/scripts/schema.js
+++ b/scripts/schema.js
@@ -34,9 +34,7 @@ const settings = {
 	noExtraProps: true,
 	defaultProps: false,
 };
-const compilerOptions = {
-	strictNullChecks: true,
-};
+
 const Excluded = [
 	"DefaultSchema",
 	"Schema",
@@ -57,16 +55,10 @@ const Excluded = [
 	"PropertiesSchema",
 	"AsyncSchema",
 	"AnySchema",
+	"SMTPConnection.CustomAuthenticationResponse",
+	"TransportMakeRequestResponse",
 ];
 
-function modify(obj) {
-	for (var k in obj) {
-		if (typeof obj[k] === "object" && obj[k] !== null) {
-			modify(obj[k]);
-		}
-	}
-}
-
 function main() {
 	const program = TJS.programFromConfig(
 		path.join(__dirname, "..", "tsconfig.json"),
@@ -75,14 +67,14 @@ function main() {
 	const generator = TJS.buildGenerator(program, settings);
 	if (!generator || !program) return;
 
-	let schemas = generator
-		.getUserSymbols()
-		.filter(
-			(x) =>
-				(x.endsWith("Schema") || x.endsWith("Response")) &&
-				!Excluded.includes(x),
+	let schemas = generator.getUserSymbols().filter((x) => {
+		return (
+			(x.endsWith("Schema") ||
+				x.endsWith("Response") ||
+				x.startsWith("API")) &&
+			!Excluded.includes(x)
 		);
-	console.log(schemas);
+	});
 
 	var definitions = {};
 
@@ -109,32 +101,12 @@ function main() {
 					delete part.properties[key];
 					continue;
 				}
-
-				// if (part.properties[key].anyOf) {
-				// 	const nullIndex = part.properties[key].anyOf.findIndex(
-				// 		(x) => x.type == "null",
-				// 	);
-				// 	if (nullIndex != -1) {
-				// 		part.properties[key].nullable = true;
-				// 		part.properties[key].anyOf.splice(nullIndex, 1);
-
-				// 		if (part.properties[key].anyOf.length == 1) {
-				// 			Object.assign(
-				// 				part.properties[key],
-				// 				part.properties[key].anyOf[0],
-				// 			);
-				// 			delete part.properties[key].anyOf;
-				// 		}
-				// 	}
-				// }
 			}
 		}
 
 		definitions = { ...definitions, [name]: { ...part } };
 	}
 
-	modify(definitions);
-
 	fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
 }
 
diff --git a/scripts/util/getRouteDescriptions.js b/scripts/util/getRouteDescriptions.js
index fe36c238..a79dac96 100644
--- a/scripts/util/getRouteDescriptions.js
+++ b/scripts/util/getRouteDescriptions.js
@@ -1,80 +1,64 @@
-/*
-	Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
-	Copyright (C) 2023 Spacebar and Spacebar Contributors
-	
-	This program is free software: you can redistribute it and/or modify
-	it under the terms of the GNU Affero General Public License as published
-	by the Free Software Foundation, either version 3 of the License, or
-	(at your option) any later version.
-	
-	This program is distributed in the hope that it will be useful,
-	but WITHOUT ANY WARRANTY; without even the implied warranty of
-	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-	GNU Affero General Public License for more details.
-	
-	You should have received a copy of the GNU Affero General Public License
-	along with this program.  If not, see <https://www.gnu.org/licenses/>.
-*/
-
-const { traverseDirectory } = require("lambert-server");
-const path = require("path");
 const express = require("express");
+const path = require("path");
+const { traverseDirectory } = require("lambert-server");
 const RouteUtility = require("../../dist/api/util/handlers/route.js");
-const Router = express.Router;
 
+const methods = ["get", "post", "put", "delete", "patch"];
 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);
-		opts.file = sourceFile;
-		// console.log(method, urlPath, opts);
-	} else {
-		console.log(
-			`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`,
-		);
-	}
-}
+let currentPath = "";
 
-function routeOptions(opts) {
-	return opts;
-}
+/*
+	For some reason, if a route exports multiple functions, it won't be registered here!
+	If someone could fix that I'd really appreciate it, but for now just, don't do that :p
+*/
 
-RouteUtility.route = routeOptions;
+const proxy = (file, method, prefix, path, ...args) => {
+	const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true);
+	if (!opts)
+		return console.error(
+			`${file} has route without route() description middleware`,
+		);
 
-express.Router = (opts) => {
-	const path = currentPath;
-	const file = currentFile;
-	const router = Router(opts);
+	console.log(prefix + path + " - " + method);
+	opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
+	routes.set(prefix + path + "|" + method, opts());
+};
 
-	for (const method of methods) {
-		router[method] = registerPath.bind(null, file, method, path);
-	}
+express.Router = () => {
+	return Object.fromEntries(
+		methods.map((method) => [
+			method,
+			proxy.bind(null, currentFile, method, currentPath),
+		]),
+	);
+};
 
-	return router;
+RouteUtility.route = (opts) => {
+	const func = function () {
+		return opts;
+	};
+	func.prototype.OPTS_MARKER = true;
+	return func;
 };
 
 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;
+
+		currentPath = file.replace(root.slice(0, -1), "");
+		currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
+		currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
+		if (currentPath.endsWith("/index"))
+			currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
 
 		try {
 			require(file);
-		} catch (error) {
-			console.error("error loading file " + file, error);
+		} catch (e) {
+			console.error(e);
 		}
 	});
+
 	return routes;
 };