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