generate open api schema based on body and db entities

This commit is contained in:
Flam3rboy
2021-09-01 23:33:14 +02:00
parent 3c8cb59cb0
commit 0fbe9131ad
10 changed files with 1853 additions and 768 deletions

View File

@@ -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);

View 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();