summary refs log tree commit diff
path: root/api/scripts
diff options
context:
space:
mode:
authorFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-01 23:33:14 +0200
committerFlam3rboy <34555296+Flam3rboy@users.noreply.github.com>2021-09-01 23:33:14 +0200
commit1a76c663d285167aa1b8c748f60c04cff5ae0dce (patch)
tree547efb9dd2ab175530d54e197405fb8a12d6310e /api/scripts
parentMerge pull request #302 from sudenoh/master (diff)
downloadserver-1a76c663d285167aa1b8c748f60c04cff5ae0dce.tar.xz
:sparkles: generate open api schema based on body and db entities
Diffstat (limited to 'api/scripts')
-rw-r--r--api/scripts/config_generator.js93
-rw-r--r--api/scripts/generate_openapi_schema.ts191
2 files changed, 191 insertions, 93 deletions
diff --git a/api/scripts/config_generator.js b/api/scripts/config_generator.js
deleted file mode 100644

index 5b5c52d4..00000000 --- a/api/scripts/config_generator.js +++ /dev/null
@@ -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); - diff --git a/api/scripts/generate_openapi_schema.ts b/api/scripts/generate_openapi_schema.ts new file mode 100644
index 00000000..c45a43eb --- /dev/null +++ b/api/scripts/generate_openapi_schema.ts
@@ -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();