Merge pull request #1192 from DEVTomatoCake/feat/improve-schema-openapi-generation

This commit is contained in:
Madeline 2024-08-22 09:49:21 +10:00 committed by GitHub
commit 4f19ee19bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 323 additions and 69856 deletions

View File

@ -2,7 +2,7 @@
"openapi": "3.1.0", "openapi": "3.1.0",
"info": { "info": {
"title": "Spacebar Server", "title": "Spacebar Server",
"description": "Spacebar is a free open source selfhostable discord compatible chat, voice and video platform", "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": { "license": {
"name": "AGPLV3", "name": "AGPLV3",
"url": "https://www.gnu.org/licenses/agpl-3.0.en.html" "url": "https://www.gnu.org/licenses/agpl-3.0.en.html"
@ -61,110 +61,209 @@
"read_states" "read_states"
] ]
}, },
"DiagnosticsChannel.Response": { "ConnectedAccountCommonOAuthTokenResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"statusCode": { "access_token": {
"type": "integer"
},
"statusText": {
"type": "string" "type": "string"
}, },
"headers": { "token_type": {
"type": "string"
},
"scope": {
"type": "string"
},
"refresh_token": {
"type": "string"
},
"expires_in": {
"type": "integer"
}
},
"required": [
"access_token",
"scope",
"token_type"
]
},
"ApplicationAuthorizeSchema": {
"type": "object",
"properties": {
"authorize": {
"type": "boolean"
},
"guild_id": {
"type": "string"
},
"permissions": {
"type": "string"
},
"captcha_key": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"authorize",
"guild_id",
"permissions"
]
},
"ApplicationCreateSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"team_id": {
"type": [
"string",
"integer"
]
}
},
"required": [
"name"
]
},
"ApplicationModifySchema": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"interactions_endpoint_url": {
"type": "string"
},
"max_participants": {
"type": "integer",
"nullable": true
},
"name": {
"type": "string"
},
"privacy_policy_url": {
"type": "string"
},
"role_connections_verification_url": {
"type": "string"
},
"tags": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "string"
"additionalProperties": false, }
"patternProperties": { },
"^[0-9]+$": { "terms_of_service_url": {
"type": "string"
},
"bot_public": {
"type": "boolean"
},
"bot_require_code_grant": {
"type": "boolean"
},
"flags": {
"type": "integer" "type": "integer"
} }
} }
}
}
}, },
"required": [ "BackupCodesChallengeSchema": {
"headers",
"statusCode",
"statusText"
]
},
"Headers": {
"type": "object", "type": "object",
"properties": { "properties": {
"append": { "password": {
"type": "object", "type": "string"
"additionalProperties": false
},
"delete": {
"type": "object",
"additionalProperties": false
},
"get": {
"type": "object",
"additionalProperties": false
},
"has": {
"type": "object",
"additionalProperties": false
},
"set": {
"type": "object",
"additionalProperties": false
},
"getSetCookie": {
"type": "object",
"additionalProperties": false
},
"forEach": {
"description": "Performs the specified action for each element in an array.",
"type": "object",
"additionalProperties": false
},
"keys": {
"description": "Returns an array consisting of the keys of the object",
"type": "object",
"additionalProperties": false
},
"values": {
"type": "object",
"additionalProperties": false
},
"entries": {
"description": "Returns an array consisting of the key value pairs of the object",
"type": "object",
"additionalProperties": false
},
"__@iterator": {
"type": "object",
"additionalProperties": false
} }
}, },
"required": [ "required": [
"__@iterator", "password"
"append",
"delete",
"entries",
"forEach",
"get",
"getSetCookie",
"has",
"keys",
"set",
"values"
] ]
}, },
"ResponseType": { "BanCreateSchema": {
"enum": [ "type": "object",
"basic", "properties": {
"cors", "delete_message_seconds": {
"default",
"error",
"opaque",
"opaqueredirect"
],
"type": "string" "type": "string"
}, },
"delete_message_days": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"BanModeratorSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"user_id": {
"type": "string"
},
"guild_id": {
"type": "string"
},
"executor_id": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"executor_id",
"guild_id",
"id",
"user_id"
]
},
"BanRegistrySchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"user_id": {
"type": "string"
},
"guild_id": {
"type": "string"
},
"executor_id": {
"type": "string"
},
"ip": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"executor_id",
"guild_id",
"id",
"user_id"
]
},
"BotModifySchema": {
"type": "object",
"properties": {
"avatar": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"ChannelPermissionOverwriteType": { "ChannelPermissionOverwriteType": {
"enum": [ "enum": [
0, 0,
@ -4729,372 +4828,6 @@
"webauthn" "webauthn"
] ]
}, },
"_Response": {
"type": "object",
"properties": {
"headers": {
"$ref": "#/components/schemas/Headers"
},
"ok": {
"type": "boolean"
},
"status": {
"type": "integer"
},
"statusText": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/ResponseType"
},
"url": {
"type": "string"
},
"redirected": {
"type": "boolean"
},
"body": {
"anyOf": [
{
"$ref": "#/components/schemas/ReadableStream<any>"
},
{
"type": "null"
}
]
},
"bodyUsed": {
"type": "boolean"
},
"arrayBuffer": {
"type": "object",
"additionalProperties": false
},
"blob": {
"type": "object",
"additionalProperties": false
},
"formData": {
"type": "object",
"additionalProperties": false
},
"json": {
"type": "object",
"additionalProperties": false
},
"text": {
"type": "object",
"additionalProperties": false
},
"clone": {
"type": "object",
"additionalProperties": false
}
},
"required": [
"arrayBuffer",
"blob",
"body",
"bodyUsed",
"clone",
"formData",
"headers",
"json",
"ok",
"redirected",
"status",
"statusText",
"text",
"type",
"url"
]
},
"global.Response": {
"type": "object",
"properties": {
"headers": {
"$ref": "#/components/schemas/Headers"
},
"ok": {
"type": "boolean"
},
"status": {
"type": "integer"
},
"statusText": {
"type": "string"
},
"type": {
"$ref": "#/components/schemas/ResponseType"
},
"url": {
"type": "string"
},
"redirected": {
"type": "boolean"
},
"body": {
"anyOf": [
{
"$ref": "#/components/schemas/ReadableStream<any>"
},
{
"type": "null"
}
]
},
"bodyUsed": {
"type": "boolean"
},
"arrayBuffer": {
"type": "object",
"additionalProperties": false
},
"blob": {
"type": "object",
"additionalProperties": false
},
"formData": {
"type": "object",
"additionalProperties": false
},
"json": {
"type": "object",
"additionalProperties": false
},
"text": {
"type": "object",
"additionalProperties": false
},
"clone": {
"type": "object",
"additionalProperties": false
}
},
"required": [
"arrayBuffer",
"blob",
"body",
"bodyUsed",
"clone",
"formData",
"headers",
"json",
"ok",
"redirected",
"status",
"statusText",
"text",
"type",
"url"
]
},
"ConnectedAccountCommonOAuthTokenResponse": {
"type": "object",
"properties": {
"access_token": {
"type": "string"
},
"token_type": {
"type": "string"
},
"scope": {
"type": "string"
},
"refresh_token": {
"type": "string"
},
"expires_in": {
"type": "integer"
}
},
"required": [
"access_token",
"scope",
"token_type"
]
},
"ExpressResponse": {
"type": "object"
},
"ApplicationAuthorizeSchema": {
"type": "object",
"properties": {
"authorize": {
"type": "boolean"
},
"guild_id": {
"type": "string"
},
"permissions": {
"type": "string"
},
"captcha_key": {
"type": "string"
},
"code": {
"type": "string"
}
},
"required": [
"authorize",
"guild_id",
"permissions"
]
},
"ApplicationCreateSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"team_id": {
"type": [
"string",
"integer"
]
}
},
"required": [
"name"
]
},
"ApplicationModifySchema": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"icon": {
"type": "string"
},
"interactions_endpoint_url": {
"type": "string"
},
"max_participants": {
"type": "integer",
"nullable": true
},
"name": {
"type": "string"
},
"privacy_policy_url": {
"type": "string"
},
"role_connections_verification_url": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"terms_of_service_url": {
"type": "string"
},
"bot_public": {
"type": "boolean"
},
"bot_require_code_grant": {
"type": "boolean"
},
"flags": {
"type": "integer"
}
}
},
"BackupCodesChallengeSchema": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
},
"required": [
"password"
]
},
"BanCreateSchema": {
"type": "object",
"properties": {
"delete_message_seconds": {
"type": "string"
},
"delete_message_days": {
"type": "string"
},
"reason": {
"type": "string"
}
}
},
"BanModeratorSchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"user_id": {
"type": "string"
},
"guild_id": {
"type": "string"
},
"executor_id": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"executor_id",
"guild_id",
"id",
"user_id"
]
},
"BanRegistrySchema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"user_id": {
"type": "string"
},
"guild_id": {
"type": "string"
},
"executor_id": {
"type": "string"
},
"ip": {
"type": "string"
},
"reason": {
"type": "string"
}
},
"required": [
"executor_id",
"guild_id",
"id",
"user_id"
]
},
"BotModifySchema": {
"type": "object",
"properties": {
"avatar": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"ChannelPermissionOverwriteSchema": { "ChannelPermissionOverwriteSchema": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -6184,7 +5917,6 @@
"properties": { "properties": {
"username": { "username": {
"minLength": 2, "minLength": 2,
"maxLength": 32,
"type": "string" "type": "string"
}, },
"password": { "password": {
@ -6600,8 +6332,7 @@
"type": "object", "type": "object",
"properties": { "properties": {
"username": { "username": {
"minLength": 1, "minLength": 2,
"maxLength": 100,
"type": "string" "type": "string"
}, },
"avatar": { "avatar": {
@ -10845,6 +10576,12 @@
}, },
"tags": [ "tags": [
"updates" "updates"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -10857,6 +10594,12 @@
}, },
"tags": [ "tags": [
"track" "track"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11119,6 +10862,12 @@
}, },
"tags": [ "tags": [
"scheduled-maintenances" "scheduled-maintenances"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11190,6 +10939,12 @@
}, },
"tags": [ "tags": [
"policies" "policies"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11209,6 +10964,12 @@
}, },
"tags": [ "tags": [
"policies" "policies"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11228,6 +10989,12 @@
}, },
"tags": [ "tags": [
"policies" "policies"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11247,6 +11014,12 @@
}, },
"tags": [ "tags": [
"policies" "policies"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -11266,6 +11039,12 @@
}, },
"tags": [ "tags": [
"ping" "ping"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -17512,6 +17291,12 @@
}, },
"tags": [ "tags": [
"auth" "auth"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -17551,6 +17336,12 @@
}, },
"tags": [ "tags": [
"auth" "auth"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -17669,6 +17460,12 @@
], ],
"tags": [ "tags": [
"auth" "auth"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -18152,6 +17949,12 @@
}, },
"tags": [ "tags": [
"-" "-"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
}, },
@ -18164,6 +17967,12 @@
}, },
"tags": [ "tags": [
"-" "-"
],
"x-badges": [
{
"label": "Spacebar-only",
"color": "red"
}
] ]
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -28,15 +28,13 @@ require("missing-native-js-functions");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
// const specification = JSON.parse(
// fs.readFileSync(openapiPath, { encoding: "utf8" }),
// );
let specification = { let specification = {
openapi: "3.1.0", openapi: "3.1.0",
info: { info: {
title: "Spacebar Server", title: "Spacebar Server",
description: description:
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform", "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: { license: {
name: "AGPLV3", name: "AGPLV3",
url: "https://www.gnu.org/licenses/agpl-3.0.en.html", url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
@ -68,8 +66,9 @@ let specification = {
paths: {}, paths: {},
}; };
const schemaRegEx = new RegExp(/^[\w.]+$/);
function combineSchemas(schemas) { function combineSchemas(schemas) {
var definitions = {}; let definitions = {};
for (const name in schemas) { for (const name in schemas) {
definitions = { definitions = {
@ -84,9 +83,8 @@ function combineSchemas(schemas) {
} }
for (const key in definitions) { for (const key in definitions) {
const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm"); if (!schemaRegEx.test(key)) {
if (!reg.test(key)) { console.error(`Invalid schema name: ${key}`);
console.error(`Invalid schema name: ${key} (${reg.test(key)})`);
continue; continue;
} }
specification.components = specification.components || {}; specification.components = specification.components || {};
@ -116,7 +114,7 @@ function getTag(key) {
return key.match(/\/([\w-]+)/)[1]; return key.match(/\/([\w-]+)/)[1];
} }
function apiRoutes() { function apiRoutes(missingRoutes) {
const routes = getRouteDescriptions(); const routes = getRouteDescriptions();
// populate tags // populate tags
@ -157,32 +155,30 @@ function apiRoutes() {
}, },
}, },
}, },
}.merge(obj.requestBody); };
} }
if (route.responses) { if (route.responses) {
for (const [k, v] of Object.entries(route.responses)) { obj.responses = {};
let schema = {
$ref: `#/components/schemas/${v.body}`,
};
obj.responses = { for (const [k, v] of Object.entries(route.responses)) {
[k]: { if (v.body)
...(v.body obj.responses[k] = {
? { description: obj?.responses?.[k]?.description || "",
description:
obj?.responses?.[k]?.description || "",
content: { content: {
"application/json": { "application/json": {
schema: schema, schema: {
$ref: `#/components/schemas/${v.body}`,
}, },
}, },
}
: {
description: "No description available",
}),
}, },
}.merge(obj.responses); };
else
obj.responses[k] = {
description:
obj?.responses?.[k]?.description ||
"No description available",
};
} }
} else { } else {
obj.responses = { obj.responses = {
@ -218,6 +214,15 @@ function apiRoutes() {
obj.tags = [...(obj.tags || []), getTag(p)].unique(); obj.tags = [...(obj.tags || []), getTag(p)].unique();
if (missingRoutes.additional.includes(path.replace(/\/$/, ""))) {
obj["x-badges"] = [
{
label: "Spacebar-only",
color: "red",
},
];
}
specification.paths[path] = Object.assign( specification.paths[path] = Object.assign(
specification.paths[path] || {}, specification.paths[path] || {},
{ {
@ -227,10 +232,21 @@ function apiRoutes() {
}); });
} }
function main() { 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();
combineSchemas(schemas); combineSchemas(schemas);
apiRoutes(); apiRoutes(missingRoutes);
fs.writeFileSync( fs.writeFileSync(
openapiPath, openapiPath,

View File

@ -41,11 +41,16 @@ const Excluded = [
"EntitySchema", "EntitySchema",
"ServerResponse", "ServerResponse",
"Http2ServerResponse", "Http2ServerResponse",
"ExpressResponse",
"global.Express.Response", "global.Express.Response",
"global.Response",
"Response", "Response",
"e.Response", "e.Response",
"request.Response", "request.Response",
"supertest.Response", "supertest.Response",
"DiagnosticsChannel.Response",
"_Response",
"ReadableStream<any>",
// TODO: Figure out how to exclude schemas from node_modules? // TODO: Figure out how to exclude schemas from node_modules?
"SomeJSONSchema", "SomeJSONSchema",