mirror of
https://github.com/spacebarchat/server.git
synced 2026-04-17 11:05:47 +00:00
✨ generate open api schema based on body and db entities
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
const { Snowflake } = require("@fosscord/server-util");
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
const defaultConfig = {
|
||||
// TODO: Get the network interfaces dinamically
|
||||
gateway: "ws://localhost",
|
||||
general: {
|
||||
instance_id: Snowflake.generate(),
|
||||
},
|
||||
permissions: {
|
||||
user: {
|
||||
createGuilds: true,
|
||||
}
|
||||
},
|
||||
limits: {
|
||||
user: {
|
||||
maxGuilds: 100,
|
||||
maxUsername: 32,
|
||||
maxFriends: 1000,
|
||||
},
|
||||
guild: {
|
||||
maxRoles: 250,
|
||||
maxMembers: 250000,
|
||||
maxChannels: 500,
|
||||
maxChannelsInCategory: 50,
|
||||
hideOfflineMember: 1000,
|
||||
},
|
||||
message: {
|
||||
characters: 2000,
|
||||
ttsCharacters: 200,
|
||||
maxReactions: 20,
|
||||
maxAttachmentSize: 8388608,
|
||||
maxBulkDelete: 100,
|
||||
},
|
||||
channel: {
|
||||
maxPins: 50,
|
||||
maxTopic: 1024,
|
||||
},
|
||||
rate: {
|
||||
ip: {
|
||||
enabled: true,
|
||||
count: 1000,
|
||||
timespan: 1000 * 60 * 10,
|
||||
},
|
||||
routes: {},
|
||||
},
|
||||
},
|
||||
security: {
|
||||
jwtSecret: crypto.randomBytes(256).toString("base64"),
|
||||
forwadedFor: null,
|
||||
// forwadedFor: "X-Forwarded-For" // nginx/reverse proxy
|
||||
// forwadedFor: "CF-Connecting-IP" // cloudflare:
|
||||
captcha: {
|
||||
enabled: false,
|
||||
service: null,
|
||||
sitekey: null,
|
||||
secret: null,
|
||||
},
|
||||
},
|
||||
login: {
|
||||
requireCaptcha: false,
|
||||
},
|
||||
register: {
|
||||
email: {
|
||||
necessary: true,
|
||||
allowlist: false,
|
||||
blocklist: true,
|
||||
domains: [], // TODO: efficiently save domain blocklist in database
|
||||
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
|
||||
},
|
||||
dateOfBirth: {
|
||||
necessary: true,
|
||||
minimum: 13,
|
||||
},
|
||||
requireInvite: false,
|
||||
requireCaptcha: true,
|
||||
allowNewRegistration: true,
|
||||
allowMultipleAccounts: true,
|
||||
password: {
|
||||
minLength: 8,
|
||||
minNumbers: 2,
|
||||
minUpperCase: 2,
|
||||
minSymbols: 0,
|
||||
blockInsecureCommonPasswords: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let data = JSON.stringify(defaultConfig);
|
||||
fs.writeFileSync('./.docker/config/api.json', data);
|
||||
|
||||
191
api/scripts/generate_openapi_schema.ts
Normal file
191
api/scripts/generate_openapi_schema.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
// 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 generateSchemas() {
|
||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
|
||||
const schemas = [
|
||||
"Application",
|
||||
"Attachment",
|
||||
"Message",
|
||||
"AuditLog",
|
||||
"Ban",
|
||||
"Channel",
|
||||
"Emoji",
|
||||
"Guild",
|
||||
"Invite",
|
||||
"ReadState",
|
||||
"Recipient",
|
||||
"Relationship",
|
||||
"Role",
|
||||
"Sticker",
|
||||
"Team",
|
||||
"TeamMember",
|
||||
"Template",
|
||||
"VoiceState",
|
||||
"Webhook",
|
||||
"User",
|
||||
"UserPublic"
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
const definitions = combineSchemas({ schemas, generator, program });
|
||||
|
||||
for (const key in definitions) {
|
||||
specification.components.schemas[key] = definitions[key];
|
||||
delete definitions[key].additionalProperties;
|
||||
}
|
||||
}
|
||||
|
||||
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, ...part.definitions, [name]: { ...part, definitions: undefined, $schema: undefined } };
|
||||
}
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
function generateBodies() {
|
||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions);
|
||||
const generator = TJS.buildGenerator(program, settings);
|
||||
|
||||
const schemas = [
|
||||
"BanCreateSchema",
|
||||
"DmChannelCreateSchema",
|
||||
"ChannelModifySchema",
|
||||
"ChannelGuildPositionUpdateSchema",
|
||||
"ChannelGuildPositionUpdateSchema",
|
||||
"EmojiCreateSchema",
|
||||
"GuildCreateSchema",
|
||||
"GuildUpdateSchema",
|
||||
"GuildTemplateCreateSchema",
|
||||
"GuildUpdateWelcomeScreenSchema",
|
||||
"InviteCreateSchema",
|
||||
"MemberCreateSchema",
|
||||
"MemberNickChangeSchema",
|
||||
"MemberChangeSchema",
|
||||
"MessageCreateSchema",
|
||||
"RoleModifySchema",
|
||||
"TemplateCreateSchema",
|
||||
"TemplateModifySchema",
|
||||
"UserModifySchema",
|
||||
"UserSettingsSchema",
|
||||
"WidgetModifySchema"
|
||||
];
|
||||
|
||||
// @ts-ignore
|
||||
const definitions = combineSchemas({ schemas, generator, program });
|
||||
|
||||
for (const key in definitions) {
|
||||
specification.components.requestBodies[key] = {
|
||||
content: {
|
||||
"application/json": { schema: definitions[key] }
|
||||
},
|
||||
description: ""
|
||||
};
|
||||
|
||||
delete definitions[key].additionalProperties;
|
||||
delete definitions[key].$schema;
|
||||
}
|
||||
}
|
||||
|
||||
function addDefaultResponses() {
|
||||
Object.values(specification.paths).forEach((path: any) =>
|
||||
Object.values(path).forEach((request: any) => {
|
||||
if (!request.responses?.["401"]) {
|
||||
request.responses["401"] = {
|
||||
description: "Unauthorized",
|
||||
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } }
|
||||
};
|
||||
}
|
||||
if (!request.responses?.["429"]) {
|
||||
request.responses["429"] = {
|
||||
description: "Rate limit exceeded",
|
||||
content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } },
|
||||
headers: {
|
||||
"X-RateLimit-Bucket": {
|
||||
description:
|
||||
"A unique string denoting the rate limit being encountered (non-inclusive of major parameters in the route path)",
|
||||
schema: { type: "string" }
|
||||
},
|
||||
"X-Rate-Limit-Limit": {
|
||||
description: "The number of allowed requests in the current period",
|
||||
schema: {
|
||||
type: "integer"
|
||||
}
|
||||
},
|
||||
"X-Rate-Limit-Remaining": {
|
||||
description: "The number of remaining requests in the current period",
|
||||
schema: {
|
||||
type: "integer"
|
||||
}
|
||||
},
|
||||
"X-Rate-Limit-Reset": {
|
||||
description: "Date when current period is over in seconds since the Unix epoch",
|
||||
schema: {
|
||||
type: "integer"
|
||||
}
|
||||
},
|
||||
"X-Rate-Limit-Reset-After": {
|
||||
description: "Number of seconds when current period will reset (can have decimal)",
|
||||
schema: {
|
||||
type: "number"
|
||||
}
|
||||
},
|
||||
"Retry-After": {
|
||||
description: "Same as X-Rate-Limit-Reset-After but an integer",
|
||||
schema: {
|
||||
type: "integer"
|
||||
}
|
||||
},
|
||||
"X-RateLimit-Global": {
|
||||
description: "Indicates whether or not all requests from your ip are rate limited",
|
||||
schema: {
|
||||
type: "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function main() {
|
||||
addDefaultResponses();
|
||||
generateSchemas();
|
||||
specification = JSON.parse(JSON.stringify(specification).replaceAll("#/definitions", "#/components/schemas"));
|
||||
|
||||
generateBodies();
|
||||
|
||||
fs.writeFileSync(
|
||||
openapiPath,
|
||||
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/requestBodies").replaceAll("bigint", "number")
|
||||
);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user