diff --git a/scripts/openapi.js b/scripts/openapi.js
index 39396d8d..5afee152 100644
--- a/scripts/openapi.js
+++ b/scripts/openapi.js
@@ -32,227 +32,227 @@ const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
let specification = {
- openapi: "3.1.0",
- info: {
- title: "Spacebar Server",
- description:
- "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.",
- 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: {},
+ openapi: "3.1.0",
+ info: {
+ title: "Spacebar Server",
+ description:
+ "Spacebar is a Discord.com server implementation and extension, with the goal of complete feature parity with Discord.com, all while adding some additional goodies, security, privacy, and configuration options.",
+ 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: {},
};
const schemaRegEx = new RegExp(/^[\w.]+$/);
function combineSchemas(schemas) {
- let definitions = {};
+ let definitions = {};
- for (const name in schemas) {
- definitions = {
- ...definitions,
- ...schemas[name].definitions,
- [name]: {
- ...schemas[name],
- definitions: undefined,
- $schema: undefined,
- },
- };
- }
+ for (const name in schemas) {
+ definitions = {
+ ...definitions,
+ ...schemas[name].definitions,
+ [name]: {
+ ...schemas[name],
+ definitions: undefined,
+ $schema: undefined,
+ },
+ };
+ }
- for (const key in definitions) {
- if (!schemaRegEx.test(key)) {
- console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]);
- continue;
- }
- 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];
+ for (const key in definitions) {
+ if (!schemaRegEx.test(key)) {
+ console.error(` \x1b[5m${bgRedBright("ERROR")}\x1b[25m Invalid schema name: ${key}, context:`, definitions[key]);
+ continue;
+ }
+ 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;
- }
- }
- }
- }
- }
+ 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;
+ return definitions;
}
function getTag(key) {
- return key.match(/\/([\w-]+)/)[1];
+ return key.match(/\/([\w-]+)/)[1];
}
function apiRoutes(missingRoutes) {
- const routes = getRouteDescriptions();
+ const routes = getRouteDescriptions();
- // populate tags
- const tags = Array.from(routes.keys())
- .map((x) => getTag(x))
- .sort((a, b) => a.localeCompare(b));
- specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
+ // populate tags
+ const tags = Array.from(routes.keys())
+ .map((x) => getTag(x))
+ .sort((a, b) => a.localeCompare(b));
+ specification.tags = [...new Set(tags)].map((x) => ({ name: x }));
- routes.forEach((route, pathAndMethod) => {
- const [p, method] = pathAndMethod.split("|");
- const path = p.replace(/:(\w+)/g, "{$1}");
+ routes.forEach((route, pathAndMethod) => {
+ const [p, method] = pathAndMethod.split("|");
+ const path = p.replace(/:(\w+)/g, "{$1}");
- let obj = specification.paths[path]?.[method] || {};
- obj["x-right-required"] = route.right;
- obj["x-permission-required"] = route.permission;
- obj["x-fires-event"] = route.event;
+ let obj = specification.paths[path]?.[method] || {};
+ obj["x-right-required"] = route.right;
+ obj["x-permission-required"] = route.permission;
+ obj["x-fires-event"] = route.event;
- if (
- !NO_AUTHORIZATION_ROUTES.some((x) => {
- if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x);
- return x.test(method.toUpperCase() + " " + path);
- })
- ) {
- obj.security = [{ bearer: [] }];
- }
+ if (
+ !NO_AUTHORIZATION_ROUTES.some((x) => {
+ if (typeof x === "string") return (method.toUpperCase() + " " + path).startsWith(x);
+ return x.test(method.toUpperCase() + " " + path);
+ })
+ ) {
+ obj.security = [{ bearer: [] }];
+ }
- if (route.description) obj.description = route.description;
- if (route.summary) obj.summary = route.summary;
- if (route.deprecated) obj.deprecated = route.deprecated;
+ 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.requestBody}`,
- },
- },
- },
- };
- }
+ if (route.requestBody) {
+ obj.requestBody = {
+ required: true,
+ content: {
+ "application/json": {
+ schema: {
+ $ref: `#/components/schemas/${route.requestBody}`,
+ },
+ },
+ },
+ };
+ }
- if (route.responses) {
- obj.responses = {};
+ if (route.responses) {
+ obj.responses = {};
- for (const [k, v] of Object.entries(route.responses)) {
- if (v.body)
- obj.responses[k] = {
- description: obj?.responses?.[k]?.description || "",
- content: {
- "application/json": {
- schema: {
- $ref: `#/components/schemas/${v.body}`,
- },
- },
- },
- };
- else
- obj.responses[k] = {
- description: obj?.responses?.[k]?.description || "No description available",
- };
- }
- } else {
- obj.responses = {
- default: {
- description: "No description available",
- },
- };
- }
+ for (const [k, v] of Object.entries(route.responses)) {
+ if (v.body)
+ obj.responses[k] = {
+ description: obj?.responses?.[k]?.description || "",
+ content: {
+ "application/json": {
+ schema: {
+ $ref: `#/components/schemas/${v.body}`,
+ },
+ },
+ },
+ };
+ else
+ obj.responses[k] = {
+ description: obj?.responses?.[k]?.description || "No description available",
+ };
+ }
+ } else {
+ obj.responses = {
+ default: {
+ description: "No description available",
+ },
+ };
+ }
- // handles path parameters
- if (p.includes(":")) {
- obj.parameters = p.match(/:\w+/g)?.map((x) => ({
- name: x.replace(":", ""),
- in: "path",
- required: true,
- schema: { type: "string" },
- description: x.replace(":", ""),
- }));
- }
+ // handles path parameters
+ if (p.includes(":")) {
+ obj.parameters = p.match(/:\w+/g)?.map((x) => ({
+ name: x.replace(":", ""),
+ in: "path",
+ required: true,
+ schema: { type: "string" },
+ 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,
- }));
+ 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.parameters = [...(obj.parameters || []), ...query];
+ }
- obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
+ obj.tags = [...new Set([...(obj.tags || []), getTag(p)])];
- if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
- obj["x-badges"] = [
- {
- label: "Spacebar-only",
- color: "red",
- },
- ];
- }
+ if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
+ obj["x-badges"] = [
+ {
+ label: "Spacebar-only",
+ color: "red",
+ },
+ ];
+ }
- specification.paths[path] = Object.assign(specification.paths[path] || {}, {
- [method]: obj,
- });
- });
+ specification.paths[path] = Object.assign(specification.paths[path] || {}, {
+ [method]: obj,
+ });
+ });
}
async function main() {
- console.log("Generating OpenAPI Specification...");
+ console.log("Generating OpenAPI Specification...");
- const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", {
- headers: {
- Accept: "application/json",
- },
- });
- const missingRoutes = await routesRes.json();
+ const routesRes = await fetch("https://github.com/spacebarchat/missing-routes/raw/main/missing.json", {
+ headers: {
+ Accept: "application/json",
+ },
+ });
+ const missingRoutes = await routesRes.json();
- combineSchemas(schemas);
- apiRoutes(missingRoutes);
+ combineSchemas(schemas);
+ apiRoutes(missingRoutes);
- fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number"));
- console.log("Wrote OpenAPI specification to", openapiPath);
- const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
- console.log(
- "Specification contains",
- Object.keys(specification.paths).length,
- "paths and",
- Object.keys(specification.components.schemas).length,
- "schemas in",
- elapsedMs,
- "ms.",
- );
+ fs.writeFileSync(openapiPath, JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number"));
+ console.log("Wrote OpenAPI specification to", openapiPath);
+ const elapsedMs = Number(totalSw.elapsed().totalMilliseconds + "." + totalSw.elapsed().microseconds);
+ console.log(
+ "Specification contains",
+ Object.keys(specification.paths).length,
+ "paths and",
+ Object.keys(specification.components.schemas).length,
+ "schemas in",
+ elapsedMs,
+ "ms.",
+ );
}
main();
|