mirror of
https://github.com/spacebarchat/server.git
synced 2026-10-23 19:33:05 +00:00
Merge branch 'master' into feat/refactorIdentify
This commit is contained in:
180 files changed
+4862
-2375
No files matched your search
+109
-77
@@ -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.test.body) schema = schema.allOf[0];
|
||||
if (route.responses) {
|
||||
for (const [k, v] of Object.entries(route.responses)) {
|
||||
let schema = {
|
||||
$ref: `#/components/schemas/${v.body}`,
|
||||
};
|
||||
|
||||
obj.responses = {
|
||||
[status]: {
|
||||
...(route.test.response.body
|
||||
? {
|
||||
description:
|
||||
obj?.responses?.[status]?.description || "",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schema,
|
||||
obj.responses = {
|
||||
[k]: {
|
||||
...(v.body
|
||||
? {
|
||||
description:
|
||||
obj?.responses?.[k]?.description || "",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schema,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
: {
|
||||
description: "No description available",
|
||||
}),
|
||||
},
|
||||
}.merge(obj.responses);
|
||||
}
|
||||
} else {
|
||||
obj.responses = {
|
||||
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();
|
||||
|
||||
|
||||
+10
-38
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,80 +1,64 @@
|
||||
const express = require("express");
|
||||
const path = require("path");
|
||||
const { traverseDirectory } = require("lambert-server");
|
||||
const RouteUtility = require("../../dist/api/util/handlers/route.js");
|
||||
|
||||
const methods = ["get", "post", "put", "delete", "patch"];
|
||||
const routes = new Map();
|
||||
let currentFile = "";
|
||||
let currentPath = "";
|
||||
|
||||
/*
|
||||
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/>.
|
||||
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
|
||||
*/
|
||||
|
||||
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);
|
||||
opts.file = sourceFile;
|
||||
// console.log(method, urlPath, opts);
|
||||
} else {
|
||||
console.log(
|
||||
`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`,
|
||||
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`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function routeOptions(opts) {
|
||||
return opts;
|
||||
}
|
||||
console.log(prefix + path + " - " + method);
|
||||
opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
||||
routes.set(prefix + path + "|" + method, opts());
|
||||
};
|
||||
|
||||
RouteUtility.route = routeOptions;
|
||||
express.Router = () => {
|
||||
return Object.fromEntries(
|
||||
methods.map((method) => [
|
||||
method,
|
||||
proxy.bind(null, currentFile, method, currentPath),
|
||||
]),
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
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;
|
||||
};
|
||||
Reference in new issue
Block a user